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

Andrzej Hunt andrzej at ahunt.org
Mon Mar 16 00:47:58 PDT 2015


 sc/inc/sc.hrc                      |    4 -
 sc/inc/units.hxx                   |   37 +++++++++++
 sc/qa/unit/units.cxx               |  101 ++++++++++++++++++++++++++++--
 sc/source/core/data/column3.cxx    |    1 
 sc/source/core/units/unitsimpl.cxx |  118 ++++++++++++++++++++++++++++++-----
 sc/source/core/units/unitsimpl.hxx |   22 ++++++
 sc/source/core/units/utunit.hxx    |   23 ++++++
 sc/source/ui/inc/viewfunc.hxx      |    9 ++
 sc/source/ui/src/units.src         |    8 +-
 sc/source/ui/view/viewfunc.cxx     |  122 ++++++++++++++++++++++++++++++++++++-
 10 files changed, 411 insertions(+), 34 deletions(-)

New commits:
commit 3d422829e56c46c81a389e5be63be9336941257c
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 42e9db8..4a2e38a 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 343b138220fc95cdc748898dfe2fdff5c79db3f9
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 b484ae6..a7de1dd 100644
--- a/sc/inc/sc.hrc
+++ b/sc/inc/sc.hrc
@@ -994,8 +994,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 ec7598a..50353f3 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 {
@@ -371,6 +373,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 3073fe6..d0a2108 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 1cdfc67ab9a6df577a83064d0c8c29f232412eb2
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 05f8128864bf110506715ff7c195f386e2ed16f5
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 48088e0d37dd85cf57ba1e6cdb299e05fc00d135
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;
+
+    // Test that we don't expect conversion for an non-united value cell
+    CPPUNIT_ASSERT(!mpUnitsImpl->isCellConversionRecommended(address, mpDoc, sHeaderUnit, aHeaderAddress, sCellUnit));
+    CPPUNIT_ASSERT(sHeaderUnit.isEmpty());
+    CPPUNIT_ASSERT(sCellUnit.isEmpty());
+    CPPUNIT_ASSERT(!aHeaderAddress.IsValid());
+
+    // And now set up cells with local units
+    SvNumberFormatter* pFormatter = mpDoc->GetFormatTable();
+    sal_uInt32 nKeyCM, nKeyKG;
+
+    sal_Int32 nCheckPos; // Passed by reference - unused
+    short nType = css::util::NumberFormat::DEFINED;
+
+    OUString sCM = "#\"cm\"";
+    pFormatter->PutEntry(sCM, nCheckPos, nType, nKeyCM);
+    OUString sKG = "#\"kg\"";
+    pFormatter->PutEntry(sKG, nCheckPos, nType, nKeyKG);
+
+    // First united cell: "cm" (convertible to "m")
+    address.IncRow();
+    mpDoc->SetNumberFormat(address, nKeyCM);
+    mpDoc->SetValue(address, 10);
+
+    CPPUNIT_ASSERT(mpUnitsImpl->isCellConversionRecommended(address, mpDoc, sHeaderUnit, aHeaderAddress, sCellUnit));
+    CPPUNIT_ASSERT(sHeaderUnit == "m");
+    CPPUNIT_ASSERT(sCellUnit == "cm");
+    CPPUNIT_ASSERT(aHeaderAddress == ScAddress(20, 0, 0));
+
+    // Second united cell: "kg" (not convertible to "m")
+    address.IncRow();
+    mpDoc->SetNumberFormat(address, nKeyKG);
+    mpDoc->SetValue(address, 50);
+
+    CPPUNIT_ASSERT(!mpUnitsImpl->isCellConversionRecommended(address, mpDoc, sHeaderUnit, aHeaderAddress, sCellUnit));
+    CPPUNIT_ASSERT(sHeaderUnit.isEmpty());
+    CPPUNIT_ASSERT(sCellUnit.isEmpty());
+    CPPUNIT_ASSERT(!aHeaderAddress.IsValid());
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(UnitsTest);
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 40228e4..50ba25a 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -454,4 +454,29 @@ bool UnitsImpl::splitUnitsFromInputString(const OUString& rInput, OUString& rVal
     }
 }
 
