include/oox/vml/vmlshape.hxx             |    2 
 oox/source/vml/vmlshape.cxx              |    4 -
 oox/source/vml/vmlshapecontext.cxx       |  120 +++++++++++++++++++++++--------
 sc/source/filter/oox/drawingfragment.cxx |   51 +++++++++++++
 4 files changed, 148 insertions(+), 29 deletions(-)

New commits:
commit 2a3f53c4aeb915337e9bce5aba88f4a40305780b
Author:     Johann LORBER <[email protected]>
AuthorDate: Fri Aug 22 11:30:04 2025 +0200
Commit:     Justin Luth <[email protected]>
CommitDate: Tue Nov 25 18:25:40 2025 +0100

    tdf#166724 - Checkbox anchor wrongly imported
    
    Form Controls are not being "fully" imported as the MoveWithCell and 
SizeWithCell properties are not set internally in Calc.
    Contrary to their own documentation, Microsoft Excel seems to declare (in 
the VML file) those properties with no value when they are false, and does not 
declare them when they are true.
    Test xlsx documents generated with Excel 2007, 2010, 2013 and 365 have led 
me to this conclusion.
    
    Change-Id: I7ba64983ae8036df8d672d6c94b2ca54d740a064
    Signed-off-by: Johann LORBER <[email protected]>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/190166
    Tested-by: Jenkins
    Reviewed-by: Justin Luth <[email protected]>
    Code-Style: Justin Luth <[email protected]>

diff --git a/include/oox/vml/vmlshape.hxx b/include/oox/vml/vmlshape.hxx
index 6348b539f689..c960d9d8da46 100644
--- a/include/oox/vml/vmlshape.hxx
+++ b/include/oox/vml/vmlshape.hxx
@@ -197,6 +197,8 @@ struct ClientData
     bool                mbMultiLine;        ///< True = textbox allows line 
breaks.
     bool                mbVScroll;          ///< True = textbox has a vertical 
scrollbar.
     bool                mbSecretEdit;       ///< True = textbox is a password 
edit field.
+    bool                mbMoveWithCells;    ///< True = shape moves with the 
cell (anchored to cell).
+    bool                mbSizeWithCells;    ///< True = shape resizes with the 
cell.
 
     explicit            ClientData();
 };
diff --git a/oox/source/vml/vmlshape.cxx b/oox/source/vml/vmlshape.cxx
index 7908dd7527c4..bf4a06c32993 100644
--- a/oox/source/vml/vmlshape.cxx
+++ b/oox/source/vml/vmlshape.cxx
@@ -260,7 +260,9 @@ ClientData::ClientData() :
     mbNo3D2( false ),
     mbMultiLine( false ),
     mbVScroll( false ),
-    mbSecretEdit( false )
+    mbSecretEdit( false ),
+    mbMoveWithCells( true ),
+    mbSizeWithCells( true )
 {
 }
 
