[Libreoffice-commits] core.git: Branch 'feature/unitver' - 940 commits - accessibility/inc accessibility/source android/Bootstrap android/CustomTarget_android_desktop.mk android/CustomTarget_lo_android.mk android/experimental android/Makefile android/README android/source avmedia/inc avmedia/Library_avmediagst_0_10.mk avmedia/Library_avmediagst.mk avmedia/source basctl/source basegfx/source basic/inc basic/qa basic/source bin/gbuild-to-ide bin/get-bugzilla-attachments-by-mimetype bin/lo-commit-stat bin/lo-generate-source-tarball bin/rename-sw-abbreviations.sh bridges/source canvas/source chart2/inc chart2/qa chart2/source cli_ure/source comphelper/source compilerplugins/clang config_host/config_java.h.in config_host.mk.in configure.ac connectivity/inc connectivity/source connectivity/workben cppcanvas/inc cppcanvas/source cppuhelper/source cppu/source cui/source cui/uiconfig dbaccess/source desktop/inc desktop/scripts desktop/source dictionaries download.lst drawinglayer/source editeng/source emb eddedobj/source embeddedobj/test extensions/source external/harfbuzz external/libetonyek external/libexttextcat external/libmwaw external/libodfgen external/libwps external/Module_external.mk external/nss external/python3 external/udunits2 extras/source filter/Configuration_filter.mk filter/qa filter/source forms/source formula/source fpicker/source framework/inc framework/source .gitignore helpcompiler/Executable_helpindexer.mk helpcompiler/Executable_HelpIndexer.mk helpcompiler/Executable_helplinker.mk helpcompiler/Executable_HelpLinker.mk helpcompiler/Module_helpcompiler.mk helpcontent2 hwpfilter/source i18nlangtag/source i18npool/qa i18npool/source icon-themes/breeze icon-themes/classic icon-themes/galaxy icon-themes/hicontrast icon-themes/human icon-themes/oxygen icon-themes/sifr icon-themes/tango icon-themes/tango_testing idlc/inc idlc/source idl/source include/basic include/comphelper include/cppuhelper include/drawinglayer include/editeng include/filter include/formula inclu de/framework include/LibreOfficeKit include/o3tl include/oox include/sal include/sfx2 include/svl include/svtools include/svx include/test include/tools include/unotools include/vbahelper include/vcl include/writerperfect include/xmloff ios/CustomTarget_LibreOffice_app.mk ios/CustomTarget_MobileLibreOffice_app.mk ios/MobileLibreOffice ios/shared jvmfwk/inc jvmfwk/source l10ntools/source librelogo/source libreofficekit/qa libreofficekit/source lingucomponent/source Makefile.fetch Makefile.in odk/CustomTarget_doxygen.mk offapi/com offapi/org officecfg/registry oox/inc oox/source pyuno/source readlicense_oo/license README.cross registry/source reportdesign/source RepositoryExternal.mk rsc/source sal/cppunittester sal/Module_sal.mk sal/osl sal/qa sal/textenc sax/source sc/AllLangResTarget_sc.mk sc/CppunitTest_sc_copypaste.mk sc/CppunitTest_sc_subsequent_filters_test.mk sc/CppunitTest_sc_ucalc.mk sc/CppunitTest_sc_units.mk sc/inc sc/Library_sc.mk sc/Module_sc.mk scp2/source sc/qa scripti ng/source sc/sdi sc/source sc/workben sd/qa sd/source sd/uiconfig sfx2/sdi sfx2/source shell/source slideshow/source slideshow/test solenv/bin solenv/gbuild solenv/README sot/source starmath/inc starmath/Library_sm.mk starmath/sdi starmath/source starmath/uiconfig starmath/UIConfig_smath.mk stoc/source store/source svl/qa svl/source svtools/inc svtools/source svtools/uiconfig svx/sdi svx/source svx/workben sw/CppunitTest_sw_docbookexport.mk swext/mediawiki sw/inc sw/Module_sw.mk sw/ooxmlexport_setup.mk sw/qa sw/README sw/sdi sw/source sw/uiconfig test/source toolkit/source tools/source translations ucb/source unotools/source unoxml/source unusedcode.easy uui/source uui/uiconfig vbahelper/source vcl/android vcl/generic vcl/inc vcl/Library_vcl.mk vcl/opengl vcl/osx vcl/qa vcl/quartz vcl/README.GDIMetaFile vcl/source vcl/unx vcl/win vcl/workben writerfilter/inc writerfilter/source writerperfect/CppunitTest_writerperfect_stream.mk writerperfect/inc writerperfect/Library_wpftcalc.mk writ erperfect/Library_wpftdraw.mk writerperfect/Library_wpftimpress.mk writerperfect/Library_wpftwriter.mk writerperfect/Library_writerperfect.mk writerperfect/Module_writerperfect.mk writerperfect/qa writerperfect/source writerperfect/uiconfig writerperfect/UIConfig_writerperfect.mk xmloff/inc xmloff/source xmlscript/source xmlsecurity/source

