include/rtl/textenc.h | 1 offapi/UnoApi_offapi.mk | 1 offapi/com/sun/star/sheet/SensitivityReport.idl | 55 ++ officecfg/registry/schema/org/openoffice/Office/Calc.xcs | 7 sc/inc/globstr.hrc | 14 sc/inc/miscuno.hxx | 4 sc/inc/scabstdlg.hxx | 3 sc/inc/strings.hrc | 2 sc/qa/extras/scsolverobj.cxx | 28 - sc/qa/uitest/csv_dialog/tdf117868.py | 15 sc/qa/uitest/textToColumns/tdf143008.py | 3 sc/qa/uitest/textToColumns/tdf51700.py | 3 sc/qa/uitest/textToColumns/tdf69981.py | 3 sc/qa/uitest/textToColumns/tdf73006.py | 3 sc/qa/uitest/textToColumns/tdf82398.py | 3 sc/qa/uitest/textToColumns/tdf85979.py | 3 sc/qa/uitest/textToColumns/tdf89907.py | 3 sc/qa/uitest/textToColumns/tdf92423.py | 3 sc/qa/uitest/textToColumns/textToColumns.py | 18 sc/source/ui/attrdlg/scdlgfact.cxx | 5 sc/source/ui/attrdlg/scdlgfact.hxx | 3 sc/source/ui/dbgui/asciiopt.cxx | 31 + sc/source/ui/dbgui/csvtablebox.cxx | 36 - sc/source/ui/dbgui/scuiasciiopt.cxx | 338 +++++++-------- sc/source/ui/docshell/docsh.cxx | 2 sc/source/ui/inc/asciiopt.hxx | 2 sc/source/ui/inc/csvtablebox.hxx | 2 sc/source/ui/inc/optsolver.hxx | 4 sc/source/ui/inc/scuiasciiopt.hxx | 14 sc/source/ui/miscdlgs/optsolver.cxx | 152 ++++++ sc/source/ui/unoobj/filtuno.cxx | 12 sc/source/ui/view/cellsh2.cxx | 1 sc/uiconfig/scalc/ui/textimportcsv.ui | 45 + sccomp/inc/strings.hrc | 1 sccomp/source/solver/LpsolveSolver.cxx | 118 +++++ sccomp/source/solver/SolverComponent.cxx | 24 - sccomp/source/solver/SolverComponent.hxx | 14 sfx2/source/doc/objstor.cxx | 25 - 38 files changed, 739 insertions(+), 262 deletions(-)
New commits: commit 565b619d57a3b98b0826c4b49dee6606f9ae70e0 Author: Gabriel Masei <[email protected]> AuthorDate: Tue Apr 9 13:07:18 2024 +0300 Commit: Andras Timar <[email protected]> CommitDate: Wed Oct 9 14:49:11 2024 +0200 tdf#160582 Preserve settings saving in csv import dialog Also, improve detection algorithm by replacing the limit of 20 lines with a time limit of 500ms. Change-Id: Iac519b6ebe675b91ce84b900646d9d320ea9ddc1 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/165905 Reviewed-by: Andras Timar <[email protected]> Tested-by: Jenkins diff --git a/include/rtl/textenc.h b/include/rtl/textenc.h index af4a16e5c422..10d69e734a4f 100644 --- a/include/rtl/textenc.h +++ b/include/rtl/textenc.h @@ -141,6 +141,7 @@ typedef sal_uInt16 rtl_TextEncoding; */ #define RTL_TEXTENCODING_USER_START (RTL_TEXTENC_CAST( 0x8000 )) +#define RTL_TEXTENCODING_USER_DETECTED (RTL_TEXTENCODING_USER_START + 0) #define RTL_TEXTENCODING_USER_END (RTL_TEXTENC_CAST( 0xEFFF )) #define RTL_TEXTENCODING_UCS4 (RTL_TEXTENC_CAST( 0xFFFE )) diff --git a/officecfg/registry/schema/org/openoffice/Office/Calc.xcs b/officecfg/registry/schema/org/openoffice/Office/Calc.xcs index cb557431362b..770b31e7a5fd 100644 --- a/officecfg/registry/schema/org/openoffice/Office/Calc.xcs +++ b/officecfg/registry/schema/org/openoffice/Office/Calc.xcs @@ -1012,6 +1012,13 @@ </info> <value>false</value> </prop> + <prop oor:name="SeparatorType" oor:type="xs:short" oor:nillable="false"> + <info> + <desc>Fixed width, separator or detected separator</desc> + <label>SeparatorType</label> + </info> + <value>2</value> + </prop> <prop oor:name="QuotedFieldAsText" oor:type="xs:boolean" oor:nillable="false"> <info> <desc>If true, quoted field is always imported as text with no exception.</desc> diff --git a/sc/inc/miscuno.hxx b/sc/inc/miscuno.hxx index 6ac2a925090b..71cd9390de49 100644 --- a/sc/inc/miscuno.hxx +++ b/sc/inc/miscuno.hxx @@ -152,8 +152,8 @@ public: const css::uno::Reference<css::beans::XPropertySet>& xProp, const OUString& rName, const OUString& rDefault ); - SC_DLLPUBLIC static bool GetBoolFromAny( const css::uno::Any& aAny ); - static sal_Int16 GetInt16FromAny( const css::uno::Any& aAny ); + SC_DLLPUBLIC static bool GetBoolFromAny( const css::uno::Any& aAny ); + SC_DLLPUBLIC static sal_Int16 GetInt16FromAny( const css::uno::Any& aAny ); static sal_Int32 GetInt32FromAny( const css::uno::Any& aAny ); static sal_Int32 GetEnumFromAny( const css::uno::Any& aAny ); diff --git a/sc/inc/scabstdlg.hxx b/sc/inc/scabstdlg.hxx index 158501225a58..957c9605a116 100644 --- a/sc/inc/scabstdlg.hxx +++ b/sc/inc/scabstdlg.hxx @@ -419,8 +419,7 @@ public: virtual VclPtr<AbstractScImportAsciiDlg> CreateScImportAsciiDlg(weld::Window* pParent, const OUString& aDatName, SvStream* pInStream, - ScImportAsciiCall eCall, - ScAsciiOptions* aOptions = nullptr) = 0; + ScImportAsciiCall eCall) = 0; virtual VclPtr<AbstractScTextImportOptionsDlg> CreateScTextImportOptionsDlg(weld::Window* pParent) = 0; diff --git a/sc/inc/strings.hrc b/sc/inc/strings.hrc index 3e23a800974e..faaa5e64998b 100644 --- a/sc/inc/strings.hrc +++ b/sc/inc/strings.hrc @@ -85,6 +85,8 @@ #define SCSTR_FIELDSEP_SPACE NC_("SCSTR_FIELDSEP_SPACE", "space") #define SCSTR_UNDO_GRAFFILTER NC_("SCSTR_UNDO_GRAFFILTER", "Image Filter") #define STR_CAPTION_DEFAULT_TEXT NC_("STR_CAPTION_DEFAULT_TEXT", "Text") +#define SCSTR_DETECTED NC_("SCSTR_DETECTED", "Detected (%1)") +#define SCSTR_AUTOMATIC NC_("SCSTR_AUTOMATIC", "Automatic") // Select tables dialog title #define STR_DLG_SELECTTABLES_TITLE NC_("STR_DLG_SELECTTABLES_TITLE", "Select Sheets") #define STR_DLG_SELECTTABLE_TITLE NC_("STR_DLG_SELECTTABLE_TITLE", "Go to Sheet") diff --git a/sc/qa/uitest/csv_dialog/tdf117868.py b/sc/qa/uitest/csv_dialog/tdf117868.py index d5306117e372..405a18f12acc 100644 --- a/sc/qa/uitest/csv_dialog/tdf117868.py +++ b/sc/qa/uitest/csv_dialog/tdf117868.py @@ -17,21 +17,12 @@ class Td117868(UITestCase): with load_csv_file(self, "tdf117868.csv", False) as xDialog: # Set text delimiter in case it's changed by another test - xSeparatedBy = xDialog.getChild("toseparatedby") - xSeparatedBy.executeAction("CLICK", tuple()) - - # Without the fix in place, this test would have failed with - # AssertionError: 'true' != 'false' - self.assertEqual('true', get_state_as_dict(xDialog.getChild("other"))['Selected']) - self.assertEqual('false', get_state_as_dict(xDialog.getChild("tab"))['Selected']) - self.assertEqual('false', get_state_as_dict(xDialog.getChild("comma"))['Selected']) - self.assertEqual('false', get_state_as_dict(xDialog.getChild("semicolon"))['Selected']) + self.assertEqual('Detected (|)', get_state_as_dict(xDialog.getChild("todetectseparator"))['Text']) + xDetected = xDialog.getChild("todetectseparator") + xDetected.executeAction("CLICK", tuple()) self.assertEqual('1', get_state_as_dict(xDialog.getChild("fromrow"))['Text']) - xInputOther = xDialog.getChild("inputother") - self.assertEqual("|", get_state_as_dict(xInputOther)['Text']) - document = self.ui_test.get_component() self.assertEqual("LETTER", get_cell_by_position(document, 0, 0, 1).getString()) diff --git a/sc/qa/uitest/textToColumns/tdf143008.py b/sc/qa/uitest/textToColumns/tdf143008.py index 087a7b5a8fd5..a14c671d26f6 100644 --- a/sc/qa/uitest/textToColumns/tdf143008.py +++ b/sc/qa/uitest/textToColumns/tdf143008.py @@ -22,6 +22,9 @@ class tdf143008(UITestCase): enter_text_to_cell(gridwin, "A1", "22/06/2021 10:02 PM") with self.ui_test.execute_dialog_through_command(".uno:TextToColumns") as xDialog: + xSeparatedBy = xDialog.getChild("toseparatedby") + xSeparatedBy.executeAction("CLICK", tuple()) + xGrid = xDialog.getChild("csvgrid") xColumnType = xDialog.getChild("columntype") diff --git a/sc/qa/uitest/textToColumns/tdf51700.py b/sc/qa/uitest/textToColumns/tdf51700.py index 98c33d8f3104..b4fdf9b24462 100644 --- a/sc/qa/uitest/textToColumns/tdf51700.py +++ b/sc/qa/uitest/textToColumns/tdf51700.py @@ -28,6 +28,9 @@ class tdf51700(UITestCase): self.xUITest.executeCommand(".uno:SelectColumn") # Data - Text to Columns with self.ui_test.execute_dialog_through_command(".uno:TextToColumns") as xDialog: + xSeparatedBy = xDialog.getChild("toseparatedby") + xSeparatedBy.executeAction("CLICK", tuple()) + xcomma = xDialog.getChild("comma") if (get_state_as_dict(xcomma)["Selected"]) == "false": xcomma.executeAction("CLICK", tuple()) diff --git a/sc/qa/uitest/textToColumns/tdf69981.py b/sc/qa/uitest/textToColumns/tdf69981.py index 87c09e8ffcbf..16a1e9eb305e 100644 --- a/sc/qa/uitest/textToColumns/tdf69981.py +++ b/sc/qa/uitest/textToColumns/tdf69981.py @@ -23,6 +23,9 @@ class tdf69981(UITestCase): gridwin.executeAction("SELECT", mkPropertyValues({"RANGE": "A2:A7"})) #Data - Text to Columns with self.ui_test.execute_dialog_through_command(".uno:TextToColumns", close_button="") as xDialog: + xSeparatedBy = xDialog.getChild("toseparatedby") + xSeparatedBy.executeAction("CLICK", tuple()) + xtab = xDialog.getChild("tab") xcomma = xDialog.getChild("comma") xtab.executeAction("CLICK", tuple()) diff --git a/sc/qa/uitest/textToColumns/tdf73006.py b/sc/qa/uitest/textToColumns/tdf73006.py index 1ac519f73c4b..0ffa5e69731e 100644 --- a/sc/qa/uitest/textToColumns/tdf73006.py +++ b/sc/qa/uitest/textToColumns/tdf73006.py @@ -26,6 +26,9 @@ class tdf73006(UITestCase): self.xUITest.executeCommand(".uno:SelectColumn") # Data - Text to Columns with self.ui_test.execute_dialog_through_command(".uno:TextToColumns") as xDialog: + xSeparatedBy = xDialog.getChild("toseparatedby") + xSeparatedBy.executeAction("CLICK", tuple()) + xspace = xDialog.getChild("space") if (get_state_as_dict(xspace)["Selected"]) == "false": xspace.executeAction("CLICK", tuple()) diff --git a/sc/qa/uitest/textToColumns/tdf82398.py b/sc/qa/uitest/textToColumns/tdf82398.py index b0722fcbd14c..2ddf0b60d654 100644 --- a/sc/qa/uitest/textToColumns/tdf82398.py +++ b/sc/qa/uitest/textToColumns/tdf82398.py @@ -29,6 +29,9 @@ class tdf82398(UITestCase): self.xUITest.executeCommand(".uno:NumberFormatDate") # Data - Text to Columns with self.ui_test.execute_dialog_through_command(".uno:TextToColumns") as xDialog: + xSeparatedBy = xDialog.getChild("toseparatedby") + xSeparatedBy.executeAction("CLICK", tuple()) + xother = xDialog.getChild("other") xinputother = xDialog.getChild("inputother") diff --git a/sc/qa/uitest/textToColumns/tdf85979.py b/sc/qa/uitest/textToColumns/tdf85979.py index ca5808947e70..a3be674b0d69 100644 --- a/sc/qa/uitest/textToColumns/tdf85979.py +++ b/sc/qa/uitest/textToColumns/tdf85979.py @@ -23,6 +23,9 @@ class tdf85979(UITestCase): gridwin.executeAction("SELECT", mkPropertyValues({"RANGE": "C1:C5"})) # Data - Text to Columns with self.ui_test.execute_dialog_through_command(".uno:TextToColumns") as xDialog: + xSeparatedBy = xDialog.getChild("toseparatedby") + xSeparatedBy.executeAction("CLICK", tuple()) + xspace = xDialog.getChild("space") if (get_state_as_dict(xspace)["Selected"]) == "false": xspace.executeAction("CLICK", tuple()) diff --git a/sc/qa/uitest/textToColumns/tdf89907.py b/sc/qa/uitest/textToColumns/tdf89907.py index ef5f164f9261..a7ea4c3c0207 100644 --- a/sc/qa/uitest/textToColumns/tdf89907.py +++ b/sc/qa/uitest/textToColumns/tdf89907.py @@ -37,6 +37,9 @@ class tdf89907(UITestCase): # Data - Text to Columns with self.ui_test.execute_dialog_through_command(".uno:TextToColumns") as xDialog: + xSeparatedBy = xDialog.getChild("toseparatedby") + xSeparatedBy.executeAction("CLICK", tuple()) + xother = xDialog.getChild("other") xinputother = xDialog.getChild("inputother") if (get_state_as_dict(xother)["Selected"]) == "false": diff --git a/sc/qa/uitest/textToColumns/tdf92423.py b/sc/qa/uitest/textToColumns/tdf92423.py index 99486bb2f00e..ce9cadf3f348 100644 --- a/sc/qa/uitest/textToColumns/tdf92423.py +++ b/sc/qa/uitest/textToColumns/tdf92423.py @@ -39,6 +39,9 @@ class tdf92423(UITestCase): self.assertEqual(gridWinState["MarkedArea"], "Sheet1.A7:Sheet1.A9") # Data - Text to Columns with self.ui_test.execute_dialog_through_command(".uno:TextToColumns") as xDialog: + xSeparatedBy = xDialog.getChild("toseparatedby") + xSeparatedBy.executeAction("CLICK", tuple()) + xSemicolon = xDialog.getChild("semicolon") #check semicolon checkbox if (get_state_as_dict(xSemicolon)["Selected"]) == "false": xSemicolon.executeAction("CLICK", tuple()) diff --git a/sc/qa/uitest/textToColumns/textToColumns.py b/sc/qa/uitest/textToColumns/textToColumns.py index c67e879d1835..678a75b04c77 100644 --- a/sc/qa/uitest/textToColumns/textToColumns.py +++ b/sc/qa/uitest/textToColumns/textToColumns.py @@ -26,6 +26,9 @@ class CalcTextToColumns(UITestCase): #Data - Text to Columns with self.ui_test.execute_dialog_through_command(".uno:TextToColumns", close_button="") as xDialog: #Untag Tab as separator and tag other. Put a dot into the input field next to the other checkbox + xSeparatedBy = xDialog.getChild("toseparatedby") + xSeparatedBy.executeAction("CLICK", tuple()) + xother = xDialog.getChild("other") xinputother = xDialog.getChild("inputother") @@ -85,6 +88,9 @@ class CalcTextToColumns(UITestCase): # Data - Text to Columns with self.ui_test.execute_dialog_through_command(".uno:TextToColumns", close_button="") as xDialog: # Untag Tab as separator and tag comma. + xSeparatedBy = xDialog.getChild("toseparatedby") + xSeparatedBy.executeAction("CLICK", tuple()) + xComma = xDialog.getChild("comma") if (get_state_as_dict(xComma)["Selected"]) == "false": xComma.executeAction("CLICK", tuple()) @@ -142,6 +148,9 @@ class CalcTextToColumns(UITestCase): # Data - Text to Columns with self.ui_test.execute_dialog_through_command(".uno:TextToColumns", close_button="") as xDialog: # Untag comma as separator and tag Semicolon + xSeparatedBy = xDialog.getChild("toseparatedby") + xSeparatedBy.executeAction("CLICK", tuple()) + xSemicolon = xDialog.getChild("semicolon") if (get_state_as_dict(xSemicolon)["Selected"]) == "false": xSemicolon.executeAction("CLICK", tuple()) @@ -199,6 +208,9 @@ class CalcTextToColumns(UITestCase): # Data - Text to Columns with self.ui_test.execute_dialog_through_command(".uno:TextToColumns", close_button="") as xDialog: # Untag comma as separator and tag Semicolon + xSeparatedBy = xDialog.getChild("toseparatedby") + xSeparatedBy.executeAction("CLICK", tuple()) + xSpace = xDialog.getChild("space") if (get_state_as_dict(xSpace)["Selected"]) == "false": xSpace.executeAction("CLICK", tuple()) @@ -257,6 +269,9 @@ class CalcTextToColumns(UITestCase): # Data - Text to Columns with self.ui_test.execute_dialog_through_command(".uno:TextToColumns", close_button="") as xDialog: # Untag comma as separator and tag Semicolon + xSeparatedBy = xDialog.getChild("toseparatedby") + xSeparatedBy.executeAction("CLICK", tuple()) + xother = xDialog.getChild("other") xinputother = xDialog.getChild("inputother") if (get_state_as_dict(xother)["Selected"]) == "false": @@ -315,6 +330,9 @@ class CalcTextToColumns(UITestCase): gridwin.executeAction("SELECT", mkPropertyValues({"RANGE": "A1:A5"})) # Data - Text to Columns with self.ui_test.execute_dialog_through_command(".uno:TextToColumns", close_button="") as xDialog: + xSeparatedBy = xDialog.getChild("toseparatedby") + xSeparatedBy.executeAction("CLICK", tuple()) + xspace = xDialog.getChild("space") xother = xDialog.getChild("other") xinputother = xDialog.getChild("inputother") diff --git a/sc/source/ui/attrdlg/scdlgfact.cxx b/sc/source/ui/attrdlg/scdlgfact.cxx index 47e81dc7c09f..af95b1e025fa 100644 --- a/sc/source/ui/attrdlg/scdlgfact.cxx +++ b/sc/source/ui/attrdlg/scdlgfact.cxx @@ -1073,10 +1073,9 @@ bool AbstractScSelEntryDlg_Impl::StartExecuteAsync(VclAbstractDialog::AsyncConte // =========================Factories for createdialog =================== VclPtr<AbstractScImportAsciiDlg> ScAbstractDialogFactory_Impl::CreateScImportAsciiDlg(weld::Window* pParent, const OUString& aDatName, - SvStream* pInStream, ScImportAsciiCall eCall, - ScAsciiOptions* aOptions) + SvStream* pInStream, ScImportAsciiCall eCall) { - return VclPtr<AbstractScImportAsciiDlg_Impl>::Create(std::make_shared<ScImportAsciiDlg>(pParent, aDatName,pInStream, eCall, aOptions)); + return VclPtr<AbstractScImportAsciiDlg_Impl>::Create(std::make_shared<ScImportAsciiDlg>(pParent, aDatName,pInStream, eCall)); } VclPtr<AbstractScTextImportOptionsDlg> ScAbstractDialogFactory_Impl::CreateScTextImportOptionsDlg(weld::Window* pParent) diff --git a/sc/source/ui/attrdlg/scdlgfact.hxx b/sc/source/ui/attrdlg/scdlgfact.hxx index 0f8077240a73..d7dab5e4c4b8 100644 --- a/sc/source/ui/attrdlg/scdlgfact.hxx +++ b/sc/source/ui/attrdlg/scdlgfact.hxx @@ -666,8 +666,7 @@ public: virtual VclPtr<AbstractScImportAsciiDlg> CreateScImportAsciiDlg(weld::Window* pParent, const OUString& aDatName, SvStream* pInStream, - ScImportAsciiCall eCall, - ScAsciiOptions* aOptions = nullptr) override; + ScImportAsciiCall eCall) override; virtual VclPtr<AbstractScTextImportOptionsDlg> CreateScTextImportOptionsDlg(weld::Window* pParent) override; diff --git a/sc/source/ui/dbgui/asciiopt.cxx b/sc/source/ui/dbgui/asciiopt.cxx index c9a4d881baed..4c470793f9ac 100644 --- a/sc/source/ui/dbgui/asciiopt.cxx +++ b/sc/source/ui/dbgui/asciiopt.cxx @@ -22,9 +22,11 @@ #include <comphelper/string.hxx> #include <osl/thread.h> #include <o3tl/string_view.hxx> +#include <sfx2/objsh.hxx> constexpr std::u16string_view pStrFix = u"FIX"; constexpr std::u16string_view pStrMrg = u"MRG"; +constexpr std::u16string_view pStrDet = u"DETECT"; ScAsciiOptions::ScAsciiOptions() : bFixedLen ( false ), @@ -86,9 +88,10 @@ static OUString lcl_decodeSepString( std::u16string_view rSepNums, bool & o_bMer // The options string must not contain semicolons (because of the pick list), // use comma as separator. -void ScAsciiOptions::ReadFromString( std::u16string_view rString ) +void ScAsciiOptions::ReadFromString( std::u16string_view rString, SvStream* pStream4Detect ) { sal_Int32 nPos = rString.empty() ? -1 : 0; + bool bDetectSep = false; // Token 0: Field separator. if ( nPos >= 0 ) @@ -96,9 +99,14 @@ void ScAsciiOptions::ReadFromString( std::u16string_view rString ) bFixedLen = bMergeFieldSeps = false; const std::u16string_view aToken = o3tl::getToken(rString, 0, ',', nPos); - if ( aToken == pStrFix ) - bFixedLen = true; - aFieldSeps = lcl_decodeSepString( aToken, bMergeFieldSeps); + if ( aToken == pStrDet) + bDetectSep = true; + else + { + if ( aToken == pStrFix ) + bFixedLen = true; + aFieldSeps = lcl_decodeSepString( aToken, bMergeFieldSeps); + } } // Token 1: Text separator. @@ -111,9 +119,22 @@ void ScAsciiOptions::ReadFromString( std::u16string_view rString ) // Token 2: Text encoding. if ( nPos >= 0 ) { - eCharSet = ScGlobal::GetCharsetValue( o3tl::getToken(rString, 0, ',', nPos) ); + const std::u16string_view aToken = o3tl::getToken(rString, 0, ',', nPos); + SvStreamEndian endian; + bool bDetectCharSet = aToken == pStrDet; + if ( bDetectCharSet && pStream4Detect ) + { + SfxObjectShell::DetectCharSet(*pStream4Detect, eCharSet, endian); + if (eCharSet == RTL_TEXTENCODING_UNICODE) + pStream4Detect->SetEndian(endian); + } + else if (!bDetectCharSet) + eCharSet = ScGlobal::GetCharsetValue( aToken ); } + if (bDetectSep && pStream4Detect) + SfxObjectShell::DetectCsvSeparators(*pStream4Detect, eCharSet, aFieldSeps, cTextSep); + // Token 3: Number of start row. if ( nPos >= 0 ) { diff --git a/sc/source/ui/dbgui/csvtablebox.cxx b/sc/source/ui/dbgui/csvtablebox.cxx index 2a3a16c0c550..ab8231013009 100644 --- a/sc/source/ui/dbgui/csvtablebox.cxx +++ b/sc/source/ui/dbgui/csvtablebox.cxx @@ -57,6 +57,26 @@ ScCsvTableBox::~ScCsvTableBox() // common table box handling -------------------------------------------------- +void ScCsvTableBox::Refresh() +{ + mxGrid->DisableRepaint(); + mxGrid->Execute( CSVCMD_SETLINEOFFSET, 0 ); + if (mbFixedMode) + { + mxGrid->Execute( CSVCMD_SETPOSCOUNT, mnFixedWidth ); + mxGrid->SetSplits( mxRuler->GetSplits() ); + mxGrid->SetColumnStates( std::vector(maFixColStates) ); + } + else + { + mxGrid->Execute( CSVCMD_SETPOSCOUNT, 1 ); + mxGrid->Execute( CSVCMD_NEWCELLTEXTS ); + mxGrid->SetColumnStates( std::vector(maSepColStates) ); + } + InitControls(); + mxGrid->EnableRepaint(); +} + void ScCsvTableBox::SetSeparatorsMode() { if( !mbFixedMode ) @@ -68,13 +88,7 @@ void ScCsvTableBox::SetSeparatorsMode() // switch to separators mode mbFixedMode = false; // reset and reinitialize controls - mxGrid->DisableRepaint(); - mxGrid->Execute( CSVCMD_SETLINEOFFSET, 0 ); - mxGrid->Execute( CSVCMD_SETPOSCOUNT, 1 ); - mxGrid->Execute( CSVCMD_NEWCELLTEXTS ); - mxGrid->SetColumnStates( std::vector(maSepColStates) ); - InitControls(); - mxGrid->EnableRepaint(); + Refresh(); } void ScCsvTableBox::SetFixedWidthMode() @@ -87,13 +101,7 @@ void ScCsvTableBox::SetFixedWidthMode() // switch to fixed width mode mbFixedMode = true; // reset and reinitialize controls - mxGrid->DisableRepaint(); - mxGrid->Execute( CSVCMD_SETLINEOFFSET, 0 ); - mxGrid->Execute( CSVCMD_SETPOSCOUNT, mnFixedWidth ); - mxGrid->SetSplits( mxRuler->GetSplits() ); - mxGrid->SetColumnStates( std::vector(maFixColStates) ); - InitControls(); - mxGrid->EnableRepaint(); + Refresh(); } void ScCsvTableBox::Init() diff --git a/sc/source/ui/dbgui/scuiasciiopt.cxx b/sc/source/ui/dbgui/scuiasciiopt.cxx index 304424806aa5..2789d33096c8 100644 --- a/sc/source/ui/dbgui/scuiasciiopt.cxx +++ b/sc/source/ui/dbgui/scuiasciiopt.cxx @@ -41,6 +41,8 @@ #include <o3tl/string_view.hxx> #include <unicode/ucsdet.h> +#include <sfx2/objsh.hxx> +#include <svx/txenctab.hxx> //! TODO make dynamic const SCSIZE ASCIIDLG_MAXROWS = MAXROWCOUNT; @@ -67,6 +69,7 @@ enum CSVImportOptionsIndex CSVIO_FixedWidth, CSVIO_RemoveSpace, CSVIO_EvaluateFormulas, + CSVIO_SeparatorType, // Settings for *all* dialog invocations above. // Settings not for SC_TEXTTOCOLUMNS below. CSVIO_FromRow, @@ -80,6 +83,13 @@ enum CSVImportOptionsIndex CSVIO_PasteSkipEmptyCells }; +enum SeparatorType +{ + FIXED, + SEPARATOR, + DETECT_SEPARATOR +}; + } // Config items for all three paths are defined in @@ -93,6 +103,7 @@ const ::std::vector<OUString> CSVImportOptionNames = u"FixedWidth"_ustr, u"RemoveSpace"_ustr, u"EvaluateFormulas"_ustr, + u"SeparatorType"_ustr, u"FromRow"_ustr, u"CharSet"_ustr, u"QuotedFieldAsText"_ustr, @@ -176,16 +187,16 @@ static void lcl_CreatePropertiesNames ( OUString& rSepPath, Sequence<OUString>& { case SC_IMPORTFILE: rSepPath = aSep_Path; - nProperties = 12; + nProperties = 13; break; case SC_PASTETEXT: rSepPath = aSep_Path_Clpbrd; - nProperties = 13; + nProperties = 14; break; case SC_TEXTTOCOLUMNS: default: rSepPath = aSep_Path_Text2Col; - nProperties = 7; + nProperties = 8; break; } rNames.realloc( nProperties ); @@ -196,6 +207,7 @@ static void lcl_CreatePropertiesNames ( OUString& rSepPath, Sequence<OUString>& pNames[ CSVIO_FixedWidth ] = CSVImportOptionNames[ CSVIO_FixedWidth ]; pNames[ CSVIO_RemoveSpace ] = CSVImportOptionNames[ CSVIO_RemoveSpace ]; pNames[ CSVIO_EvaluateFormulas ] = CSVImportOptionNames[ CSVIO_EvaluateFormulas ]; + pNames[ CSVIO_SeparatorType ] = CSVImportOptionNames[ CSVIO_SeparatorType ]; if (eCall != SC_TEXTTOCOLUMNS) { pNames[ CSVIO_FromRow ] = CSVImportOptionNames[ CSVIO_FromRow ]; @@ -215,9 +227,9 @@ static void lcl_CreatePropertiesNames ( OUString& rSepPath, Sequence<OUString>& static void lcl_LoadSeparators( OUString& rFieldSeparators, OUString& rTextSeparators, bool& rMergeDelimiters, bool& rQuotedAsText, bool& rDetectSpecialNum, bool& rDetectScientificNum, - bool& rFixedWidth, sal_Int32& rFromRow, sal_Int32& rCharSet, + SeparatorType& rSepType, sal_Int32& rFromRow, sal_Int32& rCharSet, sal_Int32& rLanguage, bool& rSkipEmptyCells, bool& rRemoveSpace, - bool& rEvaluateFormulas, ScImportAsciiCall eCall ) + bool& rEvaluateFormulas, ScImportAsciiCall eCall, bool& rBeforeDetection ) { Sequence<Any>aValues; const Any *pProperties; @@ -240,8 +252,14 @@ static void lcl_LoadSeparators( OUString& rFieldSeparators, OUString& rTextSepar if( pProperties[ CSVIO_TextSeparators ].hasValue() ) pProperties[ CSVIO_TextSeparators ] >>= rTextSeparators; - if( pProperties[ CSVIO_FixedWidth ].hasValue() ) - rFixedWidth = ScUnoHelpFunctions::GetBoolFromAny( pProperties[ CSVIO_FixedWidth ] ); + rBeforeDetection = true; + if( pProperties[ CSVIO_SeparatorType ].hasValue() ) + { + rBeforeDetection = false; + rSepType = static_cast<SeparatorType>(ScUnoHelpFunctions::GetInt16FromAny( pProperties[ CSVIO_SeparatorType ] )); + } + else if( pProperties[ CSVIO_FixedWidth ].hasValue() ) + rSepType = (ScUnoHelpFunctions::GetBoolFromAny( pProperties[ CSVIO_FixedWidth ] ) ? SeparatorType::FIXED : SeparatorType::DETECT_SEPARATOR); if( pProperties[ CSVIO_EvaluateFormulas ].hasValue() ) rEvaluateFormulas = ScUnoHelpFunctions::GetBoolFromAny( pProperties[ CSVIO_EvaluateFormulas ] ); @@ -277,7 +295,7 @@ static void lcl_LoadSeparators( OUString& rFieldSeparators, OUString& rTextSepar static void lcl_SaveSeparators( const OUString& sFieldSeparators, const OUString& sTextSeparators, bool bMergeDelimiters, bool bQuotedAsText, - bool bDetectSpecialNum, bool bDetectScientificNum, bool bFixedWidth, sal_Int32 nFromRow, + bool bDetectSpecialNum, bool bDetectScientificNum, SeparatorType rSepType, sal_Int32 nFromRow, sal_Int32 nCharSet, sal_Int32 nLanguage, bool bSkipEmptyCells, bool bRemoveSpace, bool bEvaluateFormulas, ScImportAsciiCall eCall ) { @@ -294,8 +312,8 @@ static void lcl_SaveSeparators( pProperties[ CSVIO_RemoveSpace ] <<= bRemoveSpace; pProperties[ CSVIO_Separators ] <<= sFieldSeparators; pProperties[ CSVIO_TextSeparators ] <<= sTextSeparators; - pProperties[ CSVIO_FixedWidth ] <<= bFixedWidth; pProperties[ CSVIO_EvaluateFormulas ] <<= bEvaluateFormulas; + pProperties[ CSVIO_SeparatorType ] <<= static_cast<sal_Int16>(rSepType); if (eCall != SC_TEXTTOCOLUMNS) { pProperties[ CSVIO_FromRow ] <<= nFromRow; @@ -316,21 +334,24 @@ static void lcl_SaveSeparators( } ScImportAsciiDlg::ScImportAsciiDlg(weld::Window* pParent, std::u16string_view aDatName, - SvStream* pInStream, ScImportAsciiCall eCall, - const ScAsciiOptions* aOptions) + SvStream* pInStream, ScImportAsciiCall eCall) : GenericDialogController(pParent, u"modules/scalc/ui/textimportcsv.ui"_ustr, u"TextImportCsvDialog"_ustr) , mpDatStream(pInStream) , mnStreamPos(pInStream ? pInStream->Tell() : 0) + , mnStreamInitPos(mnStreamPos) , mnRowPosCount(0) , mcTextSep(ScAsciiOptions::cDefaultTextSep) + , meDetectedCharSet(RTL_TEXTENCODING_DONTKNOW) + , mbCharSetDetect(true) , meCall(eCall) - , mbDetectSep(eCall != SC_TEXTTOCOLUMNS) , mxFtCharSet(m_xBuilder->weld_label(u"textcharset"_ustr)) , mxLbCharSet(new SvxTextEncodingBox(m_xBuilder->weld_combo_box(u"charset"_ustr))) + , mxFtDetectedCharSet(m_xBuilder->weld_label(u"textdetectedcharset"_ustr)) , mxFtCustomLang(m_xBuilder->weld_label(u"textlanguage"_ustr)) , mxLbCustomLang(new SvxLanguageBox(m_xBuilder->weld_combo_box(u"language"_ustr))) , mxFtRow(m_xBuilder->weld_label(u"textfromrow"_ustr)) , mxNfRow(m_xBuilder->weld_spin_button(u"fromrow"_ustr)) + , mxRbDetectSep(m_xBuilder->weld_radio_button(u"todetectseparator"_ustr)) , mxRbFixed(m_xBuilder->weld_radio_button(u"tofixedwidth"_ustr)) , mxRbSeparated(m_xBuilder->weld_radio_button(u"toseparatedby"_ustr)) , mxCkbTab(m_xBuilder->weld_check_button(u"tab"_ustr)) @@ -376,40 +397,23 @@ ScImportAsciiDlg::ScImportAsciiDlg(weld::Window* pParent, std::u16string_view aD OUString sFieldSeparators(u",; "_ustr); OUString sTextSeparators(mcTextSep); bool bMergeDelimiters = false; - bool bFixedWidth = false; + SeparatorType eSepType = DETECT_SEPARATOR; bool bQuotedFieldAsText = false; bool bDetectSpecialNum = true; bool bDetectScientificNum = true; bool bEvaluateFormulas = (meCall != SC_IMPORTFILE); bool bSkipEmptyCells = true; bool bRemoveSpace = false; + bool bBeforeDetection = false; sal_Int32 nFromRow = 1; sal_Int32 nCharSet = -1; sal_Int32 nLanguage = 0; - if (aOptions) - { - if (!aOptions->GetFieldSeps().isEmpty()) - sFieldSeparators = aOptions->GetFieldSeps(); - if (aOptions->GetTextSep()) - sTextSeparators = OUStringChar(aOptions->GetTextSep()); - bMergeDelimiters = aOptions->IsMergeSeps(); - bFixedWidth = aOptions->IsFixedLen(); - bQuotedFieldAsText = aOptions->IsQuotedAsText(); - bDetectSpecialNum = aOptions->IsDetectSpecialNumber(); - bDetectScientificNum = aOptions->IsDetectScientificNumber(); - bEvaluateFormulas = aOptions->IsEvaluateFormulas(); - bSkipEmptyCells = aOptions->IsSkipEmptyCells(); - bRemoveSpace = aOptions->IsRemoveSpace(); - nFromRow = aOptions->GetStartRow(); - nCharSet = aOptions->GetCharSet(); - nLanguage = static_cast<sal_uInt16>(aOptions->GetLanguage()); - } - else - lcl_LoadSeparators (sFieldSeparators, sTextSeparators, bMergeDelimiters, - bQuotedFieldAsText, bDetectSpecialNum, bDetectScientificNum, bFixedWidth, nFromRow, - nCharSet, nLanguage, bSkipEmptyCells, bRemoveSpace, bEvaluateFormulas, meCall); - // load from saved settings + lcl_LoadSeparators (sFieldSeparators, sTextSeparators, bMergeDelimiters, + bQuotedFieldAsText, bDetectSpecialNum, bDetectScientificNum, eSepType, nFromRow, + nCharSet, nLanguage, bSkipEmptyCells, bRemoveSpace, bEvaluateFormulas, meCall, + bBeforeDetection); + maFieldSeparators = sFieldSeparators; if( bMergeDelimiters && !bIsTSV ) @@ -430,95 +434,48 @@ ScImportAsciiDlg::ScImportAsciiDlg(weld::Window* pParent, std::u16string_view aD mxCkbEvaluateFormulas->set_active(true); if (bSkipEmptyCells) mxCkbSkipEmptyCells->set_active(true); - if (bFixedWidth && !bIsTSV) - mxRbFixed->set_active(true); - if (nFromRow != 1) - mxNfRow->set_value(nFromRow); - - // Clipboard is always Unicode, else detect. - rtl_TextEncoding ePreselectUnicode = (aOptions ? aOptions->GetCharSet() : (meCall == SC_IMPORTFILE ? - RTL_TEXTENCODING_DONTKNOW : RTL_TEXTENCODING_UNICODE)); - // Sniff for Unicode / not - if( ePreselectUnicode == RTL_TEXTENCODING_DONTKNOW && mpDatStream ) + if (eSepType == SeparatorType::FIXED) { - mpDatStream->Seek( 0 ); - constexpr size_t buffsize = 4096; - sal_Int8 bytes[buffsize] = { 0 }; - sal_Int32 nRead = mpDatStream->ReadBytes( bytes, buffsize ); - mpDatStream->Seek( 0 ); - - if ( nRead > 0 ) + if (bIsTSV) { - UErrorCode uerr = U_ZERO_ERROR; - UCharsetDetector* ucd = ucsdet_open( &uerr ); - ucsdet_setText( ucd, reinterpret_cast<const char*>(bytes), nRead, &uerr ); - - if ( const UCharsetMatch* match = ucsdet_detect(ucd, &uerr) ) - { - const char* pEncodingName = ucsdet_getName( match, &uerr ); - - if ( U_SUCCESS(uerr) && !strcmp("UTF-8", pEncodingName) ) - { - ePreselectUnicode = RTL_TEXTENCODING_UTF8; // UTF-8 - mpDatStream->StartReadingUnicodeText( RTL_TEXTENCODING_UTF8 ); - } - else if ( U_SUCCESS(uerr) && !strcmp("UTF-16LE", pEncodingName) ) - { - ePreselectUnicode = RTL_TEXTENCODING_UNICODE; // UTF-16LE - mpDatStream->SetEndian( SvStreamEndian::LITTLE ); - mpDatStream->StartReadingUnicodeText( RTL_TEXTENCODING_UNICODE ); - } - else if ( U_SUCCESS(uerr) && !strcmp("UTF-16BE", pEncodingName) ) - { - ePreselectUnicode = RTL_TEXTENCODING_UNICODE; // UTF-16BE - mpDatStream->SetEndian( SvStreamEndian::BIG ); - mpDatStream->StartReadingUnicodeText( RTL_TEXTENCODING_UNICODE ); - } - else // other - mpDatStream->StartReadingUnicodeText( RTL_TEXTENCODING_DONTKNOW ); - } - - ucsdet_close( ucd ); + eSepType = SeparatorType::SEPARATOR; + mxRbSeparated->set_active(true); } + else + mxRbFixed->set_active(true); + } + else if (eSepType == SeparatorType::SEPARATOR) + mxRbSeparated->set_active(true); + else + mxRbDetectSep->set_active(true); + if (nFromRow != 1) + mxNfRow->set_value(nFromRow); - mnStreamPos = mpDatStream->Tell(); + // Clipboard is always Unicode, else rely on default/config. + rtl_TextEncoding ePreselectUnicode = (meCall == SC_IMPORTFILE ? + RTL_TEXTENCODING_DONTKNOW : RTL_TEXTENCODING_UNICODE); + + // Detect character set only once and then use it for "Detect" option. + SvStreamEndian eEndian; + SfxObjectShell::DetectCharSet(*mpDatStream, meDetectedCharSet, eEndian); + if (meDetectedCharSet == RTL_TEXTENCODING_UNICODE) + mpDatStream->SetEndian(eEndian); + else if ( meDetectedCharSet == RTL_TEXTENCODING_DONTKNOW ) + { + meDetectedCharSet = osl_getThreadTextEncoding(); + // Prefer UTF-8, as UTF-16 would have already been detected from the stream. + // This gives a better chance that the file is going to be opened correctly. + if ( meDetectedCharSet == RTL_TEXTENCODING_UNICODE && mpDatStream ) + meDetectedCharSet = RTL_TEXTENCODING_UTF8; } - if (aOptions && !maFieldSeparators.isEmpty()) - SetSeparators(0); - else if (bIsTSV) + if (bIsTSV) SetSeparators(' '); else - { - // Some MS-Excel convention is the first line containing the field - // separator as "sep=|" (without quotes and any field separator - // character). The second possibility seems to be it is present *with* - // quotes so it shows up as cell content *including* the separator and - // can be preserved during round trips. Check for an exact match of - // any such and set separator. - /* TODO: it is debatable whether the unquoted form should rather be - * treated special to actually include the separator in the field data. - * Currently it does not. */ - sal_Unicode cSep = 0; - OUString aLine; - // Try to read one more character, if more than 7 it can't be an exact - // match of any. - mpDatStream->ReadUniOrByteStringLine( aLine, mpDatStream->GetStreamCharSet(), 8); - mpDatStream->Seek(mnStreamPos); - if (aLine.getLength() == 8) - ; // nothing - else if (aLine.getLength() == 5 && aLine.startsWithIgnoreAsciiCase("sep=")) - cSep = aLine[4]; - else if (aLine.getLength() == 7 && aLine[6] == '"' && aLine.startsWithIgnoreAsciiCase("\"sep=")) - cSep = aLine[5]; - - // Set Separators in the dialog from maFieldSeparators (empty are not - // set) or an optionally defined by file content field separator. - SetSeparators(cSep); - } + SetSeparators(0); // Get Separators from the dialog (empty are set from default) - maFieldSeparators = GetSeparators(); + maFieldSeparators = GetActiveSeparators(); mxNfRow->connect_value_changed( LINK( this, ScImportAsciiDlg, FirstRowHdl ) ); @@ -551,22 +508,15 @@ ScImportAsciiDlg::ScImportAsciiDlg(weld::Window* pParent, std::u16string_view aD // Insert one "SYSTEM" entry for compatibility in AsciiOptions and system // independent document linkage. mxLbCharSet->InsertTextEncoding( RTL_TEXTENCODING_DONTKNOW, ScResId( SCSTR_CHARSET_USER ) ); - if ( ePreselectUnicode == RTL_TEXTENCODING_DONTKNOW ) - { - rtl_TextEncoding eSystemEncoding = osl_getThreadTextEncoding(); - // Prefer UTF-8, as UTF-16 would have already been detected from the stream. - // This gives a better chance that the file is going to be opened correctly. - if ( ( eSystemEncoding == RTL_TEXTENCODING_UNICODE ) && mpDatStream ) - eSystemEncoding = RTL_TEXTENCODING_UTF8; - mxLbCharSet->SelectTextEncoding( eSystemEncoding ); - } - else - { - mxLbCharSet->SelectTextEncoding( ePreselectUnicode ); - } + // Insert one for detecting charset. + mxLbCharSet->InsertTextEncoding( RTL_TEXTENCODING_USER_DETECTED, "- " + ScResId( SCSTR_AUTOMATIC ) + " -" ); - if (nCharSet >= 0 && ePreselectUnicode == RTL_TEXTENCODING_DONTKNOW) + if (ePreselectUnicode != RTL_TEXTENCODING_DONTKNOW) + mxLbCharSet->SelectTextEncoding( ePreselectUnicode ); + else if (nCharSet >= 0 && !bBeforeDetection) mxLbCharSet->set_active(nCharSet); + else + mxLbCharSet->SelectTextEncoding(RTL_TEXTENCODING_USER_DETECTED); SetSelectedCharSet(); mxLbCharSet->connect_changed( LINK( this, ScImportAsciiDlg, CharSetHdl ) ); @@ -592,10 +542,10 @@ ScImportAsciiDlg::ScImportAsciiDlg(weld::Window* pParent, std::u16string_view aD mxTableBox->InitTypes( *mxLbType ); mxTableBox->SetColTypeHdl( LINK( this, ScImportAsciiDlg, ColTypeHdl ) ); + mxRbDetectSep->connect_toggled( LINK( this, ScImportAsciiDlg, RbSepFixHdl ) ); mxRbSeparated->connect_toggled( LINK( this, ScImportAsciiDlg, RbSepFixHdl ) ); mxRbFixed->connect_toggled( LINK( this, ScImportAsciiDlg, RbSepFixHdl ) ); - SetupSeparatorCtrls(); RbSepFix(); UpdateVertical(); @@ -715,9 +665,9 @@ void ScImportAsciiDlg::GetOptions( ScAsciiOptions& rOpt ) rOpt.SetFixedLen( mxRbFixed->get_active() ); rOpt.SetStartRow( mxNfRow->get_value() ); mxTableBox->FillColumnData( rOpt ); - if( mxRbSeparated->get_active() ) + if( mxRbSeparated->get_active() || mxRbDetectSep->get_active()) { - rOpt.SetFieldSeps( GetSeparators() ); + rOpt.SetFieldSeps( GetActiveSeparators() ); rOpt.SetMergeSeps( mxCkbAsOnce->get_active() ); rOpt.SetRemoveSpace( mxCkbRemoveSpace->get_active() ); rOpt.SetTextSep( lcl_CharFromCombo( *mxCbTextSep, SCSTR_TEXTSEP ) ); @@ -732,9 +682,9 @@ void ScImportAsciiDlg::GetOptions( ScAsciiOptions& rOpt ) void ScImportAsciiDlg::SaveParameters() { - lcl_SaveSeparators( maFieldSeparators, mxCbTextSep->get_active_text(), mxCkbAsOnce->get_active(), + lcl_SaveSeparators( GetSeparators(), mxCbTextSep->get_active_text(), mxCkbAsOnce->get_active(), mxCkbQuotedAsText->get_active(), mxCkbDetectNumber->get_active(), mxCkbDetectScientificNumber->get_active(), - mxRbFixed->get_active(), + mxRbFixed->get_active() ? FIXED : (mxRbDetectSep->get_active() ? DETECT_SEPARATOR : SEPARATOR), mxNfRow->get_value(), mxLbCharSet->get_active(), static_cast<sal_uInt16>(mxLbCustomLang->get_active_id()), @@ -788,10 +738,27 @@ void ScImportAsciiDlg::SetSeparators( sal_Unicode cSep ) void ScImportAsciiDlg::SetSelectedCharSet() { + rtl_TextEncoding eOldCharSet = meCharSet; meCharSet = mxLbCharSet->GetSelectTextEncoding(); + mbCharSetDetect = (meCharSet == RTL_TEXTENCODING_USER_DETECTED); mbCharSetSystem = (meCharSet == RTL_TEXTENCODING_DONTKNOW); - if( mbCharSetSystem ) + if (mbCharSetDetect) + { + meCharSet = meDetectedCharSet; + mxFtDetectedCharSet->set_label(SvxTextEncodingTable::GetTextString(meCharSet)); + } + else if( mbCharSetSystem ) + { meCharSet = osl_getThreadTextEncoding(); + mxFtDetectedCharSet->set_label(SvxTextEncodingTable::GetTextString(meCharSet)); + } + else + mxFtDetectedCharSet->set_label(SvxTextEncodingTable::GetTextString(meCharSet)); + + if (eOldCharSet != meCharSet) + DetectCsvSeparators(); + + RbSepFix(); } OUString ScImportAsciiDlg::GetSeparators() const @@ -810,6 +777,17 @@ OUString ScImportAsciiDlg::GetSeparators() const return aSepChars; } +OUString ScImportAsciiDlg::GetActiveSeparators() const +{ + if (mxRbSeparated->get_active()) + return GetSeparators(); + + if (mxRbDetectSep->get_active()) + return maDetectedFieldSeps; + + return OUString(); +} + void ScImportAsciiDlg::SetupSeparatorCtrls() { bool bEnable = mxRbSeparated->get_active(); @@ -817,12 +795,41 @@ void ScImportAsciiDlg::SetupSeparatorCtrls() mxCkbSemicolon->set_sensitive( bEnable ); mxCkbComma->set_sensitive( bEnable ); mxCkbSpace->set_sensitive( bEnable ); - mxCkbRemoveSpace->set_sensitive( bEnable ); mxCkbOther->set_sensitive( bEnable ); mxEdOther->set_sensitive( bEnable ); + + bEnable = bEnable || mxRbDetectSep->get_active(); + mxCkbRemoveSpace->set_sensitive( bEnable ); mxCkbAsOnce->set_sensitive( bEnable ); mxFtTextSep->set_sensitive( bEnable ); mxCbTextSep->set_sensitive( bEnable ); + + OUString aSepName; + if (maDetectedFieldSeps.isEmpty()) + aSepName += ScResId(SCSTR_NONE); + else + { + for (int idx = 0; idx < maDetectedFieldSeps.getLength(); idx ++) + { + if (idx > 0) + aSepName += u" "; + + if (maDetectedFieldSeps[idx] == u' ') + aSepName += ScResId(SCSTR_FIELDSEP_SPACE); + else if (maDetectedFieldSeps[idx] == u' ') + aSepName += ScResId(SCSTR_FIELDSEP_TAB); + else + aSepName += OUStringChar(maDetectedFieldSeps[idx]); + } + } + mxRbDetectSep->set_label(ScResId(SCSTR_DETECTED).replaceFirst( "%1", aSepName)); +} + +void ScImportAsciiDlg::DetectCsvSeparators() +{ + mpDatStream->Seek(mnStreamInitPos); + SfxObjectShell::DetectCsvSeparators(*mpDatStream, meCharSet, maDetectedFieldSeps, mcTextSep); + mpDatStream->Seek(mnStreamPos); } void ScImportAsciiDlg::UpdateVertical() @@ -835,10 +842,17 @@ void ScImportAsciiDlg::UpdateVertical() void ScImportAsciiDlg::RbSepFix() { weld::WaitObject aWaitObj(m_xDialog.get()); - if( mxRbFixed->get_active() ) - mxTableBox->SetFixedWidthMode(); + if (mxRbSeparated->get_active() || mxRbDetectSep->get_active()) + { + maFieldSeparators = GetActiveSeparators(); + if (mxTableBox->IsFixedWidthMode()) + mxTableBox->SetSeparatorsMode(); + else + mxTableBox->Refresh(); + } else - mxTableBox->SetSeparatorsMode(); + mxTableBox->SetFixedWidthMode(); + SetupSeparatorCtrls(); } @@ -892,13 +906,26 @@ void ScImportAsciiDlg::SeparatorHdl(const weld::Widget* pCtrl) mxCkbOther->set_active(!mxEdOther->get_text().isEmpty()); OUString aOldFldSeps( maFieldSeparators); - maFieldSeparators = GetSeparators(); sal_Unicode cOldSep = mcTextSep; mcTextSep = lcl_CharFromCombo( *mxCbTextSep, SCSTR_TEXTSEP ); // Any separator changed may result in completely different lines due to // embedded line breaks. - if (cOldSep != mcTextSep || aOldFldSeps != maFieldSeparators) - UpdateVertical(); + if (cOldSep != mcTextSep) + { + DetectCsvSeparators(); + + SetupSeparatorCtrls(); + + maFieldSeparators = GetActiveSeparators(); + if (aOldFldSeps != maFieldSeparators) + { + UpdateVertical(); + mxTableBox->Refresh(); + return; + } + } + else + maFieldSeparators = GetActiveSeparators(); mxTableBox->GetGrid().Execute( CSVCMD_NEWCELLTEXTS ); } @@ -931,14 +958,7 @@ IMPL_LINK(ScImportAsciiDlg, LbColTypeHdl, weld::ComboBox&, rListBox, void) IMPL_LINK_NOARG(ScImportAsciiDlg, UpdateTextHdl, ScCsvTableBox&, void) { - // Checking the separator can only be done once for the very first time - // when the dialog wasn't already presented to the user. - // As a side effect this has the benefit that the check is only done on the - // first set of visible lines. - mbDetectSep = (mbDetectSep && !mxRbFixed->get_active() - && (!mxCkbTab->get_active() || !mxCkbSemicolon->get_active() - || !mxCkbComma->get_active() || !mxCkbSpace->get_active())); - sal_Unicode cDetectSep = (mbDetectSep ? 0 : 0xffff); + sal_Unicode cDetectSep = 0xffff; sal_Int32 nBaseLine = mxTableBox->GetGrid().GetFirstVisLine(); sal_Int32 nRead = mxTableBox->GetGrid().GetVisLineCount(); @@ -958,24 +978,6 @@ IMPL_LINK_NOARG(ScImportAsciiDlg, UpdateTextHdl, ScCsvTableBox&, void) for (; i < CSV_PREVIEW_LINES; i++) maPreviewLine[i].clear(); - if (mbDetectSep) - { - mbDetectSep = false; - if (cDetectSep) - { - // Expect separator to be appended by now so all subsequent - // GetLine()/ReadCsvLine() actually used it. - assert(maFieldSeparators.endsWith(OUStringChar(cDetectSep))); - // Preselect separator in UI. - switch (cDetectSep) - { - case ' ': mxCkbTab->set_active(true); break; - case ';': mxCkbSemicolon->set_active(true); break; - case ',': mxCkbComma->set_active(true); break; - case ' ': mxCkbSpace->set_active(true); break; - } - } - } mxTableBox->GetGrid().Execute( CSVCMD_SETLINECOUNT, mnRowPosCount); bool bMergeSep = mxCkbAsOnce->get_active(); diff --git a/sc/source/ui/docshell/docsh.cxx b/sc/source/ui/docshell/docsh.cxx index 4b8e5f041c8d..7ef50cc7a8d3 100644 --- a/sc/source/ui/docshell/docsh.cxx +++ b/sc/source/ui/docshell/docsh.cxx @@ -1354,7 +1354,7 @@ bool ScDocShell::ConvertFrom( SfxMedium& rMedium ) if ( const SfxStringItem* pOptionsItem = rMedium.GetItemSet().GetItemIfSet( SID_FILE_FILTEROPTIONS ) ) { - aOptions.ReadFromString( pOptionsItem->GetValue() ); + aOptions.ReadFromString( pOptionsItem->GetValue(), rMedium.GetInStream() ); bOptInit = true; } diff --git a/sc/source/ui/inc/asciiopt.hxx b/sc/source/ui/inc/asciiopt.hxx index 6028b8825d94..af97d4b0f2ba 100644 --- a/sc/source/ui/inc/asciiopt.hxx +++ b/sc/source/ui/inc/asciiopt.hxx @@ -52,7 +52,7 @@ public: static const sal_Unicode cDefaultTextSep = '"'; - void ReadFromString( std::u16string_view rString ); + void ReadFromString( std::u16string_view rString, SvStream* pStream4Detect = nullptr ); OUString WriteToString() const; rtl_TextEncoding GetCharSet() const { return eCharSet; } diff --git a/sc/source/ui/inc/csvtablebox.hxx b/sc/source/ui/inc/csvtablebox.hxx index e2392a478f4c..9d626493fd54 100644 --- a/sc/source/ui/inc/csvtablebox.hxx +++ b/sc/source/ui/inc/csvtablebox.hxx @@ -72,10 +72,12 @@ public: // common table box handling ---------------------------------------------- public: + void Refresh(); /** Sets the control to separators mode. */ void SetSeparatorsMode(); /** Sets the control to fixed width mode. */ void SetFixedWidthMode(); + bool IsFixedWidthMode(){ return mbFixedMode; } ScCsvRuler& GetRuler() { return *mxRuler; } ScCsvGrid& GetGrid() { return *mxGrid; } diff --git a/sc/source/ui/inc/scuiasciiopt.hxx b/sc/source/ui/inc/scuiasciiopt.hxx index ee8ca78b221d..5b19b8c4d3ee 100644 --- a/sc/source/ui/inc/scuiasciiopt.hxx +++ b/sc/source/ui/inc/scuiasciiopt.hxx @@ -31,29 +31,34 @@ class SvxTextEncodingBox; class ScImportAsciiDlg : public weld::GenericDialogController { - SvStream* mpDatStream; + SvStream* mpDatStream; sal_uLong mnStreamPos; + sal_uLong mnStreamInitPos; std::unique_ptr<sal_uLong[]> mpRowPosArray; sal_uLong mnRowPosCount; OUString maPreviewLine[ CSV_PREVIEW_LINES ]; OUString maFieldSeparators; // selected field separators + OUString maDetectedFieldSeps; // detected field seps sal_Unicode mcTextSep; rtl_TextEncoding meCharSet; /// Selected char set. + rtl_TextEncoding meDetectedCharSet; /// This is computed only once at initialization, so store it. bool mbCharSetSystem; /// Is System char set selected? + bool mbCharSetDetect; /// Should we autodetect character set ? ScImportAsciiCall meCall; /// How the dialog is called (see asciiopt.hxx) - bool mbDetectSep; /// Whether to detect a possible separator. std::unique_ptr<weld::Label> mxFtCharSet; std::unique_ptr<SvxTextEncodingBox> mxLbCharSet; + std::unique_ptr<weld::Label> mxFtDetectedCharSet; std::unique_ptr<weld::Label> mxFtCustomLang; std::unique_ptr<SvxLanguageBox> mxLbCustomLang; std::unique_ptr<weld::Label> mxFtRow; std::unique_ptr<weld::SpinButton> mxNfRow; + std::unique_ptr<weld::RadioButton> mxRbDetectSep; std::unique_ptr<weld::RadioButton> mxRbFixed; std::unique_ptr<weld::RadioButton> mxRbSeparated; @@ -83,8 +88,7 @@ class ScImportAsciiDlg : public weld::GenericDialogController public: ScImportAsciiDlg( weld::Window* pParent, std::u16string_view aDatName, - SvStream* pInStream, ScImportAsciiCall eCall, - const ScAsciiOptions* aOptions = nullptr ); + SvStream* pInStream, ScImportAsciiCall eCall); virtual ~ScImportAsciiDlg() override; void GetOptions( ScAsciiOptions& rOpt ); @@ -98,6 +102,8 @@ private: void SetSeparators( sal_Unicode cSep ); /** Returns all separator characters in a string. */ OUString GetSeparators() const; + OUString GetActiveSeparators() const; + void DetectCsvSeparators(); /** Enables or disables all separator checkboxes and edit fields. */ void SetupSeparatorCtrls(); diff --git a/sc/source/ui/unoobj/filtuno.cxx b/sc/source/ui/unoobj/filtuno.cxx index e5ee8de17d29..575d66147b64 100644 --- a/sc/source/ui/unoobj/filtuno.cxx +++ b/sc/source/ui/unoobj/filtuno.cxx @@ -179,25 +179,15 @@ sal_Int16 SAL_CALL ScFilterOptionsObj::execute() { // ascii import is special... - ScAsciiOptions aInOptions, *pInOptions = nullptr; INetURLObject aURL( aFileName ); // tdf#132421 - don't URL encode filename for the import ASCII dialog title OUString aPrivDatName(aURL.GetLastName(INetURLObject::DecodeMechanism::Unambiguous)); std::unique_ptr<SvStream> pInStream; if ( xInputStream.is() ) - { pInStream = utl::UcbStreamHelper::CreateStream( xInputStream ); - if (aFilterOptions.isEmpty()) - aFilterOptions = "DETECT,34,DETECT,,,,,,,,,,,,"; - SfxObjectShell::DetectCsvFilterOptions(*pInStream, aFilterOptions); - - aInOptions.ReadFromString(aFilterOptions); - pInOptions = &aInOptions; - } - ScopedVclPtr<AbstractScImportAsciiDlg> pDlg(pFact->CreateScImportAsciiDlg(Application::GetFrameWeld(xDialogParent), aPrivDatName, - pInStream.get(), SC_IMPORTFILE, pInOptions)); + pInStream.get(), SC_IMPORTFILE)); if ( pDlg->Execute() == RET_OK ) { ScAsciiOptions aOptions; diff --git a/sc/source/ui/view/cellsh2.cxx b/sc/source/ui/view/cellsh2.cxx index 0323ca8c219f..bb7c4cf831b3 100644 --- a/sc/source/ui/view/cellsh2.cxx +++ b/sc/source/ui/view/cellsh2.cxx @@ -1040,6 +1040,7 @@ void ScCellShell::ExecuteDB( SfxRequest& rReq ) ScImportExport::SetNoEndianSwap( aStream ); aExport.ExportStream( aStream, OUString(), SotClipboardFormatId::STRING ); + aStream.Seek(0); ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); ScopedVclPtr<AbstractScImportAsciiDlg> pDlg(pFact->CreateScImportAsciiDlg( pTabViewShell->GetFrameWeld(), OUString(), &aStream, SC_TEXTTOCOLUMNS)); diff --git a/sc/uiconfig/scalc/ui/textimportcsv.ui b/sc/uiconfig/scalc/ui/textimportcsv.ui index a7ab406a563c..0f841c5e4910 100644 --- a/sc/uiconfig/scalc/ui/textimportcsv.ui +++ b/sc/uiconfig/scalc/ui/textimportcsv.ui @@ -137,7 +137,7 @@ </object> <packing> <property name="left-attach">0</property> - <property name="top-attach">1</property> + <property name="top-attach">2</property> </packing> </child> <child> @@ -151,7 +151,7 @@ </object> <packing> <property name="left-attach">0</property> - <property name="top-attach">2</property> + <property name="top-attach">3</property> </packing> </child> <child> @@ -169,6 +169,18 @@ <property name="top-attach">0</property> </packing> </child> + <child> + <object class="GtkLabel" id="textdetectedcharset"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="label" translatable="no" context="textimportcsv|textcharset"></property> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">1</property> + </packing> + </child> <child> <object class="GtkComboBoxText" id="language"> <property name="visible">True</property> @@ -181,7 +193,7 @@ </object> <packing> <property name="left-attach">1</property> - <property name="top-attach">1</property> + <property name="top-attach">2</property> </packing> </child> <child> @@ -200,7 +212,7 @@ </object> <packing> <property name="left-attach">1</property> - <property name="top-attach">2</property> + <property name="top-attach">3</property> </packing> </child> </object> @@ -252,7 +264,7 @@ <property name="receives-default">False</property> <property name="use-underline">True</property> <property name="draw-indicator">True</property> - <property name="group">toseparatedby</property> + <property name="group">todetectseparator</property> <child internal-child="accessible"> <object class="AtkObject" id="tofixedwidth-atkobject"> <property name="AtkObject::accessible-description" translatable="yes" context="textimportcsv|extended_tip|tofixedwidth">Separates fixed-width data (equal number of characters) into columns.</property> @@ -272,8 +284,8 @@ <property name="can-focus">True</property> <property name="receives-default">False</property> <property name="use-underline">True</property> - <property name="active">True</property> <property name="draw-indicator">True</property> + <property name="group">todetectseparator</property> <child internal-child="accessible"> <object class="AtkObject" id="toseparatedby-atkobject"> <property name="AtkObject::accessible-description" translatable="yes" context="textimportcsv|extended_tip|toseparatedby">Select the separator used in your data.</property> @@ -286,6 +298,27 @@ <property name="position">1</property> </packing> </child> + <child> + <object class="GtkRadioButton" id="todetectseparator"> + <property name="label" translatable="yes" context="textimportcsv|todetectseparator">Detected</property> + <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="active">True</property> + <property name="draw-indicator">True</property> + <child internal-child="accessible"> + <object class="AtkObject" id="todetectseparator-atkobject"> + <property name="AtkObject::accessible-description" translatable="yes" context="textimportcsv|extended_tip|todetectseparator">Use detected separator.</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> </object> <packing> <property name="expand">False</property> diff --git a/sfx2/source/doc/objstor.cxx b/sfx2/source/doc/objstor.cxx index 17f34ec5ad88..7a9f94da4337 100644 --- a/sfx2/source/doc/objstor.cxx +++ b/sfx2/source/doc/objstor.cxx @@ -119,7 +119,6 @@ #include "objstor.hxx" #include "exoticfileloadexception.hxx" #include <unicode/ucsdet.h> -#include <unicode/ucnv.h> #include <o3tl/string_view.hxx> using namespace ::com::sun::star; @@ -960,7 +959,7 @@ void SfxObjectShell::DetectCsvSeparators(SvStream& stream, rtl_TextEncoding& eCh std::vector<std::unordered_map<sal_Unicode, sal_uInt32>> aLinesCharsCount; std::unordered_map<sal_Unicode, sal_uInt32> aCharsCount; std::unordered_map<sal_Unicode, std::pair<sal_uInt32, sal_uInt32>> aStats; - constexpr sal_uInt32 nMaxLinesToProcess = 20; + constexpr sal_uInt32 nTimeout = 500; // Timeout for detection in ms sal_uInt32 nLinesCount = 0; OUString sInitSeps; OUString sCommonSeps = u", ;:| \/"_ustr;//Sorted by importance @@ -970,17 +969,18 @@ void SfxObjectShell::DetectCsvSeparators(SvStream& stream, rtl_TextEncoding& eCh sal_uInt32 nMaxLinesSameChar = 0; sal_uInt32 nMinDiffs = 0xFFFFFFFF; sal_uInt64 nInitPos = stream.Tell(); + sal_uInt64 nStartTime = tools::Time::GetSystemTicks(); if (!cStringDelimiter) cStringDelimiter = '\"'; for (sal_Int32 nComSepIdx = sCommonSeps.getLength() - 1; nComSepIdx >= 0; nComSepIdx --) usetCommonSeps.insert(sCommonSeps[nComSepIdx]); - aLinesCharsCount.reserve(nMaxLinesToProcess); + aLinesCharsCount.reserve(128); separators = ""; stream.StartReadingUnicodeText(eCharSet); - while (stream.ReadUniOrByteStringLine(sLine, eCharSet) && aLinesCharsCount.size() < nMaxLinesToProcess) + while (stream.ReadUniOrByteStringLine(sLine, eCharSet) && (tools::Time::GetSystemTicks() - nStartTime < nTimeout)) { if (sLine.isEmpty()) continue; @@ -1034,7 +1034,7 @@ void SfxObjectShell::DetectCsvSeparators(SvStream& stream, rtl_TextEncoding& eCh { auto aCurStats = aStats.find(aCurLineChar->first); if (aCurStats == aStats.cend()) - aStats.insert(std::pair<sal_Unicode, std::pair<sal_uInt32, sal_uInt32>>(aCurLineChar->first, std::pair<sal_uInt32, sal_uInt32>(1, 1))); + aCurStats = aStats.insert(std::pair<sal_Unicode, std::pair<sal_uInt32, sal_uInt32>>(aCurLineChar->first, std::pair<sal_uInt32, sal_uInt32>(1, 1))).first; else { aCurStats->second.first ++;// Increment number of lines that contain the current character @@ -1048,17 +1048,19 @@ void SfxObjectShell::DetectCsvSeparators(SvStream& stream, rtl_TextEncoding& eCh } if (aPrevLineChar == aLinesCharsCount.cend()) aCurStats->second.second ++;// Increment number of different number of occurrences. - - // Update the maximum of number of lines that contain the same character. This is a global value. - if (nMaxLinesSameChar < aCurStats->second.first) - nMaxLinesSameChar = aCurStats->second.first; } + + // Update the maximum of number of lines that contain the same character. This is a global value. + if (nMaxLinesSameChar < aCurStats->second.first) + nMaxLinesSameChar = aCurStats->second.first; } aLinesCharsCount.emplace_back(); aLinesCharsCount[aLinesCharsCount.size() - 1].swap(aCharsCount); } + SAL_INFO("sfx.doc", "" << nLinesCount << " lines processed in " << tools::Time::GetSystemTicks() - nStartTime << " ms while detecting separator."); + // Compute the global minimum of different number of occurrences. // But only for characters which occur in a maximum number of lines (previously computed). for (auto it=aStats.cbegin(); it != aStats.cend(); it++) @@ -1086,8 +1088,6 @@ void SfxObjectShell::DetectCsvSeparators(SvStream& stream, rtl_TextEncoding& eCh } } - if (nInitSepIdx >= 0) - break; } stream.Seek(nInitPos); @@ -1133,9 +1133,9 @@ void SfxObjectShell::DetectCsvFilterOptions(SvStream& stream, OUString& aFilterO //Detect separators - aFilterOptions = ""; if (aSeps == aDetect) { + aFilterOptions = ""; OUString separators; DetectCsvSeparators(stream, eCharSet, separators, static_cast<sal_Unicode>(o3tl::toInt32(aDelimiter))); @@ -1198,7 +1198,6 @@ ErrCode SfxObjectShell::HandleFilter( SfxMedium* pMedium, SfxObjectShell const * // FilterOptions should not be detected here (the detection is done before entering // interactive state). For now this is focused on CSV files. DetectFilterOptions(pMedium); - //::sleep(30); if ( !pData && (bTiledRendering || !pOptions) ) { commit 2f1dcf01d713f786ed1bfdc2ba3b6c9e06fb8ecf Author: Rafael Lima <[email protected]> AuthorDate: Fri Sep 20 20:47:12 2024 +0200 Commit: Mike Kaganski <[email protected]> CommitDate: Wed Oct 9 14:49:07 2024 +0200 tdf#157519 Implement Sensitivity Report in LpSolve solver This patch implements sensitivity analysis when using the LpSolve solver engine. It also adds the infrastructure needed for future implementations in other solver engines via the css::sheet::SensitivityReport struct. Change-Id: I74c2ed9c6201a0b2ffc29ef612d2b778d11a3bef Reviewed-on: https://gerrit.libreoffice.org/c/core/+/173642 Tested-by: Jenkins Reviewed-by: Mike Kaganski <[email protected]> diff --git a/offapi/UnoApi_offapi.mk b/offapi/UnoApi_offapi.mk index 6ff00937bd81..2a21d9acc791 100644 --- a/offapi/UnoApi_offapi.mk +++ b/offapi/UnoApi_offapi.mk @@ -3467,6 +3467,7 @@ $(eval $(call gb_UnoApi_add_idlfiles,offapi,com/sun/star/sheet,\ RangeSelectionEvent \ ReferenceFlags \ ResultEvent \ + SensitivityReport \ SheetLinkMode \ SingleReference \ SolverConstraint \ diff --git a/offapi/com/sun/star/sheet/SensitivityReport.idl b/offapi/com/sun/star/sheet/SensitivityReport.idl new file mode 100644 index 000000000000..7c074b64f583 --- /dev/null +++ b/offapi/com/sun/star/sheet/SensitivityReport.idl @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + + +module com { module sun { module star { module sheet { + + +/** Stores all the information related to the sensitivity report of a linear programming model + * + * @since LibreOffice 25.2 + */ +struct SensitivityReport +{ + // Indicates whether a sensitivity report was successfully generated + boolean HasReport; + + // Coefficients of the objective function + sequence<double> ObjCoefficients; + + // Reduced costs of the variables in the objective function + sequence<double> ObjReducedCosts; + + // Allowable decrease in the coefficients of the objective function + sequence<double> ObjAllowableDecreases; + + // Allowable increase in the coefficients of the objective function + sequence<double> ObjAllowableIncreases; + + // Value of the constraint at the solution + sequence<double> ConstrValues; + + // Right-hand side of the constraints + sequence<double> ConstrRHS; + + // Shadow prices of constraints + sequence<double> ConstrShadowPrices; + + // Allowable decrease in the constraint resources + sequence<double> ConstrAllowableDecreases; + + // Allowable increase in the constraint resources + sequence<double> ConstrAllowableIncreases; +}; + + +}; }; }; }; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/inc/globstr.hrc b/sc/inc/globstr.hrc index 76f1859c51c4..e2174c4e5c80 100644 --- a/sc/inc/globstr.hrc +++ b/sc/inc/globstr.hrc @@ -575,6 +575,20 @@ #define STR_UNDO_THEME_CHANGE NC_("STR_UNDO_THEME_CHANGE", "Theme Change") #define STR_UNDO_THEME_COLOR_CHANGE NC_("STR_UNDO_THEME_COLOR_CHANGE", "Theme Color Change") #define STR_ERR_INSERT_CELLS NC_("STR_ERR_INSERT_CELLS", "Failed to insert cells") +#define STR_SOLVER_ENGINE NC_("STR_SOLVER_ENGINE", "Solver Engine:") +#define STR_SENSITIVITY NC_("STR_SENSITIVITY", "Sensitivity") +#define STR_SENSITIVITY_TITLE NC_("STR_SENSITIVITY_TITLE", "Sensitivity Report") +#define STR_SENSITIVITY_OBJCELL NC_("STR_SENSITIVITY_OBJCELL", "Objective Cell") +#define STR_SENSITIVITY_VARCELLS NC_("STR_SENSITIVITY_VARCELLS", "Variable Cells") +#define STR_SENSITIVITY_CONSTRAINTS NC_("STR_SENSITIVITY_CONSTRAINTS", "Constraints") +#define STR_SENSITIVITY_CELL NC_("STR_SENSITIVITY_CELL", "Cell") +#define STR_SENSITIVITY_FINALVALUE NC_("STR_SENSITIVITY_CELL", "Final Value") +#define STR_SENSITIVITY_REDUCED NC_("STR_SENSITIVITY_CELL", "Reduced Cost") +#define STR_SENSITIVITY_OBJCOEFF NC_("STR_SENSITIVITY_CELL", "Objective Coefficient") +#define STR_SENSITIVITY_DECREASE NC_("STR_SENSITIVITY_CELL", "Allowable Decrease") +#define STR_SENSITIVITY_INCREASE NC_("STR_SENSITIVITY_CELL", "Allowable Increase") +#define STR_SENSITIVITY_SHADOWPRICE NC_("STR_SENSITIVITY_SHADOWPRICE", "Shadow Price") +#define STR_SENSITIVITY_RHS NC_("STR_SENSITIVITY_RHS", "Constraint R.H. Side") #endif diff --git a/sc/qa/extras/scsolverobj.cxx b/sc/qa/extras/scsolverobj.cxx index d6feccb3a5d3..0537b6e4f4ff 100644 --- a/sc/qa/extras/scsolverobj.cxx +++ b/sc/qa/extras/scsolverobj.cxx @@ -148,14 +148,16 @@ void ScSolverSettingsObj::testXSolverSettings() uno::Sequence<beans::PropertyValue> aEngProps = xSolverModel->getEngineOptions(); CPPUNIT_ASSERT_EQUAL(OUString("EpsilonLevel"), aEngProps[0].Name); CPPUNIT_ASSERT_EQUAL(uno::Any(static_cast<sal_Int32>(0)), aEngProps[0].Value); - CPPUNIT_ASSERT_EQUAL(u"Integer"_ustr, aEngProps[1].Name); + CPPUNIT_ASSERT_EQUAL(u"GenSensitivityReport"_ustr, aEngProps[1].Name); CPPUNIT_ASSERT_EQUAL(uno::Any(false), aEngProps[1].Value); - CPPUNIT_ASSERT_EQUAL(u"LimitBBDepth"_ustr, aEngProps[2].Name); - CPPUNIT_ASSERT_EQUAL(uno::Any(true), aEngProps[2].Value); - CPPUNIT_ASSERT_EQUAL(u"NonNegative"_ustr, aEngProps[3].Name); + CPPUNIT_ASSERT_EQUAL(u"Integer"_ustr, aEngProps[2].Name); + CPPUNIT_ASSERT_EQUAL(uno::Any(false), aEngProps[2].Value); + CPPUNIT_ASSERT_EQUAL(u"LimitBBDepth"_ustr, aEngProps[3].Name); CPPUNIT_ASSERT_EQUAL(uno::Any(true), aEngProps[3].Value); - CPPUNIT_ASSERT_EQUAL(u"Timeout"_ustr, aEngProps[4].Name); - CPPUNIT_ASSERT_EQUAL(uno::Any(static_cast<sal_Int32>(10)), aEngProps[4].Value); + CPPUNIT_ASSERT_EQUAL(u"NonNegative"_ustr, aEngProps[4].Name); + CPPUNIT_ASSERT_EQUAL(uno::Any(true), aEngProps[4].Value); + CPPUNIT_ASSERT_EQUAL(u"Timeout"_ustr, aEngProps[5].Name); + CPPUNIT_ASSERT_EQUAL(uno::Any(static_cast<sal_Int32>(10)), aEngProps[5].Value); // Save file and reload to check if solver settings are still there saveAndReload(u"calc8"_ustr); @@ -191,14 +193,16 @@ void ScSolverSettingsObj::testXSolverSettings() uno::Sequence<beans::PropertyValue> aEngProps2 = xSolverModel2->getEngineOptions(); CPPUNIT_ASSERT_EQUAL(OUString("EpsilonLevel"), aEngProps2[0].Name); CPPUNIT_ASSERT_EQUAL(uno::Any(static_cast<sal_Int32>(0)), aEngProps2[0].Value); - CPPUNIT_ASSERT_EQUAL(u"Integer"_ustr, aEngProps2[1].Name); + CPPUNIT_ASSERT_EQUAL(u"GenSensitivityReport"_ustr, aEngProps2[1].Name); CPPUNIT_ASSERT_EQUAL(uno::Any(false), aEngProps2[1].Value); - CPPUNIT_ASSERT_EQUAL(u"LimitBBDepth"_ustr, aEngProps2[2].Name); - CPPUNIT_ASSERT_EQUAL(uno::Any(true), aEngProps2[2].Value); - CPPUNIT_ASSERT_EQUAL(u"NonNegative"_ustr, aEngProps2[3].Name); + CPPUNIT_ASSERT_EQUAL(u"Integer"_ustr, aEngProps2[2].Name); + CPPUNIT_ASSERT_EQUAL(uno::Any(false), aEngProps2[2].Value); + CPPUNIT_ASSERT_EQUAL(u"LimitBBDepth"_ustr, aEngProps2[3].Name); CPPUNIT_ASSERT_EQUAL(uno::Any(true), aEngProps2[3].Value); - CPPUNIT_ASSERT_EQUAL(u"Timeout"_ustr, aEngProps2[4].Name); - CPPUNIT_ASSERT_EQUAL(uno::Any(static_cast<sal_Int32>(10)), aEngProps2[4].Value); + CPPUNIT_ASSERT_EQUAL(u"NonNegative"_ustr, aEngProps2[4].Name); + CPPUNIT_ASSERT_EQUAL(uno::Any(true), aEngProps2[4].Value); + CPPUNIT_ASSERT_EQUAL(u"Timeout"_ustr, aEngProps2[5].Name); + CPPUNIT_ASSERT_EQUAL(uno::Any(static_cast<sal_Int32>(10)), aEngProps2[5].Value); } void ScSolverSettingsObj::setUp() diff --git a/sc/source/ui/inc/optsolver.hxx b/sc/source/ui/inc/optsolver.hxx index 4f28a59d5fcd..35570f7d09d4 100644 --- a/sc/source/ui/inc/optsolver.hxx +++ b/sc/source/ui/inc/optsolver.hxx @@ -24,6 +24,7 @@ #include "docsh.hxx" #include <SolverSettings.hxx> #include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/table/CellAddress.hpp> #include <string_view> #include <vector> @@ -157,6 +158,9 @@ private: static sc::ConstraintOperator OperatorIndexToConstraintOperator(sal_Int32 nIndex); + // Return the string representation of a css::table::CellAddress + OUString GetCellStrAddress(css::table::CellAddress aUnoAddress); + DECL_LINK( BtnHdl, weld::Button&, void ); DECL_LINK( DelBtnHdl, weld::Button&, void ); DECL_LINK( GetEditFocusHdl, formula::RefEdit&, void ); diff --git a/sc/source/ui/miscdlgs/optsolver.cxx b/sc/source/ui/miscdlgs/optsolver.cxx index a406e361c998..40e7a87fa3e1 100644 --- a/sc/source/ui/miscdlgs/optsolver.cxx +++ b/sc/source/ui/miscdlgs/optsolver.cxx @@ -38,11 +38,14 @@ #include <comphelper/sequence.hxx> #include <optsolver.hxx> #include <table.hxx> +#include <TableFillingAndNavigationTools.hxx> +#include <com/sun/star/beans/XPropertySetInfo.hpp> #include <com/sun/star/sheet/SolverConstraint.hpp> #include <com/sun/star/sheet/SolverConstraintOperator.hpp> #include <com/sun/star/sheet/XSolverDescription.hpp> #include <com/sun/star/sheet/XSolver.hpp> +#include <com/sun/star/sheet/SensitivityReport.hpp> using namespace com::sun::star; @@ -916,6 +919,14 @@ bool ScOptSolverDlg::FindTimeout( sal_Int32& rTimeout ) return bFound; } +OUString ScOptSolverDlg::GetCellStrAddress(css::table::CellAddress aUnoAddress) +{ + ScAddress aScAddr; + ScUnoConversion::FillScAddress(aScAddr, aUnoAddress); + ScRange aRange(aScAddr); + return aRange.Format(mrDoc, ScRefFlags::RANGE_ABS); +} + bool ScOptSolverDlg::CallSolver() // return true -> close dialog after calling { // show progress dialog @@ -1189,6 +1200,147 @@ bool ScOptSolverDlg::CallSolver() // return true -> close dialog after cal mpDocShell->UnlockPaint(); } + // Generate sensitivity report if user wants it + uno::Reference<css::beans::XPropertySetInfo> xInfo = xOptProp->getPropertySetInfo(); + bool bUserWantsReport = false; + if (xInfo->hasPropertyByName("GenSensitivityReport")) + xOptProp->getPropertyValue("GenSensitivityReport") >>= bUserWantsReport; + + if (bSuccess && bUserWantsReport) + { + // Retrieve the sensitivity analysis report + css::sheet::SensitivityReport aSensitivity; + bool bHasReportObj = xOptProp->getPropertyValue("SensitivityReport") >>= aSensitivity; + + if (bHasReportObj && aSensitivity.HasReport) + { + // Define the Tab name where the sensitivity analysis will be written to + OUString sNewTabName; + SCTAB nNewTab; + mrDoc.GetName(mnCurTab, sNewTabName); + sNewTabName += "_" + ScResId(STR_SENSITIVITY); + // Chech if the new Tab name exists + if (mrDoc.GetTable(sNewTabName, nNewTab)) + { + // Add numbers to the end of the Tab name to make it unique + SCTAB i = 1; + OUString aName; + do + { + i++; + aName = sNewTabName + "_" + OUString::number(static_cast<sal_Int32>(i)); + } + while(mrDoc.GetTable(aName, nNewTab)); + sNewTabName = aName; + } + + // Insert new sheet to the document and start writing the report + ScDocFunc &rFunc = mpDocShell->GetDocFunc(); + rFunc.InsertTable(mnCurTab + 1, sNewTabName, false, false); + SCTAB nReportTab; + mrDoc.GetTable(sNewTabName, nReportTab); + + // Used to input data in the new sheet + ScAddress aOutputAddress(0, 0, nReportTab); + ScAddress::Details mAddressDetails(mrDoc, aOutputAddress); + AddressWalkerWriter aOutput(aOutputAddress, mpDocShell, mrDoc, + formula::FormulaGrammar::mergeToGrammar(formula::FormulaGrammar::GRAM_ENGLISH, mAddressDetails.eConv)); + aOutput.writeBoldString(ScResId(STR_SENSITIVITY_TITLE)); + aOutput.newLine(); + aOutput.writeString(ScResId(STR_SOLVER_ENGINE) + " " + maEngine); + aOutput.newLine(); + aOutput.newLine(); + + // Objective cell section + aOutput.writeBoldString(ScResId(STR_SENSITIVITY_OBJCELL)); + aOutput.newLine(); + aOutput.writeString(ScResId(STR_SENSITIVITY_CELL)); + aOutput.nextColumn(); + aOutput.writeString(ScResId(STR_SENSITIVITY_FINALVALUE)); + aOutput.newLine(); + aOutput.writeString(GetCellStrAddress(xSolver->getObjective())); + aOutput.nextColumn(); + aOutput.writeValue(xSolver->getResultValue()); + aOutput.newLine(); + aOutput.newLine(); + + // Variable cell section + aOutput.writeBoldString(ScResId(STR_SENSITIVITY_VARCELLS)); + aOutput.newLine(); + aOutput.writeString(ScResId(STR_SENSITIVITY_CELL)); + aOutput.nextColumn(); + aOutput.writeString(ScResId(STR_SENSITIVITY_FINALVALUE)); + aOutput.nextColumn(); + aOutput.writeString(ScResId(STR_SENSITIVITY_REDUCED)); + aOutput.nextColumn(); + aOutput.writeString(ScResId(STR_SENSITIVITY_OBJCOEFF)); + aOutput.nextColumn(); + aOutput.writeString(ScResId(STR_SENSITIVITY_DECREASE)); + aOutput.nextColumn(); + aOutput.writeString(ScResId(STR_SENSITIVITY_INCREASE)); + aOutput.newLine(); + + uno::Sequence<double> aSolution = xSolver->getSolution(); + uno::Sequence<double> aObjCoefficients = aSensitivity.ObjCoefficients; + uno::Sequence<double> aObjReducedCosts = aSensitivity.ObjReducedCosts; + uno::Sequence<double> aObjAllowableDecreases = aSensitivity.ObjAllowableDecreases; + uno::Sequence<double> aObjAllowableIncreases = aSensitivity.ObjAllowableIncreases; + for (sal_Int32 i = 0; i < aVariables.getLength(); i++) + { + aOutput.writeString(GetCellStrAddress(aVariables[i])); + aOutput.nextColumn(); + aOutput.writeValue(aSolution[i]); + aOutput.nextColumn(); + aOutput.writeValue(aObjReducedCosts[i]); + aOutput.nextColumn(); + aOutput.writeValue(aObjCoefficients[i]); + aOutput.nextColumn(); + aOutput.writeValue(aObjAllowableDecreases[i]); + aOutput.nextColumn(); + aOutput.writeValue(aObjAllowableIncreases[i]); + aOutput.newLine(); + } + aOutput.newLine(); + + // Constraints section + aOutput.writeBoldString(ScResId(STR_SENSITIVITY_CONSTRAINTS)); + aOutput.newLine(); + aOutput.writeString(ScResId(STR_SENSITIVITY_CELL)); + aOutput.nextColumn(); + aOutput.writeString(ScResId(STR_SENSITIVITY_FINALVALUE)); + aOutput.nextColumn(); + aOutput.writeString(ScResId(STR_SENSITIVITY_SHADOWPRICE)); + aOutput.nextColumn(); + aOutput.writeString(ScResId(STR_SENSITIVITY_RHS)); + aOutput.nextColumn(); + aOutput.writeString(ScResId(STR_SENSITIVITY_DECREASE)); + aOutput.nextColumn(); + aOutput.writeString(ScResId(STR_SENSITIVITY_INCREASE)); + aOutput.newLine(); + + uno::Sequence<double> aConstrValues = aSensitivity.ConstrValues; + uno::Sequence<double> aConstrRHS = aSensitivity.ConstrRHS; + uno::Sequence<double> aConstrShadowPrices = aSensitivity.ConstrShadowPrices; + uno::Sequence<double> aConstrAllowableDecreases = aSensitivity.ConstrAllowableDecreases; + uno::Sequence<double> aConstrAllowableIncreases = aSensitivity.ConstrAllowableIncreases; + for (sal_Int32 i = 0; i < aConstraints.getLength(); i++) + { + aOutput.writeString(GetCellStrAddress(aConstraints[i].Left)); + aOutput.nextColumn(); + aOutput.writeValue(aConstrValues[i]); + aOutput.nextColumn(); + aOutput.writeValue(aConstrShadowPrices[i]); + aOutput.nextColumn(); + aOutput.writeValue(aConstrRHS[i]); + aOutput.nextColumn(); + aOutput.writeValue(aConstrAllowableDecreases[i]); + aOutput.nextColumn(); + aOutput.writeValue(aConstrAllowableIncreases[i]); + aOutput.newLine(); + } + } + } + return bClose; } diff --git a/sccomp/inc/strings.hrc b/sccomp/inc/strings.hrc index b1b8506c3908..c7826cc228ac 100644 --- a/sccomp/inc/strings.hrc +++ b/sccomp/inc/strings.hrc @@ -30,6 +30,7 @@ #define RID_PROPERTY_EPSILONLEVEL NC_("RID_PROPERTY_EPSILONLEVEL", "Epsilon level (0-3)") #define RID_PROPERTY_LIMITBBDEPTH NC_("RID_PROPERTY_LIMITBBDEPTH", "Limit branch-and-bound depth") #define RID_PROPERTY_ALGORITHM NC_("RID_PROPERTY_ALGORITHM", "Swarm algorithm (0 - Differential Evolution, 1 - Particle Swarm Optimization)") +#define RID_PROPERTY_SENSITIVITY NC_("RID_PROPERTY_SENSITIVITY", "Generate sensitivity report") #define RID_ERROR_NONLINEAR NC_("RID_ERROR_NONLINEAR", "The model is not linear.") #define RID_ERROR_EPSILONLEVEL NC_("RID_ERROR_EPSILONLEVEL", "The epsilon level is invalid.") #define RID_ERROR_INFEASIBLE NC_("RID_ERROR_INFEASIBLE", "The model is infeasible. Check limiting conditions.") diff --git a/sccomp/source/solver/LpsolveSolver.cxx b/sccomp/source/solver/LpsolveSolver.cxx index e80bd87e1a41..70a9239c5083 100644 --- a/sccomp/source/solver/LpsolveSolver.cxx +++ b/sccomp/source/solver/LpsolveSolver.cxx @@ -37,6 +37,7 @@ ************************************************************************/ #include <sal/config.h> +#include <sal/log.hxx> #undef LANGUAGE_NONE #if defined _WIN32 @@ -107,6 +108,10 @@ void SAL_CALL LpsolveSolver::solve() size_t nVariables = aVariableCells.size(); size_t nVar = 0; + // Store all RHS values + sal_uInt32 nConstraints = maConstraints.size(); + m_aConstrRHS.realloc(nConstraints); + // collect all dependent cells ScSolverCellHashMap aCellsHash; @@ -197,6 +202,9 @@ void SAL_CALL LpsolveSolver::solve() set_add_rowmode(lp, TRUE); + sal_uInt32 nConstrCount(0); + double* pConstrRHS = m_aConstrRHS.getArray(); + for (const auto& rConstr : maConstraints) { // integer constraints are set later @@ -237,6 +245,9 @@ void SAL_CALL LpsolveSolver::solve() else fRightValue += fDirectValue; + // Remember the RHS value used for sensitivity analysis later + pConstrRHS[nConstrCount] = fRightValue; + int nConstrType = LE; switch ( eOp ) { @@ -247,6 +258,7 @@ void SAL_CALL LpsolveSolver::solve() OSL_FAIL( "unexpected enum type" ); } add_constraint( lp, pValues.get(), nConstrType, fRightValue ); + nConstrCount++; } } @@ -311,6 +323,112 @@ void SAL_CALL LpsolveSolver::solve() std::copy_n(pResultVar, nVariables, maSolution.getArray()); mfResultValue = get_objective( lp ); + + // Initially set to false because getting the report might fail + m_aSensitivityReport.HasReport = false; + + // Get sensitivity report if the user set SensitivityReport parameter to true + if (mbGenSensitivity) + { + // Get sensitivity data about the objective function + // LpSolve returns an interval for the coefficients of the objective function + // instead of returning an allowable increase/decrease (which is what we want to show + // in the sensitivity report; so we these from/till values are converted into increase + // and decrease values later) + REAL* pObjFrom = nullptr; + REAL* pObjTill = nullptr; + bool bHasObjReport = false; + bHasObjReport = get_ptr_sensitivity_obj(lp, &pObjFrom, &pObjTill); + + // Get sensitivity data about constraints + // Similarly to the objective function, the sensitivity values returned for the + // constraints are in the form from/till and are later converted to increase and + // decrease values later + REAL* pConstrValue = nullptr; + REAL* pConstrDual = nullptr; + REAL* pConstrFrom = nullptr; + REAL* pConstrTill = nullptr; + bool bHasConstrReport = false; + bHasConstrReport = get_ptr_sensitivity_rhs(lp, &pConstrDual, &pConstrFrom, &pConstrTill); + + // When successfull, store sensitivity data in the solver component + if (bHasObjReport && bHasConstrReport) + { + m_aSensitivityReport.HasReport = true; + m_aObjDecrease.realloc(nVariables); + m_aObjIncrease.realloc(nVariables); + double* pObjDecrease = m_aObjDecrease.getArray(); + double* pObjIncrease = m_aObjIncrease.getArray(); + for (size_t i = 0; i < nVariables; i++) + { + // Allowed decrease. Note that the indices of rObjCoeff are offset by 1 + // because of the objective function + if (static_cast<bool>(is_infinite(lp, pObjFrom[i]))) + pObjDecrease[i] = get_infinite(lp); + else + pObjDecrease[i] = rObjCoeff[i + 1] - pObjFrom[i]; + + // Allowed increase + if (static_cast<bool>(is_infinite(lp, pObjTill[i]))) + pObjIncrease[i] = get_infinite(lp); + else + pObjIncrease[i] = pObjTill[i] - rObjCoeff[i + 1]; + } + + // Save objective coefficients for the sensitivity report + double* pObjCoefficients(new double[nVariables]); + for (size_t i = 0; i < nVariables; i++) + pObjCoefficients[i] = rObjCoeff[i + 1]; + m_aObjCoefficients.realloc(nVariables); + std::copy_n(pObjCoefficients, nVariables, m_aObjCoefficients.getArray()); + + // The reduced costs are in pConstrDual after the constraints + double* pObjRedCost(new double[nVariables]); + for (size_t i = 0; i < nVariables; i++) + pObjRedCost[i] = pConstrDual[nConstraints + i]; + m_aObjRedCost.realloc(nVariables); + std::copy_n(pObjRedCost, nVariables, m_aObjRedCost.getArray()); + + // Final value of constraints + get_ptr_constraints(lp, &pConstrValue); + m_aConstrValue.realloc(nConstraints); + std::copy_n(pConstrValue, nConstraints, m_aConstrValue.getArray()); + + // The RHS contains information for each constraint + m_aConstrDual.realloc(nConstraints); + m_aConstrDecrease.realloc(nConstraints); + m_aConstrIncrease.realloc(nConstraints); + std::copy_n(pConstrDual, nConstraints, m_aConstrDual.getArray()); + double* pConstrDecrease = m_aConstrDecrease.getArray(); + double* pConstrIncrease = m_aConstrIncrease.getArray(); + + for (sal_uInt32 i = 0; i < nConstraints; i++) + { + // Allowed decrease + pConstrDecrease[i] = m_aConstrRHS[i] - pConstrFrom[i]; + if (static_cast<bool>(is_infinite(lp, pConstrFrom[i])) + && maConstraints[i].Operator == sheet::SolverConstraintOperator_LESS_EQUAL) + pConstrDecrease[i] = m_aConstrRHS[i] - m_aConstrValue[i]; + + // Allowed increase + pConstrIncrease[i] = pConstrTill[i] - m_aConstrRHS[i]; + if (static_cast<bool>(is_infinite(lp, pConstrTill[i])) + && maConstraints[i].Operator == sheet::SolverConstraintOperator_GREATER_EQUAL) + pConstrIncrease[i] = m_aConstrValue[i] - m_aConstrRHS[i]; + } + + // Set all values of the SensitivityReport object + m_aSensitivityReport.ObjCoefficients = m_aObjCoefficients; + m_aSensitivityReport.ObjReducedCosts = m_aObjRedCost; + m_aSensitivityReport.ObjAllowableDecreases = m_aObjDecrease; + m_aSensitivityReport.ObjAllowableIncreases = m_aObjIncrease; + m_aSensitivityReport.ConstrValues = m_aConstrValue; + m_aSensitivityReport.ConstrRHS = m_aConstrRHS; + m_aSensitivityReport.ConstrShadowPrices = m_aConstrDual; + m_aSensitivityReport.ConstrAllowableDecreases = m_aConstrDecrease; + m_aSensitivityReport.ConstrAllowableIncreases = m_aConstrIncrease; + } + } } else if ( nResult == INFEASIBLE ) maStatus = SolverComponent::GetResourceString( RID_ERROR_INFEASIBLE ); diff --git a/sccomp/source/solver/SolverComponent.cxx b/sccomp/source/solver/SolverComponent.cxx index b7038090e56c..a06b65f4c46a 100644 --- a/sccomp/source/solver/SolverComponent.cxx +++ b/sccomp/source/solver/SolverComponent.cxx @@ -37,6 +37,8 @@ constexpr OUStringLiteral STR_INTEGER = u"Integer"; constexpr OUStringLiteral STR_TIMEOUT = u"Timeout"; constexpr OUStringLiteral STR_EPSILONLEVEL = u"EpsilonLevel"; constexpr OUStringLiteral STR_LIMITBBDEPTH = u"LimitBBDepth"; +constexpr OUStringLiteral STR_GEN_SENSITIVITY = u"GenSensitivityReport"; +constexpr OUStringLiteral STR_SENSITIVITY_REPORT = u"SensitivityReport"; // Resources from tools are used for translated strings @@ -64,7 +66,9 @@ namespace PROP_INTEGER, PROP_TIMEOUT, PROP_EPSILONLEVEL, - PROP_LIMITBBDEPTH + PROP_LIMITBBDEPTH, + PROP_GEN_SENSITIVITY, + PROP_SENSITIVITY_REPORT }; } @@ -95,15 +99,20 @@ SolverComponent::SolverComponent() : mnTimeout( 100 ), mnEpsilonLevel( 0 ), mbLimitBBDepth( true ), + mbGenSensitivity(false), mbSuccess( false ), mfResultValue( 0.0 ) { // for XPropertySet implementation: - registerProperty( STR_NONNEGATIVE, PROP_NONNEGATIVE, 0, &mbNonNegative, cppu::UnoType<decltype(mbNonNegative)>::get() ); - registerProperty( STR_INTEGER, PROP_INTEGER, 0, &mbInteger, cppu::UnoType<decltype(mbInteger)>::get() ); - registerProperty( STR_TIMEOUT, PROP_TIMEOUT, 0, &mnTimeout, cppu::UnoType<decltype(mnTimeout)>::get() ); - registerProperty( STR_EPSILONLEVEL, PROP_EPSILONLEVEL, 0, &mnEpsilonLevel, cppu::UnoType<decltype(mnEpsilonLevel)>::get() ); - registerProperty( STR_LIMITBBDEPTH, PROP_LIMITBBDEPTH, 0, &mbLimitBBDepth, cppu::UnoType<decltype(mbLimitBBDepth)>::get() ); + registerProperty(STR_NONNEGATIVE, PROP_NONNEGATIVE, 0, &mbNonNegative, cppu::UnoType<decltype(mbNonNegative)>::get()); + registerProperty(STR_INTEGER, PROP_INTEGER, 0, &mbInteger, cppu::UnoType<decltype(mbInteger)>::get()); + registerProperty(STR_TIMEOUT, PROP_TIMEOUT, 0, &mnTimeout, cppu::UnoType<decltype(mnTimeout)>::get()); + registerProperty(STR_EPSILONLEVEL, PROP_EPSILONLEVEL, 0, &mnEpsilonLevel, cppu::UnoType<decltype(mnEpsilonLevel)>::get()); + registerProperty(STR_LIMITBBDEPTH, PROP_LIMITBBDEPTH, 0, &mbLimitBBDepth, cppu::UnoType<decltype(mbLimitBBDepth)>::get()); + registerProperty(STR_GEN_SENSITIVITY, PROP_GEN_SENSITIVITY, 0, &mbGenSensitivity, cppu::UnoType<decltype(mbGenSensitivity)>::get()); + + // Sensitivity report + registerProperty(STR_SENSITIVITY_REPORT, PROP_SENSITIVITY_REPORT, 0, &m_aSensitivityReport, cppu::UnoType<decltype(m_aSensitivityReport)>::get()); } SolverComponent::~SolverComponent() @@ -158,6 +167,9 @@ OUString SAL_CALL SolverComponent::getPropertyDescription( const OUString& rProp case PROP_LIMITBBDEPTH: pResId = RID_PROPERTY_LIMITBBDEPTH; break; + case PROP_GEN_SENSITIVITY: + pResId = RID_PROPERTY_SENSITIVITY; + break; default: { // unknown - leave empty diff --git a/sccomp/source/solver/SolverComponent.hxx b/sccomp/source/solver/SolverComponent.hxx index 7b5ff1dd49f0..543eeedea282 100644 --- a/sccomp/source/solver/SolverComponent.hxx +++ b/sccomp/source/solver/SolverComponent.hxx @@ -21,6 +21,7 @@ #include <com/sun/star/sheet/XSolver.hpp> #include <com/sun/star/sheet/XSolverDescription.hpp> +#include <com/sun/star/sheet/SensitivityReport.hpp> #include <com/sun/star/table/CellAddress.hpp> #include <com/sun/star/lang/XServiceInfo.hpp> #include <cppuhelper/implbase.hxx> @@ -76,12 +77,25 @@ protected: sal_Int32 mnTimeout; sal_Int32 mnEpsilonLevel; bool mbLimitBBDepth; + bool mbGenSensitivity; // results bool mbSuccess; double mfResultValue; css::uno::Sequence< double > maSolution; OUString maStatus; + // Sensitivity report + css::uno::Sequence<double> m_aObjCoefficients; + css::uno::Sequence<double> m_aObjDecrease; + css::uno::Sequence<double> m_aObjIncrease; + css::uno::Sequence<double> m_aObjRedCost; + css::uno::Sequence<double> m_aConstrValue; + css::uno::Sequence<double> m_aConstrRHS; + css::uno::Sequence<double> m_aConstrDual; + css::uno::Sequence<double> m_aConstrIncrease; + css::uno::Sequence<double> m_aConstrDecrease; + css::sheet::SensitivityReport m_aSensitivityReport; + static OUString GetResourceString(TranslateId aId); static css::uno::Reference<css::table::XCell> GetCell( const css::uno::Reference<css::sheet::XSpreadsheetDocument>& xDoc,