diff --git a/oox/source/vml/vmlshapecontext.cxx 
b/oox/source/vml/vmlshapecontext.cxx
index 85c70347e0e8..5839fa511116 100644
--- a/oox/source/vml/vmlshapecontext.cxx
+++ b/oox/source/vml/vmlshapecontext.cxx
@@ -196,34 +196,98 @@ void ClientDataContext::onEndElement()
 {
     switch( getCurrentElement() )
     {
-        case VMLX_TOKEN( Anchor ):      mrClientData.maAnchor = maElementText; 
                                         break;
-        case VMLX_TOKEN( FmlaMacro ):   mrClientData.maFmlaMacro = 
maElementText;                                       break;
-        case VMLX_TOKEN( FmlaPict ):    mrClientData.maFmlaPict = 
maElementText;                                        break;
-        case VMLX_TOKEN( FmlaLink ):    mrClientData.maFmlaLink = 
maElementText;                                        break;
-        case VMLX_TOKEN( FmlaRange ):   mrClientData.maFmlaRange = 
maElementText;                                       break;
-        case VMLX_TOKEN( FmlaGroup ):   mrClientData.maFmlaGroup = 
maElementText;                                       break;
-        case VMLX_TOKEN( TextHAlign ):  mrClientData.mnTextHAlign = 
AttributeConversion::decodeToken( maElementText );  break;
-        case VMLX_TOKEN( TextVAlign ):  mrClientData.mnTextVAlign = 
AttributeConversion::decodeToken( maElementText );  break;
-        case VMLX_TOKEN( Column ):      mrClientData.mnCol = 
maElementText.toInt32();                                   break;
-        case VMLX_TOKEN( Row ):         mrClientData.mnRow = 
maElementText.toInt32();                                   break;
-        case VMLX_TOKEN( Checked ):     mrClientData.mnChecked = 
maElementText.toInt32();                               break;
-        case VMLX_TOKEN( DropStyle ):   mrClientData.mnDropStyle = 
AttributeConversion::decodeToken( maElementText );   break;
-        case VMLX_TOKEN( DropLines ):   mrClientData.mnDropLines = 
maElementText.toInt32();                             break;
-        case VMLX_TOKEN( Val ):         mrClientData.mnVal = 
maElementText.toInt32();                                   break;
-        case VMLX_TOKEN( Min ):         mrClientData.mnMin = 
maElementText.toInt32();                                   break;
-        case VMLX_TOKEN( Max ):         mrClientData.mnMax = 
maElementText.toInt32();                                   break;
-        case VMLX_TOKEN( Inc ):         mrClientData.mnInc = 
maElementText.toInt32();                                   break;
-        case VMLX_TOKEN( Page ):        mrClientData.mnPage = 
maElementText.toInt32();                                  break;
-        case VMLX_TOKEN( SelType ):     mrClientData.mnSelType = 
AttributeConversion::decodeToken( maElementText );     break;
-        case VMLX_TOKEN( VTEdit ):      mrClientData.mnVTEdit = 
maElementText.toInt32();                                break;
-        case VMLX_TOKEN( PrintObject ): mrClientData.mbPrintObject = 
lclDecodeVmlxBool( maElementText, true );          break;
-        case VMLX_TOKEN( Visible ):     mrClientData.mbVisible = 
lclDecodeVmlxBool( maElementText, true );              break;
-        case VMLX_TOKEN( DDE ):         mrClientData.mbDde = 
lclDecodeVmlxBool( maElementText, true );                  break;
-        case VMLX_TOKEN( NoThreeD ):    mrClientData.mbNo3D = 
lclDecodeVmlxBool( maElementText, true );                 break;
-        case VMLX_TOKEN( NoThreeD2 ):   mrClientData.mbNo3D2 = 
lclDecodeVmlxBool( maElementText, true );                break;
-        case VMLX_TOKEN( MultiLine ):   mrClientData.mbMultiLine = 
lclDecodeVmlxBool( maElementText, true );            break;
-        case VMLX_TOKEN( VScroll ):     mrClientData.mbVScroll = 
lclDecodeVmlxBool( maElementText, true );              break;
-        case VMLX_TOKEN( SecretEdit ):  mrClientData.mbSecretEdit = 
lclDecodeVmlxBool( maElementText, true );           break;
+        case VMLX_TOKEN(Anchor):
+            mrClientData.maAnchor = maElementText;
+            break;
+        case VMLX_TOKEN(FmlaMacro):
+            mrClientData.maFmlaMacro = maElementText;
+            break;
+        case VMLX_TOKEN(FmlaPict):
+            mrClientData.maFmlaPict = maElementText;
+            break;
+        case VMLX_TOKEN(FmlaLink):
+            mrClientData.maFmlaLink = maElementText;
+            break;
+        case VMLX_TOKEN(FmlaRange):
+            mrClientData.maFmlaRange = maElementText;
+            break;
+        case VMLX_TOKEN(FmlaGroup):
+            mrClientData.maFmlaGroup = maElementText;
+            break;
+        case VMLX_TOKEN(TextHAlign):
+            mrClientData.mnTextHAlign = 
AttributeConversion::decodeToken(maElementText);
+            break;
+        case VMLX_TOKEN(TextVAlign):
+            mrClientData.mnTextVAlign = 
AttributeConversion::decodeToken(maElementText);
+            break;
+        case VMLX_TOKEN(Column):
+            mrClientData.mnCol = maElementText.toInt32();
+            break;
+        case VMLX_TOKEN(Row):
+            mrClientData.mnRow = maElementText.toInt32();
+            break;
+        case VMLX_TOKEN(Checked):
+            mrClientData.mnChecked = maElementText.toInt32();
+            break;
+        case VMLX_TOKEN(DropStyle):
+            mrClientData.mnDropStyle = 
AttributeConversion::decodeToken(maElementText);
+            break;
+        case VMLX_TOKEN(DropLines):
+            mrClientData.mnDropLines = maElementText.toInt32();
+            break;
+        case VMLX_TOKEN(Val):
+            mrClientData.mnVal = maElementText.toInt32();
+            break;
+        case VMLX_TOKEN(Min):
+            mrClientData.mnMin = maElementText.toInt32();
+            break;
+        case VMLX_TOKEN(Max):
+            mrClientData.mnMax = maElementText.toInt32();
+            break;
+        case VMLX_TOKEN(Inc):
+            mrClientData.mnInc = maElementText.toInt32();
+            break;
+        case VMLX_TOKEN(Page):
+            mrClientData.mnPage = maElementText.toInt32();
+            break;
+        case VMLX_TOKEN(SelType):
+            mrClientData.mnSelType = 
AttributeConversion::decodeToken(maElementText);
+            break;
+        case VMLX_TOKEN(VTEdit):
+            mrClientData.mnVTEdit = maElementText.toInt32();
+            break;
+        case VMLX_TOKEN(PrintObject):
+            mrClientData.mbPrintObject = lclDecodeVmlxBool(maElementText, 
true);
+            break;
+        case VMLX_TOKEN(Visible):
+            mrClientData.mbVisible = lclDecodeVmlxBool(maElementText, true);
+            break;
+        case VMLX_TOKEN(DDE):
+            mrClientData.mbDde = lclDecodeVmlxBool(maElementText, true);
+            break;
+        case VMLX_TOKEN(NoThreeD):
+            mrClientData.mbNo3D = lclDecodeVmlxBool(maElementText, true);
+            break;
+        case VMLX_TOKEN(NoThreeD2):
+            mrClientData.mbNo3D2 = lclDecodeVmlxBool(maElementText, true);
+            break;
+        case VMLX_TOKEN(MultiLine):
+            mrClientData.mbMultiLine = lclDecodeVmlxBool(maElementText, true);
+            break;
+        case VMLX_TOKEN(VScroll):
+            mrClientData.mbVScroll = lclDecodeVmlxBool(maElementText, true);
+            break;
+        case VMLX_TOKEN(SecretEdit):
+            mrClientData.mbSecretEdit = lclDecodeVmlxBool(maElementText, true);
+            break;
+        // Oddly, Excel will declare MoveWithCells and SizeWithCells with no 
value if these are false (therefore, bDefaultForEmpty == false)
+        // When MoveWithCells and SizeWithCells are NOT declared they should 
be assumed true.
+        case VMLX_TOKEN(MoveWithCells):
+            mrClientData.mbMoveWithCells = lclDecodeVmlxBool(maElementText, 
false);
+            break;
+        case VMLX_TOKEN(SizeWithCells):
+            mrClientData.mbSizeWithCells = lclDecodeVmlxBool(maElementText, 
false);
+            break;
     }
 }
 