Andrzej Hunt andrzej at ahunt.org
Sun May 24 02:51:10 PDT 2015


Rebased ref, commits from common ancestor:
commit c7cb454e1137346ea3b8ad1bc6090db4e213e9fe
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sun May 24 10:35:39 2015 +0100

    Update units error messsage for clarity.
    
    Change-Id: Ib59b2317f962d01bcc65a981040c3452cee7206e

diff --git a/sc/source/ui/src/units.src b/sc/source/ui/src/units.src
index be4f970..7a818e9 100644
--- a/sc/source/ui/src/units.src
+++ b/sc/source/ui/src/units.src
@@ -21,7 +21,7 @@
 
 String STR_UNITS_ERRORINCELL
 {
-    Text [ en-US ] = "Error in formula in Cell $1" ;
+    Text [ en-US ] = "Units error in formula in Cell $1" ;
 };
 
 PushButton BT_UNITS_EDIT_CELL
commit a8b7811610f52480511841d55496dc869ef05439
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 551979dc1ebe1d712ad1d1a78cbdb3f71e752e62
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 1a376321ea4d6e09029e70479fd3e9c6a8d20023
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 b2fb7bf0424915ae20f3cca98eecffa70ee5b34c
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 f88b099479db3ea9a36db30a955adc5430761570
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 57d31b0b35de19f7148da17f6a9301d2add3ff58
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 9aa11b052658c59cd1c45264b90af948ad84804b
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 b43c5f8c7a3e1d4fa12c5ab80d9d7634cdc3071e
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 c03508ed5db8296f4765bcd01c170f273d0c57c8
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
commit ada6b639faea8fd707d3d3957f1608b308ef9354
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Mon May 11 15:13:16 2015 +0100

    Implement unit conversion for ranges.
    
    Not entirely finished yet, some further refactoring needed elsewhere
    to allow sensible implementation of the header editing.
    
    Change-Id: I81af74d698098f901b17fcda413e7aac04c94274

diff --git a/sc/inc/units.hxx b/sc/inc/units.hxx
index 9837d36..381ec7d 100644
--- a/sc/inc/units.hxx
+++ b/sc/inc/units.hxx
@@ -16,6 +16,7 @@
 
 class ScAddress;
 class ScDocument;
+class ScRange;
 class ScTokenArray;
 
 namespace sc {
@@ -75,6 +76,27 @@ public:
                                          const OUString& rsNewUnit,
                                          const OUString& rsOldUnit) = 0;
 
+    /**
+     * Convert cells from one unit to another.
+     *
+     * 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).
+     *
+     * 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
+     * annotation is also updated as appropriate. If instead the header is excluded,
+     * but all other cells are selected in a column, then local annotations are added.
+     *
+     * rsInputUnit overrides the automatic determination of input units, i.e. disables
+     * input unit detection.
+     */
+    virtual bool convertCellUnits(const ScRange& rRange,
+                                  ScDocument* pDoc,
+                                  const OUString& rsOutputUnit) = 0;
 
     virtual ~Units() {}
 };
diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index e6ed779..8e83483 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -48,6 +48,7 @@ public:
     void testUnitFromHeaderExtraction();
 
     void testCellConversion();
+    void testRangeConversion();
 
     CPPUNIT_TEST_SUITE(UnitsTest);
 
@@ -59,6 +60,7 @@ public:
     CPPUNIT_TEST(testUnitFromHeaderExtraction);
 
     CPPUNIT_TEST(testCellConversion);
+    CPPUNIT_TEST(testRangeConversion);
 
     CPPUNIT_TEST_SUITE_END();
 
@@ -524,6 +526,88 @@ void UnitsTest::testCellConversion() {
     // to pass in the output of isCellConversionRecommended).
 }
 