+bool UnitsImpl::isCellConversionRecommended(const ScAddress& rCellAddress,
+                                 ScDocument* pDoc,
+                                 OUString& rsHeaderUnit,
+                                 ScAddress& rHeaderCellAddress,
+                                 OUString& rsCellUnit) {
+    assert(rCellAddress.IsValid());
+
+    UtUnit aCellUnit;
+    rsCellUnit = extractUnitStringForCell(rCellAddress, pDoc);
+
+    if (!rsCellUnit.isEmpty() && UtUnit::createUnit(rsCellUnit, aCellUnit, mpUnitSystem)) {
+        UtUnit aHeaderUnit = findHeaderUnitForCell(rCellAddress, pDoc, rsHeaderUnit, rHeaderCellAddress);
+        if (rHeaderCellAddress.IsValid()) {
+            if (aHeaderUnit.areConvertibleTo(aCellUnit)) {
+                return true;
+            }
+        }
+    }
+
+    rsHeaderUnit.clear();
+    rHeaderCellAddress.SetInvalid();
+    rsCellUnit.clear();
+    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 b9da24e..eec34e2 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -62,6 +62,11 @@ public:
 
     virtual bool verifyFormula(ScTokenArray* pArray, const ScAddress& rFormulaAddress, ScDocument* pDoc) SAL_OVERRIDE;
     virtual bool splitUnitsFromInputString(const OUString& rInput, OUString& rValue, OUString& rUnit) SAL_OVERRIDE;
+    virtual bool isCellConversionRecommended(const ScAddress& rCellAddress,
+                                             ScDocument* pDoc,
+                                             OUString& rHeaderUnit,
+                                             ScAddress& rHeaderCellAddress,
+                                             OUString& rCellUnit) SAL_OVERRIDE;
 
 private:
     UtUnit getOutputUnitsForOpCode(std::stack< UtUnit >& rUnitStack, const OpCode& rOpCode);
commit b6e8eb0449ac43c742cc7641c8561da412f8592a
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sun Mar 15 19:31:27 2015 +0000

    Implement UtUnit::areConvertibleTo
    
    Change-Id: I7e8599a363e297c0c0a36c1f9cc68a5e234787b6

diff --git a/sc/source/core/units/utunit.hxx b/sc/source/core/units/utunit.hxx
index cbdd348..557a5fb 100644
--- a/sc/source/core/units/utunit.hxx
+++ b/sc/source/core/units/utunit.hxx
@@ -95,6 +95,10 @@ public:
         // i.e. we are working with this / rUnit.
         return UtUnit(ut_divide(this->get(), rUnit.get()));
     }
+
+    bool areConvertibleTo(const UtUnit& rUnit) {
+        return ut_are_convertible(this->get(), rUnit.get());
+    }
 };
 
 template< typename charT, typename traits >
commit b7eee6a6441bca9f8e89cf423a6c01d0fd8a217f
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sun Mar 15 14:16:41 2015 +0000

    Refactor header finding into it's own method.
    
    We need this for e.g. verifying when a conversion of local units
    may be required.
    
    Change-Id: Ie1239b4142065546ea7543dad05240fc9a3e34f7

diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 2d3b8db..40228e4 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -312,11 +312,28 @@ UtUnit UnitsImpl::getUnitForRef(FormulaToken* pToken, const ScAddress& rFormulaA
         return aUnit;
     }
 