diff --git a/sc/source/filter/oox/drawingfragment.cxx 
b/sc/source/filter/oox/drawingfragment.cxx
index 6b58204ddc1d..eb9c20451560 100644
--- a/sc/source/filter/oox/drawingfragment.cxx
+++ b/sc/source/filter/oox/drawingfragment.cxx
@@ -23,6 +23,7 @@
 #include <comphelper/propertyvalue.hxx>
 
 #include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/XIndexAccess.hpp>
 #include <com/sun/star/container/XIndexContainer.hpp>
 #include <com/sun/star/container/XNameReplace.hpp>
 #include <com/sun/star/document/XEventsSupplier.hpp>
@@ -31,6 +32,8 @@
 #include <com/sun/star/drawing/XShapes.hpp>
 #include <com/sun/star/script/ScriptEventDescriptor.hpp>
 #include <com/sun/star/script/XEventAttacherManager.hpp>
+#include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
+#include <com/sun/star/sheet/XSpreadsheet.hpp>
 #include <rtl/strbuf.hxx>
 #include <svx/svdobj.hxx>
 #include <drwlayer.hxx>
@@ -752,6 +755,54 @@ void VmlDrawing::notifyXShapeInserted( const Reference< 
XShape >& rxShape,
     catch( Exception& )
     {
     }
+
+    // Anchor to cell holding top left corner of the shape if true
+    if (pClientData->mbMoveWithCells)
+    {
+        Reference< css::sheet::XSpreadsheet > xSheet;
+        Reference< css::sheet::XSpreadsheetDocument > xSSDoc( 
this->getFilter().getModel(), UNO_QUERY );
+        if ( xSSDoc.is() )
+        {
+            Reference< XIndexAccess > xSheets( xSSDoc->getSheets(), UNO_QUERY 
);
+            if ( xSheets.is() )
+            {
+                Any aSheetAny = xSheets->getByIndex( getSheetIndex() );
+                aSheetAny >>= xSheet;
+            }
+        }
+        if ( xSheet.is() )
+        {
+            sal_Int32 nCol = -1;
+            sal_Int32 nRow = -1;
+
+            if ( pClientData->mnCol >= 0 && pClientData->mnRow >= 0 )
+            {
+                nCol = pClientData->mnCol;
+                nRow = pClientData->mnRow;
+            }
+            else if ( !pClientData->maAnchor.isEmpty() )
+            {
+                // maAnchor is "LeftColumn, LeftOffset, TopRow, TopOffset, 
RightColumn, RightOffset, BottomRow, BottomOffset". Extract tokens 0 and 2.
+                sal_Int32 nIndex = 0;
+                nCol = o3tl::toInt32(o3tl::getToken(pClientData->maAnchor, 0, 
',', nIndex));
+                // skip LeftOffset
+                o3tl::getToken(pClientData->maAnchor, 0, ',', nIndex);
+                nRow = o3tl::toInt32(o3tl::getToken(pClientData->maAnchor, 0, 
',', nIndex));
+            }
+
+            if ( nCol >= 0 && nRow >= 0 )
+            {
+                Reference< css::table::XCell > xCell = 
xSheet->getCellByPosition( nCol, nRow );
+                if ( xCell.is() )
+                {
+                    Reference< XPropertySet > aPropertySet( rxShape, 
UNO_QUERY_THROW );
+                    aPropertySet->setPropertyValue( "Anchor", Any( xCell ) );
+                    if (pClientData->mbSizeWithCells)
+                        aPropertySet->setPropertyValue( "ResizeWithCell", Any( 
true ) );
+                }
+            }
+        }
+    }
 }
 
 // private --------------------------------------------------------------------

Reply via email to