+void UnitsTest::testRangeConversion() {
+    const SCTAB nTab = 1;
+    mpDoc->EnsureTable(nTab);
+
+    // Column 1: convert [cm] to [cm].
+    ScAddress headerAddress(0, 0, nTab);
+    mpDoc->SetString(headerAddress, "length [cm]");
+
+    ScAddress address(headerAddress);
+
+    vector<double> values({10, 20, 30, 40, 1, 0.5, 0.25});
+    address.IncRow();
+    mpDoc->SetValues(address, values);
+
+    // Test conversion of range _not_ including header
+    ScAddress endAddress( address.Col(), address.Row() + values.size() - 1, nTab);
+
+    ScRange aRange(address, endAddress);
+    CPPUNIT_ASSERT(mpUnitsImpl->convertCellUnits(aRange, mpDoc, "cm"));
+    CPPUNIT_ASSERT(!mpUnitsImpl->convertCellUnits(aRange, mpDoc, "kg"));
+
+    CPPUNIT_ASSERT(mpDoc->GetString(headerAddress) == "length [cm]");
+
+    for (double d: values) {
+        // Test that the value is unchanged
+        CPPUNIT_ASSERT(mpDoc->GetValue(address) == d);
+        // And NO annotation has been added
+        CPPUNIT_ASSERT(mpDoc->GetString(address) == OUString::number(d));
+        address.IncRow();
+    }
+
+    // Test conversion of range including header (from cm to cm)
+    aRange = ScRange(headerAddress, endAddress);
+    CPPUNIT_ASSERT(mpUnitsImpl->convertCellUnits(aRange, mpDoc, "cm"));
+    CPPUNIT_ASSERT(!mpUnitsImpl->convertCellUnits(aRange, mpDoc, "kg"));
+
+    CPPUNIT_ASSERT(mpDoc->GetString(headerAddress) == "length [cm]");
+
+    address = headerAddress;
+    address.IncRow();
+    for (double d: values) {
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(mpDoc->GetValue(address), d, 1e-7);
+        // And NO annotation has been added
+        CPPUNIT_ASSERT(mpDoc->GetString(address) == OUString::number(d));
+        address.IncRow();
+    }
+
+    // Convert just the values (but not header): [cm] to [m]
+    address.SetRow(1);
+    aRange = ScRange(address, endAddress);
+    CPPUNIT_ASSERT(mpUnitsImpl->convertCellUnits(aRange, mpDoc, "m"));
+
+    CPPUNIT_ASSERT(mpDoc->GetString(headerAddress) == "length [cm]");
+
+    for (double d: values) {
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(mpDoc->GetValue(address), d/100, 1e-7);
+        // AND test annotation
+        // Disabled for now until the precision problems are figured out
+        // CPPUNIT_ASSERT(mpDoc->GetString(address) == OUString::number(d/100) + "m");
+        address.IncRow();
+    }
+
+    // Convert everything (including header) to mm: [m] to [mm]
+    aRange = ScRange(headerAddress, endAddress);
+    CPPUNIT_ASSERT(mpUnitsImpl->convertCellUnits(aRange, mpDoc, "mm"));
+
+    CPPUNIT_ASSERT(mpDoc->GetString(headerAddress) == "length [mm]");
+
+    address.SetRow(1);
+
+    for (double d: values) {
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(mpDoc->GetValue(address), d*10, 1e-7);
+        // And the annotation has been REMOVED
+        CPPUNIT_ASSERT(mpDoc->GetString(address) == OUString::number(d*10));
+        address.IncRow();
+    }
+
+    // TODO: we need to test:
+    // 1. mixture of units that can't be converted
+    // 2. mixtures of local and header annotations
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(UnitsTest);
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index e8fb261..aa3fc1e 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -690,4 +690,99 @@ 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++) {
+        ScAddress aCurrentHeaderAddress(ScAddress::INITIALIZE_INVALID);
+        UtUnit aCurrentHeaderUnit;
+        OUString sHeaderUnitString;
+
+        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).
+                OUString sHeader = pDoc->GetString(aCurrent);
+                sHeader = sHeader.replaceAll(sHeaderUnitString, rsOutputUnit);
+                pDoc->SetString(aCurrent, sHeader);
+
+                aCurrentHeaderAddress.SetInvalid();
+            } else if (pDoc->GetCellType(aCurrent) != CELLTYPE_STRING) {
+                if (!aCurrentHeaderUnit.isValid()) {
+                    aCurrentHeaderUnit = findHeaderUnitForCell(aCurrent, pDoc, sHeaderUnitString, aCurrentHeaderAddress);
+
+                    // 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);
+                    }
+                }
+
+                OUString sLocalUnit(extractUnitStringForCell(aCurrent, pDoc));
+                UtUnit aLocalUnit;
+                if (sLocalUnit.isEmpty()) {
+                    aLocalUnit = aCurrentHeaderUnit;
+                } 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(aCurrentHeaderAddress)) &&
+                                                (aOutputUnit != aCurrentHeaderUnit);
+                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);
+                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 + "\"";
+                    sal_uInt32 nFormatKey;
+                    short nType = css::util::NumberFormat::DEFINED;
+                    sal_Int32 nErrorPosition; // Unused, because we should be creating working number formats.
+
+                    SvNumberFormatter* pFormatter = pDoc->GetFormatTable();
+                    pFormatter->PutEntry(sNewFormat, nErrorPosition, nType, nFormatKey);
+                    pDoc->SetNumberFormat(aCurrent, nFormatKey);
+                } else {
+                    // The number formats will by definition be wrong once we've converted, so just reset completely.
+                    pDoc->SetNumberFormat(aCurrent, 0);
+                }
+            }
+
+        }
+    }
+
+    return true;
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index 0eea129..ff927f2 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -92,6 +92,10 @@ public:
                                          const OUString& rsNewUnit,
                                          const OUString& rsOldUnit) SAL_OVERRIDE;
 