+    OUString aHeaderUnitString; // Unused -- passed by reference below
+    ScAddress aHeaderAddress; // Unused too
+    UtUnit aHeaderUnit = findHeaderUnitForCell(aInputAddress, pDoc, aHeaderUnitString, aHeaderAddress);
+    if (aHeaderUnit.isValid())
+        return aHeaderUnit;
+
+    SAL_INFO("sc.units", "no unit obtained for token at cell " << aInputAddress.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::findHeaderUnitForCell(const ScAddress& rCellAddress,
+                                        ScDocument* pDoc,
+                                        OUString& rsHeaderUnitString,
+                                        ScAddress& rHeaderAddress) {
     // Scan UPwards from the current cell to find a header. This is since we could potentially
     // have two different sets of data sharing a column, hence finding the closest header is necessary.
-    ScAddress aAddress = aInputAddress;
-    while (aAddress.Row() > 0) {
-        aAddress.IncRow(-1);
+    rHeaderAddress = rCellAddress;
+    while (rHeaderAddress.Row() > 0) {
+        rHeaderAddress.IncRow(-1);
 
         // We specifically test for string cells as intervening data cells could have
         // differently defined units of their own. (However as these intervening cells
@@ -336,12 +353,9 @@ UtUnit UnitsImpl::getUnitForRef(FormulaToken* pToken, const ScAddress& rFormulaA
             return aUnit;
         }
     }
-
-    SAL_INFO("sc.units", "no unit obtained for token at cell " << aInputAddress.GetColRowString());
-
-    // We return the dimensionless unit 1 if we don't find any other data suggesting a unit.
-    UtUnit::createUnit("", aUnit, mpUnitSystem);
-    return aUnit;
+    rHeaderAddress.SetInvalid();
+    rsHeaderUnitString.clear();
+    return UtUnit();
 }
 
 // getUnitForRef: check format -> if not in format, use more complicated method? (Format overrides header definition)
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index 33c2fb9..b9da24e 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -71,6 +71,17 @@ private:
     UtUnit getUnitForRef(formula::FormulaToken* pToken,
                          const ScAddress& rFormulaAddress,
                          ScDocument* pDoc);
+
+    /**
+     * Return both the UtUnit and the String as we usually want the UtUnit
+     * (which is created from the String, and has to be created to ensure
+     * that there is a valid unit), but we might also need the original
+     * String (which can't necessarily be regenerated from the UtUnit).
+     */
+    UtUnit findHeaderUnitForCell(const ScAddress& rCellAddress,
+                                 ScDocument* pDoc,
+                                 OUString& rsHeaderUnitString,
+                                 ScAddress& rHeaderAddress);
 };
 
 }} // namespace sc::units
commit 1ca0113593e9d5b1d97f44cedcef4ffff9af1aeb
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sun Mar 15 14:14:39 2015 +0000

    Implement operator!= for UtUnit.
    
    Change-Id: Iafcc26411f518fc2b8741ec1cb9d5e9eda28a184

diff --git a/sc/source/core/units/utunit.hxx b/sc/source/core/units/utunit.hxx
index 97b6305..cbdd348 100644
--- a/sc/source/core/units/utunit.hxx
+++ b/sc/source/core/units/utunit.hxx
@@ -82,6 +82,10 @@ public:
         return ut_compare(this->get(), rUnit.get()) == 0;
     }
 
+    bool operator!=(const UtUnit& rUnit) {
+        return !operator==(rUnit);
+    }
+
     UtUnit operator*(const UtUnit& rUnit) {
         return UtUnit(ut_multiply(this->get(), rUnit.get()));
     }
commit 225b86c0015adf55147e1a78911da2dbe8ad8826
Author: Andrzej Hunt <andrzej at ahunt.org>
Date:   Sun Mar 15 14:14:25 2015 +0000

    Return original unit string for extractUnitFromHeaderString too.
    
    Change-Id: I060322d3a05be4c2c2a5ff8e482671dd9765d785

diff --git a/sc/qa/unit/units.cxx b/sc/qa/unit/units.cxx
index 97acef6..4ceb123 100644
--- a/sc/qa/unit/units.cxx
+++ b/sc/qa/unit/units.cxx
@@ -259,43 +259,51 @@ void UnitsTest::testUnitValueStringSplitting() {
 
 void UnitsTest::testUnitFromHeaderExtraction() {
     UtUnit aUnit;
+    OUString sUnitString;
 
     OUString sEmpty = "";
-    CPPUNIT_ASSERT(!mpUnitsImpl->extractUnitFromHeaderString(sEmpty, aUnit));
+    CPPUNIT_ASSERT(!mpUnitsImpl->extractUnitFromHeaderString(sEmpty, aUnit, sUnitString));
     CPPUNIT_ASSERT(aUnit == UtUnit());
+    CPPUNIT_ASSERT(sUnitString.isEmpty());
 
     OUString sSimple = "bla bla [cm/s]";
-    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sSimple, aUnit));
+    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sSimple, aUnit, sUnitString));
     // We need to test in Units (rather than testing Unit::getString()) as
     // any given unit can have multiple string representations (and utunits defaults to
     // representing e.g. cm as (I think) "0.01m").
     UtUnit aTestUnit;
     CPPUNIT_ASSERT(UtUnit::createUnit("cm/s", aTestUnit, mpUnitsImpl->mpUnitSystem));
     CPPUNIT_ASSERT(aUnit == aTestUnit);
