diff options
Diffstat (limited to 'sc')
40 files changed, 1231 insertions, 126 deletions
diff --git a/sc/inc/SolverSettings.hxx b/sc/inc/SolverSettings.hxx index ec1ef994a7..985e8d30f7 100644 --- a/sc/inc/SolverSettings.hxx +++ b/sc/inc/SolverSettings.hxx @@ -39,11 +39,34 @@ enum SolverParameter SP_LO_ENGINE, // Engine name used in LO SP_MS_ENGINE, // Engine ID used in MSO SP_INTEGER, // Assume all variables are integer (0: no, 1: yes) + // LpSolve, CoinMP and SwarmSolver SP_NON_NEGATIVE, // Assume non negativity (1: yes, 2: no) SP_EPSILON_LEVEL, // Epsilon level SP_LIMIT_BBDEPTH, // Branch and bound depth SP_TIMEOUT, // Time limit to return a solution - SP_ALGORITHM // Algorithm used by the SwarmSolver (1, 2 or 3) + SP_ALGORITHM, // Algorithm used by the SwarmSolver (1, 2 or 3) + // Engine options common for DEPS and SCO + SP_SWARM_SIZE, // Size of Swarm + SP_LEARNING_CYCLES, // Learning Cycles + SP_GUESS_VARIABLE_RANGE, // Variable Bounds Guessing + SP_VARIABLE_RANGE_THRESHOLD, // Variable Bounds Threshold (when guessing) + SP_ACR_COMPARATOR, // Use ACR Comparator (instead of BCH) + SP_RND_STARTING_POINT, // Use Random starting point + SP_STRONGER_PRNG, // Use a stronger random generator (slower) + SP_STAGNATION_LIMIT, // Stagnation Limit + SP_STAGNATION_TOLERANCE, // Stagnation Tolerance + SP_ENHANCED_STATUS, // Show enhanced solver status + // DEPS Options + SP_AGENT_SWITCH_RATE, // Agent Switch Rate (DE Probability) + SP_SCALING_MIN, // DE: Min Scaling Factor (0-1.2) + SP_SCALING_MAX, // DE: Max Scaling Factor (0-1.2) + SP_CROSSOVER_PROB, // DE: Crossover Probability (0-1) + SP_COGNITIVE_CONST, // Cognitive Constant + SP_SOCIAL_CONST, // Social Constant + SP_CONSTRICTION_COEFF, // PS: Constriction Coefficient + SP_MUTATION_PROB, // Mutation Probability (0-0.005) + // SCO Options + SP_LIBRARY_SIZE, // Size of library }; // Starts at 1 to maintain MS compatibility @@ -123,6 +146,28 @@ private: OUString m_sLimitBBDepth; OUString m_sTimeout; OUString m_sAlgorithm; + // DEPS and SCO + OUString m_sSwarmSize; + OUString m_sLearningCycles; + OUString m_sGuessVariableRange; + OUString m_sVariableRangeThreshold; + OUString m_sUseACRComparator; + OUString m_sUseRandomStartingPoint; + OUString m_sUseStrongerPRNG; + OUString m_sStagnationLimit; + OUString m_sTolerance; + OUString m_sEnhancedSolverStatus; + // DEPS only + OUString m_sAgentSwitchRate; + OUString m_sScalingFactorMin; + OUString m_sScalingFactorMax; + OUString m_sCrossoverProbability; + OUString m_sCognitiveConstant; + OUString m_sSocialConstant; + OUString m_sConstrictionCoeff; + OUString m_sMutationProbability; + OUString m_sLibrarySize; + css::uno::Sequence<css::beans::PropertyValue> m_aEngineOptions; std::vector<ModelConstraint> m_aConstraints; @@ -131,7 +176,9 @@ private: // Used to create or read a single solver parameter based on its named range bool ReadParamValue(SolverParameter eParam, OUString& rValue, bool bRemoveQuotes = false); + bool ReadDoubleParamValue(SolverParameter eParam, OUString& rValue); void WriteParamValue(SolverParameter eParam, OUString sValue, bool bQuoted = false); + void WriteDoubleParamValue(SolverParameter eParam, std::u16string_view sValue); // Creates or reads all constraints stored in named ranges void ReadConstraints(); @@ -149,19 +196,46 @@ private: // Maps solver parameters to named ranges std::map<SolverParameter, OUString> m_mNamedRanges - = { { SP_OBJ_CELL, "solver_opt" }, { SP_OBJ_TYPE, "solver_typ" }, - { SP_OBJ_VAL, "solver_val" }, { SP_VAR_CELLS, "solver_adj" }, - { SP_CONSTR_COUNT, "solver_num" }, { SP_LO_ENGINE, "solver_lo_eng" }, - { SP_MS_ENGINE, "solver_eng" }, { SP_INTEGER, "solver_int" }, - { SP_NON_NEGATIVE, "solver_neg" }, { SP_EPSILON_LEVEL, "solver_eps" }, - { SP_LIMIT_BBDEPTH, "solver_bbd" }, { SP_TIMEOUT, "solver_tim" }, - { SP_ALGORITHM, "solver_alg" } }; + = { { SP_OBJ_CELL, "solver_opt" }, + { SP_OBJ_TYPE, "solver_typ" }, + { SP_OBJ_VAL, "solver_val" }, + { SP_VAR_CELLS, "solver_adj" }, + { SP_CONSTR_COUNT, "solver_num" }, + { SP_LO_ENGINE, "solver_lo_eng" }, + { SP_MS_ENGINE, "solver_eng" }, + { SP_INTEGER, "solver_int" }, + { SP_NON_NEGATIVE, "solver_neg" }, + { SP_EPSILON_LEVEL, "solver_eps" }, + { SP_LIMIT_BBDEPTH, "solver_bbd" }, + { SP_TIMEOUT, "solver_tim" }, + { SP_ALGORITHM, "solver_alg" }, + { SP_SWARM_SIZE, "solver_ssz" }, + { SP_LEARNING_CYCLES, "solver_lcy" }, + { SP_GUESS_VARIABLE_RANGE, "solver_gvr" }, + { SP_VARIABLE_RANGE_THRESHOLD, "solver_vrt" }, + { SP_ACR_COMPARATOR, "solver_acr" }, + { SP_RND_STARTING_POINT, "solver_rsp" }, + { SP_STRONGER_PRNG, "solver_prng" }, + { SP_STAGNATION_LIMIT, "solver_slim" }, + { SP_STAGNATION_TOLERANCE, "solver_stol" }, + { SP_ENHANCED_STATUS, "solver_enst" }, + { SP_AGENT_SWITCH_RATE, "solver_asr" }, + { SP_SCALING_MIN, "solver_smin" }, + { SP_SCALING_MAX, "solver_smax" }, + { SP_CROSSOVER_PROB, "solver_crpb" }, + { SP_COGNITIVE_CONST, "solver_cog" }, + { SP_SOCIAL_CONST, "solver_soc" }, + { SP_CONSTRICTION_COEFF, "solver_ccoeff" }, + { SP_MUTATION_PROB, "solver_mtpb" }, + { SP_LIBRARY_SIZE, "solver_lbsz" } }; // Maps LO solver implementation names to MS engine codes std::map<OUString, OUString> SolverNamesToExcelEngines = { { "com.sun.star.comp.Calc.CoinMPSolver", "2" }, // Simplex LP { "com.sun.star.comp.Calc.LpsolveSolver", "2" }, // Simplex LP - { "com.sun.star.comp.Calc.SwarmSolver", "1" } // GRG Nonlinear + { "com.sun.star.comp.Calc.SwarmSolver", "1" }, // GRG Nonlinear + { "com.sun.star.comp.Calc.NLPSolver.DEPSSolverImpl", "3" }, // DEPS + { "com.sun.star.comp.Calc.NLPSolver.SCOSolverImpl", "3" } // SCO }; // Maps MS solver engine codes to LO solver implementation names @@ -180,7 +254,30 @@ private: { "EpsilonLevel", { SP_EPSILON_LEVEL, "solver_eps", "int" } }, { "LimitBBDepth", { SP_LIMIT_BBDEPTH, "solver_bbd", "bool" } }, { "Timeout", { SP_TIMEOUT, "solver_tim", "int" } }, - { "Algorithm", { SP_ALGORITHM, "solver_alg", "int" } } }; + { "Algorithm", { SP_ALGORITHM, "solver_alg", "int" } }, + // SCO and DEPS + { "AssumeNonNegative", { SP_NON_NEGATIVE, "solver_neg", "bool" } }, + { "SwarmSize", { SP_SWARM_SIZE, "solver_ssz", "int" } }, + { "LearningCycles", { SP_LEARNING_CYCLES, "solver_lcy", "int" } }, + { "GuessVariableRange", { SP_GUESS_VARIABLE_RANGE, "solver_gvr", "bool" } }, + { "VariableRangeThreshold", { SP_VARIABLE_RANGE_THRESHOLD, "solver_vrt", "double" } }, + { "UseACRComparator", { SP_ACR_COMPARATOR, "solver_acr", "bool" } }, + { "UseRandomStartingPoint", { SP_RND_STARTING_POINT, "solver_rsp", "bool" } }, + { "UseStrongerPRNG", { SP_STRONGER_PRNG, "solver_prng", "bool" } }, + { "StagnationLimit", { SP_STAGNATION_LIMIT, "solver_slim", "int" } }, + { "Tolerance", { SP_STAGNATION_TOLERANCE, "solver_stol", "double" } }, + { "EnhancedSolverStatus", { SP_ENHANCED_STATUS, "solver_enst", "bool" } }, + // DEPS only + { "AgentSwitchRate", { SP_AGENT_SWITCH_RATE, "solver_asr", "double" } }, + { "DEFactorMin", { SP_SCALING_MIN, "solver_smin", "double" } }, + { "DEFactorMax", { SP_SCALING_MAX, "solver_smax", "double" } }, + { "DECR", { SP_CROSSOVER_PROB, "solver_crpb", "double" } }, + { "PSC1", { SP_COGNITIVE_CONST, "solver_cog", "double" } }, + { "PSC2", { SP_SOCIAL_CONST, "solver_soc", "double" } }, + { "PSWeight", { SP_CONSTRICTION_COEFF, "solver_ccoeff", "double" } }, + { "PSCL", { SP_MUTATION_PROB, "solver_mtpb", "double" } }, + // SCO only + { "LibrarySize", { SP_LIBRARY_SIZE, "solver_lbsz", "int" } } }; // Stores the roots used for named ranges of constraint parts // Items here must be in the same order as in ConstraintPart enum diff --git a/sc/inc/clipcontext.hxx b/sc/inc/clipcontext.hxx index b3ce874a6a..d93e6acb45 100644 --- a/sc/inc/clipcontext.hxx +++ b/sc/inc/clipcontext.hxx @@ -161,12 +161,15 @@ public: class CopyToClipContext final : public ClipContextBase { bool mbKeepScenarioFlags:1; + bool mbCopyChartRanges : 1 = false; // Copying ranges not included in selection: + // only copy data, not cell attributes public: - CopyToClipContext(ScDocument& rDoc, bool bKeepScenarioFlags); + CopyToClipContext(ScDocument& rDoc, bool bKeepScenarioFlags, bool bCopyChartRanges = false); virtual ~CopyToClipContext() override; bool isKeepScenarioFlags() const; + bool isCopyChartRanges() const { return mbCopyChartRanges; } }; class CopyToDocContext final : public ClipContextBase diff --git a/sc/inc/drwlayer.hxx b/sc/inc/drwlayer.hxx index 6f1322b4b9..6bad650fd4 100644 --- a/sc/inc/drwlayer.hxx +++ b/sc/inc/drwlayer.hxx @@ -215,7 +215,8 @@ public: static ScDrawObjData* GetObjDataTab( SdrObject* pObj, SCTAB nTab ); /** Returns true, if the passed object is the caption of a cell note. */ - static bool IsNoteCaption( SdrObject* pObj ); + static bool IsNoteCaption(const ScDrawObjData* pData); + static bool IsNoteCaption(SdrObject* pObj) { return IsNoteCaption(GetObjData(pObj)); } /** Returns the object data, if the passed object is a cell note caption. */ static ScDrawObjData* GetNoteCaptionData( SdrObject* pObj, SCTAB nTab ); diff --git a/sc/inc/filterentries.hxx b/sc/inc/filterentries.hxx index 02110c879b..1ec3f22a32 100644 --- a/sc/inc/filterentries.hxx +++ b/sc/inc/filterentries.hxx @@ -18,11 +18,14 @@ struct ScFilterEntries { std::vector<ScTypedStrData> maStrData; bool mbHasDates; - bool mbHasEmpties; + bool mbHasHiddenEmpties; + bool mbHasUnHiddenEmpties; std::set<Color> maTextColors; std::set<Color> maBackgroundColors; - ScFilterEntries() : mbHasDates(false), mbHasEmpties(false) {} + ScFilterEntries() : mbHasDates(false), + mbHasHiddenEmpties(false), + mbHasUnHiddenEmpties(false) {} std::vector<ScTypedStrData>::iterator begin() { return maStrData.begin(); } std::vector<ScTypedStrData>::iterator end() { return maStrData.end(); } diff --git a/sc/qa/filter/html/data/formula.html b/sc/qa/filter/html/data/formula.html new file mode 100644 index 0000000000..f6c9245d4c --- /dev/null +++ b/sc/qa/filter/html/data/formula.html @@ -0,0 +1,7 @@ +<table> + <tr> + <td data-sheets-value="{"1":3,"3":1}">1</td> + <td data-sheets-value="{"1":3,"3":2}">2</td> + <td data-sheets-value="{"1":3,"3":3}" data-sheets-formula="=SUM(R[0]C[-2]:R[0]C[-1])">3</td> + </tr> +</table> diff --git a/sc/qa/filter/html/data/numberformat.html b/sc/qa/filter/html/data/numberformat.html new file mode 100644 index 0000000000..3f7b3f56d6 --- /dev/null +++ b/sc/qa/filter/html/data/numberformat.html @@ -0,0 +1,8 @@ +<table> + <tr> + <td data-sheets-value="{"1":3,"3":1000}" data-sheets-numberformat="{"1":2,"2":"#,##0.00","3":1}">1,000.00</td> + </tr> + <tr> + <td data-sheets-value="{"1":3,"3":2000}" data-sheets-numberformat="{"1":2,"2":"#,##0.00","3":1}">2,000.00</td> + </tr> +</table> diff --git a/sc/qa/filter/html/data/single-cell.html b/sc/qa/filter/html/data/single-cell.html new file mode 100644 index 0000000000..0b5613f7e2 --- /dev/null +++ b/sc/qa/filter/html/data/single-cell.html @@ -0,0 +1 @@ +<span style="font-size:10pt;font-family:Arial;font-style:normal;text-align:right;" data-sheets-root="1" data-sheets-value="{"1":3,"3":3}" data-sheets-formula="=SUM(R[0]C[-2]:R[0]C[-1])">3</span> diff --git a/sc/qa/filter/html/html.cxx b/sc/qa/filter/html/html.cxx index 6ab2cc7fb0..391806be03 100644 --- a/sc/qa/filter/html/html.cxx +++ b/sc/qa/filter/html/html.cxx @@ -116,6 +116,192 @@ CPPUNIT_TEST_FIXTURE(Test, testPasteTdAsBools) CPPUNIT_ASSERT_EQUAL(OUString("BOOLEAN"), pNumberFormat->GetFormatstring()); CPPUNIT_ASSERT_EQUAL(static_cast<double>(0), pDoc->GetValue(/*col=*/0, /*row=*/1, /*tab=*/0)); } + +CPPUNIT_TEST_FIXTURE(Test, testPasteTdAsFormattedNumber) +{ + // Given an empty document: + createScDoc(); + + // When pasting HTML with cells containing formatted numbers: + ScDocument* pDoc = getScDoc(); + ScAddress aCellPos(/*nColP=*/0, /*nRowP=*/0, /*nTabP=*/0); + ScImportExport aImporter(*pDoc, aCellPos); + SvFileStream aFile(createFileURL(u"numberformat.html"), StreamMode::READ); + SvMemoryStream aMemory; + aMemory.WriteStream(aFile); + aMemory.Seek(0); + CPPUNIT_ASSERT(aImporter.ImportStream(aMemory, OUString(), SotClipboardFormatId::HTML)); + + // Then make sure A1's type is a formatted number, value is 1000: + sal_uInt32 nNumberFormat = pDoc->GetNumberFormat(/*col=*/0, /*row=*/0, /*tab=*/0); + const SvNumberformat* pNumberFormat = pDoc->GetFormatTable()->GetEntry(nNumberFormat); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: #,##0.00 + // - Actual : General + // i.e. the number was wasted without a matching number format. + CPPUNIT_ASSERT_EQUAL(OUString("#,##0.00"), pNumberFormat->GetFormatstring()); + CPPUNIT_ASSERT_EQUAL(static_cast<double>(1000), + pDoc->GetValue(/*col=*/0, /*row=*/0, /*tab=*/0)); +} + +CPPUNIT_TEST_FIXTURE(Test, testPasteTdAsFormula) +{ + // Given an empty document: + createScDoc(); + + // When pasting HTML with cells containing a formula: + ScDocument* pDoc = getScDoc(); + ScAddress aCellPos(/*nColP=*/0, /*nRowP=*/0, /*nTabP=*/0); + ScImportExport aImporter(*pDoc, aCellPos); + SvFileStream aFile(createFileURL(u"formula.html"), StreamMode::READ); + SvMemoryStream aMemory; + aMemory.WriteStream(aFile); + aMemory.Seek(0); + CPPUNIT_ASSERT(aImporter.ImportStream(aMemory, OUString(), SotClipboardFormatId::HTML)); + + // Then make sure C1 is a sum and it evaluates to 3: + // Without the accompanying fix in place, this test would have failed with: + // - Expected: =SUM(A1:B1) + // - Actual : + // i.e. only the formula result was imported, not the formula. + CPPUNIT_ASSERT_EQUAL(OUString("=SUM(A1:B1)"), + pDoc->GetFormula(/*col=*/2, /*row=*/0, /*tab=*/0)); + CPPUNIT_ASSERT_EQUAL(static_cast<double>(3), pDoc->GetValue(/*col=*/2, /*row=*/0, /*tab=*/0)); +} + +CPPUNIT_TEST_FIXTURE(Test, testPasteSingleCell) +{ + // Given a document with '1' in A1 and '2' in B1: + createScDoc(); + ScDocument* pDoc = getScDoc(); + pDoc->SetValue(ScAddress(0, 0, 0), 1.0); + pDoc->SetValue(ScAddress(1, 0, 0), 2.0); + + // When pasting SUM(A1:B1) into C1: + ScAddress aCellPos(/*nColP=*/2, /*nRowP=*/0, /*nTabP=*/0); + ScImportExport aImporter(*pDoc, aCellPos); + SvFileStream aFile(createFileURL(u"single-cell.html"), StreamMode::READ); + CPPUNIT_ASSERT(aImporter.ImportStream(aFile, OUString(), SotClipboardFormatId::HTML)); + + // Then make sure C1 is a sum and it evaluates to 3: + // Without the accompanying fix in place, this test would have failed with: + // - Expected: =SUM(A1:B1) + // - Actual : + // i.e. data-sheets-* on <td> worked, but not on <span>. + CPPUNIT_ASSERT_EQUAL(OUString("=SUM(A1:B1)"), + pDoc->GetFormula(/*col=*/2, /*row=*/0, /*tab=*/0)); + CPPUNIT_ASSERT_EQUAL(static_cast<double>(3), pDoc->GetValue(/*col=*/2, /*row=*/0, /*tab=*/0)); +} + +CPPUNIT_TEST_FIXTURE(Test, testCopyText) +{ + // Given a document with 01 in A1: + createScDoc(); + ScDocument* pDoc = getScDoc(); + ScAddress aCellPos(/*nColP=*/0, /*nRowP=*/0, /*nTabP=*/0); + pDoc->SetString(aCellPos, "'01"); + + // When copying that text from A1: + ScImportExport aExporter(*pDoc, aCellPos); + SvMemoryStream aStream; + CPPUNIT_ASSERT(aExporter.ExportStream(aStream, OUString(), SotClipboardFormatId::HTML)); + + // Then make sure A1 is text: + // Without the accompanying fix in place, this test would have failed with: + // - XPath '//td' no attribute 'data-sheets-value' exist + // i.e. metadata was missing to avoid converting 01 to 1 (number). + aStream.Seek(0); + htmlDocUniquePtr pHtmlDoc = parseHtmlStream(&aStream); + assertXPath(pHtmlDoc, "//td"_ostr, "data-sheets-value"_ostr, "{ \"1\": 2, \"2\": \"01\"}"); +} + +CPPUNIT_TEST_FIXTURE(Test, testCopyBoolean) +{ + // Given a document with boolean values in A1-A2: + createScDoc(); + ScDocument* pDoc = getScDoc(); + ScAddress aCellPos1(/*nColP=*/0, /*nRowP=*/0, /*nTabP=*/0); + pDoc->SetString(aCellPos1, "TRUE"); + ScAddress aCellPos2(/*nColP=*/0, /*nRowP=*/1, /*nTabP=*/0); + pDoc->SetString(aCellPos2, "FALSE"); + + // When copying those values: + ScImportExport aExporter(*pDoc, ScRange(aCellPos1, aCellPos2)); + SvMemoryStream aStream; + CPPUNIT_ASSERT(aExporter.ExportStream(aStream, OUString(), SotClipboardFormatId::HTML)); + + // Then make sure the values are booleans: + aStream.Seek(0); + htmlDocUniquePtr pHtmlDoc = parseHtmlStream(&aStream); + // Without the accompanying fix in place, this test would have failed with: + // - XPath '//td' no attribute 'data-sheets-value' exist + // i.e. metadata was missing to avoid converting TRUE to text. + assertXPath(pHtmlDoc, "(//td)[1]"_ostr, "data-sheets-value"_ostr, "{ \"1\": 4, \"4\": 1}"); + assertXPath(pHtmlDoc, "(//td)[2]"_ostr, "data-sheets-value"_ostr, "{ \"1\": 4, \"4\": 0}"); +} + +CPPUNIT_TEST_FIXTURE(Test, testCopyFormattedNumber) +{ + // Given a document with formatted numbers in A1-A2: + createScDoc(); + ScDocument* pDoc = getScDoc(); + sal_Int32 nCheckPos; + SvNumFormatType nType; + sal_uInt32 nFormat; + OUString aNumberFormat("#,##0.00"); + SvNumberFormatter* pFormatter = pDoc->GetFormatTable(); + pFormatter->PutEntry(aNumberFormat, nCheckPos, nType, nFormat); + ScAddress aCellPos1(/*nColP=*/0, /*nRowP=*/0, /*nTabP=*/0); + pDoc->SetNumberFormat(aCellPos1, nFormat); + pDoc->SetString(aCellPos1, "1000"); + ScAddress aCellPos2(/*nColP=*/0, /*nRowP=*/1, /*nTabP=*/0); + pDoc->SetNumberFormat(aCellPos2, nFormat); + pDoc->SetString(aCellPos2, "2000"); + + // When copying those values: + ScImportExport aExporter(*pDoc, ScRange(aCellPos1, aCellPos2)); + SvMemoryStream aStream; + CPPUNIT_ASSERT(aExporter.ExportStream(aStream, OUString(), SotClipboardFormatId::HTML)); + + // Then make sure the values are numbers: + aStream.Seek(0); + htmlDocUniquePtr pHtmlDoc = parseHtmlStream(&aStream); + // Without the accompanying fix in place, this test would have failed with: + // - XPath '(//td)[1]' no attribute 'data-sheets-value' exist + // i.e. only a formatted number string was written, without a float value. + assertXPath(pHtmlDoc, "(//td)[1]"_ostr, "data-sheets-value"_ostr, "{ \"1\": 3, \"3\": 1000}"); + assertXPath(pHtmlDoc, "(//td)[1]"_ostr, "data-sheets-numberformat"_ostr, + "{ \"1\": 2, \"2\": \"#,##0.00\", \"3\": 1}"); + assertXPath(pHtmlDoc, "(//td)[2]"_ostr, "data-sheets-value"_ostr, "{ \"1\": 3, \"3\": 2000}"); + assertXPath(pHtmlDoc, "(//td)[2]"_ostr, "data-sheets-numberformat"_ostr, + "{ \"1\": 2, \"2\": \"#,##0.00\", \"3\": 1}"); +} + +CPPUNIT_TEST_FIXTURE(Test, testCopyFormula) +{ + // Given a document with a formula in A3: + createScDoc(); + ScDocument* pDoc = getScDoc(); + ScAddress aCellPos1(/*nColP=*/0, /*nRowP=*/0, /*nTabP=*/0); + pDoc->SetString(aCellPos1, "1000"); + ScAddress aCellPos2(/*nColP=*/0, /*nRowP=*/1, /*nTabP=*/0); + pDoc->SetString(aCellPos2, "2000"); + ScAddress aCellPos3(/*nColP=*/0, /*nRowP=*/2, /*nTabP=*/0); + pDoc->SetFormula(aCellPos3, "=SUM(A1:A2)", pDoc->GetGrammar()); + + // When copying those cells: + ScImportExport aExporter(*pDoc, ScRange(aCellPos1, aCellPos3)); + SvMemoryStream aStream; + CPPUNIT_ASSERT(aExporter.ExportStream(aStream, OUString(), SotClipboardFormatId::HTML)); + + // Then make sure the formula is exported in A3: + aStream.Seek(0); + htmlDocUniquePtr pHtmlDoc = parseHtmlStream(&aStream); + // Without the accompanying fix in place, this test would have failed with: + // - XPath '(//td)[3]' no attribute 'data-sheets-formula' exist + // i.e. only the formula result was exported, not the formula. + assertXPath(pHtmlDoc, "(//td)[3]"_ostr, "data-sheets-formula"_ostr, "=SUM(R[-2]C:R[-1]C)"); +} } CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sc/qa/uitest/autofilter2/tdf159420.py b/sc/qa/uitest/autofilter2/tdf159420.py new file mode 100644 index 0000000000..87ee159d22 --- /dev/null +++ b/sc/qa/uitest/autofilter2/tdf159420.py @@ -0,0 +1,126 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-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/. +# +from uitest.framework import UITestCase +from uitest.uihelper.calc import enter_text_to_cell +from uitest.uihelper.common import get_state_as_dict +from libreoffice.uno.propertyvalue import mkPropertyValues +from libreoffice.calc.document import is_row_hidden + +class tdf159420(UITestCase): + + def testTdf159420(self): + with self.ui_test.create_doc_in_start_center("calc") as calcDoc: + xCalcDoc = self.xUITest.getTopFocusWindow() + xGridWin = xCalcDoc.getChild("grid_window") + + # Fill the sheet with test data + enter_text_to_cell(xGridWin, "A1", "a") + enter_text_to_cell(xGridWin, "A2", "2") + enter_text_to_cell(xGridWin, "A3", "2") + enter_text_to_cell(xGridWin, "A4", "2") + enter_text_to_cell(xGridWin, "A5", "4") + + enter_text_to_cell(xGridWin, "B1", "b") + enter_text_to_cell(xGridWin, "B2", "") + enter_text_to_cell(xGridWin, "B3", "") + enter_text_to_cell(xGridWin, "B4", "8") + enter_text_to_cell(xGridWin, "B5", "8") + + enter_text_to_cell(xGridWin, "C1", "c") + + # Select the data range and set autofilter + xGridWin.executeAction("SELECT", mkPropertyValues({"RANGE": "A1:C5"})) + self.xUITest.executeCommand(".uno:DataFilterAutoFilter") + + # Click the autofilter dropdown in column A + xGridWin.executeAction("LAUNCH", mkPropertyValues({"AUTOFILTER": "", "COL": "0", "ROW": "0"})) + xFloatWindow = self.xUITest.getFloatWindow() + xCheckListMenu = xFloatWindow.getChild("FilterDropDown") + xTreeList = xCheckListMenu.getChild("check_list_box") + + self.assertEqual(2, len(xTreeList.getChildren())) + + xEntry1 = xTreeList.getChild(0) + self.assertEqual("2", get_state_as_dict(xEntry1)['Text']) + self.assertEqual("true", get_state_as_dict(xEntry1)['IsChecked']) + self.assertEqual("false", get_state_as_dict(xEntry1)['IsSemiTransparent']) + + xEntry2 = xTreeList.getChild(1) + self.assertEqual("4", get_state_as_dict(xEntry2)['Text']) + self.assertEqual("true", get_state_as_dict(xEntry2)['IsChecked']) + self.assertEqual("false", get_state_as_dict(xEntry2)['IsSemiTransparent']) + + # Uncheck the second entry + xEntry2.executeAction("CLICK", tuple()) + + xOkButton = xFloatWindow.getChild("ok") + xOkButton.executeAction("CLICK", tuple()) + + # Check that only row#2 is visible + self.assertFalse(is_row_hidden(calcDoc, 1)) + self.assertFalse(is_row_hidden(calcDoc, 2)) + self.assertFalse(is_row_hidden(calcDoc, 3)) + self.assertTrue(is_row_hidden(calcDoc, 4)) + + # Click the autofilter dropdown in column B + xGridWin.executeAction("LAUNCH", mkPropertyValues({"AUTOFILTER": "", "COL": "1", "ROW": "0"})) + xFloatWindow = self.xUITest.getFloatWindow() + xCheckListMenu = xFloatWindow.getChild("FilterDropDown") + xTreeList = xCheckListMenu.getChild("check_list_box") + + self.assertEqual(2, len(xTreeList.getChildren())) + + xEntry1 = xTreeList.getChild(0) + self.assertEqual("(empty)", get_state_as_dict(xEntry1)['Text']) + self.assertEqual("true", get_state_as_dict(xEntry1)['IsChecked']) + self.assertEqual("false", get_state_as_dict(xEntry1)['IsSemiTransparent']) + + xEntry2 = xTreeList.getChild(1) + self.assertEqual("8", get_state_as_dict(xEntry2)['Text']) + self.assertEqual("true", get_state_as_dict(xEntry2)['IsChecked']) + self.assertEqual("false", get_state_as_dict(xEntry2)['IsSemiTransparent']) + + # Uncheck the first entry + xEntry1.executeAction("CLICK", tuple()) + + # Close the popup window + xOkButton = xFloatWindow.getChild("ok") + xOkButton.executeAction("CLICK", tuple()) + + self.assertTrue(is_row_hidden(calcDoc, 1)) + self.assertTrue(is_row_hidden(calcDoc, 2)) + self.assertFalse(is_row_hidden(calcDoc, 3)) + self.assertTrue(is_row_hidden(calcDoc, 4)) + + # Click the autofilter dropdown in column C + xGridWin.executeAction("LAUNCH", mkPropertyValues({"AUTOFILTER": "", "COL": "2", "ROW": "0"})) + xFloatWindow = self.xUITest.getFloatWindow() + xCheckListMenu = xFloatWindow.getChild("FilterDropDown") + xTreeList = xCheckListMenu.getChild("check_list_box") + + self.assertEqual(1, len(xTreeList.getChildren())) + + xEntry1 = xTreeList.getChild(0) + self.assertEqual("(empty)", get_state_as_dict(xEntry1)['Text']) + + # Without the fix in place, this test would have failed with + # AssertionError: 'true' != 'false' + self.assertEqual("true", get_state_as_dict(xEntry1)['IsChecked']) + self.assertEqual("false", get_state_as_dict(xEntry1)['IsSemiTransparent']) + + # Close the popup window + xOkButton = xFloatWindow.getChild("ok") + xOkButton.executeAction("CLICK", tuple()) + + self.assertTrue(is_row_hidden(calcDoc, 1)) + self.assertTrue(is_row_hidden(calcDoc, 2)) + self.assertFalse(is_row_hidden(calcDoc, 3)) + self.assertTrue(is_row_hidden(calcDoc, 4)) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/sc/qa/uitest/data/tdf129701.ods b/sc/qa/uitest/data/tdf129701.ods Binary files differnew file mode 100644 index 0000000000..94407fcd56 --- /dev/null +++ b/sc/qa/uitest/data/tdf129701.ods diff --git a/sc/qa/uitest/pasteSpecial/tdf129701-PasteUnformated.py b/sc/qa/uitest/pasteSpecial/tdf129701-PasteUnformated.py new file mode 100644 index 0000000000..596cd62f8b --- /dev/null +++ b/sc/qa/uitest/pasteSpecial/tdf129701-PasteUnformated.py @@ -0,0 +1,70 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-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/. +# +from uitest.framework import UITestCase +from libreoffice.uno.propertyvalue import mkPropertyValues +from uitest.uihelper.common import get_url_for_data_file +from uitest.uihelper.common import get_state_as_dict +from libreoffice.calc.document import get_cell_by_position + +class tdf129701(UITestCase): + + def test_tdf129701(self): + + with self.ui_test.load_file(get_url_for_data_file("tdf129701.ods")): + xCalcDoc = self.xUITest.getTopFocusWindow() + gridwin = xCalcDoc.getChild("grid_window") + gridwin.executeAction("SELECT", mkPropertyValues({"RANGE": "D21:F25"})) + self.xUITest.executeCommand(".uno:Copy") + + gridwin.executeAction("SELECT", mkPropertyValues({"CELL": "J4"})) + with self.ui_test.execute_dialog_through_command(".uno:PasteUnformatted", close_button="ok") as xDialog: + + xSkipEmtyCells = xDialog.getChild("skipemptycells") + xSeparatedBy = xDialog.getChild("toseparatedby") + xTab = xDialog.getChild("tab") + xMergeDelimiters = xDialog.getChild("mergedelimiters") + + xSeparatedBy.executeAction("CLICK", tuple()) + if get_state_as_dict(xTab)['Selected'] == 'false': + xTab.executeAction("CLICK", tuple()) + if get_state_as_dict(xMergeDelimiters)['Selected'] == 'true': + xMergeDelimiters.executeAction("CLICK", tuple()) + if get_state_as_dict(xSkipEmtyCells)['Selected'] == 'true': + xSkipEmtyCells.executeAction("CLICK", tuple()) + # Check wether Skip empty cells is unselected + self.assertEqual('false', get_state_as_dict(xSkipEmtyCells)['Selected']) + + document = self.ui_test.get_component() + # Without the fix in place, this test would have failed with + # non empty cells in column 11 + + self.assertEqual( "x1", get_cell_by_position(document, 0, 9, 3).getString()) + self.assertEqual( "" , get_cell_by_position(document, 0,10, 3).getString()) + self.assertEqual( "" , get_cell_by_position(document, 0,11, 3).getString()) + self.assertEqual("A16", get_cell_by_position(document, 0,12, 3).getString()) + self.assertEqual( "" , get_cell_by_position(document, 0, 9, 4).getString()) + self.assertEqual( "x2", get_cell_by_position(document, 0,10, 4).getString()) + self.assertEqual( "" , get_cell_by_position(document, 0,11, 4).getString()) + self.assertEqual("A17", get_cell_by_position(document, 0,12, 4).getString()) + self.assertEqual( "" , get_cell_by_position(document, 0, 9, 5).getString()) + self.assertEqual( "" , get_cell_by_position(document, 0,10, 5).getString()) + self.assertEqual( "x3", get_cell_by_position(document, 0,11, 5).getString()) + self.assertEqual("A18", get_cell_by_position(document, 0,12, 5).getString()) + self.assertEqual( "" , get_cell_by_position(document, 0, 9, 6).getString()) + self.assertEqual( "x4", get_cell_by_position(document, 0,10, 6).getString()) + self.assertEqual( "" , get_cell_by_position(document, 0,11, 6).getString()) + self.assertEqual("A19", get_cell_by_position(document, 0,12, 6).getString()) + self.assertEqual( "x5", get_cell_by_position(document, 0, 9, 7).getString()) + self.assertEqual( "x6", get_cell_by_position(document, 0,10, 7).getString()) + self.assertEqual( "x7", get_cell_by_position(document, 0,11, 7).getString()) + self.assertEqual("A20", get_cell_by_position(document, 0,12, 7).getString()) + + self.ui_test.close_doc() + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/sc/qa/unit/data/ods/tdf158735.ods b/sc/qa/unit/data/ods/tdf158735.ods Binary files differnew file mode 100644 index 0000000000..6003f29bf3 --- /dev/null +++ b/sc/qa/unit/data/ods/tdf158735.ods diff --git a/sc/qa/unit/data/ods/tdf160003_page_anchored_object.ods b/sc/qa/unit/data/ods/tdf160003_page_anchored_object.ods Binary files differnew file mode 100644 index 0000000000..565eb1bf6a --- /dev/null +++ b/sc/qa/unit/data/ods/tdf160003_page_anchored_object.ods diff --git a/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataInSync.xlsx b/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataInSync.xlsx Binary files differnew file mode 100644 index 0000000000..f425f978cb --- /dev/null +++ b/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataInSync.xlsx diff --git a/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithCacheData.xlsx b/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithCacheData.xlsx Binary files differnew file mode 100644 index 0000000000..0cb21cd325 --- /dev/null +++ b/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithCacheData.xlsx diff --git a/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithoutCacheData.xlsx b/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithoutCacheData.xlsx Binary files differnew file mode 100644 index 0000000000..91297320b9 --- /dev/null +++ b/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithoutCacheData.xlsx diff --git a/sc/qa/unit/pivottable_filters_test.cxx b/sc/qa/unit/pivottable_filters_test.cxx index 8d6b1ad5d3..31fb49351c 100644 --- a/sc/qa/unit/pivottable_filters_test.cxx +++ b/sc/qa/unit/pivottable_filters_test.cxx @@ -2647,6 +2647,69 @@ CPPUNIT_TEST_FIXTURE(ScPivotTableFiltersTest, testPivotTableCompactLayoutXLSX) testThis(*getScDoc()); } +CPPUNIT_TEST_FIXTURE(ScPivotTableFiltersTest, + testPivotTableXLSX_OutOfSyncPivotTableCachedDefinitionImport) +{ + // This tests that a out-of-sync sheet data and pivot table cached definitions + // still get imported correctly as expected. + + // It is perfectly valid that the sheet data and pivot table are out-of-sync, + // but even if the sheet data is heavily modified, the pivot table should still + // be imported. + + // The test document has columns named A-K where only A and K are used in the + // pivot table. The columns B-J were removed in the sheet data, but the pivot table + // was not updated, so the cached data still has those and the pivot table + // description still relies on those columns to be present. + + auto testThis = [](ScDocument& rDocument) { + ScDPCollection* pDPs = rDocument.GetDPCollection(); + CPPUNIT_ASSERT_MESSAGE("Failed to get a live ScDPCollection instance.", pDPs); + CPPUNIT_ASSERT_EQUAL_MESSAGE("There should be exactly one pivot table instance.", size_t(1), + pDPs->GetCount()); + + const ScDPObject* pDPObj = &(*pDPs)[0]; + CPPUNIT_ASSERT(pDPObj); + ScDPSaveData* pSaveData = pDPObj->GetSaveData(); + CPPUNIT_ASSERT(pSaveData); + + // Do we have a dim named "A" + ScDPSaveDimension* pSaveDimA = pSaveData->GetExistingDimensionByName(u"A"); + CPPUNIT_ASSERT(pSaveDimA); + + // Do we have a dim named "K" + ScDPSaveDimension* pSaveDimK = pSaveData->GetExistingDimensionByName(u"K"); + CPPUNIT_ASSERT(pSaveDimK); + + // Check the headers + CPPUNIT_ASSERT_EQUAL(OUString("K"), rDocument.GetString(ScAddress(0, 2, 0))); // A3 + CPPUNIT_ASSERT_EQUAL(OUString("Sum of A"), rDocument.GetString(ScAddress(1, 2, 0))); //B3 + + // Check the values + CPPUNIT_ASSERT_EQUAL(OUString("1"), rDocument.GetString(ScAddress(0, 3, 0))); //A4 + CPPUNIT_ASSERT_EQUAL(OUString("2"), rDocument.GetString(ScAddress(0, 4, 0))); //A5 + CPPUNIT_ASSERT_EQUAL(OUString("5"), rDocument.GetString(ScAddress(1, 3, 0))); //B4 + CPPUNIT_ASSERT_EQUAL(OUString("5"), rDocument.GetString(ScAddress(1, 4, 0))); //B5 + }; + + // test document with sheet data and pivot table in sync + createScDoc("xlsx/PivotTable_CachedDefinitionAndDataInSync.xlsx"); + testThis(*getScDoc()); + + // test document with sheet data and pivot table in out-of-sync - B-J columns removed, + // but the pivot table cache still hass all the data + createScDoc( + "xlsx/PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithCacheData.xlsx"); + testThis(*getScDoc()); + + // test document with sheet data and pivot table in out-of-sync - B-J columns removed, + // but the pivot table cache is not saved, only the cached definitions are available + createScDoc("xlsx/" + "PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithoutCacheData." + "xlsx"); + testThis(*getScDoc()); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/qa/unit/scshapetest.cxx b/sc/qa/unit/scshapetest.cxx index b5083544f1..5e48270053 100644 --- a/sc/qa/unit/scshapetest.cxx +++ b/sc/qa/unit/scshapetest.cxx @@ -1207,6 +1207,30 @@ CPPUNIT_TEST_FIXTURE(ScShapeTest, testTdf154821_shape_in_group) CPPUNIT_ASSERT_RECTANGLE_EQUAL_WITH_TOLERANCE(aRectOrig, aRectReload, 1); } +CPPUNIT_TEST_FIXTURE(ScShapeTest, testTdf160003_copy_page_anchored) +{ + // Load a document, which has a chart anchored to page on sheet2. Copy&paste to other document + // had lost the chart object. + createScDoc("ods/tdf160003_page_anchored_object.ods"); + + // copy range with chart + goToCell("$Sheet2.$A$1:$L$24"); + dispatchCommand(mxComponent, ".uno:Copy", {}); + + // close document and create new one + createScDoc(); + + // paste clipboard + goToCell("$Sheet1.$A$1"); + dispatchCommand(mxComponent, ".uno:Paste", {}); + + // Make sure the chart object exists. + ScDocument* pDoc = getScDoc(); + ScDrawLayer* pDrawLayer = pDoc->GetDrawLayer(); + const SdrPage* pPage = pDrawLayer->GetPage(0); + CPPUNIT_ASSERT_EQUAL(size_t(1), pPage->GetObjCount()); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/qa/unit/ucalc_solver.cxx b/sc/qa/unit/ucalc_solver.cxx index 47770ec0c0..7834597e9c 100644 --- a/sc/qa/unit/ucalc_solver.cxx +++ b/sc/qa/unit/ucalc_solver.cxx @@ -163,4 +163,33 @@ CPPUNIT_TEST_FIXTURE(SolverTest, tdf156815) CPPUNIT_ASSERT_EQUAL(OUString("$NewName.$B$2"), aConstraints[0].aRightStr); } +// Tests if settings for the DEPS and SCO solvers are kept in the file +CPPUNIT_TEST_FIXTURE(SolverTest, tdf158735) +{ + createScDoc("ods/tdf158735.ods"); + ScDocument* pDoc = getScDoc(); + + // Test the non-default values of the DEPS model + ScTable* pTable = pDoc->FetchTable(0); + std::shared_ptr<sc::SolverSettings> pSettings = pTable->GetSolverSettings(); + CPPUNIT_ASSERT(pSettings); + CPPUNIT_ASSERT_EQUAL(OUString("com.sun.star.comp.Calc.NLPSolver.DEPSSolverImpl"), + pSettings->GetParameter(SP_LO_ENGINE)); + CPPUNIT_ASSERT_EQUAL(OUString("0.45"), pSettings->GetParameter(SP_AGENT_SWITCH_RATE)); + CPPUNIT_ASSERT_EQUAL(OUString("0.85"), pSettings->GetParameter(SP_CROSSOVER_PROB)); + CPPUNIT_ASSERT_EQUAL(OUString("1500"), pSettings->GetParameter(SP_LEARNING_CYCLES)); + CPPUNIT_ASSERT_EQUAL(OUString("0"), pSettings->GetParameter(SP_ENHANCED_STATUS)); + + // Test the non-default values of the SCO model + pTable = pDoc->FetchTable(1); + pSettings = pTable->GetSolverSettings(); + CPPUNIT_ASSERT(pSettings); + CPPUNIT_ASSERT_EQUAL(OUString("com.sun.star.comp.Calc.NLPSolver.SCOSolverImpl"), + pSettings->GetParameter(SP_LO_ENGINE)); + CPPUNIT_ASSERT_EQUAL(OUString("180"), pSettings->GetParameter(SP_LIBRARY_SIZE)); + CPPUNIT_ASSERT_EQUAL(OUString("0.00055"), pSettings->GetParameter(SP_STAGNATION_TOLERANCE)); + CPPUNIT_ASSERT_EQUAL(OUString("1"), pSettings->GetParameter(SP_RND_STARTING_POINT)); + CPPUNIT_ASSERT_EQUAL(OUString("80"), pSettings->GetParameter(SP_STAGNATION_LIMIT)); +} + CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sc/source/core/data/SolverSettings.cxx b/sc/source/core/data/SolverSettings.cxx index 60eb747f55..64badb8c27 100644 --- a/sc/source/core/data/SolverSettings.cxx +++ b/sc/source/core/data/SolverSettings.cxx @@ -11,6 +11,7 @@ #include <global.hxx> #include <table.hxx> #include <docsh.hxx> +#include <rtl/math.hxx> #include <solverutil.hxx> #include <unotools/charclass.hxx> #include <SolverSettings.hxx> @@ -73,6 +74,28 @@ void SolverSettings::Initialize() ReadParamValue(SP_LIMIT_BBDEPTH, m_sLimitBBDepth); ReadParamValue(SP_TIMEOUT, m_sTimeout); ReadParamValue(SP_ALGORITHM, m_sAlgorithm); + // Engine options common for DEPS and SCO + ReadParamValue(SP_SWARM_SIZE, m_sSwarmSize); + ReadParamValue(SP_LEARNING_CYCLES, m_sLearningCycles); + ReadParamValue(SP_GUESS_VARIABLE_RANGE, m_sGuessVariableRange); + ReadDoubleParamValue(SP_VARIABLE_RANGE_THRESHOLD, m_sVariableRangeThreshold); + ReadParamValue(SP_ACR_COMPARATOR, m_sUseACRComparator); + ReadParamValue(SP_RND_STARTING_POINT, m_sUseRandomStartingPoint); + ReadParamValue(SP_STRONGER_PRNG, m_sUseStrongerPRNG); + ReadParamValue(SP_STAGNATION_LIMIT, m_sStagnationLimit); + ReadDoubleParamValue(SP_STAGNATION_TOLERANCE, m_sTolerance); + ReadParamValue(SP_ENHANCED_STATUS, m_sEnhancedSolverStatus); + // DEPS Options + ReadDoubleParamValue(SP_AGENT_SWITCH_RATE, m_sAgentSwitchRate); + ReadDoubleParamValue(SP_SCALING_MIN, m_sScalingFactorMin); + ReadDoubleParamValue(SP_SCALING_MAX, m_sScalingFactorMax); + ReadDoubleParamValue(SP_CROSSOVER_PROB, m_sCrossoverProbability); + ReadDoubleParamValue(SP_COGNITIVE_CONST, m_sCognitiveConstant); + ReadDoubleParamValue(SP_SOCIAL_CONST, m_sSocialConstant); + ReadDoubleParamValue(SP_CONSTRICTION_COEFF, m_sConstrictionCoeff); + ReadDoubleParamValue(SP_MUTATION_PROB, m_sMutationProbability); + // SCO Options + ReadParamValue(SP_LIBRARY_SIZE, m_sLibrarySize); } // Returns the current value of the parameter in the object as a string @@ -119,6 +142,63 @@ OUString SolverSettings::GetParameter(SolverParameter eParam) case SP_ALGORITHM: return m_sAlgorithm; break; + case SP_SWARM_SIZE: + return m_sSwarmSize; + break; + case SP_LEARNING_CYCLES: + return m_sLearningCycles; + break; + case SP_GUESS_VARIABLE_RANGE: + return m_sGuessVariableRange; + break; + case SP_VARIABLE_RANGE_THRESHOLD: + return m_sVariableRangeThreshold; + break; + case SP_ACR_COMPARATOR: + return m_sUseACRComparator; + break; + case SP_RND_STARTING_POINT: + return m_sUseRandomStartingPoint; + break; + case SP_STRONGER_PRNG: + return m_sUseStrongerPRNG; + break; + case SP_STAGNATION_LIMIT: + return m_sStagnationLimit; + break; + case SP_STAGNATION_TOLERANCE: + return m_sTolerance; + break; + case SP_ENHANCED_STATUS: + return m_sEnhancedSolverStatus; + break; + case SP_AGENT_SWITCH_RATE: + return m_sAgentSwitchRate; + break; + case SP_SCALING_MIN: + return m_sScalingFactorMin; + break; + case SP_SCALING_MAX: + return m_sScalingFactorMax; + break; + case SP_CROSSOVER_PROB: + return m_sCrossoverProbability; + break; + case SP_COGNITIVE_CONST: + return m_sCognitiveConstant; + break; + case SP_SOCIAL_CONST: + return m_sSocialConstant; + break; + case SP_CONSTRICTION_COEFF: + return m_sConstrictionCoeff; + break; + case SP_MUTATION_PROB: + return m_sMutationProbability; + break; + case SP_LIBRARY_SIZE: + return m_sLibrarySize; + break; default: return ""; } @@ -188,6 +268,75 @@ void SolverSettings::SetParameter(SolverParameter eParam, OUString sValue) m_sAlgorithm = sValue; } break; + case SP_SWARM_SIZE: + m_sSwarmSize = sValue; + break; + case SP_LEARNING_CYCLES: + m_sLearningCycles = sValue; + break; + case SP_GUESS_VARIABLE_RANGE: + m_sGuessVariableRange = sValue; + break; + case SP_VARIABLE_RANGE_THRESHOLD: + m_sVariableRangeThreshold = sValue; + break; + case SP_ACR_COMPARATOR: + { + if (sValue == "0" || sValue == "1") + m_sUseACRComparator = sValue; + } + break; + case SP_RND_STARTING_POINT: + { + if (sValue == "0" || sValue == "1") + m_sUseRandomStartingPoint = sValue; + } + break; + case SP_STRONGER_PRNG: + { + if (sValue == "0" || sValue == "1") + m_sUseStrongerPRNG = sValue; + } + break; + case SP_STAGNATION_LIMIT: + m_sStagnationLimit = sValue; + break; + case SP_STAGNATION_TOLERANCE: + m_sTolerance = sValue; + break; + case SP_ENHANCED_STATUS: + { + if (sValue == "0" || sValue == "1") + m_sEnhancedSolverStatus = sValue; + } + break; + case SP_AGENT_SWITCH_RATE: + m_sAgentSwitchRate = sValue; + break; + case SP_SCALING_MIN: + m_sScalingFactorMin = sValue; + break; + case SP_SCALING_MAX: + m_sScalingFactorMax = sValue; + break; + case SP_CROSSOVER_PROB: + m_sCrossoverProbability = sValue; + break; + case SP_COGNITIVE_CONST: + m_sCognitiveConstant = sValue; + break; + case SP_SOCIAL_CONST: + m_sSocialConstant = sValue; + break; + case SP_CONSTRICTION_COEFF: + m_sConstrictionCoeff = sValue; + break; + case SP_MUTATION_PROB: + m_sMutationProbability = sValue; + break; + case SP_LIBRARY_SIZE: + m_sLibrarySize = sValue; + break; default: break; } @@ -321,12 +470,35 @@ void SolverSettings::SaveSolverSettings() sal_Int32 nConstrCount = m_aConstraints.size(); WriteParamValue(SP_CONSTR_COUNT, OUString::number(nConstrCount)); + // Solver engine options WriteParamValue(SP_INTEGER, m_sInteger); WriteParamValue(SP_NON_NEGATIVE, m_sNonNegative); WriteParamValue(SP_EPSILON_LEVEL, m_sEpsilonLevel); WriteParamValue(SP_LIMIT_BBDEPTH, m_sLimitBBDepth); WriteParamValue(SP_TIMEOUT, m_sTimeout); WriteParamValue(SP_ALGORITHM, m_sAlgorithm); + // Engine options common for DEPS and SCO + WriteParamValue(SP_SWARM_SIZE, m_sSwarmSize); + WriteParamValue(SP_LEARNING_CYCLES, m_sLearningCycles); + WriteParamValue(SP_GUESS_VARIABLE_RANGE, m_sGuessVariableRange); + WriteDoubleParamValue(SP_VARIABLE_RANGE_THRESHOLD, m_sVariableRangeThreshold); + WriteParamValue(SP_ACR_COMPARATOR, m_sUseACRComparator); + WriteParamValue(SP_RND_STARTING_POINT, m_sUseRandomStartingPoint); + WriteParamValue(SP_STRONGER_PRNG, m_sUseStrongerPRNG); + WriteParamValue(SP_STAGNATION_LIMIT, m_sStagnationLimit); + WriteDoubleParamValue(SP_STAGNATION_TOLERANCE, m_sTolerance); + WriteParamValue(SP_ENHANCED_STATUS, m_sEnhancedSolverStatus); + // DEPS Options + WriteDoubleParamValue(SP_AGENT_SWITCH_RATE, m_sAgentSwitchRate); + WriteDoubleParamValue(SP_SCALING_MIN, m_sScalingFactorMin); + WriteDoubleParamValue(SP_SCALING_MAX, m_sScalingFactorMax); + WriteDoubleParamValue(SP_CROSSOVER_PROB, m_sCrossoverProbability); + WriteDoubleParamValue(SP_COGNITIVE_CONST, m_sCognitiveConstant); + WriteDoubleParamValue(SP_SOCIAL_CONST, m_sSocialConstant); + WriteDoubleParamValue(SP_CONSTRICTION_COEFF, m_sConstrictionCoeff); + WriteDoubleParamValue(SP_MUTATION_PROB, m_sMutationProbability); + // SCO Options + WriteParamValue(SP_LIBRARY_SIZE, m_sLibrarySize); if (m_pDocShell) m_pDocShell->SetDocumentModified(); @@ -354,6 +526,26 @@ bool SolverSettings::ReadParamValue(SolverParameter eParam, OUString& rValue, bo return false; } +// Reads a parameter value of type 'double' from the named range and into rValue +bool SolverSettings::ReadDoubleParamValue(SolverParameter eParam, OUString& rValue) +{ + const auto iter = m_mNamedRanges.find(eParam); + assert(iter != m_mNamedRanges.end()); + OUString sRange = iter->second; + ScRangeData* pRangeData + = m_pRangeName->findByUpperName(ScGlobal::getCharClass().uppercase(sRange)); + if (pRangeData) + { + OUString sLocalizedValue = pRangeData->GetSymbol(); + double fValue = rtl::math::stringToDouble(sLocalizedValue, + ScGlobal::getLocaleData().getNumDecimalSep()[0], + ScGlobal::getLocaleData().getNumThousandSep()[0]); + rValue = OUString::number(fValue); + return true; + } + return false; +} + /* Writes a parameter value to the file as a named range. * Argument bQuoted indicates whether the value should be enclosed with quotes or not (used * for string expressions that must be enclosed with quotes) @@ -375,6 +567,22 @@ void SolverSettings::WriteParamValue(SolverParameter eParam, OUString sValue, bo m_pRangeName->insert(pNewEntry); } +// Writes a parameter value of type 'double' to the file as a named range +// The argument 'sValue' uses dot as decimal separator and needs to be localized before +// being written to the file +void SolverSettings::WriteDoubleParamValue(SolverParameter eParam, std::u16string_view sValue) +{ + const auto iter = m_mNamedRanges.find(eParam); + assert(iter != m_mNamedRanges.end()); + OUString sRange = iter->second; + double fValue = rtl::math::stringToDouble(sValue, '.', ','); + OUString sLocalizedValue = rtl::math::doubleToUString( + fValue, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, + ScGlobal::getLocaleData().getNumDecimalSep()[0], true); + ScRangeData* pNewEntry = new ScRangeData(m_rDoc, sRange, sLocalizedValue); + m_pRangeName->insert(pNewEntry); +} + void SolverSettings::GetEngineOptions(css::uno::Sequence<css::beans::PropertyValue>& aOptions) { sal_Int32 nOptionsSize = aOptions.getLength(); @@ -398,6 +606,12 @@ void SolverSettings::GetEngineOptions(css::uno::Sequence<css::beans::PropertyVal pParamValues[i] = css::beans::PropertyValue(sLOParamName, -1, nValue, css::beans::PropertyState_DIRECT_VALUE); } + if (sParamType == "double") + { + css::uno::Any fValue(sParamValue.toDouble()); + pParamValues[i] = css::beans::PropertyValue(sLOParamName, -1, fValue, + css::beans::PropertyState_DIRECT_VALUE); + } if (sParamType == "bool") { // The parameter NonNegative is a special case for MS compatibility @@ -438,6 +652,12 @@ void SolverSettings::SetEngineOptions(css::uno::Sequence<css::beans::PropertyVal aProp.Value >>= nValue; SetParameter(eParamId, OUString::number(nValue)); } + if (sParamType == "double") + { + double fValue = 0; + aProp.Value >>= fValue; + SetParameter(eParamId, OUString::number(fValue)); + } if (sParamType == "bool") { bool bValue = false; diff --git a/sc/source/core/data/clipcontext.cxx b/sc/source/core/data/clipcontext.cxx index ce6974d423..be145f9954 100644 --- a/sc/source/core/data/clipcontext.cxx +++ b/sc/source/core/data/clipcontext.cxx @@ -403,8 +403,8 @@ bool CopyFromClipContext::isDateCell( const ScColumn& rCol, SCROW nRow ) const } CopyToClipContext::CopyToClipContext( - ScDocument& rDoc, bool bKeepScenarioFlags) : - ClipContextBase(rDoc), mbKeepScenarioFlags(bKeepScenarioFlags) {} + ScDocument& rDoc, bool bKeepScenarioFlags, bool bCopyChartRanges) : + ClipContextBase(rDoc), mbKeepScenarioFlags(bKeepScenarioFlags), mbCopyChartRanges(bCopyChartRanges) {} CopyToClipContext::~CopyToClipContext() {} diff --git a/sc/source/core/data/column.cxx b/sc/source/core/data/column.cxx index 5bae9b7b9d..ceadfc2f3e 100644 --- a/sc/source/core/data/column.cxx +++ b/sc/source/core/data/column.cxx @@ -880,14 +880,16 @@ public: void ScColumn::CopyToClip( sc::CopyToClipContext& rCxt, SCROW nRow1, SCROW nRow2, ScColumn& rColumn ) const { - pAttrArray->CopyArea( nRow1, nRow2, 0, *rColumn.pAttrArray, - rCxt.isKeepScenarioFlags() ? (ScMF::All & ~ScMF::Scenario) : ScMF::All ); + if (!rCxt.isCopyChartRanges()) // No need to copy attributes for chart ranges + pAttrArray->CopyArea( nRow1, nRow2, 0, *rColumn.pAttrArray, + rCxt.isKeepScenarioFlags() ? (ScMF::All & ~ScMF::Scenario) : ScMF::All ); { CopyToClipHandler aFunc(GetDoc(), *this, rColumn, rCxt.getBlockPosition(rColumn.nTab, rColumn.nCol)); sc::ParseBlock(maCells.begin(), maCells, aFunc, nRow1, nRow2); } + if (!rCxt.isCopyChartRanges()) // No need to copy attributes for chart ranges { CopyTextAttrToClipHandler aFunc(rColumn.maCellTextAttrs); sc::ParseBlock(maCellTextAttrs.begin(), maCellTextAttrs, aFunc, nRow1, nRow2); diff --git a/sc/source/core/data/column3.cxx b/sc/source/core/data/column3.cxx index 7902722638..f0f4cc8326 100644 --- a/sc/source/core/data/column3.cxx +++ b/sc/source/core/data/column3.cxx @@ -2582,10 +2582,21 @@ class FilterEntriesHandler if (bIsEmptyCell) { - if (!mrFilterEntries.mbHasEmpties) + if (mbFilteredRow) { - mrFilterEntries.push_back(ScTypedStrData(OUString(), 0.0, 0.0, ScTypedStrData::Standard, false, mbFilteredRow)); - mrFilterEntries.mbHasEmpties = true; + if (!mrFilterEntries.mbHasHiddenEmpties) + { + mrFilterEntries.push_back(ScTypedStrData(OUString(), 0.0, 0.0, ScTypedStrData::Standard, false, true)); + mrFilterEntries.mbHasHiddenEmpties = true; + } + } + else + { + if (!mrFilterEntries.mbHasUnHiddenEmpties) + { + mrFilterEntries.push_back(ScTypedStrData(OUString(), 0.0, 0.0, ScTypedStrData::Standard, false, false)); + mrFilterEntries.mbHasUnHiddenEmpties = true; + } } return; } diff --git a/sc/source/core/data/document.cxx b/sc/source/core/data/document.cxx index 15bb28fe61..2bca3a798f 100644 --- a/sc/source/core/data/document.cxx +++ b/sc/source/core/data/document.cxx @@ -2218,6 +2218,7 @@ void ScDocument::CopyToClip(const ScClipParam& rClipParam, sc::CopyToClipContext aCxt(*pClipDoc, bKeepScenarioFlags); CopyRangeNamesToClip(pClipDoc, aClipRange, pMarks); + // 1. Copy selected cells for (SCTAB i = 0; i < nEndTab; ++i) { if (!maTabs[i] || i >= pClipDoc->GetTableCount() || !pClipDoc->maTabs[i]) @@ -2227,12 +2228,17 @@ void ScDocument::CopyToClip(const ScClipParam& rClipParam, continue; maTabs[i]->CopyToClip(aCxt, rClipParam.maRanges, pClipDoc->maTabs[i].get()); + } - if (mpDrawLayer && bIncludeObjects) + // 2. Copy drawing objects in the selection. Do in after the first "copy cells" pass, because + // the embedded objects (charts) coud reference cells from tabs not (yet) copied; doing it now + // allows to know what is already copied, to not owerwrite attributes of already copied data. + if (mpDrawLayer && bIncludeObjects) + { + for (SCTAB i = 0; i < nEndTab; ++i) { - // also copy drawing objects - tools::Rectangle aObjRect = GetMMRect( - aClipRange.aStart.Col(), aClipRange.aStart.Row(), aClipRange.aEnd.Col(), aClipRange.aEnd.Row(), i); + tools::Rectangle aObjRect = GetMMRect(aClipRange.aStart.Col(), aClipRange.aStart.Row(), + aClipRange.aEnd.Col(), aClipRange.aEnd.Row(), i); mpDrawLayer->CopyToClip(pClipDoc, i, aObjRect); } } diff --git a/sc/source/core/data/drwlayer.cxx b/sc/source/core/data/drwlayer.cxx index 3f98fc770a..d5f2cc09b9 100644 --- a/sc/source/core/data/drwlayer.cxx +++ b/sc/source/core/data/drwlayer.cxx @@ -88,6 +88,7 @@ #include <docpool.hxx> #include <detfunc.hxx> #include <basegfx/matrix/b2dhommatrix.hxx> +#include <clipcontext.hxx> #include <clipparam.hxx> #include <memory> @@ -1786,76 +1787,104 @@ void ScDrawLayer::CopyToClip( ScDocument* pClipDoc, SCTAB nTab, const tools::Rec ScRange aClipRange = lcl_getClipRangeFromClipDoc(pClipDoc, nTab); SdrObjListIter aIter( pSrcPage, SdrIterMode::Flat ); - SdrObject* pOldObject = aIter.Next(); - while (pOldObject) + while (SdrObject* pOldObject = aIter.Next()) { + // do not copy internal objects (detective) and note captions + if (pOldObject->GetLayer() == SC_LAYER_INTERN) + continue; + + const ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pOldObject); + if (IsNoteCaption(pObjData)) + continue; + // Catch objects where the object itself is inside the rectangle to be copied. bool bObjectInArea = rRange.Contains(pOldObject->GetCurrentBoundRect()); // Catch objects whose anchor is inside the rectangle to be copied. - const ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pOldObject); - if (pObjData) - bObjectInArea = bObjectInArea || aClipRange.Contains(pObjData->maStart); + if (!bObjectInArea && pObjData) + bObjectInArea = aClipRange.Contains(pObjData->maStart); + if (!bObjectInArea) + continue; + + if (!pDestModel) + { + pDestModel = pClipDoc->GetDrawLayer(); // does the document already have a drawing layer? + if (!pDestModel) + { + // allocate drawing layer in clipboard document only if there are objects to copy - // do not copy internal objects (detective) and note captions - if (bObjectInArea && pOldObject->GetLayer() != SC_LAYER_INTERN - && !IsNoteCaption(pOldObject)) + pClipDoc->InitDrawLayer(); //TODO: create contiguous pages + pDestModel = pClipDoc->GetDrawLayer(); + } + if (pDestModel) + pDestPage = pDestModel->GetPage(static_cast<sal_uInt16>(nTab)); + } + + OSL_ENSURE(pDestPage, "no page"); + if (pDestPage) { - if ( !pDestModel ) + // Clone to target SdrModel + rtl::Reference<SdrObject> pNewObject(pOldObject->CloneSdrObject(*pDestModel)); + uno::Reference< chart2::XChartDocument > xOldChart( ScChartHelper::GetChartFromSdrObject( pOldObject ) ); + if(!xOldChart.is())//#i110034# do not move charts as they lose all their data references otherwise { - pDestModel = pClipDoc->GetDrawLayer(); // does the document already have a drawing layer? - if ( !pDestModel ) + if (pObjData) { - // allocate drawing layer in clipboard document only if there are objects to copy - - pClipDoc->InitDrawLayer(); //TODO: create contiguous pages - pDestModel = pClipDoc->GetDrawLayer(); + // The object is anchored to cell. The position is determined by the start + // address. Copying into the clipboard does not change the anchor. + // ToDo: Adapt Offset relative to anchor cell size for cell anchored. + // ToDo: Adapt Offset and size for cell-anchored with resize objects. + // ToDo: Exclude object from resize if disallowed at object. + } + else + { + // The object is anchored to page. We make its position so, that the + // cell behind the object will have the same address in clipboard document as + // in source document. So we will be able to reconstruct the original cell + // address from position when pasting the object. + tools::Rectangle aObjRect = pOldObject->GetSnapRect(); + ScRange aPseudoAnchor = pDoc->GetRange(nTab, aObjRect, true /*bHiddenAsZero*/); + tools::Rectangle aSourceCellRect + = GetCellRect(*pDoc, aPseudoAnchor.aStart, false /*bMergedCell*/); + tools::Rectangle aDestCellRect + = GetCellRect(*pClipDoc, aPseudoAnchor.aStart, false); + Point aMove = aDestCellRect.TopLeft() - aSourceCellRect.TopLeft(); + pNewObject->NbcMove(Size(aMove.getX(), aMove.getY())); } - if (pDestModel) - pDestPage = pDestModel->GetPage( static_cast<sal_uInt16>(nTab) ); } - OSL_ENSURE( pDestPage, "no page" ); - if (pDestPage) + pDestPage->InsertObject(pNewObject.get()); + + // Store the chart's source data to the clipboad document, even when it's out of the + // copied range. It will be ignored when pasted to the same document; when pasted to + // another document, ScDocument::mpClipParam will provide the actually copied ranges, + // and the data copied here will be used to break connection and switch to own data + // in ScDrawLayer::CopyFromClip. + if (xOldChart && !xOldChart->hasInternalDataProvider()) { - // Clone to target SdrModel - rtl::Reference<SdrObject> pNewObject(pOldObject->CloneSdrObject(*pDestModel)); - uno::Reference< chart2::XChartDocument > xOldChart( ScChartHelper::GetChartFromSdrObject( pOldObject ) ); - if(!xOldChart.is())//#i110034# do not move charts as they lose all their data references otherwise + sc::CopyToClipContext aCxt(*pClipDoc, false, true); + OUString aChartName = static_cast<SdrOle2Obj*>(pOldObject)->GetPersistName(); + std::vector<ScRangeList> aRangesVector; + pDoc->GetChartRanges(aChartName, aRangesVector, *pDoc); + for (const ScRangeList& ranges : aRangesVector) { - if (pObjData) - { - // The object is anchored to cell. The position is determined by the start - // address. Copying into the clipboard does not change the anchor. - // ToDo: Adapt Offset relative to anchor cell size for cell anchored. - // ToDo: Adapt Offset and size for cell-anchored with resize objects. - // ToDo: Exclude object from resize if disallowed at object. - } - else + for (const ScRange& r : ranges) { - // The object is anchored to page. We make its position so, that the - // cell behind the object will have the same address in clipboard document as - // in source document. So we will be able to reconstruct the original cell - // address from position when pasting the object. - tools::Rectangle aObjRect = pOldObject->GetSnapRect(); - ScRange aPseudoAnchor - = pDoc->GetRange(nTab, aObjRect, true /*bHiddenAsZero*/); - tools::Rectangle aSourceCellRect - = GetCellRect(*pDoc, aPseudoAnchor.aStart, false /*bMergedCell*/); - tools::Rectangle aDestCellRect - = GetCellRect(*pClipDoc, aPseudoAnchor.aStart, false); - Point aMove = aDestCellRect.TopLeft() - aSourceCellRect.TopLeft(); - pNewObject->NbcMove(Size(aMove.getX(), aMove.getY())); + for (SCTAB i = r.aStart.Tab(); i <= r.aEnd.Tab(); ++i) + { + ScTable* pTab = pDoc->FetchTable(i); + ScTable* pClipTab = pClipDoc->FetchTable(i); + if (!pTab || !pClipTab) + continue; + pTab->CopyToClip(aCxt, r.aStart.Col(), r.aStart.Row(), r.aEnd.Col(), + r.aEnd.Row(), pClipTab); + } } } - - pDestPage->InsertObject( pNewObject.get() ); - - // no undo needed in clipboard document - // charts are not updated } - } - pOldObject = aIter.Next(); + // no undo needed in clipboard document + // charts are not updated + } } } @@ -1990,7 +2019,7 @@ void ScDrawLayer::CopyFromClip(ScDrawLayer* pClipModel, SCTAB nSourceTab, } else // Object is anchored to page. { - aSrcObjStart = pClipDoc->GetRange(nClipTab, pOldObject->GetCurrentBoundRect()).aStart; + aSrcObjStart = pClipDoc->GetRange(nSourceTab, pOldObject->GetCurrentBoundRect()).aStart; } if (!rSourceRange.Contains(aSrcObjStart)) { @@ -2895,16 +2924,15 @@ ScDrawObjData* ScDrawLayer::GetObjDataTab( SdrObject* pObj, SCTAB nTab ) return pData; } -bool ScDrawLayer::IsNoteCaption( SdrObject* pObj ) +bool ScDrawLayer::IsNoteCaption(const ScDrawObjData* pData) { - ScDrawObjData* pData = pObj ? GetObjData( pObj ) : nullptr; return pData && pData->meType == ScDrawObjData::CellNote; } ScDrawObjData* ScDrawLayer::GetNoteCaptionData( SdrObject* pObj, SCTAB nTab ) { - ScDrawObjData* pData = pObj ? GetObjDataTab( pObj, nTab ) : nullptr; - return (pData && pData->meType == ScDrawObjData::CellNote) ? pData : nullptr; + ScDrawObjData* pData = GetObjDataTab(pObj, nTab); + return IsNoteCaption(pData) ? pData : nullptr; } ScMacroInfo* ScDrawLayer::GetMacroInfo( SdrObject* pObj, bool bCreate ) diff --git a/sc/source/core/data/fillinfo.cxx b/sc/source/core/data/fillinfo.cxx index 4a573e1c21..659a539799 100644 --- a/sc/source/core/data/fillinfo.cxx +++ b/sc/source/core/data/fillinfo.cxx @@ -296,7 +296,7 @@ bool handleConditionalFormat(ScConditionalFormatList& rCondFormList, const ScCon ScCondFormatData aData = pCondForm->GetData( pInfo->maCell, rAddr); - if (!aData.aStyleName.isEmpty()) + if (!bAnyCondition && !aData.aStyleName.isEmpty()) { SfxStyleSheetBase* pStyleSheet = pStlPool->Find( aData.aStyleName, SfxStyleFamily::Para ); @@ -337,7 +337,7 @@ bool handleConditionalFormat(ScConditionalFormatList& rCondFormList, const ScCon pTableInfo->addIconSetInfo(std::move(aData.pIconSet)); } - if (pInfo->mxColorScale && pInfo->pIconSet && pInfo->pDataBar) + if (bAnyCondition && pInfo->mxColorScale && pInfo->pIconSet && pInfo->pDataBar) break; } diff --git a/sc/source/filter/excel/excel.cxx b/sc/source/filter/excel/excel.cxx index 8420cc696a..c92ef75d69 100644 --- a/sc/source/filter/excel/excel.cxx +++ b/sc/source/filter/excel/excel.cxx @@ -183,7 +183,9 @@ ErrCode ScFormatFilterPluginImpl::ScImportExcel( SfxMedium& rMedium, ScDocument* tools::SvRef<SotStorageStream> xDRMStrm = ScfTools::OpenStorageStreamRead(xRootStrg, "\011DRMContent"); if (xDRMStrm.is()) { - xRootStrg = lcl_DRMDecrypt(rMedium, xRootStrg, aNewStorageStrm); + auto pDecryptedStorage = lcl_DRMDecrypt(rMedium, xRootStrg, aNewStorageStrm); + if (pDecryptedStorage) + xRootStrg = pDecryptedStorage; } // try to open the "Book" stream diff --git a/sc/source/filter/html/htmlexp.cxx b/sc/source/filter/html/htmlexp.cxx index 2a3cb6a4f8..4413d668b4 100644 --- a/sc/source/filter/html/htmlexp.cxx +++ b/sc/source/filter/html/htmlexp.cxx @@ -87,6 +87,9 @@ #include <com/sun/star/document/XDocumentPropertiesSupplier.hpp> #include <rtl/strbuf.hxx> #include <officecfg/Office/Common.hxx> +#include <tools/json_writer.hxx> +#include <svl/numformat.hxx> +#include <svl/zformat.hxx> using ::editeng::SvxBorderLine; using namespace ::com::sun::star; @@ -671,6 +674,20 @@ void ScHTMLExport::WriteBody() } rStrm.WriteChar( '>' ); OUT_LF(); + + // A marker right after <body> can be used, so that data-sheets-* attributes are considered + // at all. This is disabled by default. + OString aMarker; + char* pEnv = getenv("SC_DEBUG_HTML_MARKER"); + if (pEnv) + { + aMarker = pEnv; + } + else if (comphelper::LibreOfficeKit::isActive()) + { + aMarker = "<google-sheets-html-origin/>"_ostr; + } + rStrm.WriteOString(aMarker); } if ( bAll ) @@ -1128,6 +1145,71 @@ void ScHTMLExport::WriteCell( sc::ColumnBlockPosition& rBlockPos, SCCOL nCol, SC aStrTD.append(HTMLOutFuncs::CreateTableDataOptionsValNum(bValueData, fVal, nFormat, *pFormatter, &aNonConvertibleChars)); + std::optional<tools::JsonWriter> oJson; + const SvNumberformat* pNumberFormat = nullptr; + if (bValueData) + { + if (nFormat) + { + const SvNumberformat* pFormatEntry = pFormatter->GetEntry(nFormat); + if (pFormatEntry) + { + OUString aNumStr = pFormatEntry->GetFormatstring(); + if (aNumStr == "BOOLEAN") + { + // 4 is boolean. + oJson.emplace(); + oJson->put("1", static_cast<sal_Int32>(4)); + oJson->put("4", static_cast<sal_Int32>(fVal)); + } + else + { + // 3 is number. + oJson.emplace(); + oJson->put("1", static_cast<sal_Int32>(3)); + oJson->put("3", static_cast<sal_Int32>(fVal)); + pNumberFormat = pFormatEntry; + } + } + } + + if (aCell.getType() == CELLTYPE_FORMULA) + { + // If it's a formula, then also emit that, grammar is R1C1 reference style. + OUString aFormula = aCell.getFormula()->GetFormula( + formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1); + aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_O_DSformula "=\"" + + HTMLOutFuncs::ConvertStringToHTML(aFormula) + "\""); + } + } + else + { + // 2 is text. + oJson.emplace(); + oJson->put("1", static_cast<sal_Int32>(2)); + oJson->put("2", pDoc->GetString(aPos)); + } + + if (oJson) + { + OUString aJsonString = OUString::fromUtf8(oJson->finishAndGetAsOString()); + aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_O_DSval "=\"" + + HTMLOutFuncs::ConvertStringToHTML(aJsonString) + "\""); + } + + if (pNumberFormat) + { + // 2 is a number format. + oJson.emplace(); + oJson->put("1", static_cast<sal_Int32>(2)); + oJson->put("2", pNumberFormat->GetFormatstring()); + // The number format is for a number. + oJson->put("3", static_cast<sal_Int32>(1)); + OUString aJsonString = OUString::fromUtf8(oJson->finishAndGetAsOString()); + aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_O_DSnum "=\"" + + HTMLOutFuncs::ConvertStringToHTML(aJsonString) + "\""); + } + TAG_ON(aStrTD.makeStringAndClear()); //write the note for this as the first thing in the tag diff --git a/sc/source/filter/html/htmlpars.cxx b/sc/source/filter/html/htmlpars.cxx index be957b1851..c9d53d93be 100644 --- a/sc/source/filter/html/htmlpars.cxx +++ b/sc/source/filter/html/htmlpars.cxx @@ -77,7 +77,6 @@ namespace /// data-sheets-value from google sheets, value is a JSON. void ParseDataSheetsValue(const OUString& rDataSheetsValue, std::optional<OUString>& rVal, std::optional<OUString>& rNum) { - // data-sheets-value from google sheets, value is a JSON. OString aEncodedOption = rDataSheetsValue.toUtf8(); const char* pEncodedOption = aEncodedOption.getStr(); std::stringstream aStream(pEncodedOption); @@ -98,6 +97,16 @@ void ParseDataSheetsValue(const OUString& rDataSheetsValue, std::optional<OUStri rNum = ";;@"; break; } + case 3: + { + // 3 is number. + it = aTree.find("3"); + if (it != aTree.not_found()) + { + rVal = OUString::fromUtf8(it->second.get_value<std::string>()); + } + break; + } case 4: { // 4 is boolean. @@ -112,6 +121,45 @@ void ParseDataSheetsValue(const OUString& rDataSheetsValue, std::optional<OUStri } } } + +/// data-sheets-numberformat from google sheets, value is a JSON. +void ParseDataSheetsNumberformat(const OUString& rDataSheetsValue, std::optional<OUString>& rNum) +{ + OString aEncodedOption = rDataSheetsValue.toUtf8(); + const char* pEncodedOption = aEncodedOption.getStr(); + std::stringstream aStream(pEncodedOption); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + // The "1" key describes the other keys. + auto it = aTree.find("1"); + if (it != aTree.not_found()) + { + int nType = std::stoi(it->second.get_value<std::string>()); + switch (nType) + { + case 2: + { + // 2 is number format. + it = aTree.find("2"); + if (it != aTree.not_found()) + { + // Leave the parse and a number language unspecified. + OUString aNum = ";;" + OUString::fromUtf8(it->second.get_value<std::string>()); + rNum = aNum; + } + break; + } + } + } +} + +/// data-sheets-formula from google sheets, grammar is R1C1 reference style. +void ParseDataSheetsFormula(const OUString& rDataSheetsFormula, std::optional<OUString>& rVal, + std::optional<formula::FormulaGrammar::Grammar>& rGrammar) +{ + rVal = rDataSheetsFormula; + rGrammar = formula::FormulaGrammar::GRAM_ENGLISH_XL_R1C1; +} } ScHTMLStyles::ScHTMLStyles() : maEmpty() {} @@ -941,6 +989,34 @@ IMPL_LINK( ScHTMLLayoutParser, HTMLImportHdl, HtmlImportInfo&, rInfo, void ) } } +void ScHTMLLayoutParser::HandleDataSheetsAttributes(const HTMLOptions& rOptions) +{ + for (const auto& rOption : rOptions) + { + switch (rOption.GetToken()) + { + case HtmlOptionId::DSVAL: + { + ParseDataSheetsValue(rOption.GetString(), mxActEntry->pValStr, mxActEntry->pNumStr); + break; + } + case HtmlOptionId::DSNUM: + { + ParseDataSheetsNumberformat(rOption.GetString(), mxActEntry->pNumStr); + break; + } + case HtmlOptionId::DSFORMULA: + { + ParseDataSheetsFormula(rOption.GetString(), mxActEntry->moFormulaStr, + mxActEntry->moFormulaGrammar); + break; + } + default: + break; + } + } +} + void ScHTMLLayoutParser::TableDataOn( HtmlImportInfo* pInfo ) { if ( bInCell ) @@ -1021,15 +1097,12 @@ void ScHTMLLayoutParser::TableDataOn( HtmlImportInfo* pInfo ) mxActEntry->pNumStr = rOption.GetString(); } break; - case HtmlOptionId::DSVAL: - { - ParseDataSheetsValue(rOption.GetString(), mxActEntry->pValStr, mxActEntry->pNumStr); - } - break; default: break; } } + HandleDataSheetsAttributes(rOptions); + mxActEntry->nCol = nColCnt; mxActEntry->nRow = nRowCnt; mxActEntry->nTab = nTable; @@ -1039,6 +1112,12 @@ void ScHTMLLayoutParser::TableDataOn( HtmlImportInfo* pInfo ) SvxHorJustifyItem( SvxCellHorJustify::Center, ATTR_HOR_JUSTIFY) ); } +void ScHTMLLayoutParser::SpanOn(HtmlImportInfo* pInfo) +{ + const HTMLOptions& rOptions = static_cast<HTMLParser*>(pInfo->pParser)->GetOptions(); + HandleDataSheetsAttributes(rOptions); +} + void ScHTMLLayoutParser::TableRowOn( const HtmlImportInfo* pInfo ) { if ( nColCnt > nColCntStart ) @@ -1569,6 +1648,11 @@ void ScHTMLLayoutParser::ProcToken( HtmlImportInfo* pInfo ) TableDataOn( pInfo ); } break; + case HtmlTokenId::SPAN_ON: + { + SpanOn(pInfo); + } + break; case HtmlTokenId::TABLEHEADER_OFF: case HtmlTokenId::TABLEDATA_OFF: // Closes cell { diff --git a/sc/source/filter/inc/eeparser.hxx b/sc/source/filter/inc/eeparser.hxx index ebc383e32d..e890a80dd1 100644 --- a/sc/source/filter/inc/eeparser.hxx +++ b/sc/source/filter/inc/eeparser.hxx @@ -56,6 +56,8 @@ struct ScEEParseEntry ESelection aSel; // Selection in EditEngine std::optional<OUString> pValStr; // HTML possibly SDVAL string + std::optional<OUString> moFormulaStr; + std::optional<formula::FormulaGrammar::Grammar> moFormulaGrammar; std::optional<OUString> pNumStr; // HTML possibly SDNUM string std::optional<OUString> diff --git a/sc/source/filter/inc/htmlpars.hxx b/sc/source/filter/inc/htmlpars.hxx index 5b2d441098..1ac9aa0002 100644 --- a/sc/source/filter/inc/htmlpars.hxx +++ b/sc/source/filter/inc/htmlpars.hxx @@ -28,6 +28,7 @@ #include <utility> #include <vector> #include <o3tl/sorted_vector.hxx> +#include <svtools/parhtml.hxx> #include <rangelst.hxx> #include "eeparser.hxx" @@ -212,6 +213,9 @@ private: void Image( HtmlImportInfo* ); void AnchorOn( HtmlImportInfo* ); void FontOn( HtmlImportInfo* ); + void SpanOn(HtmlImportInfo* pInfo); + /// Handles the various data-sheets-* attributes on <td> and <span>. + void HandleDataSheetsAttributes(const HTMLOptions& rOptions); public: ScHTMLLayoutParser( EditEngine*, OUString aBaseURL, const Size& aPageSize, ScDocument* ); diff --git a/sc/source/filter/oox/pivottablebuffer.cxx b/sc/source/filter/oox/pivottablebuffer.cxx index 252b4773cc..f434780d27 100644 --- a/sc/source/filter/oox/pivottablebuffer.cxx +++ b/sc/source/filter/oox/pivottablebuffer.cxx @@ -396,13 +396,18 @@ void PivotTableField::finalizeImport( const Reference< XDataPilotDescriptor >& r // try to get the source field and its name from passed DataPilot descriptor Reference< XIndexAccess > xDPFieldsIA( rxDPDesc->getDataPilotFields(), UNO_SET_THROW ); xDPField.set( xDPFieldsIA->getByIndex( nDatabaseIdx ), UNO_QUERY_THROW ); - Reference< XNamed > xDPFieldName( xDPField, UNO_QUERY_THROW ); - maDPFieldName = xDPFieldName->getName(); - OSL_ENSURE( !maDPFieldName.isEmpty(), "PivotTableField::finalizeImport - no field name in source data found" ); + } + catch( Exception& ) + { + } + try + { // try to convert grouping settings if( const PivotCacheField* pCacheField = mrPivotTable.getCacheField( mnFieldIndex ) ) { + maDPFieldName = pCacheField->getName(); + // numeric grouping is done inplace, no nested group fields will appear if( pCacheField->hasNumericGrouping() ) { @@ -428,6 +433,13 @@ void PivotTableField::finalizeImport( const Reference< XDataPilotDescriptor >& r mrPivotTable.finalizeParentGroupingImport( xDPField, *pCacheField, aItemNames ); } } + else + { + // No choice - check the sheet for field name + Reference< XNamed > xDPFieldName( xDPField, UNO_QUERY_THROW ); + maDPFieldName = xDPFieldName->getName(); + OSL_ENSURE( !maDPFieldName.isEmpty(), "PivotTableField::finalizeImport - no field name in source data found" ); + } } catch( Exception& ) { diff --git a/sc/source/filter/rtf/eeimpars.cxx b/sc/source/filter/rtf/eeimpars.cxx index 7056230812..57d81598bf 100644 --- a/sc/source/filter/rtf/eeimpars.cxx +++ b/sc/source/filter/rtf/eeimpars.cxx @@ -339,6 +339,11 @@ void ScEEImport::WriteToDocument( bool bSizeColsRows, double nOutputFactor, SvNu if (!aValStr.isEmpty()) mpDoc->SetValue( nCol, nRow, nTab, fVal ); + else if (pE->moFormulaStr && pE->moFormulaGrammar) + { + mpDoc->SetFormula(ScAddress(nCol, nRow, nTab), *pE->moFormulaStr, + *pE->moFormulaGrammar); + } else if ( !pE->aSel.HasRange() ) { // maybe ALT text of IMG or similar diff --git a/sc/source/ui/app/inputhdl.cxx b/sc/source/ui/app/inputhdl.cxx index a640d71cb2..519ebea36b 100644 --- a/sc/source/ui/app/inputhdl.cxx +++ b/sc/source/ui/app/inputhdl.cxx @@ -3143,7 +3143,7 @@ void ScInputHandler::EnterHandler( ScEnterMode nBlockMode, bool bBeforeSavingInL lcl_RemoveTabs(aString); lcl_RemoveTabs(aPreAutoCorrectString); - if (aString.indexOf('\n') != -1) + if (bModified && aString.indexOf('\n') != -1) { // Cell contains line breaks, enable wrapping ScLineBreakCell aBreakItem(true); diff --git a/sc/source/ui/dbgui/PivotLayoutTreeListBase.cxx b/sc/source/ui/dbgui/PivotLayoutTreeListBase.cxx index 45af29a4f1..672de9559c 100644 --- a/sc/source/ui/dbgui/PivotLayoutTreeListBase.cxx +++ b/sc/source/ui/dbgui/PivotLayoutTreeListBase.cxx @@ -67,6 +67,9 @@ void ScPivotLayoutTreeListBase::PushEntriesToPivotFieldVector(ScPivotFieldVector std::unique_ptr<weld::TreeIter> xEachEntry(mxControl->make_iterator()); if (!mxControl->get_iter_first(*xEachEntry)) return; + + std::optional<ScPivotField> oDataField; + do { ScItemValue* pItemValue = weld::fromId<ScItemValue*>(mxControl->get_id(*xEachEntry)); @@ -78,8 +81,15 @@ void ScPivotLayoutTreeListBase::PushEntriesToPivotFieldVector(ScPivotFieldVector aField.nFuncMask = rFunctionData.mnFuncMask; aField.mnDupCount = rFunctionData.mnDupCount; aField.maFieldRef = rFunctionData.maFieldRef; - rVector.push_back(aField); + + if (aField.nCol == PIVOT_DATA_FIELD) + oDataField = aField; + else + rVector.push_back(aField); } while (mxControl->iter_next(*xEachEntry)); + + if (oDataField) + rVector.push_back(*oDataField); } void ScPivotLayoutTreeListBase::InsertEntryForSourceTarget(weld::TreeView& /*pSource*/, int /*nTarget*/) diff --git a/sc/source/ui/docshell/docfunc.cxx b/sc/source/ui/docshell/docfunc.cxx index 1a8d902bea..4c333b0502 100644 --- a/sc/source/ui/docshell/docfunc.cxx +++ b/sc/source/ui/docshell/docfunc.cxx @@ -5611,26 +5611,12 @@ void ScDocFunc::ReplaceConditionalFormat( sal_uLong nOldFormat, std::unique_ptr< bool bUndo = rDoc.IsUndoEnabled(); ScDocumentUniquePtr pUndoDoc; ScRange aCombinedRange = rRanges.Combine(); - ScRange aCompleteRange; if(bUndo) { pUndoDoc.reset(new ScDocument(SCDOCMODE_UNDO)); pUndoDoc->InitUndo( rDoc, nTab, nTab ); - - if(pFormat) - { - aCompleteRange = aCombinedRange; - } - if(nOldFormat) - { - ScConditionalFormat* pOldFormat = rDoc.GetCondFormList(nTab)->GetFormat(nOldFormat); - if(pOldFormat) - aCompleteRange.ExtendTo(pOldFormat->GetRange().Combine()); - } - - rDoc.CopyToDocument(aCompleteRange.aStart.Col(),aCompleteRange.aStart.Row(),nTab, - aCompleteRange.aEnd.Col(),aCompleteRange.aEnd.Row(),nTab, - InsertDeleteFlags::ALL, false, *pUndoDoc); + if (const auto* pList = rDoc.GetCondFormList(nTab)) + pUndoDoc->SetCondFormList(new ScConditionalFormatList(*pUndoDoc, *pList), nTab); } std::unique_ptr<ScRange> pRepaintRange; @@ -5663,11 +5649,10 @@ void ScDocFunc::ReplaceConditionalFormat( sal_uLong nOldFormat, std::unique_ptr< { ScDocumentUniquePtr pRedoDoc(new ScDocument(SCDOCMODE_UNDO)); pRedoDoc->InitUndo( rDoc, nTab, nTab ); - rDoc.CopyToDocument(aCompleteRange.aStart.Col(),aCompleteRange.aStart.Row(),nTab, - aCompleteRange.aEnd.Col(),aCompleteRange.aEnd.Row(),nTab, - InsertDeleteFlags::ALL, false, *pRedoDoc); + if (const auto* pList = rDoc.GetCondFormList(nTab)) + pRedoDoc->SetCondFormList(new ScConditionalFormatList(*pRedoDoc, *pList), nTab); rDocShell.GetUndoManager()->AddUndoAction( - std::make_unique<ScUndoConditionalFormat>(&rDocShell, std::move(pUndoDoc), std::move(pRedoDoc), aCompleteRange)); + std::make_unique<ScUndoConditionalFormat>(&rDocShell, std::move(pUndoDoc), std::move(pRedoDoc), nTab)); } if(pRepaintRange) diff --git a/sc/source/ui/docshell/docsh3.cxx b/sc/source/ui/docshell/docsh3.cxx index 96546d11a5..4634c5cbf1 100644 --- a/sc/source/ui/docshell/docsh3.cxx +++ b/sc/source/ui/docshell/docsh3.cxx @@ -118,6 +118,9 @@ void ScDocShell::PostPaint( const ScRangeList& rRanges, PaintPartFlags nPart, sa SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row(); SCTAB nTab1 = rRange.aStart.Tab(), nTab2 = std::min<SCTAB>(nMaxTab, rRange.aEnd.Tab()); + if (nTab1 < 0 || nTab2 < 0) + continue; + if (!m_pDocument->ValidCol(nCol1)) nCol1 = m_pDocument->MaxCol(); if (!m_pDocument->ValidRow(nRow1)) nRow1 = m_pDocument->MaxRow(); if (!m_pDocument->ValidCol(nCol2)) nCol2 = m_pDocument->MaxCol(); diff --git a/sc/source/ui/docshell/impex.cxx b/sc/source/ui/docshell/impex.cxx index 4a585657de..8e6315db94 100644 --- a/sc/source/ui/docshell/impex.cxx +++ b/sc/source/ui/docshell/impex.cxx @@ -1662,6 +1662,7 @@ bool ScImportExport::ExtText2Doc( SvStream& rStrm ) ScDocumentImport aDocImport(rDoc); do { + SCCOL nLastCol = nEndCol; // tdf#129701 preserve value of nEndCol for( ;; ) { aLine = ReadCsvLine(rStrm, !bFixed, aSeps, cStr, cDetectSep); @@ -1745,11 +1746,14 @@ bool ScImportExport::ExtText2Doc( SvStream& rStrm ) SCCOL nSourceCol = 0; sal_uInt16 nInfoStart = 0; const sal_Unicode* p = aLine.getStr(); + // tdf#129701 if there is only one column, and user wants to treat empty cells, + // we need to detect *p = null + bool bIsLastColEmpty = !(*p) && !bSkipEmptyCells && !bDetermineRange; // Yes, the check is nCol<=rDoc.MaxCol()+1, +1 because it is only an // overflow if there is really data following to be put behind // the last column, which doesn't happen if info is // SC_COL_SKIP. - while (*p && nCol <= rDoc.MaxCol()+1) + while ( (*p || bIsLastColEmpty) && nCol <= rDoc.MaxCol()+1) { bool bIsQuoted = false; p = ScImportExport::ScanNextFieldFromString( p, aCell, @@ -1780,9 +1784,18 @@ bool ScImportExport::ExtText2Doc( SvStream& rStrm ) aTransliteration, aCalendar, pEnglishTransliteration.get(), pEnglishCalendar.get()); } - ++nCol; - } + if (bIsLastColEmpty) + { + bIsLastColEmpty = false; // toggle to stop + } + else + { + ++nCol; + // tdf#129701 detect if there is a last empty column when we need it + bIsLastColEmpty = !(*p) && !bSkipEmptyCells && !bDetermineRange && nCol == nLastCol; + } + } ++nSourceCol; } } diff --git a/sc/source/ui/inc/undoblk.hxx b/sc/source/ui/inc/undoblk.hxx index 9bda36a1e1..c6cb432d8e 100644 --- a/sc/source/ui/inc/undoblk.hxx +++ b/sc/source/ui/inc/undoblk.hxx @@ -613,11 +613,13 @@ private: void DoChange( ScDocument* pSrcDoc ) const; }; +// This class only uses conditional format lists in the undo/redo documents; +// no other tab data is needed in the documents class ScUndoConditionalFormat : public ScSimpleUndo { public: ScUndoConditionalFormat( ScDocShell* pNewDocShell, - ScDocumentUniquePtr pUndoDoc, ScDocumentUniquePtr pRedoDoc, const ScRange& rRange); + ScDocumentUniquePtr pUndoDoc, ScDocumentUniquePtr pRedoDoc, SCTAB nTab); virtual ~ScUndoConditionalFormat() override; virtual void Undo() override; @@ -631,7 +633,7 @@ private: void DoChange(ScDocument* pDoc); ScDocumentUniquePtr mpUndoDoc; ScDocumentUniquePtr mpRedoDoc; - ScRange maRange; + SCTAB mnTab; }; class ScUndoConditionalFormatList : public ScSimpleUndo diff --git a/sc/source/ui/undo/undoblk.cxx b/sc/source/ui/undo/undoblk.cxx index d352ba143b..c74d23f6e7 100644 --- a/sc/source/ui/undo/undoblk.cxx +++ b/sc/source/ui/undo/undoblk.cxx @@ -1563,11 +1563,11 @@ bool ScUndoListNames::CanRepeat(SfxRepeatTarget& rTarget) const } ScUndoConditionalFormat::ScUndoConditionalFormat(ScDocShell* pNewDocShell, - ScDocumentUniquePtr pUndoDoc, ScDocumentUniquePtr pRedoDoc, const ScRange& rRange): + ScDocumentUniquePtr pUndoDoc, ScDocumentUniquePtr pRedoDoc, SCTAB nTab): ScSimpleUndo( pNewDocShell ), mpUndoDoc(std::move(pUndoDoc)), mpRedoDoc(std::move(pRedoDoc)), - maRange(rRange) + mnTab(nTab) { } @@ -1594,9 +1594,25 @@ void ScUndoConditionalFormat::DoChange(ScDocument* pSrcDoc) { ScDocument& rDoc = pDocShell->GetDocument(); - rDoc.DeleteAreaTab( maRange, InsertDeleteFlags::ALL ); - pSrcDoc->CopyToDocument(maRange, InsertDeleteFlags::ALL, false, rDoc); - pDocShell->PostPaint( maRange, PaintPartFlags::Grid ); + // Restore all conditional formats in the tab. This is simpler and more reliable, than + // restoring formats in a specific range, and then trying to join selectively the restored + // formats with the other formats in the tab, to get the correct state. + ScRangeList aCombinedRange; + if (const auto* pOldList = rDoc.GetCondFormList(mnTab)) + aCombinedRange = pOldList->GetCombinedRange(); + + if (const auto* pNewList = pSrcDoc->GetCondFormList(mnTab)) + { + for (const auto& cond : *pNewList) + for (const auto& range : cond->GetRange()) + aCombinedRange.Join(range); + rDoc.SetCondFormList(new ScConditionalFormatList(rDoc, *pNewList), mnTab); + } + else + { + rDoc.SetCondFormList(nullptr, mnTab); + } + pDocShell->PostPaint(aCombinedRange, PaintPartFlags::Grid); pDocShell->PostDataChanged(); ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell(); if (pViewShell) |