+    virtual bool convertCellUnits(const ScRange& rRange,
+                                  ScDocument* pDoc,
+                                  const OUString& rsOutputUnit) SAL_OVERRIDE;
+
 private:
     UnitsResult getOutputUnitsForOpCode(std::stack< RAUSItem >& rStack, const formula::FormulaToken* pToken, ScDocument* pDoc);
 
commit b46695eed650d0dcb307f7a1b3bf9a8fbb1c45df
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Mon May 11 12:20:15 2015 +0100

    Use the actual default format for value/unit splitting.
    
    Change-Id: I1da34b43d273d1ae2f91591aafcfab8adf9a4f02

diff --git a/sc/source/core/data/column3.cxx b/sc/source/core/data/column3.cxx
index f0d7fa8..9666d60 100644
--- a/sc/source/core/data/column3.cxx
+++ b/sc/source/core/data/column3.cxx
@@ -1689,7 +1689,7 @@ bool ScColumn::ParseString(
                     // But in that case it would make sense to actually store units independently of number format.
 
                     // (This is all just a dirty hack for now...)
-                    OUString sNewFormat = "#\"" + sUnit + "\"";
+                    OUString sNewFormat = "General\"" + sUnit + "\"";
                     sal_uInt32 nFormatKey;
                     short nType = css::util::NumberFormat::DEFINED;
                     sal_Int32 nErrorPosition; // Unused, because we should be creating working number formats.
commit 33cd8ccb4bfa742f1ac839c6c063a1434f3c9cb5
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Mon May 11 12:19:25 2015 +0100

    Add some assertions for UtUnit::convertValueTo
    
    Change-Id: Ie606bbb3cab0b4445274b8bf92f8b1533b39c9fd

diff --git a/sc/source/core/units/utunit.hxx b/sc/source/core/units/utunit.hxx
index 6bfb120..b039c74 100644
--- a/sc/source/core/units/utunit.hxx
+++ b/sc/source/core/units/utunit.hxx
@@ -119,6 +119,9 @@ public:
     }
 
     double convertValueTo(double nOriginalValue, const UtUnit& rUnit) {
+        assert(isValid());
+        assert(rUnit.isValid());
+
         // We could write our own cv_converter wrapper too, but that
         // seems unnecessary given the limited selection of
         // operations (convert float/double, either individually
commit 64eb8bedd7c0d457774425c04429dd99b62e8509
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Mon May 11 12:19:01 2015 +0100

    UtUnit::isValid should be const.
    
    Change-Id: I209dca17b0101ec8a36e590870e90dcaca510a42

diff --git a/sc/source/core/units/utunit.hxx b/sc/source/core/units/utunit.hxx
index c137f67..6bfb120 100644
--- a/sc/source/core/units/utunit.hxx
+++ b/sc/source/core/units/utunit.hxx
@@ -69,7 +69,7 @@ public:
 
     OUString getString() const;
 
-    bool isValid() {
+    bool isValid() const {
         // We use a null pointer/empty unit to indicate an invalid unit.
         return mpUnit.get() != 0;
     }
commit c096d36ac2556921946e43bcd71cc2a28c26d516
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Mon May 11 12:18:04 2015 +0100

    Add --enable-debug for udunits.
    
    Change-Id: Ic737f7fee40ed564bfd1eec973e2b9843ed68405

diff --git a/external/udunits2/ExternalProject_udunits2.mk b/external/udunits2/ExternalProject_udunits2.mk
index 6fdbefa..070dc67 100644
--- a/external/udunits2/ExternalProject_udunits2.mk
+++ b/external/udunits2/ExternalProject_udunits2.mk
@@ -27,6 +27,7 @@ $(call gb_ExternalProject_get_state_target,udunits2,configure) :
 	$(call gb_ExternalProject_run,configure,\
 		autoreconf -i && \
 		MAKE=$(MAKE) ./configure \
+			$(if $(ENABLE_DEBUG),--enable-debug) \
 			--build=$(if $(filter WNT,$(OS)),i686-pc-cygwin,$(BUILD_PLATFORM)) \
 	)
 
commit a301e18b51228dab71cedae4d20161cf4340d23f
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sat May 9 20:35:04 2015 +0100

    Store pre-conversion value in cell annotation.
    
    Change-Id: I67d8d1a7b0190b91107987a1ae4f03f2e91b06ca

diff --git a/sc/source/ui/view/viewfunc.cxx b/sc/source/ui/view/viewfunc.cxx
index 605b79a..b156587 100644
--- a/sc/source/ui/view/viewfunc.cxx
+++ b/sc/source/ui/view/viewfunc.cxx
@@ -2971,11 +2971,27 @@ IMPL_LINK( ScViewFunc, UnitConversionRecommendedHandler, UnitConversionPushButto
 
     ScDocShellModificator aModificator( *pButton->mpDocSh );
 
+    OUString sOriginalValue = pButton->mpDoc->GetString( pButton->aCellAddress );
+
     pUnits->convertCellToHeaderUnit( pButton->aCellAddress,
                                      pButton->mpDoc,
                                      pButton->sHeaderUnit,
                                      pButton->sCellUnit );
 
+    ScPostIt* pNote = pButton->mpDoc->GetOrCreateNote( pButton->aCellAddress );
+    OUString sCurrentNote = pNote->GetText();
+
+    OUString sConversionNote("Original input: " + sOriginalValue);
+
+    if (sCurrentNote.isEmpty())
+    {
+        pNote->SetText( pButton->aCellAddress, sConversionNote );
+    }
+    else
+    {
+        pNote->SetText( pButton->aCellAddress, sCurrentNote + "\n\n" + sConversionNote );
+    }
+
     aModificator.SetDocumentModified();
 #endif
 
commit 85aa40659ed2e332d8fa9a08b7a3f9b2c1c19f9e
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sat May 9 20:34:10 2015 +0100

    Set document modified on local unit conversion.
    
    Change-Id: I6666668817a7987d14728fb1de1abe3711e34d9a

diff --git a/sc/source/ui/inc/viewfunc.hxx b/sc/source/ui/inc/viewfunc.hxx
index 20d6872..a6b36b8 100644
--- a/sc/source/ui/inc/viewfunc.hxx
+++ b/sc/source/ui/inc/viewfunc.hxx
@@ -380,7 +380,8 @@ private:
                                                      ScDocument* pDoc,
                                                      const OUString& sHeaderUnit,
                                                      const ScAddress& rHeaderAddress,
-                                                     const OUString& sCellUnit );
+                                                     const OUString& sCellUnit,
+                                                     ScDocShell* pDocSh );
     DECL_LINK( UnitConversionRecommendedHandler, UnitConversionPushButton* );
 };
 