+    CPPUNIT_ASSERT(sUnitString == "cm/s");
 
     OUString sFreeStanding = "bla bla kg/h";
-    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sFreeStanding, aUnit));
+    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sFreeStanding, aUnit, sUnitString));
     CPPUNIT_ASSERT(UtUnit::createUnit("kg/h", aTestUnit, mpUnitsImpl->mpUnitSystem));
     CPPUNIT_ASSERT(aUnit == aTestUnit);
+    CPPUNIT_ASSERT(sUnitString == "kg/h");
 
     OUString sFreeStandingWithSpaces = "bla bla m / s";
-    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sFreeStandingWithSpaces, aUnit));
+    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sFreeStandingWithSpaces, aUnit, sUnitString));
     CPPUNIT_ASSERT(UtUnit::createUnit("m/s", aTestUnit, mpUnitsImpl->mpUnitSystem));
     CPPUNIT_ASSERT(aUnit == aTestUnit);
+    CPPUNIT_ASSERT(sUnitString == "m/s");
 
     OUString sOperatorSeparated = "bla bla / t/s";
-    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sOperatorSeparated, aUnit));
+    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sOperatorSeparated, aUnit, sUnitString));
     CPPUNIT_ASSERT(UtUnit::createUnit("t/s", aTestUnit, mpUnitsImpl->mpUnitSystem));
     CPPUNIT_ASSERT(aUnit == aTestUnit);
+    CPPUNIT_ASSERT(sUnitString == "t/s");
+
 
     OUString sRoundBrackets = "bla bla (t/h)";
-    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sRoundBrackets, aUnit));
+    CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sRoundBrackets, aUnit, sUnitString));
     CPPUNIT_ASSERT(UtUnit::createUnit("t/h", aTestUnit, mpUnitsImpl->mpUnitSystem));
     CPPUNIT_ASSERT(aUnit == aTestUnit);
+    CPPUNIT_ASSERT(sUnitString == "(t/h)");
 
     // This becomes more of a nightmare to support, so let's not bother for now.
     // OUString sFreeStandingMixedSpaces = "bla bla m /s* kg";
-    // CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sFreeStanding, aUnit));
+    // CPPUNIT_ASSERT(mpUnitsImpl->extractUnitFromHeaderString(sFreeStanding, aUnit, sUnitString));
     // CPPUNIT_ASSERT(UtUnit::createUnit("m/s", aTestUnit, mpUnitsImpl->mpUnitSystem));
     // CPPUNIT_ASSERT(aUnit == aTestUnit);
 }
diff --git a/sc/source/core/units/unitsimpl.cxx b/sc/source/core/units/unitsimpl.cxx
index 1c5f1bf..2d3b8db 100644
--- a/sc/source/core/units/unitsimpl.cxx
+++ b/sc/source/core/units/unitsimpl.cxx
@@ -208,7 +208,7 @@ OUString UnitsImpl::extractUnitStringForCell(const ScAddress& rAddress, ScDocume
     return extractUnitStringFromFormat(rFormatString);
 }
 
