[Libreoffice-commits] core.git: Branch 'feature/unitver' - 3946 commits - accessibility/inc accessibility/source android/Bootstrap android/experimental android/.gitignore android/mobile-config.py android/README animations/Library_animcore.mk animations/source autogen.sh avmedia/inc avmedia/source avmedia/util basctl/source basebmp/source basebmp/test basegfx/source basegfx/test basic/inc basic/qa basic/source bean/native binaryurp/source bin/benchmark-document-loading bin/convwatch.py bin/findunusedcode bin/gbuild-to-ide bin/module-deps.pl bin/run bin/update_pch.sh bridges/inc bridges/source bridges/test canvas/Library_cairocanvas.mk canvas/source chart2/inc chart2/qa chart2/source chart2/uiconfig cli_ure/source codemaker/source comphelper/Library_comphelper.mk comphelper/source compilerplugins/clang config_host/config_cairo_canvas.h.in config_host/config_cups.h.in config_host/config_features.h.in config_host/config_liblangtag.h.in config_host/config_poppler.h.in config_host_lang.mk.in config_hos t.mk.in configmgr/source configure.ac connectivity/CppunitTest_connectivity_commontools.mk connectivity/Library_dbase.mk connectivity/Library_dbtools.mk connectivity/Library_tdeab1.mk connectivity/Library_tdeabdrv1.mk connectivity/source connectivity/workben cppcanvas/qa cppcanvas/source cppuhelper/qa cppuhelper/source cppuhelper/test cppu/qa cppu/source cpputools/source cui/inc cui/source cui/uiconfig dbaccess/qa dbaccess/source desktop/CppunitTest_desktop_version.mk desktop/inc desktop/Library_sofficeapp.mk desktop/Module_desktop.mk desktop/Pagein_common.mk desktop/source desktop/unx dictionaries download.lst drawinglayer/source dtrans/source dtrans/test editeng/source embeddedobj/source embeddedobj/test embedserv/source eventattacher/source extensions/source extensions/test extensions/workben external/coinmp external/harfbuzz external/icu external/librevenge external/Module_external.mk external/np_sdk external/openssl external/poppler external/udunits2 extras/Module_extras.mk ext ras/Package_tpl_styles.mk extras/source filter/qa filter/source forms/Library_frm.mk forms/source formula/source fpicker/source fpicker/test framework/inc framework/qa framework/source .gitignore helpcompiler/inc helpcompiler/source helpcontent2 hwpfilter/qa hwpfilter/source i18nlangtag/qa i18nlangtag/source i18npool/CustomTarget_collator.mk i18npool/inc i18npool/Library_i18npool.mk i18npool/Library_localedata_euro.mk i18npool/Library_localedata_others.mk i18npool/source i18nutil/source icon-themes/breeze icon-themes/crystal icon-themes/galaxy icon-themes/hicontrast icon-themes/human icon-themes/industrial icon-themes/oxygen icon-themes/sifr icon-themes/tango icon-themes/tango_testing idlc/inc idlc/source idl/inc idl/source include/avmedia include/basebmp include/basegfx include/basic include/canvas include/com include/comphelper include/connectivity include/cppcanvas include/cppu include/cppuhelper include/dbaccess include/drawinglayer include/editeng include/filter include/formula include/framework include/helpcompiler include/i18nlangtag include/LibreOfficeKit include/linguistic include/o3tl include/oox include/osl include/postwin.h include/registry include/rtl include/sal include/sax include/sfx2 include/sot include/store include/svl include/svtools include/svx include/test include/toolkit include/tools include/touch include/ucbhelper include/unotest include/unotools include/vbahelper include/vcl include/xmloff include/xmlscript instsetoo_native/CustomTarget_setup.mk instsetoo_native/inc_openoffice instsetoo_native/util ios/experimental io/source io/test javaunohelper/source jurt/test jvmfwk/plugins jvmfwk/source l10ntools/inc l10ntools/source Library_urelibs.mk librelogo/source libreofficekit/CppunitTest_libreofficekit_tiledrendering.mk libreofficekit/Executable_gtktiledviewer.mk libreofficekit/Library_libreofficekitgtk.mk libreofficekit/Module_libreofficekit.mk libreofficekit/qa libreofficekit/README libreofficekit/source lingucomponent/source linguistic /source lotuswordpro/qa lotuswordpro/source Makefile.fetch Makefile.in mysqlc/Library_mysqlc.mk mysqlc/source odk/config odk/examples odk/source offapi/com offapi/type_reference offapi/UnoApi_offapi.mk officecfg/Configuration_officecfg.mk officecfg/registry oox/inc oox/README oox/source opencl/inc opencl/source package/inc package/qa package/source postprocess/CustomTarget_registry.mk postprocess/CustomTarget_signing.mk postprocess/qa postprocess/Rdb_services.mk pyuno/inc pyuno/source pyuno/zipcore qadevOOo/runner qadevOOo/tests readlicense_oo/license README.Android README.Code README.cross README.md registry/source registry/test registry/tools registry/workben reportdesign/inc reportdesign/source RepositoryExternal.mk Repository.mk RepositoryModule_host.mk rsc/inc rsc/Module_rsc.mk rsc/source sal/android sal/cpprt sal/cppunittester salhelper/test sal/osl sal/qa sal/rtl sal/textenc sal/util sal/workben sax/inc sax/source sax/test scaddins/source sc/AllLangResTarget_sc.mk sccomp/sour ce sc/CppunitTest_sc_condformats.mk sc/CppunitTest_sc_new_cond_format_api.mk sc/CppunitTest_sc_ucalc.mk sc/CppunitTest_sc_units.mk sc/inc sc/Library_scd.mk sc/Library_scfilt.mk sc/Library_sc.mk sc/Module_sc.mk scp2/source sc/qa scripting/examples scripting/java scripting/Library_vbaevents.mk scripting/source sc/sdi sc/source sc/uiconfig sc/util sc/workben sd/CppunitTest_sd_export_tests.mk sd/CppunitTest_sd_import_tests.mk sd/CppunitTest_sd_tiledrendering.mk sdext/source sd/inc sd/Library_sdd.mk sd/Module_sd.mk sd/qa sd/sdi sd/source sd/uiconfig sd/util setup_native/scripts setup_native/source sfx2/inc sfx2/source sfx2/uiconfig shell/inc shell/Library_tdebe.mk shell/source slideshow/source slideshow/test smoketest/data smoketest/Module_smoketest.mk solenv/bin solenv/gbuild solenv/gcc-wrappers solenv/gdb soltools/mkdepend sot/qa sot/source sot/workben starmath/inc starmath/Library_sm.mk starmath/qa starmath/source starmath/util stoc/source stoc/test store/source svgio/inc svgio/source svl/Library_svl.mk svl/qa svl/README svl/source svl/unx svl/util svtools/inc svtools/qa svtools/source svx/Executable_pixelctl.mk svx/inc svx/Library_svxcore.mk svx/Library_svx.mk svx/README svx/sdi svx/source svx/uiconfig svx/workben sw/CppunitTest_sw_ooxmlexport6.mk sw/CppunitTest_sw_ooxmlexport7.mk sw/CppunitTest_sw_ooxmlimport.mk sw/CppunitTest_sw_ooxmlsdrexport.mk sw/CppunitTest_sw_tiledrendering.mk sw/CppunitTest_sw_uwriter.mk sw/Executable_tiledrendering.mk swext/mediawiki sw/inc sw/Library_sw.mk sw/Library_swui.mk sw/Module_sw.mk sw/qa sw/sdi sw/source sw/uiconfig sw/UIConfig_qa.mk sw/UIConfig_swriter.mk sw/util sysui/desktop test/source testtools/source toolkit/source toolkit/test tools/Library_tl.mk tools/qa tools/source translations ucbhelper/source ucb/qa ucb/source udkapi/type_reference UnoControls/source unoidl/source unotest/source unotools/qa unotools/source unoxml/qa unoxml/source unusedcode.easy uui/source vbahelper/source vcl/android vcl/CppunitTest_vcl_bitmap_te st.mk vcl/CppunitTest_vcl_lifecycle.mk vcl/Executable_mtfdemo.mk vcl/Executable_outdevgrind.mk vcl/Executable_svdemo.mk vcl/Executable_svpclient.mk vcl/Executable_svptest.mk vcl/Executable_tdefilepicker.mk vcl/Executable_vcldemo.mk vcl/generic vcl/headless vcl/inc vcl/ios vcl/Library_vcl.mk vcl/Library_vclplug_gen.mk vcl/Library_vclplug_gtk3.mk vcl/Library_vclplug_svp.mk vcl/Library_vclplug_tde.mk vcl/Module_vcl.mk vcl/null vcl/opengl vcl/osx vcl/qa vcl/quartz vcl/README.lifecycle vcl/source vcl/unx vcl/win vcl/workben winaccessibility/source wizards/com wizards/Jar_commonwizards.mk wizards/source writerfilter/documentation writerfilter/inc writerfilter/qa writerfilter/source writerperfect/qa writerperfect/source xmlhelp/source xmloff/inc xmloff/source xmlscript/source xmlsecurity/inc xmlsecurity/source

Andrzej Hunt andrzej at ahunt.org
Sat May 9 12:54:00 PDT 2015


Rebased ref, commits from common ancestor:
commit aa614666336f6e23720cd7acea88399b88f9deff
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 e5265133586501c5dccffed083fa2e1955ff765f
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 0a928a6c2111f87e0377222157340637400d22a7
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 420142127bf69385e3d5ebfeccda34914d7db1f3
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 cca98afc030ebde742df57a91bfbbb80953338c8
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:
                 assert(aResult.units); // ensure that we have the optional unit
                 assert(aResult.units->isValid());
-                aStack.push( { StackItemType::UNITS, aResult.units.get() } );
+                aStack.push( { RAUSItemType::UNITS, aResult.units.get() } );
                 break;
             }
 
@@ -603,7 +554,7 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
             UtUnit::createUnit("", aScale, mpUnitSystem); // Get the dimensionless unit 1
             aScale = aScale*(pToken->GetDouble());
 
-            aStack.push( { StackItemType::UNITS, aScale } );
+            aStack.push( { RAUSItemType::UNITS, aScale } );
 
             break;
         }
@@ -619,7 +570,7 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
     if (aStack.size() != 1) {
         SAL_WARN("sc.units", "Wrong number of units on stack, should be 1, actual number: " << aStack.size());
         return false;
-    } else if (aStack.top().type != StackItemType::UNITS) {
+    } else if (aStack.top().type != RAUSItemType::UNITS) {
         SAL_WARN("sc.units", "End of verification: item on stack does not contain units");
         return false;
     }
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index 6abffdd..379a6a7 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -13,7 +13,6 @@
 #include <boost/optional.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/shared_ptr.hpp>
-#include <boost/variant.hpp>
 #include <boost/weak_ptr.hpp>
 
 #include <formula/opcode.hxx>
@@ -25,6 +24,7 @@
 #include "rangelst.hxx"
 #include "units.hxx"
 