diff --git a/sc/source/ui/view/viewfunc.cxx b/sc/source/ui/view/viewfunc.cxx
index 595c5a9..605b79a 100644
--- a/sc/source/ui/view/viewfunc.cxx
+++ b/sc/source/ui/view/viewfunc.cxx
@@ -586,7 +586,7 @@ void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab,
             ScAddress aHeaderAddress;
 
             if ( pUnits->isCellConversionRecommended( aAddress, pDoc, sHeaderUnit, aHeaderAddress, sCellUnit ) ) {
-                NotifyUnitConversionRecommended( aAddress, pDoc, sHeaderUnit, aHeaderAddress, sCellUnit );
+                NotifyUnitConversionRecommended( aAddress, pDoc, sHeaderUnit, aHeaderAddress, sCellUnit, pDocSh );
             } else {
                 SfxViewFrame* pViewFrame = GetViewData().GetViewShell()->GetFrame();
                 OUString sAddress = aAddress.Format( SCA_BITS, pDoc );
@@ -2904,26 +2904,30 @@ struct UnitConversionPushButton: public PushButton
     ScDocument* mpDoc;
     const OUString sHeaderUnit;
     const OUString sCellUnit;
+    ScDocShell* mpDocSh;
 
     UnitConversionPushButton( vcl::Window* pParent,
                               const ResId& rResId,
                               const ScAddress& rCellAddress,
                               ScDocument* pDoc,
                               const OUString& rsHeaderUnit,
-                              const OUString& rsCellUnit ):
+                              const OUString& rsCellUnit,
+                              ScDocShell* pDocSh ):
         PushButton( pParent, rResId ),
         aCellAddress( rCellAddress ),
         mpDoc( pDoc ),
         sHeaderUnit( rsHeaderUnit ),