-bool UnitsImpl::extractUnitFromHeaderString(const OUString& rString, UtUnit& aUnit) {
+bool UnitsImpl::extractUnitFromHeaderString(const OUString& rString, UtUnit& aUnit, OUString& sUnitString) {
     com::sun::star::uno::Reference<com::sun::star::uno::XComponentContext> xContext = comphelper::getProcessComponentContext();
 
     uno::Reference<lang::XMultiServiceFactory> xFactory(xContext->getServiceManager(), uno::UNO_QUERY_THROW);
@@ -243,7 +243,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).
-            OUString sUnitString = rString.copy(aResult.endOffset[1], aResult.startOffset[1] - aResult.endOffset[1]);
+            sUnitString = rString.copy(aResult.endOffset[1], aResult.startOffset[1] - aResult.endOffset[1]);
 
             if (UtUnit::createUnit(sUnitString, aUnit, mpUnitSystem)) {
                 return true;
@@ -260,7 +260,7 @@ bool UnitsImpl::extractUnitFromHeaderString(const OUString& rString, UtUnit& aUn
     // 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"
     const OUString sOperators = "/*"; // valid
-    OUString sCurrent = "";
+    sUnitString.clear();
     for (sal_Int32 nToken = 0; nToken < nTokenCount; nToken++) {
         OUString sToken = rString.getToken(nToken, ' ');
         UtUnit aTestUnit;
@@ -268,11 +268,11 @@ bool UnitsImpl::extractUnitFromHeaderString(const OUString& rString, UtUnit& aUn
             // Only test for a separator character if we have already got something in our string, as
             // some of the operators could be used as separators from description to unit
             // (e.g. "a description / kg").
-            ((sCurrent.getLength() > 0) && (sToken.getLength() == 1) && (sOperators.indexOf(sToken[0]) != -1))) {
+            ((sUnitString.getLength() > 0) && (sToken.getLength() == 1) && (sOperators.indexOf(sToken[0]) != -1))) {
                 // we're repeatedly testing the string hence using an OUStringBuffer isn't of much use since there's
                 // no simple/efficient way of repeatedly getting a testable OUString from the buffer.
-                sCurrent += sToken;
-        } else if (sCurrent.getLength() > 0) {
+                sUnitString += sToken;
+        } else if (sUnitString.getLength() > 0) {
             // If we have units, followed by text, followed by units, we should still flag an error since
             // that's ambiguous (unless the desired units are enclose in [] in which case we've
             // already extracted these desired units in step 1 above.
@@ -282,12 +282,13 @@ bool UnitsImpl::extractUnitFromHeaderString(const OUString& rString, UtUnit& aUn
 
     // We test the length to make sure we don't return the dimensionless unit 1 if we haven't found any units
     // in the header.
-    if (sCurrent.getLength() && UtUnit::createUnit(sCurrent, aUnit, mpUnitSystem)) {
+    if (sUnitString.getLength() && UtUnit::createUnit(sUnitString, aUnit, mpUnitSystem)) {
         return true;
     }
 
     // 3. Give up
     aUnit = UtUnit(); // assign invalid
+    sUnitString.clear();
     return false;
 }
 
@@ -321,8 +322,9 @@ UtUnit UnitsImpl::getUnitForRef(FormulaToken* pToken, const ScAddress& rFormulaA
         // differently defined units of their own. (However as these intervening cells
         // will have the unit stored in the number format it would be ignored when
         // checking the cell's string anyway.)
-        if (pDoc->GetCellType(aAddress) == CELLTYPE_STRING &&
-            extractUnitFromHeaderString(pDoc->GetString(aAddress), aUnit)) {
+        UtUnit aUnit;
+        if (pDoc->GetCellType(rHeaderAddress) == CELLTYPE_STRING &&
+            extractUnitFromHeaderString(pDoc->GetString(rHeaderAddress), aUnit, rsHeaderUnitString)) {
             // TODO: one potential problem is that we could have a text only "united" data cell
             // (where the unit wasn't automatically extracted due to being entered via
             // a different spreadsheet program).
diff --git a/sc/source/core/units/unitsimpl.hxx b/sc/source/core/units/unitsimpl.hxx
index 909916a..33c2fb9 100644
--- a/sc/source/core/units/unitsimpl.hxx
+++ b/sc/source/core/units/unitsimpl.hxx
@@ -67,7 +67,7 @@ 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);
+    bool extractUnitFromHeaderString(const OUString& rString, UtUnit& aUnit, OUString& sUnitString);
     UtUnit getUnitForRef(formula::FormulaToken* pToken,
                          const ScAddress& rFormulaAddress,
                          ScDocument* pDoc);


More information about the Libreoffice-commits mailing list