[Libreoffice-commits] core.git: Branch 'feature/unitver' - 10 commits - sc/inc sc/qa sc/source

Andrzej Hunt andrzej at ahunt.org
Tue May 12 13:19:45 PDT 2015


 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 <andrzej at ahunt.org>
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 <andrzej at ahunt.org>
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 <andrzej at ahunt.org>
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 <andrzej at ahunt.org>
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 <andrzej at ahunt.org>
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 <andrzej at ahunt.org>
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 <andrzej at ahunt.org>
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 <andrzej at ahunt.org>
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 <andrzej at ahunt.org>
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 <andrzej at ahunt.org>
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


More information about the Libreoffice-commits mailing list