-        sCellUnit( rsCellUnit )
+        sCellUnit( rsCellUnit ),
+        mpDocSh( pDocSh )
         {}
 };
 
 void ScViewFunc::NotifyUnitConversionRecommended( const ScAddress& rCellAddress,
-                                                 ScDocument* pDoc,
-                                                 const OUString& rsHeaderUnit,
-                                                 const ScAddress& rHeaderAddress,
-                                                 const OUString& rsCellUnit ) {
+                                                  ScDocument* pDoc,
+                                                  const OUString& rsHeaderUnit,
+                                                  const ScAddress& rHeaderAddress,
+                                                  const OUString& rsCellUnit,
+                                                  ScDocShell* pDocSh ) {
     SfxViewFrame* pViewFrame = GetViewData().GetViewShell()->GetFrame();
 
     // As with NotifyUnitErrorInFormula we use the cell address as the infobar id.
@@ -2947,7 +2951,8 @@ void ScViewFunc::NotifyUnitConversionRecommended( const ScAddress& rCellAddress,
                                                                                  rCellAddress,
                                                                                  pDoc,
                                                                                  rsHeaderUnit,
-                                                                                 rsCellUnit );
+                                                                                 rsCellUnit,
+                                                                                 pDocSh );
     pButtonConvertCell->SetClickHdl( LINK( this, ScViewFunc, UnitConversionRecommendedHandler ) );
 
     OUString sConvertText = pButtonConvertCell->GetText();