+#include "raustack.hxx"
 #include "utunit.hxx"
 
 #include <stack>
@@ -42,16 +42,6 @@ namespace test {
     class UnitsTest;
 }
 
-enum class StackItemType {
-    UNITS,
-    RANGE
-};
-
-struct StackItem {
-    StackItemType type;
-    boost::variant< ScRange, UtUnit > item;
-};
-
 enum class UnitsStatus {
     UNITS_VALID,
     UNITS_UNKNOWN,
@@ -103,7 +93,7 @@ public:
                                          const OUString& rsOldUnit) SAL_OVERRIDE;
 
 private:
-    UnitsResult getOutputUnitsForOpCode(std::stack< StackItem >& rStack, const formula::FormulaToken* pToken, ScDocument* pDoc);
+    UnitsResult getOutputUnitsForOpCode(std::stack< RAUSItem >& rStack, const formula::FormulaToken* pToken, ScDocument* pDoc);
 
     /**
      * Find and extract a Unit in the standard header notation,
commit bf0d568a89e4b40b15bcf71d94c7082ba6357d9b
Author: Andrzej Hunt <andrzej.hunt at collabora.com>
Date:   Wed Apr 1 14:54:20 2015 +0200

    Check output unit against header definition during verification.
    
    Change-Id: I98a706d80eb442d274fc111fb6c22e43d79fb9ff

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index fbd0c2d..99e03af 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -616,7 +616,23 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
         }
     }
 
-    // TODO: only fail if actual parsing fails?
+    if (aStack.size() != 1) {
+        SAL_WARN("sc.units", "Wrong number of units on stack, should be 1, actual number: " << aStack.size());
+        return false;
+    } else if (aStack.top().type != StackItemType::UNITS) {
+        SAL_WARN("sc.units", "End of verification: item on stack does not contain units");
+        return false;
+    }
+
+    OUString sUnitString;
+    ScAddress aAddress;
+
+    UtUnit aHeaderUnit = findHeaderUnitForCell(rFormulaAddress, pDoc, sUnitString, aAddress);
+    UtUnit aResultUnit = boost::get< UtUnit>(aStack.top().item);
+
+    if (aHeaderUnit.isValid() && aHeaderUnit != aResultUnit) {
+        return false;
+    }
 
     return true;
 }
commit 6b23a4f2a1e9f95894dd704cd104740e4cbfd9e0
Author: Andrzej Hunt <andrzej.hunt at collabora.com>
Date:   Wed Apr 1 13:48:01 2015 +0200

    Use correct stack for multiparam opcodes.
    
    Also add an appropriate unit test.
    
    Change-Id: Ie2e9cce74563dea80b33f5e8238ba6ad1aae1b49

diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index ac1bfa5..7b5fd25 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -291,7 +291,6 @@ void UnitsTest::testUnitVerification() {
 
     // SUM("cm"&"kg") - multiple ranges
     address.IncRow();
-    // TODO: by default / when testing: separator is...?
     mpDoc->SetFormula(address, "=SUM(A1:A3,B1:B3)");
     pCell = mpDoc->GetFormulaCell(address);
     pTokens = pCell->GetCode();
@@ -325,6 +324,23 @@ void UnitsTest::testUnitVerification() {
     pTokens = pCell->GetCode();
     CPPUNIT_ASSERT(!mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
 
+    // Test that multiple arguments of mixed types work too
+    // Adding multiple cells from one column is ok
+    address.IncRow();
+    mpDoc->SetFormula(address, "=SUM(A1,A2:A3)");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+    // But mixing the columns fails because of mixed units
+    // (This test is primarily to ensure that we can handle arbitrary numbers
+    //  of arguments.)
+    address.IncRow();
+    mpDoc->SetFormula(address, "=SUM(A1,A2:A3,B1:B3,C1,C2,C3)");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(!mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
     // Do a quick sanity check for all the other supported functions
     // The following all expect identically united inputs, and should
     // preserve that unit:
diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index d49799f..fbd0c2d 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -274,7 +274,7 @@ UnitsResult UnitsImpl::getOutputUnitsForOpCode(stack< StackItem >& rStack, const
                         return { UnitsStatus::UNITS_INVALID, boost::none };
                     }
                 }
-                rStack.pop();
+                aUnitsStack.pop();
             }
 
             if (aIt.first()) {
commit d73c16d4c55ad6c4dba2e4ea475cfd356cf73f85
Author: Andrzej Hunt <andrzej.hunt at collabora.com>
Date:   Wed Apr 1 13:21:35 2015 +0200

    Refactor and rewrite range and multiparam opcode handling.
    
    Change-Id: Ic52bfb7daae44ea8f8af710fa70f1f30150fc274

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 6057a0e..d49799f 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -85,26 +85,76 @@ UnitsImpl::~UnitsImpl() {
     // (i.e. if udunits can't handle being used across threads)?
 }
 
-UnitsResult UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpCode& rOpCode) {
-    UtUnit pOut;
+class RangeListIterator {
+private:
+    const ScRangeList mRangeList;
+    ScDocument* mpDoc;
 
-    auto nOpCode = static_cast<std::underlying_type<const OpCode>::type>(rOpCode);
+    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) {
+    const OpCode aOpCode = pToken->GetOpCode();
+    auto nOpCode = static_cast<std::underlying_type<const OpCode>::type>(aOpCode);
+
+    UtUnit pOut;
     // TODO: sc/source/core/tool/parclass.cxx has a mapping of opcodes to possible operands, which we
     // should probably be using in practice.
 
     if (nOpCode >= SC_OPCODE_START_UN_OP &&
         nOpCode < SC_OPCODE_STOP_UN_OP) {
 
-        if (!(rUnitStack.size() >= 1)) {
-            SAL_WARN("sc.units", "no units on stack for unary operation");
+        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) {
+            return { UnitsStatus::UNITS_UNKNOWN, boost::none };
         }
 
-        UtUnit pUnit = rUnitStack.top();
-        rUnitStack.pop();
+        UtUnit pUnit = boost::get<UtUnit>(rStack.top().item);//rStack.top().item.get< UtUnit >();
+        rStack.pop();
 
-        switch (rOpCode) {
+        switch (aOpCode) {
         case ocNot:
             if (!pUnit.isDimensionless()) {
                 return { UnitsStatus::UNITS_INVALID, boost::none };
@@ -128,19 +178,24 @@ UnitsResult UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, cons
     } else if (nOpCode >= SC_OPCODE_START_BIN_OP &&
         nOpCode < SC_OPCODE_STOP_BIN_OP) {
 
-        if (!(rUnitStack.size() >= 2)) {
-            SAL_WARN("sc.units", "less than two units on stack when attempting binary operation");
-            // TODO: what should we be telling the user in this case? Can this even happen (i.e.
-            // should we just be asserting here?)
+        if (rStack.size() < 2) {
+            SAL_WARN("sc.units", "less than two items on stack when attempting binary operation");
             return { UnitsStatus::UNITS_INVALID, boost::none };
         }
 
-        UtUnit pSecondUnit = rUnitStack.top();
-        rUnitStack.pop();
-        UtUnit pFirstUnit = rUnitStack.top();
-        rUnitStack.pop();
+        if (rStack.top().type != StackItemType::UNITS) {
+            return { UnitsStatus::UNITS_UNKNOWN, boost::none };
+        }
+        UtUnit pSecondUnit = boost::get<UtUnit>(rStack.top().item);
+        rStack.pop();
+
+        if (rStack.top().type != StackItemType::UNITS) {
+            return { UnitsStatus::UNITS_UNKNOWN, boost::none };
+        }
+        UtUnit pFirstUnit = boost::get<UtUnit>(rStack.top().item);
+        rStack.pop();
 
-        switch (rOpCode) {
+        switch (aOpCode) {
         case ocAdd:
             // Adding and subtracting both require the same units on both sides
             // hence we can just fall through / use the same logic.
@@ -164,90 +219,111 @@ UnitsResult UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, cons
             SAL_INFO("sc.units", "unit verification not supported for opcode: " << nOpCode);
             assert(false);
         }
+    } else if (nOpCode >= SC_OPCODE_START_2_PAR &&
+               nOpCode < SC_OPCODE_STOP_2_PAR) {
+        sal_uInt8 nParams = pToken->GetByte();
 
-    } else {
-        SAL_INFO("sc.units", "unit verification not supported for opcode: " << nOpCode);
-        return { UnitsStatus::UNITS_UNKNOWN, boost::none };
-    }
-    // TODO: else if unary, or no params, or ...
-    // TODO: implement further sensible opcode handling
-    return { UnitsStatus::UNITS_VALID, pOut };
-}
-
-class RangeListIterator {
-private:
-    const ScRangeList mRangeList;
-    ScDocument* mpDoc;
-
-    ScCellIterator mIt;
-    size_t nCurrentIndex;
+        assert(nParams <= rStack.size());
 
-public:
-    RangeListIterator(ScDocument* pDoc, const ScRangeList& rRangeList)
-        :
-        mRangeList(rRangeList),
-        mpDoc(pDoc),
-        mIt(pDoc, ScRange()),
-        nCurrentIndex(0)
-    {
-        if (rRangeList.size() > 0) {
-            mIt = ScCellIterator(pDoc, *rRangeList[0]);
-            mIt.first();
+        // If there are no input parameters then the output unit is undefined.
+        // (In practice this would probably be nonsensical, but isn't a unit
+        //  error per-se.)
+        if (nParams == 0) {
+            return { UnitsStatus::UNITS_UNKNOWN, boost::none };
         }
-    }
 
-    const ScAddress& GetPos() const {
-        return mIt.GetPos();
-    }
+        // This is still quite an ugly solution, even better would maybe be to have
+        // a StackUnitIterator which iterates all the appropriate units given a number of stack items
+        // to iterate over?
+        ScRangeList aRangeList;
+        stack< UtUnit > aUnitsStack;
+
+        for ( ; nParams > 0; nParams--) {
+            switch (rStack.top().type) {
+            case StackItemType::UNITS:
+            {
+                aUnitsStack.push(boost::get< UtUnit >(rStack.top().item));
+                break;
+            }
+            case StackItemType::RANGE:
+            {
+                aRangeList.Append(boost::get< ScRange >(rStack.top().item));
+                break;
+            }
+            }
 
-    bool next() {
-        if (!(mRangeList.size() > 0) || nCurrentIndex >= mRangeList.size()) {
-            return false;
+            rStack.pop();
         }
 
-        if (mIt.next()) {
-            return true;
-        } else if (++nCurrentIndex < mRangeList.size()) {
-            mIt = ScCellIterator(mpDoc, *mRangeList[nCurrentIndex]);
-            mIt.first();
-            return true;
-        } else {
-            return false;
-        }
-    }
-};
+        RangeListIterator aIt(pDoc, aRangeList);
 
-UnitsResult UnitsImpl::getOutputUnitForDoubleRefOpcode(const OpCode& rOpCode, const ScRangeList& rRangeList, ScDocument* pDoc) {
-    RangeListIterator aIt(pDoc, rRangeList);
+        switch (aOpCode) {
+        case ocSum:
+        case ocMin:
+        case ocMax:
+        case ocAverage:
+        {
+            boost::optional< UtUnit > aFirstUnit;
+
+            while (aUnitsStack.size() > 0) {
+                if (!aFirstUnit) {
+                    aFirstUnit = aUnitsStack.top();
+                } else {
+                    UtUnit aCurrentUnit(aUnitsStack.top());
+                    if (aFirstUnit.get() != aCurrentUnit) {
+                        return { UnitsStatus::UNITS_INVALID, boost::none };
+                    }
+                }
+                rStack.pop();
+            }
 
-    switch (rOpCode) {
-    case ocSum:
-    case ocMin:
-    case ocMax:
-    case ocAverage:
-    {
-        UtUnit aFirstUnit = getUnitForCell(aIt.GetPos(), pDoc);
+            if (aIt.first()) {
+                do {
+                    if (!aFirstUnit) {
+                        aFirstUnit = getUnitForCell(aIt.GetPos(), pDoc);
+                    } else {
+                        UtUnit aCurrentUnit = getUnitForCell(aIt.GetPos(), pDoc);
+                        if (aFirstUnit.get() != aCurrentUnit) {
+                            return { UnitsStatus::UNITS_INVALID, boost::none };
+                        }
+                    }
+                } while (aIt.next());
 
-        while (aIt.next()) {
-            UtUnit aCurrentUnit = getUnitForCell(aIt.GetPos(), pDoc);
-            if (aFirstUnit != aCurrentUnit) {
-                return { UnitsStatus::UNITS_INVALID, boost::none };
             }
+
+            return { UnitsStatus::UNITS_VALID, aFirstUnit };
         }
+        case ocProduct:
+        {
+            // To avoid having to detect when we get the first unit (i.e. to avoid
+            // the play with the optinal< UtUnit > as above), we just start with
+            // the dimensionless Unit 1 and multiply from there.
+            // This can also be avoided if we implement a combined Unit and Range
+            // Iterator (see above for more info).
+            UtUnit aUnit;
+            UtUnit::createUnit("", aUnit, mpUnitSystem);
+
+            while (aUnitsStack.size() > 0) {
+                aUnit *= aUnitsStack.top();
+                aUnitsStack.pop();
+            }
 
-        return { UnitsStatus::UNITS_VALID, aFirstUnit };
-    }
-    case ocProduct:
-    {
-        UtUnit aUnit = getUnitForCell(aIt.GetPos(), pDoc);
-        while (aIt.next()) {
-            aUnit *= getUnitForCell(aIt.GetPos(), pDoc);
+            if (aIt.first()) {
+                do {
+                    aUnit *= getUnitForCell(aIt.GetPos(), pDoc);
+                } while (aIt.next());
+            }
+
+            return { UnitsStatus::UNITS_VALID, aUnit };
         }
-        return { UnitsStatus::UNITS_VALID, aUnit };
-    }
-    default:
+        default:
+            return { UnitsStatus::UNITS_UNKNOWN, boost::none };
+        }
+    } else {
+        SAL_INFO("sc.units", "unit verification not supported for opcode: " << nOpCode);
         return { UnitsStatus::UNITS_UNKNOWN, boost::none };
     }
+    return { UnitsStatus::UNITS_VALID, pOut };
 }
 
 OUString UnitsImpl::extractUnitStringFromFormat(const OUString& rFormatString) {
@@ -469,62 +545,42 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
     pArray->Dump();
 #endif
 
-    stack< UtUnit > aUnitStack;
+    stack< StackItem > aStack;
 
     for (FormulaToken* pToken = pArray->FirstRPN(); pToken != 0; pToken = pArray->NextRPN()) {
         switch (pToken->GetType()) {
         case formula::svSingleRef:
         {
-            UtUnit pUnit(getUnitForRef(pToken, rFormulaAddress, pDoc));
+            UtUnit aUnit(getUnitForRef(pToken, rFormulaAddress, pDoc));
 
-            if (!pUnit.isValid()) {
+            if (!aUnit.isValid()) {
                 SAL_INFO("sc.units", "no unit returned for scSingleRef, ut_status: " << getUTStatus());
 
                 // This only happens in case of parsing (or system) errors.
                 // However maybe we should be returning "unverified" for
                 // unparseable formulas?
                 // (or even have a "can't be verified" state too?)
-                // see below for more.
+                // see below for more.x
                 return false;
             }
 
-            aUnitStack.push(pUnit);
+            aStack.push( { StackItemType::UNITS, aUnit } );
             break;
         }
         case formula::svDoubleRef:
         {
             ScComplexRefData* pDoubleRef = pToken->GetDoubleRef();
-            ScRangeList aRangeList(pDoubleRef->toAbs(rFormulaAddress));
-
-            // Functions operating on DoubleRefs seem to have a variable number of input
-            // arguments (which will all preceed the opcode in the TokenArray), hence
-            // we read all the ranges in first before operating on them.
-            // The appropriate handling of the opcode depends on the specific opcode
-            // (i.e. identical units needed for sum, but not for multiplication), hence
-            // we need to handle that opcode here as opposed to as part of the usual flow
-            // (where we would simply push the units onto the stack).
-            pToken = pArray->NextRPN();
-            while (pToken->GetType() == formula::svDoubleRef) {
-                pDoubleRef = pToken->GetDoubleRef();
-                aRangeList.Append(pDoubleRef->toAbs(rFormulaAddress));
-                pToken = pArray->NextRPN();
-            }
-
-            if (!pToken) {
-                // It's possible to have DoubleRef as the last token, e.g the simplest
-                // example being "=A1:B1" - in this case the formula can't be evaluated
-                // anyways, so giving up on unit verification is the most sensible option.
-                // (I.e. the formula ends up spewing out #VALUE to the end-user - there's
-                // no point in doing any verification here.)
-                return true;
-            }
-
-            UnitsResult aResult(getOutputUnitForDoubleRefOpcode(pToken->GetOpCode(), aRangeList, pDoc));
+            ScRange aRange = pDoubleRef->toAbs(rFormulaAddress);
+            aStack.push( { StackItemType::RANGE, aRange } );
 
+            break;
+        }
+        case formula::svByte:
+        {
+            UnitsResult aResult = getOutputUnitsForOpCode(aStack, pToken, pDoc);
 
             switch (aResult.status) {
             case UnitsStatus::UNITS_INVALID:
-                SAL_INFO("sc.units", "svDoubleRef opcode unit error detected");
                 return false;
             case UnitsStatus::UNITS_UNKNOWN:
                 // Unsupported hence we stop processing.
@@ -532,26 +588,12 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
             case UnitsStatus::UNITS_VALID:
                 assert(aResult.units); // ensure that we have the optional unit
                 assert(aResult.units->isValid());
-                aUnitStack.push(aResult.units.get());
+                aStack.push( { StackItemType::UNITS, aResult.units.get() } );
                 break;
             }
 
             break;
         }
-        case formula::svByte:
-        {
-            UtUnit pOut = getOutputUnitsForOpCode(aUnitStack, pToken->GetOpCode());
-
-            // A null unit indicates either invalid units and/or other erronous input
-            // i.e. is an indication that getOutputUnitsForOpCode failed.
-            if (pOut.isValid()) {
-                aUnitStack.push(pOut);
-            } else {
-                return false;
-            }
-
-            break;
-        }
         // As far as I can tell this is only used for an actual numerical value
         // in which case we just want to use it as scaling factor
         // (for example [m] + [cm]/100 would be a valid operation)
@@ -561,7 +603,8 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
             UtUnit::createUnit("", aScale, mpUnitSystem); // Get the dimensionless unit 1
             aScale = aScale*(pToken->GetDouble());
 
-            aUnitStack.push(aScale);
+            aStack.push( { StackItemType::UNITS, aScale } );
+
             break;
         }
         default:
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index aacf9bb..6abffdd 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -13,6 +13,7 @@
 #include <boost/optional.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/shared_ptr.hpp>
+#include <boost/variant.hpp>
 #include <boost/weak_ptr.hpp>
 
 #include <formula/opcode.hxx>
@@ -41,6 +42,16 @@ namespace test {
     class UnitsTest;
 }
 
+enum class StackItemType {
+    UNITS,
+    RANGE
+};
+
+struct StackItem {
+    StackItemType type;
+    boost::variant< ScRange, UtUnit > item;
+};
+
 enum class UnitsStatus {
     UNITS_VALID,
     UNITS_UNKNOWN,
@@ -92,8 +103,7 @@ public:
                                          const OUString& rsOldUnit) SAL_OVERRIDE;
 
 private:
-    UnitsResult getOutputUnitsForOpCode(std::stack< UtUnit >& rUnitStack, const OpCode& rOpCode);
-    UnitsResult getOutputUnitForDoubleRefOpcode(const OpCode& rOpCode, const ScRangeList& rRange, ScDocument* pDoc);
+    UnitsResult getOutputUnitsForOpCode(std::stack< StackItem >& rStack, const formula::FormulaToken* pToken, ScDocument* pDoc);
 
     /**
      * Find and extract a Unit in the standard header notation,
commit 8d392e360b39a8bba1310fbcdf0e23366ed37812
Author: Andrzej Hunt <andrzej.hunt at collabora.com>
Date:   Mon Mar 30 19:57:04 2015 +0200

    Use UnitsResult for processing of opcodes too.
    
    Change-Id: I7bbbf4ff0bcbd01d70fd929cf62537ceec364c83

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 3a181b5..6057a0e 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -85,7 +85,7 @@ UnitsImpl::~UnitsImpl() {
     // (i.e. if udunits can't handle being used across threads)?
 }
 
-UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpCode& rOpCode) {
+UnitsResult UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpCode& rOpCode) {
     UtUnit pOut;
 
     auto nOpCode = static_cast<std::underlying_type<const OpCode>::type>(rOpCode);
@@ -98,7 +98,7 @@ UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpC
 
         if (!(rUnitStack.size() >= 1)) {
             SAL_WARN("sc.units", "no units on stack for unary operation");
-            return UtUnit();
+            return { UnitsStatus::UNITS_INVALID, boost::none };
         }
 
         UtUnit pUnit = rUnitStack.top();
@@ -107,7 +107,7 @@ UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpC
         switch (rOpCode) {
         case ocNot:
             if (!pUnit.isDimensionless()) {
-                return UtUnit();
+                return { UnitsStatus::UNITS_INVALID, boost::none };
             }
             // We just keep the same unit (in this case no unit) so can
             // fall through.
@@ -132,7 +132,7 @@ UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpC
             SAL_WARN("sc.units", "less than two units on stack when attempting binary operation");
             // TODO: what should we be telling the user in this case? Can this even happen (i.e.
             // should we just be asserting here?)
-            return UtUnit();
+            return { UnitsStatus::UNITS_INVALID, boost::none };
         }
 
         UtUnit pSecondUnit = rUnitStack.top();
@@ -150,6 +150,7 @@ UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpC
                 pOut = pFirstUnit;
                 SAL_INFO("sc.units", "verified equality for unit " << pFirstUnit);
             } else {
+                return { UnitsStatus::UNITS_INVALID, boost::none };
                 // TODO: notify/link UI.
             }
             break;
@@ -166,11 +167,11 @@ UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpC
 
     } else {
         SAL_INFO("sc.units", "unit verification not supported for opcode: " << nOpCode);
+        return { UnitsStatus::UNITS_UNKNOWN, boost::none };
     }
     // TODO: else if unary, or no params, or ...
     // TODO: implement further sensible opcode handling
-
-    return pOut;
+    return { UnitsStatus::UNITS_VALID, pOut };
 }
 
 class RangeListIterator {
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index d6d001c..aacf9bb 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -92,7 +92,7 @@ public:
                                          const OUString& rsOldUnit) SAL_OVERRIDE;
 
 private:
-    UtUnit getOutputUnitsForOpCode(std::stack< UtUnit >& rUnitStack, const OpCode& rOpCode);
+    UnitsResult getOutputUnitsForOpCode(std::stack< UtUnit >& rUnitStack, const OpCode& rOpCode);
     UnitsResult getOutputUnitForDoubleRefOpcode(const OpCode& rOpCode, const ScRangeList& rRange, ScDocument* pDoc);
 
     /**
commit 72d769219cdff0119ce9b6cc4dc67e8334f9e998
Author: Andrzej Hunt <andrzej.hunt at collabora.com>
Date:   Mon Mar 30 11:12:42 2015 +0200

    Implement verification of sum/product and other opcodes for ranges.
    
    Change-Id: I8d0d4cf46bddd585e633b5ee297b6bd7e0a2cf3b

diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index af5fff5..ac1bfa5 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -79,6 +79,11 @@ void UnitsTest::setUp() {
     mpDoc = &m_xDocShRef->GetDocument();
 
     mpUnitsImpl = UnitsImpl::GetUnits();
+
+    // Set separators for formulae - for now just argument separators (no matrix separators)
+    ScFormulaOptions aOptions;
+    aOptions.SetFormulaSepArg(",");
+    m_xDocShRef->SetFormulaOptions(aOptions);
 }
 
 void UnitsTest::tearDown() {
@@ -183,11 +188,19 @@ void UnitsTest::testUnitVerification() {
     mpDoc->SetNumberFormat(address, nKeyS);
     mpDoc->SetValue(address, 3);
 
-    // 4th column: 5cm/s
+    // 4th column: 5cm/s, 10cm/s, 15cm/s
     address = ScAddress(3, 0, 0);
     mpDoc->SetNumberFormat(address, nKeyCM_S);
     mpDoc->SetValue(address, 5);
 
+    address.IncRow();
+    mpDoc->SetNumberFormat(address, nKeyCM_S);
+    mpDoc->SetValue(address, 10);
+
+    address.IncRow();
+    mpDoc->SetNumberFormat(address, nKeyCM_S);
+    mpDoc->SetValue(address, 15);
+
     // 5th column: 1m
     address = ScAddress(4, 0, 0);
     mpDoc->SetNumberFormat(address, nKeyM);
@@ -256,11 +269,81 @@ void UnitsTest::testUnitVerification() {
 
     // And scaling doesn't help when adding incompatible units (kg + 100*m)
     address = ScAddress(0, 13, 0);
-    mpDoc->SetFormula(address, "=B1+100E1");
+    mpDoc->SetFormula(address, "=B1+100*E1");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(!mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+    // Test Ranges:
+    // SUM("kg")
+    address.IncRow();
+    mpDoc->SetFormula(address, "=SUM(B1:B3)");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+    // SUM("cm"&"kg")
+    address.IncRow();
+    mpDoc->SetFormula(address, "=SUM(A1:B3)");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(!mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+    // SUM("cm"&"kg") - multiple ranges
+    address.IncRow();
+    // TODO: by default / when testing: separator is...?
+    mpDoc->SetFormula(address, "=SUM(A1:A3,B1:B3)");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(!mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+    // SUM("cm")+SUM("kg")
+    address.IncRow();
+    mpDoc->SetFormula(address, "=SUM(A1:A3)+SUM(B1:B3)");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(!mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+    // SUM("cm")/SUM("s")+SUM("cm/s")
+    address.IncRow();
+    mpDoc->SetFormula(address, "=SUM(A1:A3)/SUM(C1:C3)+SUM(D1:D3)");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+    // PRODUCT("cm/","s")+"cm"
+    address.IncRow();
+    mpDoc->SetFormula(address, "=PRODUCT(C1:D1)+A1");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+    // PRODUCT("cm/","s")+"kg"
+    address.IncRow();
+    mpDoc->SetFormula(address, "=PRODUCT(C1:D1)+B1");
     pCell = mpDoc->GetFormulaCell(address);
     pTokens = pCell->GetCode();
     CPPUNIT_ASSERT(!mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
 
+    // Do a quick sanity check for all the other supported functions
+    // The following all expect identically united inputs, and should
+    // preserve that unit:
+    std::vector<OUString> aPreservingFuncs{ "SUM", "AVERAGE", "MIN", "MAX" };
+    for (const OUString& aFunc: aPreservingFuncs) {
+        // FOO(cm) + cm
+        address.IncRow();
+        mpDoc->SetFormula(address, "=" + aFunc + "(A1:A2)+A3");
+        pCell = mpDoc->GetFormulaCell(address);
+        pTokens = pCell->GetCode();
+        CPPUNIT_ASSERT(mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+        // FOO(cm) + kg
+        address.IncRow();
+        mpDoc->SetFormula(address, "=" + aFunc + "(A1:A2)+B3");
+        pCell = mpDoc->GetFormulaCell(address);
+        pTokens = pCell->GetCode();
+        CPPUNIT_ASSERT(!mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+    }
 }
 
 void UnitsTest::testUnitFromFormatStringExtraction() {
diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 6d280b4..3a181b5 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -11,6 +11,7 @@
 
 #include "util.hxx"
 
+#include "dociter.hxx"
 #include "document.hxx"
 #include "refdata.hxx"
 #include "stringutil.hxx"
@@ -172,6 +173,82 @@ UtUnit UnitsImpl::getOutputUnitsForOpCode(stack< UtUnit >& rUnitStack, const OpC
     return pOut;
 }
 
+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)
+    {
+        if (rRangeList.size() > 0) {
+            mIt = ScCellIterator(pDoc, *rRangeList[0]);
+            mIt.first();
+        }
+    }
+
+    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::getOutputUnitForDoubleRefOpcode(const OpCode& rOpCode, const ScRangeList& rRangeList, ScDocument* pDoc) {
+    RangeListIterator aIt(pDoc, rRangeList);
+
+    switch (rOpCode) {
+    case ocSum:
+    case ocMin:
+    case ocMax:
+    case ocAverage:
+    {
+        UtUnit aFirstUnit = getUnitForCell(aIt.GetPos(), pDoc);
+
+        while (aIt.next()) {
+            UtUnit aCurrentUnit = getUnitForCell(aIt.GetPos(), pDoc);
+            if (aFirstUnit != aCurrentUnit) {
+                return { UnitsStatus::UNITS_INVALID, boost::none };
+            }
+        }
+
+        return { UnitsStatus::UNITS_VALID, aFirstUnit };
+    }
+    case ocProduct:
+    {
+        UtUnit aUnit = getUnitForCell(aIt.GetPos(), pDoc);
+        while (aIt.next()) {
+            aUnit *= getUnitForCell(aIt.GetPos(), pDoc);
+        }
+        return { UnitsStatus::UNITS_VALID, aUnit };
+    }
+    default:
+        return { UnitsStatus::UNITS_UNKNOWN, boost::none };
+    }
+}
+
 OUString UnitsImpl::extractUnitStringFromFormat(const OUString& rFormatString) {
     // TODO: decide what we do for different subformats? Simplest solution
     // would be to not allow unit storage for multiple subformats.
@@ -413,6 +490,53 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
             aUnitStack.push(pUnit);
             break;
         }
+        case formula::svDoubleRef:
+        {
+            ScComplexRefData* pDoubleRef = pToken->GetDoubleRef();
+            ScRangeList aRangeList(pDoubleRef->toAbs(rFormulaAddress));
+
+            // Functions operating on DoubleRefs seem to have a variable number of input
+            // arguments (which will all preceed the opcode in the TokenArray), hence
+            // we read all the ranges in first before operating on them.
+            // The appropriate handling of the opcode depends on the specific opcode
+            // (i.e. identical units needed for sum, but not for multiplication), hence
+            // we need to handle that opcode here as opposed to as part of the usual flow
+            // (where we would simply push the units onto the stack).
+            pToken = pArray->NextRPN();
+            while (pToken->GetType() == formula::svDoubleRef) {
+                pDoubleRef = pToken->GetDoubleRef();
+                aRangeList.Append(pDoubleRef->toAbs(rFormulaAddress));
+                pToken = pArray->NextRPN();
+            }
+
+            if (!pToken) {
+                // It's possible to have DoubleRef as the last token, e.g the simplest
+                // example being "=A1:B1" - in this case the formula can't be evaluated
+                // anyways, so giving up on unit verification is the most sensible option.
+                // (I.e. the formula ends up spewing out #VALUE to the end-user - there's
+                // no point in doing any verification here.)
+                return true;
+            }
+
+            UnitsResult aResult(getOutputUnitForDoubleRefOpcode(pToken->GetOpCode(), aRangeList, pDoc));
+
+
+            switch (aResult.status) {
+            case UnitsStatus::UNITS_INVALID:
+                SAL_INFO("sc.units", "svDoubleRef opcode unit error detected");
+                return false;
+            case UnitsStatus::UNITS_UNKNOWN:
+                // Unsupported hence we stop processing.
+                return true;
+            case UnitsStatus::UNITS_VALID:
+                assert(aResult.units); // ensure that we have the optional unit
+                assert(aResult.units->isValid());
+                aUnitStack.push(aResult.units.get());
+                break;
+            }
+
+            break;
+        }
         case formula::svByte:
         {
             UtUnit pOut = getOutputUnitsForOpCode(aUnitStack, pToken->GetOpCode());
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index e1427ed..d6d001c 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -10,6 +10,7 @@
 #ifndef INCLUDED_SC_SOURCE_CORE_UNITS_UNITSIMPL_HXX
 #define INCLUDED_SC_SOURCE_CORE_UNITS_UNITSIMPL_HXX
 
+#include <boost/optional.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/weak_ptr.hpp>
@@ -20,7 +21,9 @@
 
 #include <udunits2.h>
 
-#include <units.hxx>
+#include "rangelst.hxx"
+#include "units.hxx"
+
 #include "utunit.hxx"
 
 #include <stack>
@@ -38,6 +41,22 @@ namespace test {
     class UnitsTest;
 }
 
+enum class UnitsStatus {
+    UNITS_VALID,
+    UNITS_UNKNOWN,
+    UNITS_INVALID
+};
+
+/**
+ * The result for a given units operation.
+ * If UNITS_VALID then the resulting unit is also included,
+ * otherwise units is empty.
+ */
+struct UnitsResult {
+    UnitsStatus status;
+    boost::optional<UtUnit> units;
+};
+
 class UnitsImpl: public Units {
     friend class test::UnitsTest;
 
@@ -74,6 +93,7 @@ public:
 
 private:
     UtUnit getOutputUnitsForOpCode(std::stack< UtUnit >& rUnitStack, const OpCode& rOpCode);
+    UnitsResult getOutputUnitForDoubleRefOpcode(const OpCode& rOpCode, const ScRangeList& rRange, ScDocument* pDoc);
 
     /**
      * Find and extract a Unit in the standard header notation,
commit b921c4964db4cb0dfae556bcfa40aaec3b86abf0
Author: Andrzej Hunt <andrzej.hunt at collabora.com>
Date:   Mon Mar 30 09:50:45 2015 +0200

    Move declarations to more sensible place.
    
    Change-Id: Ieb60b2a263a4d700f366b0aae7128e5a66ac4205

diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index 91730f7..e1427ed 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -74,8 +74,6 @@ public:
 
 private:
     UtUnit getOutputUnitsForOpCode(std::stack< UtUnit >& rUnitStack, const OpCode& rOpCode);
-    OUString extractUnitStringFromFormat(const OUString& rFormatString);
-    OUString extractUnitStringForCell(const ScAddress& rAddress, ScDocument* pDoc);
 
     /**
      * Find and extract a Unit in the standard header notation,
@@ -101,6 +99,9 @@ private:
 
     bool extractUnitFromHeaderString(const OUString& rHeader, UtUnit& aUnit, OUString& sUnitString);
 
+    OUString extractUnitStringFromFormat(const OUString& rFormatString);
+    OUString extractUnitStringForCell(const ScAddress& rAddress, ScDocument* pDoc);
+
     /**
      * Retrieve the units for a given cell. This probes based on the usual rules
      * for cell annotation/column header.
commit 7f87b021220584e8080a06a3875b18b83f28f5fe
Author: Andrzej Hunt <andrzej.hunt at collabora.com>
Date:   Mon Mar 30 09:50:05 2015 +0200

    Implement UtUnit::operator*=
    
    Change-Id: I2733154c3d7c42f3af273a0267158e8fa5957c23

diff --git a/sc/source/core/units/utunit.hxx b/sc/source/core/units/utunit.hxx
index 24225dd..c137f67 100644
--- a/sc/source/core/units/utunit.hxx
+++ b/sc/source/core/units/utunit.hxx
@@ -90,6 +90,11 @@ public:
         return UtUnit(ut_multiply(this->get(), rUnit.get()));
     }
 
+    UtUnit& operator*=(const UtUnit& rUnit) {
+        reset(ut_multiply(this->get(), rUnit.get()));
+        return *this;
+    }
+
     /**
      * Multiply a unit by a given constant.
      * This scales the unit by the inverse of that constant,
commit 34898954b9b08708bb228e8065bf4c344661975a
Author: Andrzej Hunt <andrzej.hunt at collabora.com>
Date:   Mon Mar 30 09:49:27 2015 +0200

    Split getUnitForRef into mapping and retrieval.
    
    We will need getUnitForCell when verifying formulas operating on ranges
    (where we need to iterate over cells in a range, and retrieve those
    cells units - as opposed to retrieving the unit for a reference in a
    token array).
    
    Change-Id: I6652057bcb3ee42becb4c5425b06ce544cd400a6

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 9492dd1..6d280b4 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -314,19 +314,8 @@ bool UnitsImpl::extractUnitFromHeaderString(const OUString& rsHeader, UtUnit& aU
     return false;
 }
 
-UtUnit UnitsImpl::getUnitForRef(FormulaToken* pToken, const ScAddress& rFormulaAddress,
-                    ScDocument* pDoc) {
-    assert(pToken->GetType() == formula::svSingleRef);
-
-    ScSingleRefData* pRef = pToken->GetSingleRef();
-    assert(pRef);
-
-    // Addresses can/will be relative to the formula, for extracting
-    // units however we will need to get the absolute address (i.e.
-    // by adding the current address to the relative formula address).
-    const ScAddress aInputAddress = pRef->toAbs( rFormulaAddress );
-
-    OUString sUnitString = extractUnitStringForCell(aInputAddress, pDoc);
+UtUnit UnitsImpl::getUnitForCell(const ScAddress& rCellAddress, ScDocument* pDoc) {
+    OUString sUnitString = extractUnitStringForCell(rCellAddress, pDoc);
 
     UtUnit aUnit;
     if (sUnitString.getLength() > 0 &&
@@ -336,15 +325,31 @@ UtUnit UnitsImpl::getUnitForRef(FormulaToken* pToken, const ScAddress& rFormulaA
 
     OUString aHeaderUnitString; // Unused -- passed by reference below
     ScAddress aHeaderAddress; // Unused too
-    UtUnit aHeaderUnit = findHeaderUnitForCell(aInputAddress, pDoc, aHeaderUnitString, aHeaderAddress);
+    UtUnit aHeaderUnit = findHeaderUnitForCell(rCellAddress, pDoc, aHeaderUnitString, aHeaderAddress);
     if (aHeaderUnit.isValid())
         return aHeaderUnit;
 
-    SAL_INFO("sc.units", "no unit obtained for token at cell " << aInputAddress.GetColRowString());
+    SAL_INFO("sc.units", "no unit obtained for token at cell " << rCellAddress.GetColRowString());
 
     // We return the dimensionless unit 1 if we don't find any other data suggesting a unit.
     UtUnit::createUnit("", aUnit, mpUnitSystem);
     return aUnit;
+
+}
+
+UtUnit UnitsImpl::getUnitForRef(FormulaToken* pToken, const ScAddress& rFormulaAddress,
+                    ScDocument* pDoc) {
+    assert(pToken->GetType() == formula::svSingleRef);
+
+    ScSingleRefData* pRef = pToken->GetSingleRef();
+    assert(pRef);
+
+    // Addresses can/will be relative to the formula, for extracting
+    // units however we will need to get the absolute address (i.e.
+    // by adding the current address to the relative formula address).
+    const ScAddress aCellAddress = pRef->toAbs( rFormulaAddress );
+
+    return getUnitForCell(aCellAddress, pDoc);
 }
 
 UtUnit UnitsImpl::findHeaderUnitForCell(const ScAddress& rCellAddress,
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index 1e8ac8f..91730f7 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -101,6 +101,12 @@ private:
 
     bool extractUnitFromHeaderString(const OUString& rHeader, UtUnit& aUnit, OUString& sUnitString);
 
+    /**
+     * 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.
+     */
+    UtUnit getUnitForCell(const ScAddress& rCellAddress, ScDocument* pDoc);
     UtUnit getUnitForRef(formula::FormulaToken* pToken,
                          const ScAddress& rFormulaAddress,
                          ScDocument* pDoc);
commit 39751bf7916cf6d397d49fcab95c778a754e26af
Author: Andrzej Hunt <andrzej.hunt at collabora.com>
Date:   Mon Mar 30 09:46:36 2015 +0200

    Typo in comment
    
    Change-Id: Ic6755b950f6b26546072afa82a9ad68caf2becc8

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 06cd4f3..9492dd1 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -257,7 +257,7 @@ bool UnitsImpl::findUnitInStandardHeader(const OUString& rsHeader, UtUnit& aUnit
 
 
 bool UnitsImpl::findFreestandingUnitInHeader(const OUString& rsHeader, UtUnit& aUnit, OUString& sUnitString) {
-    // We just split the string and test whether each token is either a valid unit in it's own right,
+    // 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
     // of the individual elements starts or begins with an operator.
commit 5c941ff7c510b7ec167a3ac69a8e09f3ebc5b72f
Author: Andrzej Hunt <andrzej.hunt at collabora.com>
Date:   Sat Mar 21 06:47:24 2015 +0100

    Handle constants in formulas for verification too.
    
    Change-Id: Ib1b07e6dabf961726b3411b5ff00c67862d95d03

diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index a0d5fa1..af5fff5 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -125,7 +125,7 @@ void UnitsTest::testUnitVerification() {
     mpDoc->EnsureTable(0);
 
     SvNumberFormatter* pFormatter = mpDoc->GetFormatTable();
-    sal_uInt32 nKeyCM, nKeyKG, nKeyS, nKeyCM_S;
+    sal_uInt32 nKeyCM, nKeyM, nKeyKG, nKeyS, nKeyCM_S;
 
     // Used to return position of error in input string for PutEntry
     // -- not needed here.
@@ -133,6 +133,8 @@ void UnitsTest::testUnitVerification() {
 
     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\"";
@@ -186,6 +188,11 @@ void UnitsTest::testUnitVerification() {
     mpDoc->SetNumberFormat(address, nKeyCM_S);
     mpDoc->SetValue(address, 5);
 
+    // 5th column: 1m
+    address = ScAddress(4, 0, 0);
+    mpDoc->SetNumberFormat(address, nKeyM);
+    mpDoc->SetValue(address, 1);
+
     ScFormulaCell* pCell;
     ScTokenArray* pTokens;
 
@@ -223,6 +230,37 @@ void UnitsTest::testUnitVerification() {
     pCell = mpDoc->GetFormulaCell(address);
     pTokens = pCell->GetCode();
     CPPUNIT_ASSERT(!mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+    // Test that addition of scaled units works (cm + 100*m)
+    address = ScAddress(0, 10, 0);
+    mpDoc->SetFormula(address, "=A1+100*E1");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+    // 10cm + 100*1m = 110cm
+    CPPUNIT_ASSERT(mpDoc->GetValue(address) == 110);
+
+    // But addition of them unscaled fails (cm + m)
+    address = ScAddress(0, 11, 0);
+    mpDoc->SetFormula(address, "=A1+E1");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(!mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+    // As does wrong scaling (cm+m/50)
+    address = ScAddress(0, 12, 0);
+    mpDoc->SetFormula(address, "=A1+E1/50");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(!mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
+    // And scaling doesn't help when adding incompatible units (kg + 100*m)
+    address = ScAddress(0, 13, 0);
+    mpDoc->SetFormula(address, "=B1+100E1");
+    pCell = mpDoc->GetFormulaCell(address);
+    pTokens = pCell->GetCode();
+    CPPUNIT_ASSERT(!mpUnitsImpl->verifyFormula(pTokens, address, mpDoc));
+
 }
 
 void UnitsTest::testUnitFromFormatStringExtraction() {
diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 58f4d14..06cd4f3 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -422,6 +422,18 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
 
             break;
         }
+        // As far as I can tell this is only used for an actual numerical value
+        // in which case we just want to use it as scaling factor
+        // (for example [m] + [cm]/100 would be a valid operation)
+        case formula::svDouble:
+        {
+            UtUnit aScale;
+            UtUnit::createUnit("", aScale, mpUnitSystem); // Get the dimensionless unit 1
+            aScale = aScale*(pToken->GetDouble());
+
+            aUnitStack.push(aScale);
+            break;
+        }
         default:
             // We can't parse any other types of tokens yet, so assume that the formula
             // was correct.
commit ac45fcb30d1033d3896169d6e1ba5e2a88eeaeba
Author: Andrzej Hunt <andrzej.hunt at collabora.com>
Date:   Sat Mar 21 06:46:15 2015 +0100

    Refactor unit extraction to separate local/header unit extraction
    
    Change-Id: I46eb09fcbb4d83fc58140cda7fada2bf287b0a65

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 5c9b28c..58f4d14 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -208,7 +208,10 @@ OUString UnitsImpl::extractUnitStringForCell(const ScAddress& rAddress, ScDocume
     return extractUnitStringFromFormat(rFormatString);
 }
 
-bool UnitsImpl::extractUnitFromHeaderString(const OUString& rString, UtUnit& aUnit, OUString& sUnitString) {
+bool UnitsImpl::findUnitInStandardHeader(const OUString& rsHeader, UtUnit& aUnit, OUString& sUnitString) {
+    // 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.
     com::sun::star::uno::Reference<com::sun::star::uno::XComponentContext> xContext = comphelper::getProcessComponentContext();
 
     uno::Reference<lang::XMultiServiceFactory> xFactory(xContext->getServiceManager(), uno::UNO_QUERY_THROW);
@@ -222,18 +225,14 @@ bool UnitsImpl::extractUnitFromHeaderString(const OUString& rString, UtUnit& aUn
     aOptions.algorithmType = util::SearchAlgorithms_REGEXP ;
     aOptions.searchFlag = util::SearchFlags::ALL_IGNORE_CASE;
 
-    // 1. We try to check for units contained within square brackets
-    // 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.
     aOptions.searchString = "\\[([^\\]]+)\\]"; // Grab the contents between [ and ].
     xSearch->setOptions( aOptions );
 
     util::SearchResult aResult;
-    sal_Int32 nStartPosition = rString.getLength();
+    sal_Int32 nStartPosition = rsHeader.getLength();
     while (nStartPosition) {
         // Search from the back since units are more likely to be at the end of the header.
-        aResult = xSearch->searchBackward(rString, nStartPosition, 0);
+        aResult = xSearch->searchBackward(rsHeader, nStartPosition, 0);
 
         // We have either 0 items (no match), or 2 (matched string + the group within)
         if (aResult.subRegExpressions != 2) {
@@ -243,7 +242,7 @@ bool UnitsImpl::extractUnitFromHeaderString(const OUString& rString, UtUnit& aUn
             // 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 = rString.copy(aResult.endOffset[1], aResult.startOffset[1] - aResult.endOffset[1]);
+            sUnitString = rsHeader.copy(aResult.endOffset[1], aResult.startOffset[1] - aResult.endOffset[1]);
 
             if (UtUnit::createUnit(sUnitString, aUnit, mpUnitSystem)) {
                 return true;
@@ -252,17 +251,26 @@ bool UnitsImpl::extractUnitFromHeaderString(const OUString& rString, UtUnit& aUn
             nStartPosition = aResult.endOffset[0];
         }
     }
+    sUnitString.clear();
+    return false;
+}
+
 
-    // 2. We try to check for any free-standing units by splitting the string and testing each split
-    const sal_Int32 nTokenCount = comphelper::string::getTokenCount(rString, ' ');
+bool UnitsImpl::findFreestandingUnitInHeader(const OUString& rsHeader, UtUnit& aUnit, OUString& sUnitString) {
+    // We just split the string and test whether each token is either a valid unit in it's 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
+    // of the individual elements starts or begins with an operator.
     // There's an inherent limit to how well we can cope with various spacing issues here without
     // a ton of computational complexity.
-    // E.g. by parsing in this way we might end up with unmatched parentheses which udunits won't like.
-    // for now operators have to be completely freestanding i.e. "cm / kg" or completely within a valid unit string e.g. "cm/kg"
+    // E.g. by parsing in this way we might end up with unmatched parentheses which udunits won't like
+    // (thus rejecting the unit) etc.
+
+    const sal_Int32 nTokenCount = comphelper::string::getTokenCount(rsHeader, ' ');
     const OUString sOperators = "/*"; // valid
     sUnitString.clear();
     for (sal_Int32 nToken = 0; nToken < nTokenCount; nToken++) {
-        OUString sToken = rString.getToken(nToken, ' ');
+        OUString sToken = rsHeader.getToken(nToken, ' ');
         UtUnit aTestUnit;
         if (UtUnit::createUnit(sToken, aTestUnit, mpUnitSystem) ||
             // Only test for a separator character if we have already got something in our string, as
@@ -285,6 +293,20 @@ bool UnitsImpl::extractUnitFromHeaderString(const OUString& rString, UtUnit& aUn
     if (sUnitString.getLength() && UtUnit::createUnit(sUnitString, aUnit, mpUnitSystem)) {
         return true;
     }
+    sUnitString.clear();
+    return false;
+}
+
+bool UnitsImpl::extractUnitFromHeaderString(const OUString& rsHeader, UtUnit& aUnit, OUString& sUnitString) {
+    // 1. Ideally we have units in a 'standard' format, i.e. enclose in square brackets:
+    if (findUnitInStandardHeader(rsHeader, aUnit, sUnitString)) {
+        return true;
+    }
+
+    // 2. But if not we check for free-standing units
+    if (findFreestandingUnitInHeader(rsHeader, aUnit, sUnitString)) {
+        return true;
+    }
 
     // 3. Give up
     aUnit = UtUnit(); // assign invalid
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index 5616379..1e8ac8f 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -76,7 +76,31 @@ private:
     UtUnit getOutputUnitsForOpCode(std::stack< UtUnit >& rUnitStack, const OpCode& rOpCode);
     OUString extractUnitStringFromFormat(const OUString& rFormatString);
     OUString extractUnitStringForCell(const ScAddress& rAddress, ScDocument* pDoc);
-    bool extractUnitFromHeaderString(const OUString& rString, UtUnit& aUnit, OUString& sUnitString);
+
+    /**
+     * 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.
+     */
+    bool findUnitInStandardHeader(const OUString& rHeader, UtUnit& aUnit, OUString& sUnitString);
+    /**
+     * Find and extract a freestanding Unit from a header string.
+     * This includes strings such as "speed m/s", "speed m / s",
+     * "speed (m/s)" etc.
+     * This is (for now) only a fallback for the case that the user hasn't defined
+     * units in the standard notation, and can only handle a limited number
+     * of ways in which units could be written in a string.
+     * It may turn out that in practice this method should be extended to support
+     * more permutations of the same unit, but this should at least cover the most
+     * obvious cases.
+     *
+     * @ return true if a unit is found.
+     */
+    bool findFreestandingUnitInHeader(const OUString& rHeader, UtUnit& aUnit, OUString& sUnitString);
+
+    bool extractUnitFromHeaderString(const OUString& rHeader, UtUnit& aUnit, OUString& sUnitString);
+
     UtUnit getUnitForRef(formula::FormulaToken* pToken,
                          const ScAddress& rFormulaAddress,
                          ScDocument* pDoc);
commit 433f531821f280dbe66dbdf457442651a8fe5cd3
Author: Andrzej Hunt <andrzej.hunt at collabora.com>
Date:   Sat Mar 21 06:42:48 2015 +0100

    Implement UtUnit::operator* for constants
    
    Change-Id: I9c44f0a4d0b78c3eb3262bd55d8304b7b07460e2

diff --git a/sc/source/core/units/utunit.hxx b/sc/source/core/units/utunit.hxx
index 058891a..24225dd 100644
--- a/sc/source/core/units/utunit.hxx
+++ b/sc/source/core/units/utunit.hxx
@@ -90,6 +90,19 @@ public:
         return UtUnit(ut_multiply(this->get(), rUnit.get()));
     }
 
+    /**
+     * Multiply a unit by a given constant.
+     * This scales the unit by the inverse of that constant,
+     * e.g. 100*m is equivalent to cm, hence
+     * the unit needs to be scaled by 1/100.
+     * (This is implemented in this way so that we can simply
+     * multiply units by any constants present in a formula
+     * in a natural way.)
+     */
+    UtUnit operator*(const double nMultiplier) {
+        return UtUnit(ut_scale(1/nMultiplier, this->get()));
+    }
+
     UtUnit operator/(const UtUnit& rUnit) {
         // the parameter is the right hand side value in the operation,
         // i.e. we are working with this / rUnit.
commit 0ec56880b73b39fe0e6df2c18e28c5f639bed4e5
Author: Andrzej Hunt <andrzej.hunt at collabora.com>
Date:   Wed Mar 18 12:28:33 2015 +0000

    Turn while into for for clarity.
    
    Change-Id: I6e55441c900b64af014fe902c9c8bf928aa7e31a

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 427aec3..5c9b28c 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -366,9 +366,7 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
 
     stack< UtUnit > aUnitStack;
 
-    FormulaToken* pToken = pArray->FirstRPN();
-
-    while (pToken != 0) {
+    for (FormulaToken* pToken = pArray->FirstRPN(); pToken != 0; pToken = pArray->NextRPN()) {
         switch (pToken->GetType()) {
         case formula::svSingleRef:
         {
@@ -409,8 +407,6 @@ bool UnitsImpl::verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAdd
             SAL_WARN("sc.units", "Unrecognised token type " << pToken->GetType());
             return true;
         }
-
-        pToken = pArray->NextRPN();
     }
 
     // TODO: only fail if actual parsing fails?
commit c56b8eaaa7a2726fee6130cd50af9ddac7879051
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Mon Mar 16 07:45:50 2015 +0000

    Add missing include.
    
    Change-Id: I596645b718e12fab46e5fbb7d5ccdc0af251c107

diff --git a/sc/source/core/data/column3.cxx b/sc/source/core/data/column3.cxx
index e05ac67..f0d7fa8 100644
--- a/sc/source/core/data/column3.cxx
+++ b/sc/source/core/data/column3.cxx
@@ -46,6 +46,7 @@
 #include "scopetools.hxx"
 #include "editutil.hxx"
 #include "sharedformula.hxx"
+#include "units.hxx"
 #include <listenercontext.hxx>
 
 #include <com/sun/star/i18n/LocaleDataItem.hpp>
commit 44ce335441fd2342f3a4f3456ded2058e89ec6c9
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Mon Mar 16 07:45:23 2015 +0000

    Show unit conversion infobar as appropriate.
    
    Change-Id: I882a67167ed5ef60411ccee546df89a2793b36fa

diff --git a/sc/inc/sc.hrc b/sc/inc/sc.hrc
index bcaf7f9..c85bdd7 100644
--- a/sc/inc/sc.hrc
+++ b/sc/inc/sc.hrc
@@ -1000,8 +1000,8 @@
 #define STR_UNITS_ERRORINCELL   (STR_START + 450)
 #define BT_UNITS_EDIT_CELL      (STR_START + 451)
 
-#define STR_UNITS_CONV_REQUIRED (STR_START + 455)
-#define BT_UNITS_CONV_THISCELL  (STR_START + 456)
+#define STR_UNITS_CONVERSION_RECOMMENDED (STR_START + 455)
+#define BT_UNITS_CONVERT_THIS_CELL  (STR_START + 456)
 #define BT_UNITS_CONV_ALL       (STR_START + 457)
 #define STR_END                 (BT_UNITS_CONV_ALL)
 
diff --git a/sc/source/ui/inc/viewfunc.hxx b/sc/source/ui/inc/viewfunc.hxx
index ed92f8a..20d6872 100644
--- a/sc/source/ui/inc/viewfunc.hxx
+++ b/sc/source/ui/inc/viewfunc.hxx
@@ -50,6 +50,8 @@ class SvxHyperlinkItem;
 class ScTransferObj;
 class ScTableProtection;
 
+struct UnitConversionPushButton;
+
 namespace editeng { class SvxBorderLine; }
 
 namespace sc {
@@ -373,6 +375,13 @@ private:
 
     void            NotifyUnitErrorInFormula( const ScAddress& rAddress, ScDocument* pDoc );
     DECL_LINK( EditUnitErrorFormulaHandler, PushButton* );
+
+    void            NotifyUnitConversionRecommended( const ScAddress& rCellAddress,
+                                                     ScDocument* pDoc,
+                                                     const OUString& sHeaderUnit,
+                                                     const ScAddress& rHeaderAddress,
+                                                     const OUString& sCellUnit );
+    DECL_LINK( UnitConversionRecommendedHandler, UnitConversionPushButton* );
 };
 
 #endif
diff --git a/sc/source/ui/src/units.src b/sc/source/ui/src/units.src
index 6165af8..be4f970 100644
--- a/sc/source/ui/src/units.src
+++ b/sc/source/ui/src/units.src
@@ -31,16 +31,16 @@ PushButton BT_UNITS_EDIT_CELL
     Text[ en-US ] = "Edit Formula";
 };
 
-String STR_UNITS_CONV_REQUIRED
+String STR_UNITS_CONVERSION_RECOMMENDED
 {
-    Text [ en-US ] = "You entered data in $1, would you like to convert to $2?" ;
+    Text [ en-US ] = "You entered data in [$1] in cell $2, where [$3] (defined at cell $4) would be expected." ;
 };
 
-PushButton BT_UNITS_CONV_THISCELL
+PushButton BT_UNITS_CONVERT_THIS_CELL
 {
     Pos = MAP_APPFONT( 0 , 0 );
     Size = MAP_APPFONT( 70 , 0 );
-    Text [ en-US ] = "Convert (only this cell)" ;
+    Text [ en-US ] = "Convert to [$1]" ;
 };
 
 PushButton BT_UNITS_CONV_ALL
diff --git a/sc/source/ui/view/viewfunc.cxx b/sc/source/ui/view/viewfunc.cxx
index 0ac6fff..595c5a9 100644
--- a/sc/source/ui/view/viewfunc.cxx
+++ b/sc/source/ui/view/viewfunc.cxx
@@ -564,19 +564,35 @@ void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab,
             }
         }
     }
-    else
+    else // ( !bFormula )
     {
         ScMarkData::iterator itr = rMark.begin(), itrEnd = rMark.end();
         for ( ; itr != itrEnd; ++itr )
         {
             bool bNumFmtSet = false;
-            rFunc.SetNormalString( bNumFmtSet, ScAddress( nCol, nRow, *itr ), rString, false );
+            const ScAddress aAddress( nCol, nRow, *itr );
+            rFunc.SetNormalString( bNumFmtSet, aAddress, rString, false );
             if (bNumFmtSet)
             {
                 /* FIXME: if set on any sheet results in changed only on
                  * sheet nTab for TestFormatArea() and DoAutoAttributes() */
                 bNumFmtChanged = true;
             }
+
+#ifdef ENABLE_CALC_UNITVERIFICATION
+            boost::shared_ptr< sc::units::Units > pUnits = sc::units::Units::GetUnits();
+
+            OUString sHeaderUnit, sCellUnit;
+            ScAddress aHeaderAddress;
+
+            if ( pUnits->isCellConversionRecommended( aAddress, pDoc, sHeaderUnit, aHeaderAddress, sCellUnit ) ) {
+                NotifyUnitConversionRecommended( aAddress, pDoc, sHeaderUnit, aHeaderAddress, sCellUnit );
+            } else {
+                SfxViewFrame* pViewFrame = GetViewData().GetViewShell()->GetFrame();
+                OUString sAddress = aAddress.Format( SCA_BITS, pDoc );
+                pViewFrame->RemoveInfoBar( sAddress );
+            }
+#endif
         }
     }
 
@@ -2872,4 +2888,106 @@ IMPL_LINK( ScViewFunc, EditUnitErrorFormulaHandler, PushButton*, pButton )
     return 0;
 }
 
+/*
+ * PushButton wrapper used to persist cell conversion data between data
+ * entry and whenever the user chooses to activate conversion.
+ * This is required as there doesn't seem to be any other way to pass
+ * data to the Handler. This is the easiest way of passing the data (we can't
+ * wrap the infobar itself as it is constructed by Viewframe's AppendInfoBar,
+ * and hidden elements seem even more hacky).
+ *
+ * Forward declaration in viewfunc.hxx
+ */
+struct UnitConversionPushButton: public PushButton
+{
+    const ScAddress aCellAddress;
+    ScDocument* mpDoc;
+    const OUString sHeaderUnit;
+    const OUString sCellUnit;
+
+    UnitConversionPushButton( vcl::Window* pParent,
+                              const ResId& rResId,
+                              const ScAddress& rCellAddress,
+                              ScDocument* pDoc,
+                              const OUString& rsHeaderUnit,
+                              const OUString& rsCellUnit ):
+        PushButton( pParent, rResId ),
+        aCellAddress( rCellAddress ),
+        mpDoc( pDoc ),
+        sHeaderUnit( rsHeaderUnit ),
+        sCellUnit( rsCellUnit )
+        {}
+};
+
+void ScViewFunc::NotifyUnitConversionRecommended( const ScAddress& rCellAddress,
+                                                 ScDocument* pDoc,
+                                                 const OUString& rsHeaderUnit,
+                                                 const ScAddress& rHeaderAddress,
+                                                 const OUString& rsCellUnit ) {
+    SfxViewFrame* pViewFrame = GetViewData().GetViewShell()->GetFrame();
+
+    // As with NotifyUnitErrorInFormula we use the cell address as the infobar id.
+    // See NotifyUnitErrorInFormula for full details on why. It's impossible for both
+    // infobars to be valid for a single cell anyways (as the UnitErrorInFormula infobar
+    // can only appear for Formulas, whereas this one can only appear for values), hence
+    // using the same id format is not an issue.
+    OUString sTitle = SC_RESSTR( STR_UNITS_CONVERSION_RECOMMENDED );
+    sTitle = sTitle.replaceAll( "$1", rsCellUnit );
+    sTitle = sTitle.replaceAll( "$2", rCellAddress.GetColRowString() );
+    sTitle = sTitle.replaceAll( "$3", rsHeaderUnit );
+    sTitle = sTitle.replaceAll( "$4", rHeaderAddress.GetColRowString() );
+
+    OUString sCellAddress = rCellAddress.Format( SCA_BITS, pDoc );
+    SfxInfoBarWindow* pInfoBar = pViewFrame->AppendInfoBar( sCellAddress, sTitle );
+
+    assert( pInfoBar );
+
+    UnitConversionPushButton* pButtonConvertCell = new UnitConversionPushButton( &pViewFrame->GetWindow(),
+                                                                                 ScResId(BT_UNITS_CONVERT_THIS_CELL),
+                                                                                 rCellAddress,
+                                                                                 pDoc,
+                                                                                 rsHeaderUnit,
+                                                                                 rsCellUnit );
+    pButtonConvertCell->SetClickHdl( LINK( this, ScViewFunc, UnitConversionRecommendedHandler ) );
+
+    OUString sConvertText = pButtonConvertCell->GetText();
+    sConvertText = sConvertText.replaceAll( "$1", rsHeaderUnit );
+    pButtonConvertCell->SetText( sConvertText );
+
+    pInfoBar->addButton( pButtonConvertCell );
+}
+
+IMPL_LINK( ScViewFunc, UnitConversionRecommendedHandler, UnitConversionPushButton*, pButton )
+{
+    // Do conversion first, and only then remove the infobar as we need data from the infobar
+    // (specifically from the pushbutton) to do the conversion.
+#ifdef ENABLE_CALC_UNITVERIFICATION
+    boost::shared_ptr< sc::units::Units > pUnits = sc::units::Units::GetUnits();
+
+    pUnits->convertCellToHeaderUnit( pButton->aCellAddress,
+                                     pButton->mpDoc,
+                                     pButton->sHeaderUnit,
+                                     pButton->sCellUnit );
+#endif
+
+    OUString sAddress;
+    {
+        // keep pInfoBar within this scope only as we'll be deleting it just below (using RemoveInfoBar)
+        SfxInfoBarWindow* pInfoBar = dynamic_cast< SfxInfoBarWindow* >( pButton->GetParent() );
+        sAddress = pInfoBar->getId();
+    }
+    SfxViewFrame* pViewFrame = GetViewData().GetViewShell()->GetFrame();
+    pViewFrame->RemoveInfoBar( sAddress );
+
+
+    // We make sure we then scroll to the desired cell to make the change clear.
+    SfxStringItem aPosition( SID_CURRENTCELL, sAddress );
+    SfxBoolItem aUnmark( FN_PARAM_1, true ); // Removes existing selection if present.
+    GetViewData().GetDispatcher().Execute( SID_CURRENTCELL,
+                                           SfxCallMode::SYNCHRON | SfxCallMode::RECORD,
+                                           &aPosition, &aUnmark, 0L );
+
+    return 0;
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
commit fd3e1f1f05f6a2d27ad2dbbfb3f3633312e78bb6
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sun Mar 15 20:37:00 2015 +0000

    Implement convertCellToHeaderUnit.
    
    Change-Id: Ia62705ea45f116e9204375cdf5658fb06b76315f

diff --git a/sc/inc/units.hxx b/sc/inc/units.hxx
index ecd2946..9837d36 100644
--- a/sc/inc/units.hxx
+++ b/sc/inc/units.hxx
@@ -57,6 +57,25 @@ public:
                                              ScAddress& rHeaderCellAddress,
                                              OUString& rCellUnit) = 0;
 
+    /**
+     * Convert a cell with local unit annotation to store data in the units represented by
+     * its (column-level) header.
+     *
+     * This method should only be used with the data provided by isCellConversionRecommended.
+     * It will remove the cell's local unit annotation, and not add any of it's own annotation.
+     * You should use the (yet to be implemented) convertCells() method for more generic
+     * unit conversion.
+     *
+     * Returns true for successful conversion, false if an error occured (e.g. if the data
+     * has been modified in the meantime, i.e. that the units no longer correspond
+     * to the expected annotation or header).
+     */
+    virtual bool convertCellToHeaderUnit(const ScAddress& rCellAddress,
+                                         ScDocument* pDoc,
+                                         const OUString& rsNewUnit,
+                                         const OUString& rsOldUnit) = 0;
+
+
     virtual ~Units() {}
 };
 
diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index c657981..a0d5fa1 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -47,7 +47,7 @@ public:
 
     void testUnitFromHeaderExtraction();
 
-    void testCellConversionRequired();
+    void testCellConversion();
 
     CPPUNIT_TEST_SUITE(UnitsTest);
 
@@ -58,7 +58,7 @@ public:
     CPPUNIT_TEST(testUnitValueStringSplitting);
     CPPUNIT_TEST(testUnitFromHeaderExtraction);
 
-    CPPUNIT_TEST(testCellConversionRequired);
+    CPPUNIT_TEST(testCellConversion);
 
     CPPUNIT_TEST_SUITE_END();
 
@@ -320,7 +320,9 @@ void UnitsTest::testUnitFromHeaderExtraction() {
     // CPPUNIT_ASSERT(aUnit == aTestUnit);
 }
 
-void UnitsTest::testCellConversionRequired() {
+void UnitsTest::testCellConversion() {
+    // We test both isCellConversionRecommended, and convertCellToHeaderUnit
+    // since their arguments are essentially shared / dependent.
     mpDoc->EnsureTable(0);
 
     // Set up a column with a normal header and a few data values
@@ -358,13 +360,18 @@ void UnitsTest::testCellConversionRequired() {
     // First united cell: "cm" (convertible to "m")
     address.IncRow();
     mpDoc->SetNumberFormat(address, nKeyCM);
-    mpDoc->SetValue(address, 10);
+    mpDoc->SetValue(address, 100);
 
     CPPUNIT_ASSERT(mpUnitsImpl->isCellConversionRecommended(address, mpDoc, sHeaderUnit, aHeaderAddress, sCellUnit));
     CPPUNIT_ASSERT(sHeaderUnit == "m");
     CPPUNIT_ASSERT(sCellUnit == "cm");
     CPPUNIT_ASSERT(aHeaderAddress == ScAddress(20, 0, 0));
 
+    // Test conversion (From cm to m)
+    CPPUNIT_ASSERT(mpUnitsImpl->convertCellToHeaderUnit(address, mpDoc, sHeaderUnit, sCellUnit));
+    CPPUNIT_ASSERT(mpDoc->GetValue(address) == 1);
+    CPPUNIT_ASSERT(mpDoc->GetNumberFormat(address) == 0);
+
     // Second united cell: "kg" (not convertible to "m")
     address.IncRow();
     mpDoc->SetNumberFormat(address, nKeyKG);
@@ -374,6 +381,10 @@ void UnitsTest::testCellConversionRequired() {
     CPPUNIT_ASSERT(sHeaderUnit.isEmpty());
     CPPUNIT_ASSERT(sCellUnit.isEmpty());
     CPPUNIT_ASSERT(!aHeaderAddress.IsValid());
+
+    // We specifically don't test conversion with invalid units since that would be nonsensical
+    // (i.e. would require us passing in made up arguments, where it's specifically necessary
+    // to pass in the output of isCellConversionRecommended).
 }
 
 CPPUNIT_TEST_SUITE_REGISTRATION(UnitsTest);
diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 50ba25a..427aec3 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -479,4 +479,45 @@ bool UnitsImpl::isCellConversionRecommended(const ScAddress& rCellAddress,
     return false;
 }
 
+bool UnitsImpl::convertCellToHeaderUnit(const ScAddress& rCellAddress,
+                             ScDocument* pDoc,
+                             const OUString& rsNewUnit,
+                             const OUString& rsOldUnit) {
+    assert(rCellAddress.IsValid());
+
+    OUString sCellUnit = extractUnitStringForCell(rCellAddress, pDoc);
+    UtUnit aOldUnit;
+    UtUnit::createUnit(sCellUnit, aOldUnit, mpUnitSystem);
+
+    OUString sHeaderUnitFound;
+    ScAddress aHeaderAddress; // Unused, but passed by reference
+    UtUnit aNewUnit = findHeaderUnitForCell(rCellAddress, pDoc, sHeaderUnitFound, aHeaderAddress);
+
+    // 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
+    // relative to isCellConversionRecommended (e.g. if the user is asked for
+    // confirmation that conversion is desired), hence it's entirely feasible
+    // for data to be changed in the document but this action to be still
+    // 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) &&
+        (pDoc->GetCellType(rCellAddress) == CELLTYPE_VALUE)) {
+        assert(aOldUnit.areConvertibleTo(aNewUnit));
+        double nOldValue = pDoc->GetValue(rCellAddress);
+        double nNewValue = aOldUnit.convertValueTo(nOldValue, aNewUnit);
+
+        pDoc->SetValue(rCellAddress, nNewValue);
+        pDoc->SetNumberFormat(rCellAddress, 0); // 0 == no number format?
+
+        return true;
+    }
+
+    // In an ideal scenario the UI is written such that we never reach this point,
+    // however that is likely to be hard to achieve, hence we still allow
+    // for this case (see above for more information).
+    SAL_INFO("sc.units", "Unit conversion cancelled: units changed in meantime.");
+    return false;
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index eec34e2..5616379 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -67,6 +67,10 @@ public:
                                              OUString& rHeaderUnit,
                                              ScAddress& rHeaderCellAddress,
                                              OUString& rCellUnit) SAL_OVERRIDE;
+    virtual bool convertCellToHeaderUnit(const ScAddress& rCellAddress,
+                                         ScDocument* pDoc,
+                                         const OUString& rsNewUnit,
+                                         const OUString& rsOldUnit) SAL_OVERRIDE;
 
 private:
     UtUnit getOutputUnitsForOpCode(std::stack< UtUnit >& rUnitStack, const OpCode& rOpCode);
commit 72adfb711aca3a311a506304f04260d6d54ac37d
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sun Mar 15 19:59:16 2015 +0000

    Implement UtUnit::convertValueTo
    
    Change-Id: Id4469c7f03b3d73a4d2f7c4582602fed64b47df7

diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index 32d48d7..c657981 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -110,6 +110,14 @@ void UnitsTest::testUTUnit() {
 
     CPPUNIT_ASSERT(aM_S*aS == aM);
     CPPUNIT_ASSERT(aM/aS == aM_S);
+
+    // Do some simple conversion testing
+    UtUnit aCM;
+    UtUnit::createUnit("cm", aCM, mpUnitsImpl->mpUnitSystem);
+    CPPUNIT_ASSERT(aCM.areConvertibleTo(aM));
+    CPPUNIT_ASSERT(!aCM.areConvertibleTo(aS));
+    CPPUNIT_ASSERT(aCM.convertValueTo(0.0, aM) == 0.0); // 0 converts to 0
+    CPPUNIT_ASSERT(aCM.convertValueTo(100.0, aM) == 1.0);
 }
 
 void UnitsTest::testUnitVerification() {
diff --git a/sc/source/core/units/utunit.hxx b/sc/source/core/units/utunit.hxx
index 557a5fb..058891a 100644
--- a/sc/source/core/units/utunit.hxx
+++ b/sc/source/core/units/utunit.hxx
@@ -99,6 +99,21 @@ public:
     bool areConvertibleTo(const UtUnit& rUnit) {
         return ut_are_convertible(this->get(), rUnit.get());
     }
+
+    double convertValueTo(double nOriginalValue, const UtUnit& rUnit) {
+        // We could write our own cv_converter wrapper too, but that
+        // seems unnecessary given the limited selection of
+        // operations (convert float/double, either individually
+        // or as an array) -- of which we only need a subset anyway
+        // (ie. only for doubles).
+        cv_converter* pConverter = ut_get_converter(this->get(), rUnit.get());
+        assert(pConverter);
+
+        float nConvertedValue = cv_convert_double(pConverter, nOriginalValue);
+
+        cv_free(pConverter);
+        return nConvertedValue;
+    }
 };
 
 template< typename charT, typename traits >
commit ae4f758b813ec1b02bd1ba7d5edb7f4c02444727
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sun Mar 15 19:44:13 2015 +0000

    Implement isCellConversionRecommended
    
    Change-Id: I76f8c3c78db66af1087380484ac60da7ec78773d

diff --git a/sc/inc/units.hxx b/sc/inc/units.hxx
index 9561b40..ecd2946 100644
--- a/sc/inc/units.hxx
+++ b/sc/inc/units.hxx
@@ -39,6 +39,24 @@ public:
      */
     virtual bool splitUnitsFromInputString(const OUString& rInput, OUString& rValue, OUString& rUnit) = 0;
 
+    /**
+     * Check whether a cell should have it's data converted to another unit for
+     * sensible unit verification.
+     *
+     * Returns true if the cell has a local unit annotation (e.g. contains "10cm",
+     * but split into value (10) and unit format ('#"cm"')), but the column header
+     * defines a different unit (e.g. "length [m]).
+     * The data returned can then be used by convertCellToHeaderUnit if the user
+     * desires conversion in this case.
+     *
+     * If returning false, the header and cell units will be empty and the address invalid.
+     */
+    virtual bool isCellConversionRecommended(const ScAddress& rCellAddress,
+                                             ScDocument* pDoc,
+                                             OUString& rHeaderUnit,
+                                             ScAddress& rHeaderCellAddress,
+                                             OUString& rCellUnit) = 0;
+
     virtual ~Units() {}
 };
 
diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index 4ceb123..32d48d7 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -47,6 +47,8 @@ public:
 
     void testUnitFromHeaderExtraction();
 
+    void testCellConversionRequired();
+
     CPPUNIT_TEST_SUITE(UnitsTest);
 
     CPPUNIT_TEST(testUTUnit);
@@ -56,6 +58,8 @@ public:
     CPPUNIT_TEST(testUnitValueStringSplitting);
     CPPUNIT_TEST(testUnitFromHeaderExtraction);
 
+    CPPUNIT_TEST(testCellConversionRequired);
+
     CPPUNIT_TEST_SUITE_END();
 
 private:
@@ -308,6 +312,62 @@ void UnitsTest::testUnitFromHeaderExtraction() {
     // CPPUNIT_ASSERT(aUnit == aTestUnit);
 }
 
+void UnitsTest::testCellConversionRequired() {
+    mpDoc->EnsureTable(0);
+
+    // Set up a column with a normal header and a few data values
+    ScAddress address(20, 0, 0);
+    mpDoc->SetString(address, "length [m]");
+
+    address.IncRow();
+    mpDoc->SetValue(address, 1);
+    address.IncRow();
+    mpDoc->SetValue(address, 2);
+    address.IncRow();
+    mpDoc->SetValue(address, 3);
+
+    ScAddress aHeaderAddress;
+    OUString sHeaderUnit, sCellUnit;
+

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list