sc/inc/units.hxx | 27 +- sc/qa/unit/units.cxx | 337 ++++++++++++++++++++++----- sc/source/core/units/unitsimpl.cxx | 362 +++++++++++++++++++----------- sc/source/core/units/unitsimpl.hxx | 46 ++- sc/source/core/units/utunit.cxx | 3 sc/source/core/units/utunit.hxx | 39 ++- sc/source/ui/condformat/condformatdlg.cxx | 33 ++ sc/source/ui/inc/condformatdlg.hxx | 4 8 files changed, 629 insertions(+), 222 deletions(-)
New commits: commit f732a2444a6cc18c016cc79b0c364a87cc7d87e8 Author: Andrzej Hunt <[email protected]> Date: Tue May 12 21:16:13 2015 +0100 Convert convertCellUnits to handle ranges Change-Id: Ibe95cbd9ea9efd08a48e0651f469434802bfa40e diff --git a/sc/inc/units.hxx b/sc/inc/units.hxx index b5e12da..8e15af4 100644 --- a/sc/inc/units.hxx +++ b/sc/inc/units.hxx @@ -107,7 +107,7 @@ public: * rsInputUnit overrides the automatic determination of input units, i.e. disables * input unit detection. */ - virtual bool convertCellUnits(const ScRange& rRange, + virtual bool convertCellUnits(const ScRangeList& rRanges, ScDocument* pDoc, const OUString& rsOutputUnit) = 0; diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx index 29dfc2b..b35cd40 100644 --- a/sc/qa/unit/units.cxx +++ b/sc/qa/unit/units.cxx @@ -820,6 +820,7 @@ void UnitsTest::testRangeConversion() { // TODO: we need to test: // 1. mixture of units that can't be converted // 2. mixtures of local and header annotations + // 3. actual sensible ranges } CPPUNIT_TEST_SUITE_REGISTRATION(UnitsTest); diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx index f2aa9e0..0f79edb 100644 --- a/sc/source/core/units/unitsimpl.cxx +++ b/sc/source/core/units/unitsimpl.cxx @@ -799,7 +799,7 @@ bool UnitsImpl::convertCellUnitsForColumnRange(const ScRange& rRange, return bAllConverted; } -bool UnitsImpl::convertCellUnits(const ScRange& rRange, +bool UnitsImpl::convertCellUnits(const ScRangeList& rRangeList, ScDocument* pDoc, const OUString& rsOutputUnit) { UtUnit aOutputUnit; @@ -807,26 +807,28 @@ bool UnitsImpl::convertCellUnits(const ScRange& rRange, return false; } - ScRange aRange(rRange); - aRange.PutInOrder(); - - SCCOL nStartCol, nEndCol; - SCROW nStartRow, nEndRow; - SCTAB nStartTab, nEndTab; - aRange.GetVars(nStartCol, nStartRow, nStartTab, - nEndCol, nEndRow, nEndTab); - - // Can only handle ranges in a single sheet for now - assert(nStartTab == nEndTab); - - // Each column is independent hence we are able to handle each separately. bool bAllConverted = true; - for (SCCOL nCol = nStartCol; nCol <= nEndCol; nCol++) { - ScRange aSubRange(ScAddress(nCol, nStartRow, nStartTab), ScAddress(nCol, nEndRow, nStartTab)); - bAllConverted = bAllConverted && - convertCellUnitsForColumnRange(aSubRange, pDoc, aOutputUnit); - } + for (size_t i = 0; i < rRangeList.size(); i++) { + ScRange aRange(*rRangeList[i]); + + aRange.PutInOrder(); + + SCCOL nStartCol, nEndCol; + SCROW nStartRow, nEndRow; + SCTAB nStartTab, nEndTab; + aRange.GetVars(nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab); + + // Each column is independent hence we are able to handle each separately. + for (SCTAB nTab = nStartTab; nTab <= nEndTab; nTab++) { + for (SCCOL nCol = nStartCol; nCol <= nEndCol; nCol++) { + ScRange aSubRange(ScAddress(nCol, nStartRow, nTab), ScAddress(nCol, nEndRow, nTab)); + bAllConverted = bAllConverted && + convertCellUnitsForColumnRange(aSubRange, pDoc, aOutputUnit); + } + } + } return bAllConverted; } diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx index e7b4597..58e0971 100644 --- a/sc/source/core/units/unitsimpl.hxx +++ b/sc/source/core/units/unitsimpl.hxx @@ -103,7 +103,7 @@ public: const OUString& rsNewUnit, const OUString& rsOldUnit) SAL_OVERRIDE; - virtual bool convertCellUnits(const ScRange& rRange, + virtual bool convertCellUnits(const ScRangeList& rRanges, ScDocument* pDoc, const OUString& rsOutputUnit) SAL_OVERRIDE; commit d60765f6ebba774787d3e553dbd680bf1de1fb0a Author: Andrzej Hunt <[email protected]> Date: Tue May 12 20:57:20 2015 +0100 Split convertCellUnits and change contract This is in preparation for rewriting convertCellUnits to handle ScRangeList's. Change-Id: I17fecdb64674af79a33f2b1a62b4b46150177af5 diff --git a/sc/inc/units.hxx b/sc/inc/units.hxx index 4d83316..b5e12da 100644 --- a/sc/inc/units.hxx +++ b/sc/inc/units.hxx @@ -96,9 +96,8 @@ public: * If possible the input unit will be determined automatically (using local * and header units). * - * Returns false if input units are not compatible with the desired output units, - * (including the case where some of the input units are compatibles but others - * aren't). + * Returns false if not input units are not compatible with the desired output units, + * however this method still converts all cells containing compatible units. * * Local and header unit annotations are modified as appropriate such that the output * remains unambiguous. Hence, if the header cell is included in rRange, its unit diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx index 497331a..f2aa9e0 100644 --- a/sc/source/core/units/unitsimpl.cxx +++ b/sc/source/core/units/unitsimpl.cxx @@ -715,78 +715,72 @@ bool UnitsImpl::convertCellToHeaderUnit(const ScAddress& rCellAddress, return false; } -bool UnitsImpl::convertCellUnits(const ScRange& rRange, - ScDocument* pDoc, - const OUString& rsOutputUnit) { - UtUnit aOutputUnit; - if (!UtUnit::createUnit(rsOutputUnit, aOutputUnit, mpUnitSystem)) { - return false; - } - - ScRange aRange(rRange); - aRange.PutInOrder(); - - SCCOL nStartCol, nEndCol; - SCROW nStartRow, nEndRow; - SCTAB nStartTab, nEndTab; - aRange.GetVars(nStartCol, nStartRow, nStartTab, - nEndCol, nEndRow, nEndTab); - - // Can only handle ranges in a single sheet for now - assert(nStartTab == nEndTab); - - // Each column is independent hence we are able to handle each separately. - for (SCCOL nCol = nStartCol; nCol <= nEndCol; nCol++) { - HeaderUnitDescriptor aHeader = { false, UtUnit(), boost::optional< ScAddress >(), "", -1 }; - - for (SCROW nRow = nEndRow; nRow >= nStartRow; nRow--) { - ScAddress aCurrent(nCol, nRow, nStartTab); - - if (aCurrent == aHeader.address) { - OUString sHeader = pDoc->GetString(aCurrent); - sHeader = sHeader.replaceAt(aHeader.unitStringPosition, aHeader.unitString.getLength(), rsOutputUnit); - pDoc->SetString(aCurrent, sHeader); - - aHeader.valid = false; - } else if (pDoc->GetCellType(aCurrent) != CELLTYPE_STRING) { +bool UnitsImpl::convertCellUnitsForColumnRange(const ScRange& rRange, + ScDocument* pDoc, + const UtUnit& rOutputUnit) { + assert(rRange.aStart.Row() <= rRange.aEnd.Row()); + assert(rRange.aStart.Col() == rRange.aEnd.Col()); + assert(rRange.aStart.Tab() == rRange.aEnd.Tab()); + assert(rOutputUnit.getInputString()); + + HeaderUnitDescriptor aHeader = { false, UtUnit(), boost::optional< ScAddress >(), "", -1 }; + + SCCOL nCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + SCROW nEndRow = rRange.aEnd.Row(); + SCTAB nTab = rRange.aStart.Tab(); + + bool bAllConverted = true; + + for (SCROW nRow = nEndRow; nRow >= nStartRow; nRow--) { + ScAddress aCurrent(nCol, nRow, nTab); + + // It's possible that the header refers to an incompatible unit, hence + // shouldn't be modified when we're converting. + if (aCurrent == aHeader.address && + aHeader.unit.areConvertibleTo(rOutputUnit)) { + OUString sHeader = pDoc->GetString(aCurrent); + sHeader = sHeader.replaceAt(aHeader.unitStringPosition, aHeader.unitString.getLength(), *rOutputUnit.getInputString()); + pDoc->SetString(aCurrent, sHeader); + + aHeader.valid = false; + } else if (pDoc->GetCellType(aCurrent) != CELLTYPE_STRING) { + if (!aHeader.valid) { + aHeader = findHeaderUnitForCell(aCurrent, pDoc); + + // If there is no header we get an invalid unit returned from findHeaderUnitForCell, + // and therfore assume the dimensionless unit 1. if (!aHeader.valid) { - aHeader = findHeaderUnitForCell(aCurrent, pDoc); - - // If there is no header we get an invalid unit returned from findHeaderUnitForCell, - // and therfore assume the dimensionless unit 1. - if (!aHeader.valid) { - UtUnit::createUnit("", aHeader.unit, mpUnitSystem); - aHeader.valid = true; - } + UtUnit::createUnit("", aHeader.unit, mpUnitSystem); + aHeader.valid = true; } + } - OUString sLocalUnit(extractUnitStringForCell(aCurrent, pDoc)); - UtUnit aLocalUnit; - if (sLocalUnit.isEmpty()) { - aLocalUnit = aHeader.unit; - } else { // override header unit with annotation unit - if (!UtUnit::createUnit(sLocalUnit, aLocalUnit, mpUnitSystem)) { - // but assume dimensionless if invalid - UtUnit::createUnit("", aLocalUnit, mpUnitSystem); - } + OUString sLocalUnit(extractUnitStringForCell(aCurrent, pDoc)); + UtUnit aLocalUnit; + if (sLocalUnit.isEmpty()) { + aLocalUnit = aHeader.unit; + } else { // override header unit with annotation unit + if (!UtUnit::createUnit(sLocalUnit, aLocalUnit, mpUnitSystem)) { + // but assume dimensionless if invalid + UtUnit::createUnit("", aLocalUnit, mpUnitSystem); } + } - bool bLocalAnnotationRequired = (!aRange.In(*aHeader.address)) && - (aOutputUnit != aHeader.unit); - double nValue = pDoc->GetValue(aCurrent); + bool bLocalAnnotationRequired = (!rRange.In(*aHeader.address)) && + (rOutputUnit != aHeader.unit); + double nValue = pDoc->GetValue(aCurrent); - if (!aLocalUnit.areConvertibleTo(aOutputUnit)) { - // TODO: in future we should undo all our changes here. - return false; - } - - double nNewValue = aLocalUnit.convertValueTo(nValue, aOutputUnit); + if (!aLocalUnit.areConvertibleTo(rOutputUnit)) { + bAllConverted = false; + } else { + double nNewValue = aLocalUnit.convertValueTo(nValue, rOutputUnit); pDoc->SetValue(aCurrent, nNewValue); if (bLocalAnnotationRequired) { // All a local dirty hack too - needs to be refactored and improved. // And ideally we should reuse the existing format. - OUString sNewFormat = "General\"" + rsOutputUnit + "\""; + OUString sNewFormat = "General\"" + *rOutputUnit.getInputString() + "\""; sal_uInt32 nFormatKey; short nType = css::util::NumberFormat::DEFINED; sal_Int32 nErrorPosition; // Unused, because we should be creating working number formats. @@ -799,11 +793,41 @@ bool UnitsImpl::convertCellUnits(const ScRange& rRange, pDoc->SetNumberFormat(aCurrent, 0); } } - } + } + return bAllConverted; +} - return true; +bool UnitsImpl::convertCellUnits(const ScRange& rRange, + ScDocument* pDoc, + const OUString& rsOutputUnit) { + UtUnit aOutputUnit; + if (!UtUnit::createUnit(rsOutputUnit, aOutputUnit, mpUnitSystem)) { + return false; + } + + ScRange aRange(rRange); + aRange.PutInOrder(); + + SCCOL nStartCol, nEndCol; + SCROW nStartRow, nEndRow; + SCTAB nStartTab, nEndTab; + aRange.GetVars(nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab); + + // Can only handle ranges in a single sheet for now + assert(nStartTab == nEndTab); + + // Each column is independent hence we are able to handle each separately. + bool bAllConverted = true; + for (SCCOL nCol = nStartCol; nCol <= nEndCol; nCol++) { + ScRange aSubRange(ScAddress(nCol, nStartRow, nStartTab), ScAddress(nCol, nEndRow, nStartTab)); + bAllConverted = bAllConverted && + convertCellUnitsForColumnRange(aSubRange, pDoc, aOutputUnit); + } + + return bAllConverted; } bool UnitsImpl::areUnitsCompatible(const OUString& rsUnit1, const OUString& rsUnit2) { diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx index ebd20b0..e7b4597 100644 --- a/sc/source/core/units/unitsimpl.hxx +++ b/sc/source/core/units/unitsimpl.hxx @@ -156,6 +156,15 @@ private: ScDocument* pDoc); /** + * Convert cells within a given range. The range MUST be restricted + * to being a group of cells within one column, in one sheet/tab. + * rOutputUnit MUST possess an input unit string. + */ + bool convertCellUnitsForColumnRange(const ScRange& rRange, + ScDocument* pDoc, + const UtUnit& rOutputUnit); + + /** * Return both the UtUnit and the String as we usually want the UtUnit * (which is created from the String, and has to be created to ensure * that there is a valid unit), but we might also need the original commit b0c23b9dc7992b15dfac065b58ae3aeedfb054a4 Author: Andrzej Hunt <[email protected]> Date: Tue May 12 20:39:00 2015 +0100 Make more methods of UtUnit const Change-Id: I652a77ad3bf547788bf6d566fdeaac525effb541 diff --git a/sc/source/core/units/utunit.hxx b/sc/source/core/units/utunit.hxx index 6f82b05..5d1442a 100644 --- a/sc/source/core/units/utunit.hxx +++ b/sc/source/core/units/utunit.hxx @@ -101,11 +101,11 @@ public: return ut_is_dimensionless(this->get()); } - bool operator==(const UtUnit& rUnit) { + bool operator==(const UtUnit& rUnit) const { return ut_compare(this->get(), rUnit.get()) == 0; } - bool operator!=(const UtUnit& rUnit) { + bool operator!=(const UtUnit& rUnit) const { return !operator==(rUnit); } @@ -137,11 +137,11 @@ public: return UtUnit(ut_divide(this->get(), rUnit.get())); } - bool areConvertibleTo(const UtUnit& rUnit) { + bool areConvertibleTo(const UtUnit& rUnit) const { return ut_are_convertible(this->get(), rUnit.get()); } - double convertValueTo(double nOriginalValue, const UtUnit& rUnit) { + double convertValueTo(double nOriginalValue, const UtUnit& rUnit) const { assert(isValid()); assert(rUnit.isValid()); commit 2b773301309a2aad18a6e48f1148d46e485d2a3f Author: Andrzej Hunt <[email protected]> Date: Tue May 12 20:16:57 2015 +0100 Implement getUnitsForRange This will be useful for e.g. the units conversion dialog. Change-Id: I36391e9aeab5689bfde1d1865549cc2e136a4812 diff --git a/sc/inc/units.hxx b/sc/inc/units.hxx index 1ba9121..4d83316 100644 --- a/sc/inc/units.hxx +++ b/sc/inc/units.hxx @@ -14,6 +14,8 @@ #include <boost/shared_ptr.hpp> +#include "rangelst.hxx" + class ScAddress; class ScDocument; class ScRange; @@ -24,6 +26,18 @@ namespace units { class UnitsImpl; +/** + * The units used for a range of data cells. + */ +struct RangeUnits { + std::vector< OUString > units; + /** + * Whether all the units in the list are compatible (i.e. data + * can be converted to any of the listed units). + */ + bool compatible; +}; + class Units { public: static ::boost::shared_ptr< Units > GetUnits(); @@ -101,6 +115,9 @@ public: virtual bool areUnitsCompatible(const OUString& rsUnit1, const OUString& rsUnit2) = 0; + virtual RangeUnits getUnitsForRange(const ScRangeList& rRangeList, + ScDocument* pDoc) = 0; + virtual ~Units() {} }; diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx index 7e9e320..29dfc2b 100644 --- a/sc/qa/unit/units.cxx +++ b/sc/qa/unit/units.cxx @@ -50,6 +50,7 @@ public: void testUnitsCompatible(); void testCellConversion(); + void testUnitsForRange(); void testRangeConversion(); CPPUNIT_TEST_SUITE(UnitsTest); @@ -63,6 +64,7 @@ public: CPPUNIT_TEST(testUnitsCompatible); CPPUNIT_TEST(testCellConversion); + CPPUNIT_TEST(testUnitsForRange); CPPUNIT_TEST(testRangeConversion); CPPUNIT_TEST_SUITE_END(); @@ -551,6 +553,193 @@ void UnitsTest::testCellConversion() { // to pass in the output of isCellConversionRecommended). } +void UnitsTest::testUnitsForRange() { + const SCTAB nTab = 2; + mpDoc->EnsureTable(nTab); + + // Column 1: just cm, with header annotation + ScAddress headerAddress1(0, 0, nTab); + mpDoc->SetString(headerAddress1, "length [cm]"); + + ScAddress address1(headerAddress1); + + vector<double> values1({10, 20, 30, 40, 1, 0.5, 0.25}); + address1.IncRow(); + mpDoc->SetValues(address1, values1); + + ScAddress endAddress1( address1.Col(), address1.Row() + values1.size() - 1, nTab); + + // Column2: header of [m], with some random units mixed in (ft, km, furlongs) + ScAddress headerAddress2(1, 0, nTab); + mpDoc->SetString(headerAddress2, "distance [m]"); + + ScAddress address2(headerAddress2); + + vector<double> values2({1, 2, 3, 4, 0.1, 0.05, 0.025}); + address2.IncRow(); + mpDoc->SetValues(address2, values2); + + address2.IncRow(); + setNumberFormatUnit(address2, "furlongs"); + address2.IncRow(); + setNumberFormatUnit(address2, "ft"); + address2.IncRow(); + setNumberFormatUnit(address2, "m"); + address2.IncRow(); + setNumberFormatUnit(address2, "km"); + address2.IncRow(); + setNumberFormatUnit(address2, "cm"); + address2 = headerAddress2; // reset to start of data range + address2.IncRow(); + + ScAddress endAddress2( address2.Col(), address2.Row() + values2.size() - 1, nTab); + + // Column3: no units in header, local weight annotations (kg, lb, g, tons) + ScAddress headerAddress3(2, 0, nTab); + mpDoc->SetString(headerAddress3, "weight"); + + ScAddress address3(headerAddress3); + + vector<double> values3({100, 200, 300, 400, 10, 5, 2.5 }); + address3.IncRow(); + mpDoc->SetValues(address3, values3); + + setNumberFormatUnit(address3, "kg"); + address3.IncRow(); + setNumberFormatUnit(address3, "kg"); + address3.IncRow(); + setNumberFormatUnit(address3, "kg"); + address3.IncRow(); + setNumberFormatUnit(address3, "lb"); + address3.IncRow(); + setNumberFormatUnit(address3, "tons"); + address3.IncRow(); + setNumberFormatUnit(address3, "g"); + address3.IncRow(); + setNumberFormatUnit(address3, "atomic_mass_unit"); + address3.IncRow(); + setNumberFormatUnit(address3, "kg"); + address3 = headerAddress3; // reset to start of data range + address3.IncRow(); + + ScAddress endAddress3( address3.Col(), address3.Row() + values3.size() - 1, nTab); + + // COLUMN 1 + // Test with just the data (not including header). + ScRange aRange1(address1, endAddress1); + + RangeUnits aUnits(mpUnitsImpl->getUnitsForRange( ScRangeList(aRange1), mpDoc)); + CPPUNIT_ASSERT(aUnits.compatible); + CPPUNIT_ASSERT_EQUAL(aUnits.units.size(), static_cast<size_t>(1)); + CPPUNIT_ASSERT(aUnits.units[0] == "cm"); + + // Test including header + aRange1 = ScRange(headerAddress1, endAddress1); + + aUnits = mpUnitsImpl->getUnitsForRange( ScRangeList(aRange1), mpDoc); + CPPUNIT_ASSERT(aUnits.compatible); + CPPUNIT_ASSERT_EQUAL(aUnits.units.size(), static_cast<size_t>(1)); + CPPUNIT_ASSERT(aUnits.units[0] == "cm"); + + // COLUMN 2 + // Test with just the data (not including header). + ScRange aRange2(address2, endAddress2); + + aUnits = mpUnitsImpl->getUnitsForRange( ScRangeList(aRange2), mpDoc); + CPPUNIT_ASSERT(aUnits.compatible); + CPPUNIT_ASSERT_EQUAL(aUnits.units.size(), static_cast<size_t>(5)); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "cm") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "ft") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "m") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "furlongs") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "km") != aUnits.units.end()); + + // Test including header + aRange2 = ScRange(headerAddress2, endAddress2); + + aUnits = mpUnitsImpl->getUnitsForRange( ScRangeList(aRange2), mpDoc); + CPPUNIT_ASSERT(aUnits.compatible); + CPPUNIT_ASSERT_EQUAL(aUnits.units.size(), static_cast<size_t>(5)); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "cm") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "ft") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "m") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "furlongs") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "km") != aUnits.units.end()); + + // COLUMN 3 + // Test without header + ScRange aRange3(address3, endAddress3); + + aUnits = mpUnitsImpl->getUnitsForRange( ScRangeList(aRange3), mpDoc); + CPPUNIT_ASSERT(aUnits.compatible); + CPPUNIT_ASSERT_EQUAL(aUnits.units.size(), static_cast<size_t>(5)); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "kg") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "lb") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "tons") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "g") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "atomic_mass_unit") != aUnits.units.end()); + + // Test including header + aRange3 = ScRange(headerAddress3, endAddress3); + + aUnits = mpUnitsImpl->getUnitsForRange( ScRangeList(aRange3), mpDoc); + CPPUNIT_ASSERT(aUnits.compatible); + CPPUNIT_ASSERT_EQUAL(aUnits.units.size(), static_cast<size_t>(5)); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "kg") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "lb") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "tons") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "g") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "atomic_mass_unit") != aUnits.units.end()); + + // ROW 2: + ScRange aRow2(ScAddress( 0, 1, nTab), ScAddress(2, 1, nTab)); + aUnits = mpUnitsImpl->getUnitsForRange(ScRangeList(aRow2), mpDoc); + CPPUNIT_ASSERT(!aUnits.compatible); + CPPUNIT_ASSERT_EQUAL(aUnits.units.size(), static_cast<size_t>(3)); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "cm") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "m") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "kg") != aUnits.units.end()); + + // ROW 2 including a blank cell + aRow2 = ScRange(ScAddress( 0, 1, nTab), ScAddress(3, 1, nTab)); + aUnits = mpUnitsImpl->getUnitsForRange(ScRangeList(aRow2), mpDoc); + CPPUNIT_ASSERT(!aUnits.compatible); + CPPUNIT_ASSERT_EQUAL(aUnits.units.size(), static_cast<size_t>(3)); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "cm") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "m") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "kg") != aUnits.units.end()); + + // Finally check that multiple ranges actually work + ScRangeList aRangeList; + aRangeList.Append(aRange1); + aRangeList.Append(aRange2); + + aUnits = mpUnitsImpl->getUnitsForRange( aRangeList, mpDoc); + CPPUNIT_ASSERT(aUnits.compatible); + CPPUNIT_ASSERT_EQUAL(aUnits.units.size(), static_cast<size_t>(5)); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "cm") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "ft") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "m") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "furlongs") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "km") != aUnits.units.end()); + + // And add the weights range: + aRangeList.Append(aRange3); + aUnits = mpUnitsImpl->getUnitsForRange( aRangeList, mpDoc); + CPPUNIT_ASSERT(!aUnits.compatible); + CPPUNIT_ASSERT_EQUAL(aUnits.units.size(), static_cast<size_t>(10)); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "cm") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "ft") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "m") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "furlongs") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "km") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "kg") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "lb") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "tons") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "g") != aUnits.units.end()); + CPPUNIT_ASSERT(std::find(aUnits.units.begin(), aUnits.units.end(), "atomic_mass_unit") != aUnits.units.end()); +} + void UnitsTest::testRangeConversion() { const SCTAB nTab = 1; mpDoc->EnsureTable(nTab); diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx index a67871d..497331a 100644 --- a/sc/source/core/units/unitsimpl.cxx +++ b/sc/source/core/units/unitsimpl.cxx @@ -817,4 +817,44 @@ bool UnitsImpl::areUnitsCompatible(const OUString& rsUnit1, const OUString& rsUn && aUnit1.areConvertibleTo(aUnit2); } +RangeUnits UnitsImpl::getUnitsForRange(const ScRangeList& rRangeList, ScDocument* pDoc) { + std::set< OUString > aUnits; + + for (size_t i = 0; i < rRangeList.size(); i++) { + ScCellIterator aIt(pDoc, *rRangeList[i]); + + if (!aIt.first()) + continue; + + do { + const ScAddress& aPos = aIt.GetPos(); + UtUnit aUnit = getUnitForCell(aPos, pDoc); + + // We ignore header cells (and comments too) + if (aUnit.isValid()) { + // Units retrieved directly must always have an input string + assert(aUnit.getInputString()); + aUnits.insert(*aUnit.getInputString()); + } + } while (aIt.next()); + } + + bool bCompatible = true; + + if (aUnits.size() > 1) { + OUString sFirstUnit = *aUnits.cbegin(); + + // start iterating from the second item (++aUnits.cbegin()) + for (auto aIt = ++aUnits.cbegin(); aIt != aUnits.cend(); aIt++) { + if (!areUnitsCompatible(sFirstUnit, *aIt)) { + bCompatible = false; + break; + } + } + } + + std::vector< OUString > aUnitsList(aUnits.begin(), aUnits.end()); + return { aUnitsList, bCompatible }; +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx index 88975b8..ebd20b0 100644 --- a/sc/source/core/units/unitsimpl.hxx +++ b/sc/source/core/units/unitsimpl.hxx @@ -110,6 +110,9 @@ public: virtual bool areUnitsCompatible(const OUString& rsUnit1, const OUString& rsUnit2) SAL_OVERRIDE; + virtual RangeUnits getUnitsForRange(const ScRangeList& rRangeList, + ScDocument* pDoc) SAL_OVERRIDE; + private: UnitsResult getOutputUnitsForOpCode(std::stack< RAUSItem >& rStack, const formula::FormulaToken* pToken, ScDocument* pDoc); commit f16793253e5144452d8c7b44dae29d1e0a2a14ca Author: Andrzej Hunt <[email protected]> Date: Tue May 12 20:16:27 2015 +0100 Store original input string for UtUnit String->UtUnit isn't necessarily reversible, hence we should store the original input too in case it is needed by the user. Change-Id: I8794a1544a9c996da574ee753d95b44f067e819f diff --git a/sc/source/core/units/utunit.cxx b/sc/source/core/units/utunit.cxx index f539502..63e7e73 100644 --- a/sc/source/core/units/utunit.cxx +++ b/sc/source/core/units/utunit.cxx @@ -18,7 +18,8 @@ bool UtUnit::createUnit(const OUString& rUnitString, UtUnit& rUnitOut, const boo // simplest just to do this during conversion: OString sUnitStringUTF8 = OUStringToOString(rUnitString.trim(), RTL_TEXTENCODING_UTF8); - UtUnit pParsedUnit(ut_parse(pUTSystem.get(), sUnitStringUTF8.getStr(), UT_UTF8)); + UtUnit pParsedUnit(ut_parse(pUTSystem.get(), sUnitStringUTF8.getStr(), UT_UTF8), + rUnitString); if (pParsedUnit.isValid()) { rUnitOut = pParsedUnit; diff --git a/sc/source/core/units/utunit.hxx b/sc/source/core/units/utunit.hxx index b039c74..6f82b05 100644 --- a/sc/source/core/units/utunit.hxx +++ b/sc/source/core/units/utunit.hxx @@ -12,6 +12,7 @@ #include <rtl/ustring.hxx> +#include <boost/optional.hpp> #include <boost/shared_ptr.hpp> #include <udunits2.h> @@ -38,12 +39,26 @@ class UtUnit { private: ::boost::shared_ptr< ut_unit > mpUnit; + /** + * The original input string used in createUnit. + * We can't necessarily convert a ut_unit back into the + * original representation (e.g. cm gets formatted as 0.01m + * by default), hence we should store the original string + * as may need to display it to the user again. + * + * There is no input string for units that are created when manipulating + * other units (i.e. multiplication/division of other UtUnits). + */ + boost::optional< OUString > msInputString; + static void freeUt(ut_unit* pUnit) { ut_free(pUnit); } - UtUnit(ut_unit* pUnit): - mpUnit(pUnit, &freeUt) + UtUnit(ut_unit* pUnit, + const boost::optional< OUString > rInputString = boost::optional< OUString >()) + : mpUnit(pUnit, &freeUt) + , msInputString(rInputString) {} void reset(ut_unit* pUnit) { @@ -55,6 +70,9 @@ private: } public: + /** + * return false if we try to create in invalid unit. + */ static bool createUnit(const OUString& rUnitString, UtUnit& rUnitOut, const boost::shared_ptr< ut_system >& pUTSystem); /* @@ -63,10 +81,15 @@ public: */ UtUnit() {}; - UtUnit(const UtUnit& rUnit): - mpUnit(rUnit.mpUnit) + UtUnit(const UtUnit& rUnit) + : mpUnit(rUnit.mpUnit) + , msInputString(rUnit.msInputString) {} + boost::optional< OUString > getInputString() const { + return msInputString; + } + OUString getString() const; bool isValid() const { commit 5151ecb6a44f1570d8e09c5b892ed069880f6d1f Author: Andrzej Hunt <[email protected]> Date: Tue May 12 20:15:15 2015 +0100 Return invalid unit for empty and string cells. Change-Id: I6dbec9be643040f9fc567e6065f860a3985f138a diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx index a7b103f..a67871d 100644 --- a/sc/source/core/units/unitsimpl.cxx +++ b/sc/source/core/units/unitsimpl.cxx @@ -441,6 +441,11 @@ HeaderUnitDescriptor UnitsImpl::extractUnitFromHeaderString(const OUString& rsHe } UtUnit UnitsImpl::getUnitForCell(const ScAddress& rCellAddress, ScDocument* pDoc) { + CellType aType(pDoc->GetCellType(rCellAddress)); + if (aType == CELLTYPE_STRING || aType == CELLTYPE_NONE) { + return UtUnit(); + } + OUString sUnitString = extractUnitStringForCell(rCellAddress, pDoc); UtUnit aUnit; diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx index ff30232..88975b8 100644 --- a/sc/source/core/units/unitsimpl.hxx +++ b/sc/source/core/units/unitsimpl.hxx @@ -144,6 +144,8 @@ private: * Retrieve the units for a given cell. This probes based on the usual rules * for cell annotation/column header. * Retrieving units for a formula cell is not yet supported. + * + * Units are undefined for any text cell (including header cells). */ UtUnit getUnitForCell(const ScAddress& rCellAddress, ScDocument* pDoc); UtUnit getUnitForRef(formula::FormulaToken* pToken, commit 7c3cc8ff1457061a596b83d47e4947481b20d089 Author: Andrzej Hunt <[email protected]> Date: Tue May 12 16:28:30 2015 +0100 Implement areUnitsCompatible API method We need this for the conversion dialog, where it's probably better if we can avoid directly fiddling with UtUnits. Change-Id: I090e59c49f3b77ffcc0571838023165c2da931a0 diff --git a/sc/inc/units.hxx b/sc/inc/units.hxx index 381ec7d..1ba9121 100644 --- a/sc/inc/units.hxx +++ b/sc/inc/units.hxx @@ -98,6 +98,9 @@ public: ScDocument* pDoc, const OUString& rsOutputUnit) = 0; + virtual bool areUnitsCompatible(const OUString& rsUnit1, + const OUString& rsUnit2) = 0; + virtual ~Units() {} }; diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx index 1e202aa..7e9e320 100644 --- a/sc/qa/unit/units.cxx +++ b/sc/qa/unit/units.cxx @@ -48,6 +48,7 @@ public: void testUnitFromHeaderExtraction(); + void testUnitsCompatible(); void testCellConversion(); void testRangeConversion(); @@ -60,6 +61,7 @@ public: CPPUNIT_TEST(testUnitValueStringSplitting); CPPUNIT_TEST(testUnitFromHeaderExtraction); + CPPUNIT_TEST(testUnitsCompatible); CPPUNIT_TEST(testCellConversion); CPPUNIT_TEST(testRangeConversion); @@ -471,6 +473,17 @@ void UnitsTest::testUnitFromHeaderExtraction() { CPPUNIT_ASSERT_EQUAL(aHeader.unitStringPosition, 8); } +void UnitsTest::testUnitsCompatible() { + // This test is primarily to ensure that our glue works correctly, it's + // assumed that UdUnits is able to correctly parse the units / determine + // their compatibility. + CPPUNIT_ASSERT(mpUnitsImpl->areUnitsCompatible("cm", "m")); + CPPUNIT_ASSERT(mpUnitsImpl->areUnitsCompatible("ft", "m")); // Sorry! + + CPPUNIT_ASSERT(!mpUnitsImpl->areUnitsCompatible("m", "kg")); + CPPUNIT_ASSERT(!mpUnitsImpl->areUnitsCompatible("s", "J")); +} + void UnitsTest::testCellConversion() { // We test both isCellConversionRecommended, and convertCellToHeaderUnit // since their arguments are essentially shared / dependent. diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx index cca532d..a7b103f 100644 --- a/sc/source/core/units/unitsimpl.cxx +++ b/sc/source/core/units/unitsimpl.cxx @@ -801,4 +801,15 @@ bool UnitsImpl::convertCellUnits(const ScRange& rRange, return true; } +bool UnitsImpl::areUnitsCompatible(const OUString& rsUnit1, const OUString& rsUnit2) { + // TODO: in future we should have some sort of map< OUString, shared_ptr<set< OUString > > + // or similar to cache compatible units, as we may have a large number of such queries. + + UtUnit aUnit1, aUnit2; + + return UtUnit::createUnit(rsUnit1, aUnit1, mpUnitSystem) + && UtUnit::createUnit(rsUnit2, aUnit2, mpUnitSystem) + && aUnit1.areConvertibleTo(aUnit2); +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx index 7320baa..ff30232 100644 --- a/sc/source/core/units/unitsimpl.hxx +++ b/sc/source/core/units/unitsimpl.hxx @@ -107,6 +107,9 @@ public: ScDocument* pDoc, const OUString& rsOutputUnit) SAL_OVERRIDE; + virtual bool areUnitsCompatible(const OUString& rsUnit1, + const OUString& rsUnit2) SAL_OVERRIDE; + private: UnitsResult getOutputUnitsForOpCode(std::stack< RAUSItem >& rStack, const formula::FormulaToken* pToken, ScDocument* pDoc); commit 04b85d76e6559b0361e8cf4397c68dd933a40203 Author: Andrzej Hunt <[email protected]> Date: Tue May 12 16:25:27 2015 +0100 Add setNumberFormatUnit utility method to units test Should simplify writing further tests. Change-Id: Idf991d12d0731d531f7e7c84e1ebe10b76d82b7a diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx index e2be853..1e202aa 100644 --- a/sc/qa/unit/units.cxx +++ b/sc/qa/unit/units.cxx @@ -36,8 +36,9 @@ public: virtual void setUp() SAL_OVERRIDE; virtual void tearDown() SAL_OVERRIDE; - ::boost::shared_ptr< UnitsImpl > mpUnitsImpl; + void setNumberFormatUnit(const ScAddress& rAddress, const OUString& sUnit); + ::boost::shared_ptr< UnitsImpl > mpUnitsImpl; void testUTUnit(); void testUnitVerification(); @@ -127,85 +128,77 @@ void UnitsTest::testUTUnit() { CPPUNIT_ASSERT(aCM.convertValueTo(100.0, aM) == 1.0); } -void UnitsTest::testUnitVerification() { - // Make sure we have at least one tab to work with - mpDoc->EnsureTable(0); - +void UnitsTest::setNumberFormatUnit(const ScAddress& rAddress, const OUString& sUnit) { SvNumberFormatter* pFormatter = mpDoc->GetFormatTable(); - sal_uInt32 nKeyCM, nKeyM, nKeyKG, nKeyS, nKeyCM_S; - - // Used to return position of error in input string for PutEntry - // -- not needed here. - sal_Int32 nCheckPos; + OUString sFormat = "#\"" + sUnit + "\""; + sal_uInt32 nKey; + sal_Int32 nCheckPos; // unused, returns the error position (shouldn't ever happen in our tests) short nType = css::util::NumberFormat::DEFINED; - OUString sM = "#\"m\""; - pFormatter->PutEntry(sM, nCheckPos, nType, nKeyM); - OUString sCM = "#\"cm\""; - pFormatter->PutEntry(sCM, nCheckPos, nType, nKeyCM); - OUString sKG = "#\"kg\""; - pFormatter->PutEntry(sKG, nCheckPos, nType, nKeyKG); - OUString sS = "#\"s\""; - pFormatter->PutEntry(sS, nCheckPos, nType, nKeyS); - OUString sCM_S = "#\"cm/s\""; - pFormatter->PutEntry(sCM_S, nCheckPos, nType, nKeyCM_S); + pFormatter->PutEntry(sFormat, nCheckPos, nType, nKey); + mpDoc->SetNumberFormat(rAddress, nKey); +} + +void UnitsTest::testUnitVerification() { + // Make sure we have at least one tab to work with + mpDoc->EnsureTable(0); // 1st column: 10cm, 20cm, 30cm ScAddress address(0, 0, 0); - mpDoc->SetNumberFormat(address, nKeyCM); + setNumberFormatUnit(address, "cm"); mpDoc->SetValue(address, 10); address.IncRow(); - mpDoc->SetNumberFormat(address, nKeyCM); + setNumberFormatUnit(address, "cm"); mpDoc->SetValue(address, 20); address.IncRow(); - mpDoc->SetNumberFormat(address, nKeyCM); + setNumberFormatUnit(address, "cm"); mpDoc->SetValue(address, 30); // 2nd column: 1kg, 2kg, 3kg address = ScAddress(1, 0, 0); - mpDoc->SetNumberFormat(address, nKeyKG); + setNumberFormatUnit(address, "kg"); mpDoc->SetValue(address, 1); address.IncRow(); - mpDoc->SetNumberFormat(address, nKeyKG); + setNumberFormatUnit(address, "kg"); mpDoc->SetValue(address, 2); address.IncRow(); - mpDoc->SetNumberFormat(address, nKeyKG); + setNumberFormatUnit(address, "kg"); mpDoc->SetValue(address, 3); // 3rd column: 1s, 2s, 3s address = ScAddress(2, 0, 0); - mpDoc->SetNumberFormat(address, nKeyS); + setNumberFormatUnit(address, "s"); mpDoc->SetValue(address, 1); address.IncRow(); - mpDoc->SetNumberFormat(address, nKeyS); + setNumberFormatUnit(address, "s"); mpDoc->SetValue(address, 2); address.IncRow(); - mpDoc->SetNumberFormat(address, nKeyS); + setNumberFormatUnit(address, "s"); mpDoc->SetValue(address, 3); // 4th column: 5cm/s, 10cm/s, 15cm/s address = ScAddress(3, 0, 0); - mpDoc->SetNumberFormat(address, nKeyCM_S); + setNumberFormatUnit(address, "cm/s"); mpDoc->SetValue(address, 5); address.IncRow(); - mpDoc->SetNumberFormat(address, nKeyCM_S); + setNumberFormatUnit(address, "cm/s"); mpDoc->SetValue(address, 10); address.IncRow(); - mpDoc->SetNumberFormat(address, nKeyCM_S); + setNumberFormatUnit(address, "cm/s"); mpDoc->SetValue(address, 15); // 5th column: 1m address = ScAddress(4, 0, 0); - mpDoc->SetNumberFormat(address, nKeyM); + setNumberFormatUnit(address, "m"); mpDoc->SetValue(address, 1); ScFormulaCell* pCell; commit adfa405c5d94c176fb7c04b4945a713567a41c3c Author: Andrzej Hunt <[email protected]> Date: Mon May 11 21:26:56 2015 +0100 Update title of Conditional Format dialog when range modified Previously the title was set during construction as "Conditional Format: $SOME$RANGE:$SELECTED$INITIALLY" However the selected range can be modified while the dialog is open, hence we update it whenever the selected range is modified. Change-Id: I63790108553102cedb51ca32d672a62477493660 diff --git a/sc/source/ui/condformat/condformatdlg.cxx b/sc/source/ui/condformat/condformatdlg.cxx index fe91602..cfd9e2e 100644 --- a/sc/source/ui/condformat/condformatdlg.cxx +++ b/sc/source/ui/condformat/condformatdlg.cxx @@ -437,23 +437,31 @@ ScCondFormatDlg::ScCondFormatDlg(vcl::Window* pParent, ScDocument* pDoc, get(mpCondFormList, "list"); mpCondFormList->init(pDoc, this, pFormat, rRange, rPos, eType); - OUStringBuffer aTitle( GetText() ); - aTitle.append(" "); - OUString aRangeString; - rRange.Format(aRangeString, SCA_VALID, pDoc, pDoc->GetAddressConvention()); - aTitle.append(aRangeString); - SetText(aTitle.makeStringAndClear()); mpBtnAdd->SetClickHdl( LINK( mpCondFormList, ScCondFormatList, AddBtnHdl ) ); mpBtnRemove->SetClickHdl( LINK( mpCondFormList, ScCondFormatList, RemoveBtnHdl ) ); mpEdRange->SetModifyHdl( LINK( this, ScCondFormatDlg, EdRangeModifyHdl ) ); mpEdRange->SetGetFocusHdl( LINK( this, ScCondFormatDlg, RangeGetFocusHdl ) ); mpEdRange->SetLoseFocusHdl( LINK( this, ScCondFormatDlg, RangeLoseFocusHdl ) ); + OUString aRangeString; + rRange.Format(aRangeString, SCA_VALID, pDoc, pDoc->GetAddressConvention()); mpEdRange->SetText(aRangeString); + msBaseTitle = GetText(); + updateTitle(); + SC_MOD()->PushNewAnyRefDlg(this); } +void ScCondFormatDlg::updateTitle() +{ + OUStringBuffer aTitle( msBaseTitle ); + aTitle.append(" "); + aTitle.append(mpEdRange->GetText()); + + SetText(aTitle.makeStringAndClear()); +} + ScCondFormatDlg::~ScCondFormatDlg() { disposeOnce(); @@ -486,6 +494,16 @@ void ScCondFormatDlg::SetActive() void ScCondFormatDlg::RefInputDone( bool bForced ) { ScAnyRefModalDlg::RefInputDone(bForced); + // ScAnyRefModalDlg::RefInputDone resets the title back + // to it's original state. + // I.e. if we open the dialog normally, and then click into the sheet + // to modify the selection, the title is updated such that the range + // is only a single cell (e.g. $A$1), after which the dialog switches + // into the RefInput mode. During the RefInput mode the title is updated + // as expected, however at the end RefInputDone overwrites the title + // with the initial (now incorrect) single cell range. Hence we correct + // it here. + updateTitle(); } bool ScCondFormatDlg::IsTableLocked() const @@ -526,6 +544,7 @@ void ScCondFormatDlg::SetReference(const ScRange& rRef, ScDocument*) OUString aRefStr(rRef.Format(n, mpDoc, ScAddress::Details(mpDoc->GetAddressConvention(), 0, 0))); pEdit->SetRefString( aRefStr ); + updateTitle(); } } @@ -564,6 +583,8 @@ IMPL_LINK( ScCondFormatDlg, EdRangeModifyHdl, Edit*, pEdit ) pEdit->SetControlBackground(GetSettings().GetStyleSettings().GetWindowColor()); else pEdit->SetControlBackground(COL_LIGHTRED); + + updateTitle(); return 0; } diff --git a/sc/source/ui/inc/condformatdlg.hxx b/sc/source/ui/inc/condformatdlg.hxx index 6036861..999c3db 100644 --- a/sc/source/ui/inc/condformatdlg.hxx +++ b/sc/source/ui/inc/condformatdlg.hxx @@ -108,6 +108,10 @@ private: VclPtr<formula::RefEdit> mpLastEdit; + OUString msBaseTitle; + + void updateTitle(); + DECL_LINK( EdRangeModifyHdl, Edit* ); protected: commit 7bdf0604e48d4c9e99930d094f118df405a4b8da Author: Andrzej Hunt <[email protected]> Date: Mon May 11 17:37:27 2015 +0100 Use HeaderUnitDescriptor to pass around header specifics Change-Id: I7c74211236b00c570941fda39cb0d69c1ce4e02c diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx index 8e83483..e2be853 100644 --- a/sc/qa/unit/units.cxx +++ b/sc/qa/unit/units.cxx @@ -409,54 +409,73 @@ void UnitsTest::testUnitValueStringSplitting() { } void UnitsTest::testUnitFromHeaderExtraction() { - UtUnit aUnit; - OUString sUnitString; + HeaderUnitDescriptor aHeader; OUString sEmpty = ""; - CPPUNIT_ASSERT(!mpUnitsImpl->extractUnitFromHeaderString(sEmpty, aUnit, sUnitString)); - CPPUNIT_ASSERT(aUnit == UtUnit()); - CPPUNIT_ASSERT(sUnitString.isEmpty()); + aHeader = mpUnitsImpl->extractUnitFromHeaderString(sEmpty); + CPPUNIT_ASSERT(!aHeader.valid); + CPPUNIT_ASSERT(aHeader.unit == UtUnit()); + CPPUNIT_ASSERT(aHeader.unitString.isEmpty()); OUString sSimple = "bla bla [cm/s]"; - CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sSimple, aUnit, sUnitString)); + aHeader = mpUnitsImpl->extractUnitFromHeaderString(sSimple); + CPPUNIT_ASSERT(aHeader.valid); // We need to test in Units (rather than testing Unit::getString()) as // any given unit can have multiple string representations (and utunits defaults to // representing e.g. cm as (I think) "0.01m"). UtUnit aTestUnit; CPPUNIT_ASSERT(UtUnit::createUnit("cm/s", aTestUnit, mpUnitsImpl->mpUnitSystem)); - CPPUNIT_ASSERT(aUnit == aTestUnit); - CPPUNIT_ASSERT(sUnitString == "cm/s"); + CPPUNIT_ASSERT(aHeader.unit == aTestUnit); + CPPUNIT_ASSERT(aHeader.unitString == "cm/s"); + CPPUNIT_ASSERT_EQUAL(aHeader.unitStringPosition, 9); + + OUString sSimple2 = "bla bla [km/s] (more text)"; + aHeader = mpUnitsImpl->extractUnitFromHeaderString(sSimple2); + CPPUNIT_ASSERT(aHeader.valid); + CPPUNIT_ASSERT(UtUnit::createUnit("km/s", aTestUnit, mpUnitsImpl->mpUnitSystem)); + CPPUNIT_ASSERT(aHeader.unit == aTestUnit); + CPPUNIT_ASSERT(aHeader.unitString == "km/s"); + CPPUNIT_ASSERT_EQUAL(aHeader.unitStringPosition, 9); OUString sFreeStanding = "bla bla kg/h"; - CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sFreeStanding, aUnit, sUnitString)); + aHeader = mpUnitsImpl->extractUnitFromHeaderString(sFreeStanding); + CPPUNIT_ASSERT(aHeader.valid); CPPUNIT_ASSERT(UtUnit::createUnit("kg/h", aTestUnit, mpUnitsImpl->mpUnitSystem)); - CPPUNIT_ASSERT(aUnit == aTestUnit); - CPPUNIT_ASSERT(sUnitString == "kg/h"); + CPPUNIT_ASSERT(aHeader.unit == aTestUnit); + CPPUNIT_ASSERT(aHeader.unitString == "kg/h"); + CPPUNIT_ASSERT_EQUAL(aHeader.unitStringPosition, 8); + + OUString sFreeStanding2 = "bla bla J/m and more text here"; + aHeader = mpUnitsImpl->extractUnitFromHeaderString(sFreeStanding2); + CPPUNIT_ASSERT(aHeader.valid); + CPPUNIT_ASSERT(UtUnit::createUnit("J/m", aTestUnit, mpUnitsImpl->mpUnitSystem)); + CPPUNIT_ASSERT(aHeader.unit == aTestUnit); + CPPUNIT_ASSERT(aHeader.unitString == "J/m"); + CPPUNIT_ASSERT_EQUAL(aHeader.unitStringPosition, 8); OUString sFreeStandingWithSpaces = "bla bla m / s"; - CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sFreeStandingWithSpaces, aUnit, sUnitString)); + aHeader = mpUnitsImpl->extractUnitFromHeaderString(sFreeStandingWithSpaces); + CPPUNIT_ASSERT(aHeader.valid); CPPUNIT_ASSERT(UtUnit::createUnit("m/s", aTestUnit, mpUnitsImpl->mpUnitSystem)); - CPPUNIT_ASSERT(aUnit == aTestUnit); - CPPUNIT_ASSERT(sUnitString == "m/s"); + CPPUNIT_ASSERT(aHeader.unit == aTestUnit); + CPPUNIT_ASSERT(aHeader.unitString == "m / s"); + CPPUNIT_ASSERT_EQUAL(aHeader.unitStringPosition, 8); - OUString sOperatorSeparated = "bla bla / t/s"; - CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sOperatorSeparated, aUnit, sUnitString)); + OUString sOperatorSeparated = "foobar / t/s"; + aHeader = mpUnitsImpl->extractUnitFromHeaderString(sOperatorSeparated); + CPPUNIT_ASSERT(aHeader.valid); CPPUNIT_ASSERT(UtUnit::createUnit("t/s", aTestUnit, mpUnitsImpl->mpUnitSystem)); - CPPUNIT_ASSERT(aUnit == aTestUnit); - CPPUNIT_ASSERT(sUnitString == "t/s"); - + CPPUNIT_ASSERT(aHeader.unit == aTestUnit); + CPPUNIT_ASSERT(aHeader.unitString == "t/s"); + CPPUNIT_ASSERT_EQUAL(aHeader.unitStringPosition, 9); OUString sRoundBrackets = "bla bla (t/h)"; - CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sRoundBrackets, aUnit, sUnitString)); + aHeader = mpUnitsImpl->extractUnitFromHeaderString(sRoundBrackets); + CPPUNIT_ASSERT(aHeader.valid); CPPUNIT_ASSERT(UtUnit::createUnit("t/h", aTestUnit, mpUnitsImpl->mpUnitSystem)); - CPPUNIT_ASSERT(aUnit == aTestUnit); - CPPUNIT_ASSERT(sUnitString == "(t/h)"); - - // This becomes more of a nightmare to support, so let's not bother for now. - // OUString sFreeStandingMixedSpaces = "bla bla m /s* kg"; - // CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sFreeStanding, aUnit, sUnitString)); - // CPPUNIT_ASSERT(UtUnit::createUnit("m/s", aTestUnit, mpUnitsImpl->mpUnitSystem)); - // CPPUNIT_ASSERT(aUnit == aTestUnit); + CPPUNIT_ASSERT(aHeader.unit == aTestUnit); + CPPUNIT_ASSERT(aHeader.unitString == "(t/h)"); + CPPUNIT_ASSERT_EQUAL(aHeader.unitStringPosition, 8); } void UnitsTest::testCellConversion() { diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx index aa3fc1e..cca532d 100644 --- a/sc/source/core/units/unitsimpl.cxx +++ b/sc/source/core/units/unitsimpl.cxx @@ -313,7 +313,7 @@ OUString UnitsImpl::extractUnitStringForCell(const ScAddress& rAddress, ScDocume return extractUnitStringFromFormat(rFormatString); } -bool UnitsImpl::findUnitInStandardHeader(const OUString& rsHeader, UtUnit& aUnit, OUString& sUnitString) { +HeaderUnitDescriptor UnitsImpl::findUnitInStandardHeader(const OUString& rsHeader) { // TODO: we should do a sanity check that there's only one such unit though (and fail if there are multiple). // Since otherwise there's no way for us to know which unit is the intended one, hence we need to get // the user to deconfuse us by correcting their header to only contain the intended unit. @@ -347,21 +347,23 @@ bool UnitsImpl::findUnitInStandardHeader(const OUString& rsHeader, UtUnit& aUnit // i.e. startOffset is the last character of the intended substring, endOffset the first character. // We specifically grab the offsets for the first actual regex group, which are stored in [1], the indexes // at [0] represent the whole matched string (i.e. including square brackets). - sUnitString = rsHeader.copy(aResult.endOffset[1], aResult.startOffset[1] - aResult.endOffset[1]); + UtUnit aUnit; + sal_Int32 nBegin = aResult.endOffset[1]; + sal_Int32 nEnd = aResult.startOffset[1] - aResult.endOffset[1]; + OUString sUnitString = rsHeader.copy( nBegin, nEnd); if (UtUnit::createUnit(sUnitString, aUnit, mpUnitSystem)) { - return true; + return { true, aUnit, boost::optional< ScAddress >(), sUnitString, nBegin }; } nStartPosition = aResult.endOffset[0]; } } - sUnitString.clear(); - return false; -} + return { false, UtUnit(), boost::optional< ScAddress >(), "", -1 }; +} -bool UnitsImpl::findFreestandingUnitInHeader(const OUString& rsHeader, UtUnit& aUnit, OUString& sUnitString) { +HeaderUnitDescriptor UnitsImpl::findFreestandingUnitInHeader(const OUString& rsHeader) { // We just split the string and test whether each token is either a valid unit in its own right, // or is an operator that could glue together multiple units (i.e. multiplication/division). // This is sufficient for when there are spaces between elements composing the unit, and none @@ -373,50 +375,69 @@ bool UnitsImpl::findFreestandingUnitInHeader(const OUString& rsHeader, UtUnit& a const sal_Int32 nTokenCount = comphelper::string::getTokenCount(rsHeader, ' '); const OUString sOperators = "/*"; // valid - sUnitString.clear(); + + OUStringBuffer sUnitStringBuf; + + sal_Int32 nStartPos = -1; + sal_Int32 nTokenPos = 0; for (sal_Int32 nToken = 0; nToken < nTokenCount; nToken++) { - OUString sToken = rsHeader.getToken(nToken, ' '); + OUString sToken = rsHeader.getToken( 0,' ', nTokenPos); UtUnit aTestUnit; + + // Only test for a separator character if we have already got something in our string, as + // some of the operators could be used as separators from description to unit + // (e.g. "a description / kg"). if (UtUnit::createUnit(sToken, aTestUnit, mpUnitSystem) || - // Only test for a separator character if we have already got something in our string, as - // some of the operators could be used as separators from description to unit - // (e.g. "a description / kg"). - ((sUnitString.getLength() > 0) && (sToken.getLength() == 1) && (sOperators.indexOf(sToken[0]) != -1))) { - // we're repeatedly testing the string hence using an OUStringBuffer isn't of much use since there's - // no simple/efficient way of repeatedly getting a testable OUString from the buffer. - sUnitString += sToken; - } else if (sUnitString.getLength() > 0) { + ((sUnitStringBuf.getLength() > 0) && (sToken.getLength() == 1) && (sOperators.indexOf(sToken[0]) != -1))) { + + if (nStartPos == -1) { + // getToken sets nTokenPos to the first position after + // the current token (or -1 if the token is at the end + // the string). + if (nTokenPos == -1) { + nStartPos = rsHeader.getLength() - sToken.getLength(); + } else { + nStartPos = nTokenPos - sToken.getLength() - 1; + } + } + + sUnitStringBuf.append(" ").append(sToken); + } else if (sUnitStringBuf.getLength() > 0) { // If we have units, followed by text, followed by units, we should still flag an error since // that's ambiguous (unless the desired units are enclose in [] in which case we've // already extracted these desired units in step 1 above. break; } } + // Remove the leading space, it doesn't count as part of the unit string. + // (We reinsert spaces above as the HeaderUnitDescriptor must have the + // the original string as found in the header, i.e. we can't remove the + // spaces.) + sUnitStringBuf.remove(0, 1); // We test the length to make sure we don't return the dimensionless unit 1 if we haven't found any units // in the header. + UtUnit aUnit; + OUString sUnitString = sUnitStringBuf.makeStringAndClear(); if (sUnitString.getLength() && UtUnit::createUnit(sUnitString, aUnit, mpUnitSystem)) { - return true; + return { true, aUnit, boost::optional< ScAddress >(), sUnitString, nStartPos }; } - sUnitString.clear(); - return false; + + return { false, UtUnit(), boost::optional< ScAddress >(), "", -1 }; } -bool UnitsImpl::extractUnitFromHeaderString(const OUString& rsHeader, UtUnit& aUnit, OUString& sUnitString) { +HeaderUnitDescriptor UnitsImpl::extractUnitFromHeaderString(const OUString& rsHeader) { // 1. Ideally we have units in a 'standard' format, i.e. enclose in square brackets: - if (findUnitInStandardHeader(rsHeader, aUnit, sUnitString)) { - return true; + HeaderUnitDescriptor aHeader = findUnitInStandardHeader(rsHeader); + if (aHeader.valid) { + return aHeader; } // 2. But if not we check for free-standing units - if (findFreestandingUnitInHeader(rsHeader, aUnit, sUnitString)) { - return true; - } - - // 3. Give up - aUnit = UtUnit(); // assign invalid - sUnitString.clear(); - return false; + aHeader = findFreestandingUnitInHeader(rsHeader); + // We return the result either way (it's either a valid unit, + // or invalid). + return aHeader; } UtUnit UnitsImpl::getUnitForCell(const ScAddress& rCellAddress, ScDocument* pDoc) { @@ -428,11 +449,11 @@ UtUnit UnitsImpl::getUnitForCell(const ScAddress& rCellAddress, ScDocument* pDoc return aUnit; } - OUString aHeaderUnitString; // Unused -- passed by reference below - ScAddress aHeaderAddress; // Unused too - UtUnit aHeaderUnit = findHeaderUnitForCell(rCellAddress, pDoc, aHeaderUnitString, aHeaderAddress); - if (aHeaderUnit.isValid()) - return aHeaderUnit; + HeaderUnitDescriptor aHeader = findHeaderUnitForCell(rCellAddress, pDoc); + + if (aHeader.valid) { + return aHeader.unit; + } SAL_INFO("sc.units", "no unit obtained for token at cell " << rCellAddress.GetColRowString()); @@ -457,23 +478,27 @@ UtUnit UnitsImpl::getUnitForRef(FormulaToken* pToken, const ScAddress& rFormulaA return getUnitForCell(aCellAddress, pDoc); } -UtUnit UnitsImpl::findHeaderUnitForCell(const ScAddress& rCellAddress, - ScDocument* pDoc, - OUString& rsHeaderUnitString, - ScAddress& rHeaderAddress) { +HeaderUnitDescriptor UnitsImpl::findHeaderUnitForCell(const ScAddress& rCellAddress, + ScDocument* pDoc) { // Scan UPwards from the current cell to find a header. This is since we could potentially // have two different sets of data sharing a column, hence finding the closest header is necessary. - rHeaderAddress = rCellAddress; - while (rHeaderAddress.Row() > 0) { - rHeaderAddress.IncRow(-1); + ScAddress address = rCellAddress; + + while (address.Row() > 0) { + address.IncRow(-1); // We specifically test for string cells as intervening data cells could have // differently defined units of their own. (However as these intervening cells // will have the unit stored in the number format it would be ignored when // checking the cell's string anyway.) UtUnit aUnit; - if (pDoc->GetCellType(rHeaderAddress) == CELLTYPE_STRING && - extractUnitFromHeaderString(pDoc->GetString(rHeaderAddress), aUnit, rsHeaderUnitString)) { + if (pDoc->GetCellType(address) == CELLTYPE_STRING) { + HeaderUnitDescriptor aHeader = extractUnitFromHeaderString(pDoc->GetString(address)); + + if (aHeader.valid) { + aHeader.address = address; + return aHeader; + } // TODO: one potential problem is that we could have a text only "united" data cell // (where the unit wasn't automatically extracted due to being entered via // a different spreadsheet program). @@ -482,12 +507,10 @@ UtUnit UnitsImpl::findHeaderUnitForCell(const ScAddress& rCellAddress, // // TODO: and what if there are multiple units in the header (for whatever reason?)? // We can probably just warn the user that we'll be giving them garbage in that case? - return aUnit; } } - rHeaderAddress.SetInvalid(); - rsHeaderUnitString.clear(); - return UtUnit(); + + return { false, UtUnit(), boost::optional< ScAddress >(), "", -1 }; } // getUnitForRef: check format -> if not in format, use more complicated method? (Format overrides header definition) @@ -575,13 +598,10 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd return false; } - OUString sUnitString; - ScAddress aAddress; - - UtUnit aHeaderUnit = findHeaderUnitForCell(rFormulaAddress, pDoc, sUnitString, aAddress); + HeaderUnitDescriptor aHeader = findHeaderUnitForCell(rFormulaAddress, pDoc); UtUnit aResultUnit = boost::get< UtUnit>(aStack.top().item); - if (aHeaderUnit.isValid() && aHeaderUnit != aResultUnit) { + if (aHeader.valid && aHeader.unit != aResultUnit) { return false; } @@ -635,11 +655,12 @@ bool UnitsImpl::isCellConversionRecommended(const ScAddress& rCellAddress, rsCellUnit = extractUnitStringForCell(rCellAddress, pDoc); if (!rsCellUnit.isEmpty() && UtUnit::createUnit(rsCellUnit, aCellUnit, mpUnitSystem)) { - UtUnit aHeaderUnit = findHeaderUnitForCell(rCellAddress, pDoc, rsHeaderUnit, rHeaderCellAddress); - if (rHeaderCellAddress.IsValid()) { - if (aHeaderUnit.areConvertibleTo(aCellUnit)) { - return true; - } + HeaderUnitDescriptor aHeader = findHeaderUnitForCell(rCellAddress, pDoc); + if (aHeader.valid && aHeader.unit.areConvertibleTo(aCellUnit)) { + rsHeaderUnit = aHeader.unitString; + assert(aHeader.address); + rHeaderCellAddress = *aHeader.address; + return true; } } @@ -659,9 +680,8 @@ bool UnitsImpl::convertCellToHeaderUnit(const ScAddress& rCellAddress, UtUnit aOldUnit; UtUnit::createUnit(sCellUnit, aOldUnit, mpUnitSystem); - OUString sHeaderUnitFound; - ScAddress aHeaderAddress; // Unused, but passed by reference - UtUnit aNewUnit = findHeaderUnitForCell(rCellAddress, pDoc, sHeaderUnitFound, aHeaderAddress); + HeaderUnitDescriptor aHeader = findHeaderUnitForCell(rCellAddress, pDoc); + assert(aHeader.valid); // We test that we still have all data in the same format as expected. // This is maybe a tad defensive, but this call is most likely to be delayed @@ -671,11 +691,11 @@ bool UnitsImpl::convertCellToHeaderUnit(const ScAddress& rCellAddress, // called afterwards (especially for non-modal interactions, e.g. // with an infobar which can remain open whilst the document is edited). if ((sCellUnit == rsOldUnit) && - (sHeaderUnitFound == rsNewUnit) && + (aHeader.unitString == rsNewUnit) && (pDoc->GetCellType(rCellAddress) == CELLTYPE_VALUE)) { - assert(aOldUnit.areConvertibleTo(aNewUnit)); + assert(aOldUnit.areConvertibleTo(aHeader.unit)); double nOldValue = pDoc->GetValue(rCellAddress); - double nNewValue = aOldUnit.convertValueTo(nOldValue, aNewUnit); + double nNewValue = aOldUnit.convertValueTo(nOldValue, aHeader.unit); pDoc->SetValue(rCellAddress, nNewValue); pDoc->SetNumberFormat(rCellAddress, 0); // 0 == no number format? @@ -712,37 +732,33 @@ bool UnitsImpl::convertCellUnits(const ScRange& rRange, // Each column is independent hence we are able to handle each separately. for (SCCOL nCol = nStartCol; nCol <= nEndCol; nCol++) { - ScAddress aCurrentHeaderAddress(ScAddress::INITIALIZE_INVALID); - UtUnit aCurrentHeaderUnit; - OUString sHeaderUnitString; + HeaderUnitDescriptor aHeader = { false, UtUnit(), boost::optional< ScAddress >(), "", -1 }; for (SCROW nRow = nEndRow; nRow >= nStartRow; nRow--) { ScAddress aCurrent(nCol, nRow, nStartTab); - if (aCurrent == aCurrentHeaderAddress) { - // TODO: rewrite this to use HeaderUnitDescriptor once implemented. - // We can't do a dumb replace since that might overwrite other characters - // (many units are just single characters). + if (aCurrent == aHeader.address) { OUString sHeader = pDoc->GetString(aCurrent); - sHeader = sHeader.replaceAll(sHeaderUnitString, rsOutputUnit); + sHeader = sHeader.replaceAt(aHeader.unitStringPosition, aHeader.unitString.getLength(), rsOutputUnit); pDoc->SetString(aCurrent, sHeader); - aCurrentHeaderAddress.SetInvalid(); + aHeader.valid = false; } else if (pDoc->GetCellType(aCurrent) != CELLTYPE_STRING) { - if (!aCurrentHeaderUnit.isValid()) { - aCurrentHeaderUnit = findHeaderUnitForCell(aCurrent, pDoc, sHeaderUnitString, aCurrentHeaderAddress); + if (!aHeader.valid) { + aHeader = findHeaderUnitForCell(aCurrent, pDoc); // If there is no header we get an invalid unit returned from findHeaderUnitForCell, // and therfore assume the dimensionless unit 1. - if (!aCurrentHeaderUnit.isValid()) { - UtUnit::createUnit("", aCurrentHeaderUnit, mpUnitSystem); + if (!aHeader.valid) { + UtUnit::createUnit("", aHeader.unit, mpUnitSystem); + aHeader.valid = true; } } OUString sLocalUnit(extractUnitStringForCell(aCurrent, pDoc)); UtUnit aLocalUnit; if (sLocalUnit.isEmpty()) { - aLocalUnit = aCurrentHeaderUnit; + aLocalUnit = aHeader.unit; } else { // override header unit with annotation unit if (!UtUnit::createUnit(sLocalUnit, aLocalUnit, mpUnitSystem)) { // but assume dimensionless if invalid @@ -750,8 +766,8 @@ bool UnitsImpl::convertCellUnits(const ScRange& rRange, } } - bool bLocalAnnotationRequired = (!aRange.In(aCurrentHeaderAddress)) && - (aOutputUnit != aCurrentHeaderUnit); + bool bLocalAnnotationRequired = (!aRange.In(*aHeader.address)) && + (aOutputUnit != aHeader.unit); double nValue = pDoc->GetValue(aCurrent); if (!aLocalUnit.areConvertibleTo(aOutputUnit)) { diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx index ff927f2..7320baa 100644 --- a/sc/source/core/units/unitsimpl.hxx +++ b/sc/source/core/units/unitsimpl.hxx @@ -58,6 +58,17 @@ struct UnitsResult { boost::optional<UtUnit> units; }; +struct HeaderUnitDescriptor { + bool valid; + UtUnit unit; + boost::optional< ScAddress > address; + // This must be the unit string copied verbatim from the header + // (i.e. including spaces) + OUString unitString; + // Position of unitString within the cell contents + sal_Int32 unitStringPosition; +}; + class UnitsImpl: public Units { friend class test::UnitsTest; @@ -103,9 +114,9 @@ private: * Find and extract a Unit in the standard header notation, * i.e. a unit enclose within square brackets (e.g. "length [cm]". * - * @return true if such a unit is found. + * @return The HeaderUnitDescriptor, with valid set to true if a unit was found. */ - bool findUnitInStandardHeader(const OUString& rHeader, UtUnit& aUnit, OUString& sUnitString); + HeaderUnitDescriptor findUnitInStandardHeader(const OUString& rHeader); /** * Find and extract a freestanding Unit from a header string. * This includes strings such as "speed m/s", "speed m / s", @@ -117,11 +128,11 @@ private: * more permutations of the same unit, but this should at least cover the most * obvious cases. * - * @ return true if a unit is found. + * @ return The HeaderUnitDescriptor, with valid set to true if a unit was found. */ - bool findFreestandingUnitInHeader(const OUString& rHeader, UtUnit& aUnit, OUString& sUnitString); + HeaderUnitDescriptor findFreestandingUnitInHeader(const OUString& rHeader); - bool extractUnitFromHeaderString(const OUString& rHeader, UtUnit& aUnit, OUString& sUnitString); + HeaderUnitDescriptor extractUnitFromHeaderString(const OUString& rHeader); static OUString extractUnitStringFromFormat(const OUString& rFormatString); static OUString extractUnitStringForCell(const ScAddress& rAddress, ScDocument* pDoc); @@ -142,10 +153,8 @@ private: * that there is a valid unit), but we might also need the original * String (which can't necessarily be regenerated from the UtUnit). */ - UtUnit findHeaderUnitForCell(const ScAddress& rCellAddress, - ScDocument* pDoc, - OUString& rsHeaderUnitString, - ScAddress& rHeaderAddress); + HeaderUnitDescriptor findHeaderUnitForCell(const ScAddress& rCellAddress, + ScDocument* pDoc); }; }} // namespace sc::units _______________________________________________ Libreoffice-commits mailing list [email protected] http://lists.freedesktop.org/mailman/listinfo/libreoffice-commits