@@ -2964,10 +2969,14 @@ IMPL_LINK( ScViewFunc, UnitConversionRecommendedHandler, UnitConversionPushButto
 #ifdef ENABLE_CALC_UNITVERIFICATION
     boost::shared_ptr< sc::units::Units > pUnits = sc::units::Units::GetUnits();
 
+    ScDocShellModificator aModificator( *pButton->mpDocSh );
+
     pUnits->convertCellToHeaderUnit( pButton->aCellAddress,
                                      pButton->mpDoc,
                                      pButton->sHeaderUnit,
                                      pButton->sCellUnit );
+
+    aModificator.SetDocumentModified();
 #endif
 
     OUString sAddress;
commit 5186857793b01f196a4d4f4d9f7ba7f295e79e7e
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sat May 9 11:09:24 2015 +0100

    Upgrade units test to use SfxModelFlags (rebase fixup).
    
    Necessary due to afc728fe76fbf1afea725afd6ff5e9af92e10b08
    
    Change-Id: Iea6dfb08a36f56485ed43a9c4cd2dcf652ff0b97

diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index 3fbcaa9..e6ed779 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -72,9 +72,9 @@ void UnitsTest::setUp() {
 
     ScDLL::Init();
     m_xDocShRef = new ScDocShell(
-        SFXMODEL_STANDARD |
-        SFXMODEL_DISABLE_EMBEDDED_SCRIPTS |
-        SFXMODEL_DISABLE_DOCUMENT_RECOVERY);
+        SfxModelFlags::EMBEDDED_OBJECT |
+        SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS |
+        SfxModelFlags::DISABLE_DOCUMENT_RECOVERY);
 
     mpDoc = &m_xDocShRef->GetDocument();
 
commit 86c7b05d526d98e67c3b0b83980e20bf15503286
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sat May 9 11:07:10 2015 +0100

    loplugin:staticmethods
    
    Change-Id: I31969836cc9e9147aaa370779fa281792efa9de2

diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index 7b5fd25..3fbcaa9 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -363,8 +363,8 @@ void UnitsTest::testUnitVerification() {
 }
 
 void UnitsTest::testUnitFromFormatStringExtraction() {
-    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitStringFromFormat("\"weight: \"0.0\"kg\"") == "kg");
-    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitStringFromFormat("#\"cm\"") == "cm");
+    CPPUNIT_ASSERT(UnitsImpl::extractUnitStringFromFormat("\"weight: \"0.0\"kg\"") == "kg");
+    CPPUNIT_ASSERT(UnitsImpl::extractUnitStringFromFormat("#\"cm\"") == "cm");
 }
 
 void UnitsTest::testUnitValueStringSplitting() {
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index 379a6a7..0eea129 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -119,8 +119,8 @@ private:
 
     bool extractUnitFromHeaderString(const OUString& rHeader, UtUnit& aUnit, OUString& sUnitString);
 
-    OUString extractUnitStringFromFormat(const OUString& rFormatString);
-    OUString extractUnitStringForCell(const ScAddress& rAddress, ScDocument* pDoc);
+    static OUString extractUnitStringFromFormat(const OUString& rFormatString);
+    static OUString extractUnitStringForCell(const ScAddress& rAddress, ScDocument* pDoc);
 
     /**
      * Retrieve the units for a given cell. This probes based on the usual rules
commit 6428703e4cf1b3aab1982c86b5ce876b17f0308c
Author: Andrzej Hunt <andrzej.hunt at collabora.com>
Date:   Fri Apr 10 11:03:33 2015 +0100

    Move and rename Range/Unit Stack.
    
    This in preparation for implementing a combined Unit
    and Range iterator.
    
    Change-Id: I08d28e175453f65c3696e9d1c6c20c7076d9b164

diff --git a/sc/Library_sc.mk b/sc/Library_sc.mk
index 5d8f086..04052c6 100644
--- a/sc/Library_sc.mk
+++ b/sc/Library_sc.mk
@@ -689,6 +689,7 @@ $(call gb_Library_add_exception_objects,sc,\
 
 ifeq ($(ENABLE_CALC_UNITVERIFICATION),TRUE)
 $(eval $(call gb_Library_add_exception_objects,sc,\
+    sc/source/core/units/raustack \
     sc/source/core/units/units \
 	sc/source/core/units/unitsimpl \
 	sc/source/core/units/util \
diff --git a/sc/source/core/units/raustack.cxx b/sc/source/core/units/raustack.cxx
new file mode 100644
index 0000000..f838acf
--- /dev/null
+++ b/sc/source/core/units/raustack.cxx
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+#include "raustack.hxx"
+
+using namespace sc::units;
+
+RangeListIterator::RangeListIterator(ScDocument* pDoc, const ScRangeList& rRangeList)
+    :
+    mRangeList(rRangeList),
+    mpDoc(pDoc),
+    mIt(pDoc, ScRange()),
+    nCurrentIndex(0)
+{
+}
+
+bool RangeListIterator::first() {
+    if (mRangeList.size() > 0) {
+        mIt = ScCellIterator(mpDoc, *mRangeList[0]);
+        return mIt.first();
+    } else {
+        return false;
+    }
+}
+
+const ScAddress& RangeListIterator::GetPos() const {
+    return mIt.GetPos();
+}
+
+bool RangeListIterator::next() {
+    if (!(mRangeList.size() > 0) || nCurrentIndex >= mRangeList.size()) {
+        return false;
+    }
+
+    if (mIt.next()) {
+        return true;
+    } else if (++nCurrentIndex < mRangeList.size()) {
+        mIt = ScCellIterator(mpDoc, *mRangeList[nCurrentIndex]);
+        mIt.first();
+        // TODO: if emtpy - skip to next...?
+        return true;
+    } else {
+        return false;
+    }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+
diff --git a/sc/source/core/units/raustack.hxx b/sc/source/core/units/raustack.hxx
new file mode 100644
index 0000000..b5cf48b
--- /dev/null
+++ b/sc/source/core/units/raustack.hxx
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+#ifndef INCLUDED_SC_SOURCE_CORE_UNITS_RAUSTACK_HXX
+#define INCLUDED_SC_SOURCE_CORE_UNITS_RAUSTACK_HXX
+
+#include <boost/variant.hpp>
+
+#include <address.hxx>
+#include <dociter.hxx>
+#include <rangelst.hxx>
+
+
+#include "utunit.hxx"
+
+namespace sc {
+namespace units {
+
+enum class RAUSItemType {
+    UNITS,
+    RANGE
+};
+
+struct RAUSItem {
+    RAUSItemType type;
+    boost::variant< ScRange, UtUnit > item;
+};
+
+class RangeListIterator {
+private:
+    const ScRangeList mRangeList;
+    ScDocument* mpDoc;
+
+    ScCellIterator mIt;
+    size_t nCurrentIndex;
+
+public:
+    RangeListIterator(ScDocument* pDoc, const ScRangeList& rRangeList);
+
+    bool first();
+
+    const ScAddress& GetPos() const;
+
+    bool next();
+};
+
+}} // sc::units
+
+// class RangeAndUnitStack {
+
+
+
+// };
+
+// class RATSIterator {
+// public:
+//     // TODO: need to be able to return non-initialisation
+//     static RATSIterator getIterator(RangeAndTokenStack& rStack, ScDoc* pDoc, int nItems);
+
+// }
+
+#endif // INCLUDED_SC_SOURCE_CORE_UNITS_RAUSTACK_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 99e03af..e8fb261 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -11,7 +11,6 @@
 
 #include "util.hxx"
 
-#include "dociter.hxx"
 #include "document.hxx"
 #include "refdata.hxx"
 #include "stringutil.hxx"
@@ -85,55 +84,7 @@ UnitsImpl::~UnitsImpl() {
     // (i.e. if udunits can't handle being used across threads)?
 }
 
-class RangeListIterator {
-private:
-    const ScRangeList mRangeList;
-    ScDocument* mpDoc;
-
-    ScCellIterator mIt;
-    size_t nCurrentIndex;
-
-public:
-    RangeListIterator(ScDocument* pDoc, const ScRangeList& rRangeList)
-        :
-        mRangeList(rRangeList),
-        mpDoc(pDoc),
-        mIt(pDoc, ScRange()),
-        nCurrentIndex(0)
-    {
-    }
-
-    bool first() {
-        if (mRangeList.size() > 0) {
-            mIt = ScCellIterator(mpDoc, *mRangeList[0]);
-            return mIt.first();
-        } else {
-            return false;
-        }
-    }
-
-    const ScAddress& GetPos() const {
-        return mIt.GetPos();
-    }
-
-    bool next() {
-        if (!(mRangeList.size() > 0) || nCurrentIndex >= mRangeList.size()) {
-            return false;
-        }
-
-        if (mIt.next()) {
-            return true;
-        } else if (++nCurrentIndex < mRangeList.size()) {
-            mIt = ScCellIterator(mpDoc, *mRangeList[nCurrentIndex]);
-            mIt.first();
-            return true;
-        } else {
-            return false;
-        }
-    }
-};
-
-UnitsResult UnitsImpl::getOutputUnitsForOpCode(stack< StackItem >& rStack, const formula::FormulaToken* pToken, ScDocument* pDoc) {
+UnitsResult UnitsImpl::getOutputUnitsForOpCode(stack< RAUSItem >& rStack, const formula::FormulaToken* pToken, ScDocument* pDoc) {
     const OpCode aOpCode = pToken->GetOpCode();
     auto nOpCode = static_cast<std::underlying_type<const OpCode>::type>(aOpCode);
 
@@ -147,7 +98,7 @@ UnitsResult UnitsImpl::getOutputUnitsForOpCode(stack< StackItem >& rStack, const
         if (rStack.size() == 0) {
             SAL_WARN("sc.units", "Single item opcode failed (no stack items, or range used)");
             return { UnitsStatus::UNITS_INVALID, boost::none };
-        } else if (rStack.top().type != StackItemType::UNITS) {
+        } else if (rStack.top().type != RAUSItemType::UNITS) {
             return { UnitsStatus::UNITS_UNKNOWN, boost::none };
         }
 
@@ -183,13 +134,13 @@ UnitsResult UnitsImpl::getOutputUnitsForOpCode(stack< StackItem >& rStack, const
             return { UnitsStatus::UNITS_INVALID, boost::none };
         }
 
-        if (rStack.top().type != StackItemType::UNITS) {
+        if (rStack.top().type != RAUSItemType::UNITS) {
             return { UnitsStatus::UNITS_UNKNOWN, boost::none };
         }
         UtUnit pSecondUnit = boost::get<UtUnit>(rStack.top().item);
         rStack.pop();
 
-        if (rStack.top().type != StackItemType::UNITS) {
+        if (rStack.top().type != RAUSItemType::UNITS) {
             return { UnitsStatus::UNITS_UNKNOWN, boost::none };
         }
         UtUnit pFirstUnit = boost::get<UtUnit>(rStack.top().item);
@@ -240,12 +191,12 @@ UnitsResult UnitsImpl::getOutputUnitsForOpCode(stack< StackItem >& rStack, const
 
         for ( ; nParams > 0; nParams--) {
             switch (rStack.top().type) {
-            case StackItemType::UNITS:
+            case RAUSItemType::UNITS:
             {
                 aUnitsStack.push(boost::get< UtUnit >(rStack.top().item));
                 break;
             }
-            case StackItemType::RANGE:
+            case RAUSItemType::RANGE:
             {
                 aRangeList.Append(boost::get< ScRange >(rStack.top().item));
                 break;
@@ -545,7 +496,7 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
     pArray->Dump();
 #endif
 
-    stack< StackItem > aStack;
+    stack< RAUSItem > aStack;
 
     for (FormulaToken* pToken = pArray->FirstRPN(); pToken != 0; pToken = pArray->NextRPN()) {
         switch (pToken->GetType()) {
@@ -564,14 +515,14 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
                 return false;
             }
 
-            aStack.push( { StackItemType::UNITS, aUnit } );
+            aStack.push( { RAUSItemType::UNITS, aUnit } );
             break;
         }
         case formula::svDoubleRef:
         {
             ScComplexRefData* pDoubleRef = pToken->GetDoubleRef();
             ScRange aRange = pDoubleRef->toAbs(rFormulaAddress);
-            aStack.push( { StackItemType::RANGE, aRange } );
+            aStack.push( { RAUSItemType::RANGE, aRange } );
 
             break;
         }
@@ -588,7 +539,7 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
             case UnitsStatus::UNITS_VALID:

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list