summaryrefslogtreecommitdiffstats
path: root/sc
diff options
context:
space:
mode:
Diffstat (limited to 'sc')
-rw-r--r--sc/inc/SolverSettings.hxx117
-rw-r--r--sc/inc/clipcontext.hxx5
-rw-r--r--sc/inc/drwlayer.hxx3
-rw-r--r--sc/inc/filterentries.hxx7
-rw-r--r--sc/qa/filter/html/data/formula.html7
-rw-r--r--sc/qa/filter/html/data/numberformat.html8
-rw-r--r--sc/qa/filter/html/data/single-cell.html1
-rw-r--r--sc/qa/filter/html/html.cxx186
-rw-r--r--sc/qa/uitest/autofilter2/tdf159420.py126
-rw-r--r--sc/qa/uitest/data/tdf129701.odsbin0 -> 23138 bytes
-rw-r--r--sc/qa/uitest/pasteSpecial/tdf129701-PasteUnformated.py70
-rw-r--r--sc/qa/unit/data/ods/tdf158735.odsbin0 -> 21112 bytes
-rw-r--r--sc/qa/unit/data/ods/tdf160003_page_anchored_object.odsbin0 -> 19164 bytes
-rw-r--r--sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataInSync.xlsxbin0 -> 13237 bytes
-rw-r--r--sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithCacheData.xlsxbin0 -> 13071 bytes
-rw-r--r--sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithoutCacheData.xlsxbin0 -> 12274 bytes
-rw-r--r--sc/qa/unit/pivottable_filters_test.cxx63
-rw-r--r--sc/qa/unit/scshapetest.cxx24
-rw-r--r--sc/qa/unit/ucalc_solver.cxx29
-rw-r--r--sc/source/core/data/SolverSettings.cxx220
-rw-r--r--sc/source/core/data/clipcontext.cxx4
-rw-r--r--sc/source/core/data/column.cxx6
-rw-r--r--sc/source/core/data/column3.cxx17
-rw-r--r--sc/source/core/data/document.cxx14
-rw-r--r--sc/source/core/data/drwlayer.cxx142
-rw-r--r--sc/source/core/data/fillinfo.cxx4
-rw-r--r--sc/source/filter/excel/excel.cxx4
-rw-r--r--sc/source/filter/html/htmlexp.cxx82
-rw-r--r--sc/source/filter/html/htmlpars.cxx96
-rw-r--r--sc/source/filter/inc/eeparser.hxx2
-rw-r--r--sc/source/filter/inc/htmlpars.hxx4
-rw-r--r--sc/source/filter/oox/pivottablebuffer.cxx18
-rw-r--r--sc/source/filter/rtf/eeimpars.cxx5
-rw-r--r--sc/source/ui/app/inputhdl.cxx2
-rw-r--r--sc/source/ui/dbgui/PivotLayoutTreeListBase.cxx12
-rw-r--r--sc/source/ui/docshell/docfunc.cxx25
-rw-r--r--sc/source/ui/docshell/docsh3.cxx3
-rw-r--r--sc/source/ui/docshell/impex.cxx19
-rw-r--r--sc/source/ui/inc/undoblk.hxx6
-rw-r--r--sc/source/ui/undo/undoblk.cxx26
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="{&quot;1&quot;:3,&quot;3&quot;:1}">1</td>
+ <td data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:2}">2</td>
+ <td data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;: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="{&quot;1&quot;:3,&quot;3&quot;:1000}" data-sheets-numberformat="{&quot;1&quot;:2,&quot;2&quot;:&quot;#,##0.00&quot;,&quot;3&quot;:1}">1,000.00</td>
+ </tr>
+ <tr>
+ <td data-sheets-value="{&quot;1&quot;:3,&quot;3&quot;:2000}" data-sheets-numberformat="{&quot;1&quot;:2,&quot;2&quot;:&quot;#,##0.00&quot;,&quot;3&quot;: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="{&quot;1&quot;:3,&quot;3&quot;: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
new file mode 100644
index 0000000000..94407fcd56
--- /dev/null
+++ b/sc/qa/uitest/data/tdf129701.ods
Binary files differ
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
new file mode 100644
index 0000000000..6003f29bf3
--- /dev/null
+++ b/sc/qa/unit/data/ods/tdf158735.ods
Binary files differ
diff --git a/sc/qa/unit/data/ods/tdf160003_page_anchored_object.ods b/sc/qa/unit/data/ods/tdf160003_page_anchored_object.ods
new file mode 100644
index 0000000000..565eb1bf6a
--- /dev/null
+++ b/sc/qa/unit/data/ods/tdf160003_page_anchored_object.ods
Binary files differ
diff --git a/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataInSync.xlsx b/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataInSync.xlsx
new file mode 100644
index 0000000000..f425f978cb
--- /dev/null
+++ b/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataInSync.xlsx
Binary files differ
diff --git a/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithCacheData.xlsx b/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithCacheData.xlsx
new file mode 100644
index 0000000000..0cb21cd325
--- /dev/null
+++ b/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithCacheData.xlsx
Binary files differ
diff --git a/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithoutCacheData.xlsx b/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithoutCacheData.xlsx
new file mode 100644
index 0000000000..91297320b9
--- /dev/null
+++ b/sc/qa/unit/data/xlsx/PivotTable_CachedDefinitionAndDataNotInSync_SheetColumnsRemoved_WithoutCacheData.xlsx
Binary files differ
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)