1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <oox/ole/vbacontrol.hxx>
24 #include <com/sun/star/awt/XControlModel.hpp>
25 #include <com/sun/star/beans/XPropertySet.hpp>
26 #include <com/sun/star/container/XNameContainer.hpp>
27 #include <com/sun/star/io/XInputStreamProvider.hpp>
28 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
29 #include <com/sun/star/uno/XComponentContext.hpp>
30 #include <osl/diagnose.h>
31 #include <rtl/ustrbuf.hxx>
32 #include <sal/log.hxx>
33 #include <xmlscript/xmldlg_imexp.hxx>
34 #include <oox/helper/attributelist.hxx>
35 #include <oox/helper/binaryinputstream.hxx>
36 #include <oox/helper/containerhelper.hxx>
37 #include <oox/helper/propertymap.hxx>
38 #include <oox/helper/propertyset.hxx>
39 #include <oox/helper/storagebase.hxx>
40 #include <oox/helper/textinputstream.hxx>
41 #include <oox/ole/vbahelper.hxx>
42 #include <oox/token/properties.hxx>
43 #include <oox/token/tokens.hxx>
44 #include <unordered_map>
49 using namespace ::com::sun::star::awt
;
50 using namespace ::com::sun::star::container
;
51 using namespace ::com::sun::star::frame
;
52 using namespace ::com::sun::star::io
;
53 using namespace ::com::sun::star::lang
;
54 using namespace ::com::sun::star::uno
;
58 const sal_uInt16 VBA_SITE_CLASSIDINDEX
= 0x8000;
59 const sal_uInt16 VBA_SITE_INDEXMASK
= 0x7FFF;
60 const sal_uInt16 VBA_SITE_FORM
= 7;
61 const sal_uInt16 VBA_SITE_IMAGE
= 12;
62 const sal_uInt16 VBA_SITE_FRAME
= 14;
63 const sal_uInt16 VBA_SITE_SPINBUTTON
= 16;
64 const sal_uInt16 VBA_SITE_COMMANDBUTTON
= 17;
65 const sal_uInt16 VBA_SITE_TABSTRIP
= 18;
66 const sal_uInt16 VBA_SITE_LABEL
= 21;
67 const sal_uInt16 VBA_SITE_TEXTBOX
= 23;
68 const sal_uInt16 VBA_SITE_LISTBOX
= 24;
69 const sal_uInt16 VBA_SITE_COMBOBOX
= 25;
70 const sal_uInt16 VBA_SITE_CHECKBOX
= 26;
71 const sal_uInt16 VBA_SITE_OPTIONBUTTON
= 27;
72 const sal_uInt16 VBA_SITE_TOGGLEBUTTON
= 28;
73 const sal_uInt16 VBA_SITE_SCROLLBAR
= 47;
74 const sal_uInt16 VBA_SITE_MULTIPAGE
= 57;
75 const sal_uInt16 VBA_SITE_UNKNOWN
= 0x7FFF;
77 const sal_uInt32 VBA_SITE_TABSTOP
= 0x00000001;
78 const sal_uInt32 VBA_SITE_VISIBLE
= 0x00000002;
79 const sal_uInt32 VBA_SITE_OSTREAM
= 0x00000010;
80 const sal_uInt32 VBA_SITE_DEFFLAGS
= 0x00000033;
82 const sal_uInt8 VBA_SITEINFO_COUNT
= 0x80;
83 const sal_uInt8 VBA_SITEINFO_MASK
= 0x7F;
85 /** Collects names of all controls in a user form or container control. Allows
86 to generate unused names for dummy controls separating option groups.
88 class VbaControlNamesSet
91 explicit VbaControlNamesSet();
93 /** Inserts the name of the passed control. */
94 void insertName( const VbaFormControl
& rControl
);
95 /** Returns a name that is not contained in this set. */
96 OUString
generateDummyName();
99 ::std::set
< OUString
>
104 static const OUStringLiteral
gaDummyBaseName( "DummyGroupSep" );
106 VbaControlNamesSet::VbaControlNamesSet() :
111 void VbaControlNamesSet::insertName( const VbaFormControl
& rControl
)
113 OUString aName
= rControl
.getControlName();
114 if( !aName
.isEmpty() )
115 maCtrlNames
.insert( aName
);
118 OUString
VbaControlNamesSet::generateDummyName()
123 aCtrlName
= gaDummyBaseName
+ OUString::number( ++mnIndex
);
125 while( maCtrlNames
.count( aCtrlName
) > 0 );
126 maCtrlNames
.insert( aCtrlName
);
130 /** Functor that inserts the name of a control into a VbaControlNamesSet. */
131 struct VbaControlNameInserter
134 VbaControlNamesSet
& mrCtrlNames
;
135 explicit VbaControlNameInserter( VbaControlNamesSet
& rCtrlNames
) : mrCtrlNames( rCtrlNames
) {}
136 void operator()( const VbaFormControl
& rControl
) { mrCtrlNames
.insertName( rControl
); }
139 /** A dummy invisible form control (fixed label without text) that is used to
140 separate two groups of option buttons.
142 class VbaDummyFormControl
: public VbaFormControl
145 explicit VbaDummyFormControl( const OUString
& rName
);
148 VbaDummyFormControl::VbaDummyFormControl( const OUString
& rName
)
150 mxSiteModel
.reset( new VbaSiteModel
);
151 mxSiteModel
->importProperty( XML_Name
, rName
);
152 mxSiteModel
->importProperty( XML_VariousPropertyBits
, OUString( '0' ) );
154 mxCtrlModel
.reset( new AxLabelModel
);
155 mxCtrlModel
->setAwtModelMode();
156 mxCtrlModel
->importProperty( XML_Size
, "10;10" );
161 VbaSiteModel::VbaSiteModel() :
164 mnHelpContextId( 0 ),
165 mnFlags( VBA_SITE_DEFFLAGS
),
168 mnClassIdOrCache( VBA_SITE_UNKNOWN
),
173 VbaSiteModel::~VbaSiteModel()
177 void VbaSiteModel::importProperty( sal_Int32 nPropId
, const OUString
& rValue
)
181 case XML_Name
: maName
= rValue
; break;
182 case XML_Tag
: maTag
= rValue
; break;
183 case XML_VariousPropertyBits
: mnFlags
= AttributeConversion::decodeUnsigned( rValue
); break;
187 bool VbaSiteModel::importBinaryModel( BinaryInputStream
& rInStrm
)
189 AxBinaryPropertyReader
aReader( rInStrm
);
190 aReader
.readStringProperty( maName
);
191 aReader
.readStringProperty( maTag
);
192 aReader
.readIntProperty
< sal_Int32
>( mnId
);
193 aReader
.readIntProperty
< sal_Int32
>( mnHelpContextId
);
194 aReader
.readIntProperty
< sal_uInt32
>( mnFlags
);
195 aReader
.readIntProperty
< sal_uInt32
>( mnStreamLen
);
196 aReader
.readIntProperty
< sal_Int16
>( mnTabIndex
);
197 aReader
.readIntProperty
< sal_uInt16
>( mnClassIdOrCache
);
198 aReader
.readPairProperty( maPos
);
199 aReader
.readIntProperty
< sal_uInt16
>( mnGroupId
);
200 aReader
.skipUndefinedProperty();
201 aReader
.readStringProperty( maToolTip
);
202 aReader
.skipStringProperty(); // license key
203 aReader
.readStringProperty( maControlSource
);
204 aReader
.readStringProperty( maRowSource
);
205 return aReader
.finalizeImport();
208 void VbaSiteModel::moveRelative( const AxPairData
& rDistance
)
210 maPos
.first
+= rDistance
.first
;
211 maPos
.second
+= rDistance
.second
;
214 bool VbaSiteModel::isContainer() const
216 return !getFlag( mnFlags
, VBA_SITE_OSTREAM
);
219 sal_uInt32
VbaSiteModel::getStreamLength() const
221 return isContainer() ? 0 : mnStreamLen
;
224 OUString
VbaSiteModel::getSubStorageName() const
228 OUStringBuffer aBuffer
;
229 aBuffer
.append( 'i' );
231 aBuffer
.append( '0' );
232 aBuffer
.append( mnId
);
233 return aBuffer
.makeStringAndClear();
238 ControlModelRef
VbaSiteModel::createControlModel( const AxClassTable
& rClassTable
) const
240 ControlModelRef xCtrlModel
;
242 sal_Int32 nTypeIndex
= static_cast< sal_Int32
>( mnClassIdOrCache
& VBA_SITE_INDEXMASK
);
243 if( !getFlag( mnClassIdOrCache
, VBA_SITE_CLASSIDINDEX
) )
247 case VBA_SITE_COMMANDBUTTON
: xCtrlModel
.reset( new AxCommandButtonModel
); break;
248 case VBA_SITE_LABEL
: xCtrlModel
.reset( new AxLabelModel
); break;
249 case VBA_SITE_IMAGE
: xCtrlModel
.reset( new AxImageModel
); break;
250 case VBA_SITE_TOGGLEBUTTON
: xCtrlModel
.reset( new AxToggleButtonModel
); break;
251 case VBA_SITE_CHECKBOX
: xCtrlModel
.reset( new AxCheckBoxModel
); break;
252 case VBA_SITE_OPTIONBUTTON
: xCtrlModel
.reset( new AxOptionButtonModel
); break;
253 case VBA_SITE_TEXTBOX
: xCtrlModel
.reset( new AxTextBoxModel
); break;
254 case VBA_SITE_LISTBOX
: xCtrlModel
.reset( new AxListBoxModel
); break;
255 case VBA_SITE_COMBOBOX
: xCtrlModel
.reset( new AxComboBoxModel
); break;
256 case VBA_SITE_SPINBUTTON
: xCtrlModel
.reset( new AxSpinButtonModel
); break;
257 case VBA_SITE_SCROLLBAR
: xCtrlModel
.reset( new AxScrollBarModel
); break;
258 case VBA_SITE_TABSTRIP
: xCtrlModel
.reset( new AxTabStripModel
);
260 case VBA_SITE_FRAME
: xCtrlModel
.reset( new AxFrameModel
); break;
261 case VBA_SITE_MULTIPAGE
: xCtrlModel
.reset( new AxMultiPageModel
);
263 case VBA_SITE_FORM
: xCtrlModel
.reset( new AxPageModel
);
265 default: OSL_FAIL( "VbaSiteModel::createControlModel - unknown type index" );
270 const OUString
* pGuid
= ContainerHelper::getVectorElement( rClassTable
, nTypeIndex
);
271 OSL_ENSURE( pGuid
, "VbaSiteModel::createControlModel - invalid class table index" );
274 if( *pGuid
== COMCTL_GUID_SCROLLBAR_60
)
275 xCtrlModel
.reset( new ComCtlScrollBarModel( 6 ) );
276 else if( *pGuid
== COMCTL_GUID_PROGRESSBAR_50
)
277 xCtrlModel
.reset( new ComCtlProgressBarModel( 5 ) );
278 else if( *pGuid
== COMCTL_GUID_PROGRESSBAR_60
)
279 xCtrlModel
.reset( new ComCtlProgressBarModel( 6 ) );
283 if( xCtrlModel
.get() )
285 // user form controls are AWT models
286 xCtrlModel
->setAwtModelMode();
288 // check that container model matches container flag in site data
289 bool bModelIsContainer
= dynamic_cast< const AxContainerModelBase
* >( xCtrlModel
.get() ) != nullptr;
290 bool bTypeMatch
= bModelIsContainer
== isContainer();
291 OSL_ENSURE( bTypeMatch
, "VbaSiteModel::createControlModel - container type does not match container flag" );
298 void VbaSiteModel::convertProperties( PropertyMap
& rPropMap
,
299 const ControlConverter
& rConv
, ApiControlType eCtrlType
, sal_Int32 nCtrlIndex
) const
301 rPropMap
.setProperty( PROP_Name
, maName
);
302 rPropMap
.setProperty( PROP_Tag
, maTag
);
304 if( eCtrlType
!= API_CONTROL_DIALOG
)
306 rPropMap
.setProperty( PROP_HelpText
, maToolTip
);
307 rPropMap
.setProperty( PROP_EnableVisible
, getFlag( mnFlags
, VBA_SITE_VISIBLE
) );
308 // we need to set the passed control index to make option button groups work
309 if( (0 <= nCtrlIndex
) && (nCtrlIndex
<= SAL_MAX_INT16
) )
310 rPropMap
.setProperty( PROP_TabIndex
, static_cast< sal_Int16
>( nCtrlIndex
) );
311 // progress bar and group box support TabIndex, but not Tabstop...
312 if( (eCtrlType
!= API_CONTROL_PROGRESSBAR
) && (eCtrlType
!= API_CONTROL_GROUPBOX
) && (eCtrlType
!= API_CONTROL_FRAME
) && (eCtrlType
!= API_CONTROL_PAGE
) )
313 rPropMap
.setProperty( PROP_Tabstop
, getFlag( mnFlags
, VBA_SITE_TABSTOP
) );
314 rConv
.convertPosition( rPropMap
, maPos
);
318 VbaFormControl::VbaFormControl()
322 VbaFormControl::~VbaFormControl()
326 void VbaFormControl::importModelOrStorage( BinaryInputStream
& rInStrm
, StorageBase
& rStrg
, const AxClassTable
& rClassTable
)
328 if( mxSiteModel
.get() )
330 if( mxSiteModel
->isContainer() )
332 StorageRef xSubStrg
= rStrg
.openSubStorage( mxSiteModel
->getSubStorageName(), false );
333 OSL_ENSURE( xSubStrg
.get(), "VbaFormControl::importModelOrStorage - cannot find storage for embedded control" );
335 importStorage( *xSubStrg
, rClassTable
);
337 else if( !rInStrm
.isEof() )
339 sal_Int64 nNextStrmPos
= rInStrm
.tell() + mxSiteModel
->getStreamLength();
340 importControlModel( rInStrm
, rClassTable
);
341 rInStrm
.seek( nNextStrmPos
);
346 OUString
VbaFormControl::getControlName() const
348 return mxSiteModel
.get() ? mxSiteModel
->getName() : OUString();
351 void VbaFormControl::createAndConvert( sal_Int32 nCtrlIndex
,
352 const Reference
< XNameContainer
>& rxParentNC
, const ControlConverter
& rConv
) const
354 if( rxParentNC
.is() && mxSiteModel
.get() && mxCtrlModel
.get() ) try
356 // create the control model
357 OUString aServiceName
= mxCtrlModel
->getServiceName();
358 Reference
< XMultiServiceFactory
> xModelFactory( rxParentNC
, UNO_QUERY_THROW
);
359 Reference
< XControlModel
> xCtrlModel( xModelFactory
->createInstance( aServiceName
), UNO_QUERY_THROW
);
361 // convert all properties and embedded controls
362 if( convertProperties( xCtrlModel
, rConv
, nCtrlIndex
) )
364 // insert into parent container
365 const OUString
& rCtrlName
= mxSiteModel
->getName();
366 OSL_ENSURE( !rxParentNC
->hasByName( rCtrlName
), "VbaFormControl::createAndConvert - multiple controls with equal name" );
367 ContainerHelper::insertByName( rxParentNC
, rCtrlName
, Any( xCtrlModel
) );
370 catch(const Exception
& )
375 // protected ------------------------------------------------------------------
377 void VbaFormControl::importControlModel( BinaryInputStream
& rInStrm
, const AxClassTable
& rClassTable
)
379 createControlModel( rClassTable
);
380 if( mxCtrlModel
.get() )
381 mxCtrlModel
->importBinaryModel( rInStrm
);
384 void VbaFormControl::importStorage( StorageBase
& rStrg
, const AxClassTable
& rClassTable
)
386 createControlModel( rClassTable
);
387 AxContainerModelBase
* pContainerModel
= dynamic_cast< AxContainerModelBase
* >( mxCtrlModel
.get() );
388 OSL_ENSURE( pContainerModel
, "VbaFormControl::importStorage - missing container control model" );
389 if( pContainerModel
)
391 /* Open the 'f' stream containing the model of this control and a list
392 of site models for all child controls. */
393 BinaryXInputStream
aFStrm( rStrg
.openInputStream( "f" ), true );
394 OSL_ENSURE( !aFStrm
.isEof(), "VbaFormControl::importStorage - missing 'f' stream" );
396 /* Read the properties of this container control and the class table
397 (into the maClassTable vector) containing a list of GUIDs for
398 exotic embedded controls. */
399 if( !aFStrm
.isEof() && pContainerModel
->importBinaryModel( aFStrm
) && pContainerModel
->importClassTable( aFStrm
, maClassTable
) )
401 /* Read the site models of all embedded controls (this fills the
402 maControls vector). Ignore failure of importSiteModels() but
403 try to import as much controls as possible. */
404 importEmbeddedSiteModels( aFStrm
);
405 /* Open the 'o' stream containing models of embedded simple
406 controls. Stream may be empty or missing, if this control
407 contains no controls or only container controls. */
408 BinaryXInputStream
aOStrm( rStrg
.openInputStream( "o" ), true );
410 /* Iterate over all embedded controls, import model from 'o'
411 stream (for embedded simple controls) or from the substorage
412 (for embedded container controls). */
413 maControls
.forEachMem( &VbaFormControl::importModelOrStorage
,
414 ::std::ref( aOStrm
), ::std::ref( rStrg
), ::std::cref( maClassTable
) );
416 // Special handling for multi-page which has non-standard
417 // containment and additionally needs to re-order Page children
418 if ( pContainerModel
->getControlType() == API_CONTROL_MULTIPAGE
)
420 AxMultiPageModel
* pMultiPage
= dynamic_cast< AxMultiPageModel
* >( pContainerModel
);
423 BinaryXInputStream
aXStrm( rStrg
.openInputStream( "x" ), true );
424 pMultiPage
->importPageAndMultiPageProperties( aXStrm
, maControls
.size() );
426 typedef std::unordered_map
< sal_uInt32
, std::shared_ptr
< VbaFormControl
> > IdToPageMap
;
427 IdToPageMap idToPage
;
428 AxArrayString sCaptions
;
430 for (auto const& control
: maControls
)
432 auto& elem
= control
->mxCtrlModel
;
435 SAL_WARN("oox", "empty control model");
438 if (elem
->getControlType() == API_CONTROL_PAGE
)
440 VbaSiteModelRef xPageSiteRef
= control
->mxSiteModel
;
441 if ( xPageSiteRef
.get() )
442 idToPage
[ xPageSiteRef
->getId() ] = control
;
446 AxTabStripModel
* pTabStrip
= static_cast<AxTabStripModel
*>(elem
.get());
447 sCaptions
= pTabStrip
->maItems
;
448 pMultiPage
->mnActiveTab
= pTabStrip
->mnListIndex
;
449 pMultiPage
->mnTabStyle
= pTabStrip
->mnTabStyle
;
452 // apply caption/titles to pages
455 // need to sort the controls according to the order of the ids
456 if ( sCaptions
.size() == idToPage
.size() )
458 AxArrayString::iterator itCaption
= sCaptions
.begin();
459 for ( const auto& rCtrlId
: pMultiPage
->mnIDs
)
461 IdToPageMap::iterator iter
= idToPage
.find( rCtrlId
);
462 if ( iter
!= idToPage
.end() )
464 AxPageModel
* pPage
= static_cast<AxPageModel
*> ( iter
->second
->mxCtrlModel
.get() );
466 pPage
->importProperty( XML_Caption
, *itCaption
);
467 maControls
.push_back( iter
->second
);
473 /* Reorder the controls (sorts all option buttons of an option
474 group together), and move all children of all embedded frames
475 (group boxes) to this control (UNO group boxes cannot contain
477 finalizeEmbeddedControls();
482 bool VbaFormControl::convertProperties( const Reference
< XControlModel
>& rxCtrlModel
,
483 const ControlConverter
& rConv
, sal_Int32 nCtrlIndex
) const
485 if( rxCtrlModel
.is() && mxSiteModel
.get() && mxCtrlModel
.get() )
487 const OUString
& rCtrlName
= mxSiteModel
->getName();
488 OSL_ENSURE( !rCtrlName
.isEmpty(), "VbaFormControl::convertProperties - control without name" );
489 if( !rCtrlName
.isEmpty() )
491 // convert all properties
492 PropertyMap aPropMap
;
493 mxSiteModel
->convertProperties( aPropMap
, rConv
, mxCtrlModel
->getControlType(), nCtrlIndex
);
494 rConv
.bindToSources( rxCtrlModel
, mxSiteModel
->getControlSource(), mxSiteModel
->getRowSource() );
495 mxCtrlModel
->convertProperties( aPropMap
, rConv
);
496 mxCtrlModel
->convertSize( aPropMap
, rConv
);
497 PropertySet
aPropSet( rxCtrlModel
);
498 aPropSet
.setProperties( aPropMap
);
500 // create and convert all embedded controls
501 if( !maControls
.empty() ) try
503 Reference
< XNameContainer
> xCtrlModelNC( rxCtrlModel
, UNO_QUERY_THROW
);
504 /* Call conversion for all controls. Pass vector index as new
505 tab order to make option button groups work correctly. */
506 maControls
.forEachMemWithIndex( &VbaFormControl::createAndConvert
,
507 ::std::cref( xCtrlModelNC
), ::std::cref( rConv
) );
509 catch(const Exception
& )
511 OSL_FAIL( "VbaFormControl::convertProperties - cannot get control container interface" );
520 // private --------------------------------------------------------------------
522 void VbaFormControl::createControlModel( const AxClassTable
& rClassTable
)
524 // derived classes may have created their own control model
525 if( !mxCtrlModel
&& mxSiteModel
.get() )
526 mxCtrlModel
= mxSiteModel
->createControlModel( rClassTable
);
529 bool VbaFormControl::importSiteModel( BinaryInputStream
& rInStrm
)
531 mxSiteModel
.reset( new VbaSiteModel
);
532 return mxSiteModel
->importBinaryModel( rInStrm
);
535 void VbaFormControl::importEmbeddedSiteModels( BinaryInputStream
& rInStrm
)
537 sal_uInt64 nAnchorPos
= rInStrm
.tell();
538 sal_uInt32 nSiteCount
, nSiteDataSize
;
539 nSiteCount
= rInStrm
.readuInt32();
540 nSiteDataSize
= rInStrm
.readuInt32();
541 sal_Int64 nSiteEndPos
= rInStrm
.tell() + nSiteDataSize
;
543 // skip the site info structure
544 sal_uInt32 nSiteIndex
= 0;
545 while( !rInStrm
.isEof() && (nSiteIndex
< nSiteCount
) )
547 rInStrm
.skip( 1 ); // site depth
548 sal_uInt8 nTypeCount
= rInStrm
.readuInt8(); // 'type-or-count' byte
549 if( getFlag( nTypeCount
, VBA_SITEINFO_COUNT
) )
551 /* Count flag is set: the 'type-or-count' byte contains the number
552 of controls in the lower bits, the type specifier follows in
553 the next byte. The type specifier should always be 1 according
554 to the specification. */
556 nSiteIndex
+= (nTypeCount
& VBA_SITEINFO_MASK
);
560 /* Count flag is not set: the 'type-or-count' byte contains the
561 type specifier of *one* control in the lower bits (this type
562 should be 1, see above). */
566 // align the stream to 32bit, relative to start of entire site info
567 rInStrm
.alignToBlock( 4, nAnchorPos
);
569 // import the site models for all embedded controls
571 bool bValid
= !rInStrm
.isEof();
572 for( nSiteIndex
= 0; bValid
&& (nSiteIndex
< nSiteCount
); ++nSiteIndex
)
574 VbaFormControlRef
xControl( new VbaFormControl
);
575 maControls
.push_back( xControl
);
576 bValid
= xControl
->importSiteModel( rInStrm
);
579 rInStrm
.seek( nSiteEndPos
);
582 void VbaFormControl::finalizeEmbeddedControls()
584 /* This function performs two tasks:
586 1) Reorder the controls appropriately (sort all option buttons of an
587 option group together to make grouping work).
588 2) Move all children of all embedded frames (group boxes) to this
589 control (UNO group boxes cannot contain other controls).
592 // first, sort all controls by original tab index
593 ::std::sort( maControls
.begin(), maControls
.end(), &compareByTabIndex
);
595 /* Collect the programmatical names of all embedded controls (needed to be
596 able to set unused names to new dummy controls created below). Also
597 collect the names of all children of embedded frames (group boxes).
598 Luckily, names of controls must be unique in the entire form, not just
599 in the current container. */
600 VbaControlNamesSet aControlNames
;
601 VbaControlNameInserter
aInserter( aControlNames
);
602 maControls
.forEach( aInserter
);
603 for (auto const& control
: maControls
)
604 if( control
->mxCtrlModel
.get() && (control
->mxCtrlModel
->getControlType() == API_CONTROL_GROUPBOX
) )
605 control
->maControls
.forEach( aInserter
);
607 /* Reprocess the sorted list and collect all option button controls that
608 are part of the same option group (determined by group name). All
609 controls will be stored in a vector of vectors, that collects every
610 option button group in one vector element, and other controls between
611 these option groups (or leading or trailing controls) in other vector
612 elements. If an option button group follows another group, a dummy
613 separator control has to be inserted. */
614 typedef RefVector
< VbaFormControlVector
> VbaFormControlVectorVector
;
615 VbaFormControlVectorVector aControlGroups
;
617 typedef RefMap
< OUString
, VbaFormControlVector
> VbaFormControlVectorMap
;
618 VbaFormControlVectorMap aOptionGroups
;
620 typedef VbaFormControlVectorMap::mapped_type VbaFormControlVectorRef
;
621 bool bLastWasOptionButton
= false;
622 for (auto const& control
: maControls
)
624 const ControlModelBase
* pCtrlModel
= control
->mxCtrlModel
.get();
626 if( const AxOptionButtonModel
* pOptButtonModel
= dynamic_cast< const AxOptionButtonModel
* >( pCtrlModel
) )
628 // check if a new option group needs to be created
629 const OUString
& rGroupName
= pOptButtonModel
->getGroupName();
630 VbaFormControlVectorRef
& rxOptionGroup
= aOptionGroups
[ rGroupName
];
633 /* If last control was an option button too, we have two
634 option groups following each other, so a dummy separator
635 control is needed. */
636 if( bLastWasOptionButton
)
638 VbaFormControlVectorRef
xDummyGroup( new VbaFormControlVector
);
639 aControlGroups
.push_back( xDummyGroup
);
640 OUString aName
= aControlNames
.generateDummyName();
641 VbaFormControlRef
xDummyControl( new VbaDummyFormControl( aName
) );
642 xDummyGroup
->push_back( xDummyControl
);
644 rxOptionGroup
.reset( new VbaFormControlVector
);
645 aControlGroups
.push_back( rxOptionGroup
);
647 /* Append the option button to the control group (which is now
648 referred by the vector aControlGroups and by the map
650 rxOptionGroup
->push_back(control
);
651 bLastWasOptionButton
= true;
655 // open a new control group, if the last group is an option group
656 if( bLastWasOptionButton
|| aControlGroups
.empty() )
658 VbaFormControlVectorRef
xControlGroup( new VbaFormControlVector
);
659 aControlGroups
.push_back( xControlGroup
);
661 // append the control to the last control group
662 VbaFormControlVector
& rLastGroup
= *aControlGroups
.back();
663 rLastGroup
.push_back(control
);
664 bLastWasOptionButton
= false;
666 // if control is a group box, move all its children to this control
667 if( pCtrlModel
&& (pCtrlModel
->getControlType() == API_CONTROL_GROUPBOX
) )
669 /* Move all embedded controls of the group box relative to the
670 position of the group box. */
671 control
->moveEmbeddedToAbsoluteParent();
672 /* Insert all children of the group box into the last control
673 group (following the group box). */
674 rLastGroup
.insert( rLastGroup
.end(), control
->maControls
.begin(), control
->maControls
.end() );
675 control
->maControls
.clear();
676 // check if last control of the group box is an option button
677 bLastWasOptionButton
= dynamic_cast< const AxOptionButtonModel
* >( rLastGroup
.back()->mxCtrlModel
.get() ) != nullptr;
682 // flatten the vector of vectors of form controls to a single vector
684 for (auto const& controlGroup
: aControlGroups
)
685 maControls
.insert( maControls
.end(), controlGroup
->begin(), controlGroup
->end() );
688 void VbaFormControl::moveRelative( const AxPairData
& rDistance
)
690 if( mxSiteModel
.get() )
691 mxSiteModel
->moveRelative( rDistance
);
694 void VbaFormControl::moveEmbeddedToAbsoluteParent()
696 if( mxSiteModel
.get() && !maControls
.empty() )
698 // distance to move is equal to position of this control in its parent
699 AxPairData aDistance
= mxSiteModel
->getPosition();
701 /* For group boxes: add half of the font height to Y position (VBA
702 positions relative to frame border line, not to 'top' of frame). */
703 const AxFontDataModel
* pFontModel
= dynamic_cast< const AxFontDataModel
* >( mxCtrlModel
.get() );
704 if( pFontModel
&& (pFontModel
->getControlType() == API_CONTROL_GROUPBOX
) )
706 // convert points to 1/100 mm (1 pt = 1/72 inch = 2.54/72 cm = 2540/72 1/100 mm)
707 sal_Int32 nFontHeight
= static_cast< sal_Int32
>( pFontModel
->getFontHeight() * 2540 / 72 );
708 aDistance
.second
+= nFontHeight
/ 2;
711 // move the embedded controls
712 maControls
.forEachMem( &VbaFormControl::moveRelative
, ::std::cref( aDistance
) );
716 bool VbaFormControl::compareByTabIndex( const VbaFormControlRef
& rxLeft
, const VbaFormControlRef
& rxRight
)
718 // sort controls without model to the end
719 sal_Int32 nLeftTabIndex
= rxLeft
->mxSiteModel
.get() ? rxLeft
->mxSiteModel
->getTabIndex() : SAL_MAX_INT32
;
720 sal_Int32 nRightTabIndex
= rxRight
->mxSiteModel
.get() ? rxRight
->mxSiteModel
->getTabIndex() : SAL_MAX_INT32
;
721 return nLeftTabIndex
< nRightTabIndex
;
726 OUString
lclGetQuotedString( const OUString
& rCodeLine
)
728 OUStringBuffer aBuffer
;
729 sal_Int32 nLen
= rCodeLine
.getLength();
730 if( (nLen
> 0) && (rCodeLine
[ 0 ] == '"') )
732 bool bExitLoop
= false;
733 for( sal_Int32 nIndex
= 1; !bExitLoop
&& (nIndex
< nLen
); ++nIndex
)
735 sal_Unicode cChar
= rCodeLine
[ nIndex
];
736 // exit on closing quote char (but check on double quote chars)
737 bExitLoop
= (cChar
== '"') && ((nIndex
+ 1 == nLen
) || (rCodeLine
[ nIndex
+ 1 ] != '"'));
740 aBuffer
.append( cChar
);
741 // skip second quote char
747 return aBuffer
.makeStringAndClear();
750 bool lclEatWhitespace( OUString
& rCodeLine
)
752 sal_Int32 nIndex
= 0;
753 while( (nIndex
< rCodeLine
.getLength()) && ((rCodeLine
[ nIndex
] == ' ') || (rCodeLine
[ nIndex
] == '\t')) )
757 rCodeLine
= rCodeLine
.copy( nIndex
);
763 bool lclEatKeyword( OUString
& rCodeLine
, const OUString
& rKeyword
)
765 if( rCodeLine
.matchIgnoreAsciiCase( rKeyword
) )
767 rCodeLine
= rCodeLine
.copy( rKeyword
.getLength() );
768 // success, if code line ends after keyword, or if whitespace follows
769 return rCodeLine
.isEmpty() || lclEatWhitespace( rCodeLine
);
776 VbaUserForm::VbaUserForm( const Reference
< XComponentContext
>& rxContext
,
777 const Reference
< XModel
>& rxDocModel
, const GraphicHelper
& rGraphicHelper
, bool bDefaultColorBgr
) :
778 mxContext( rxContext
),
779 mxDocModel( rxDocModel
),
780 maConverter( rxDocModel
, rGraphicHelper
, bDefaultColorBgr
)
782 OSL_ENSURE( mxContext
.is(), "VbaUserForm::VbaUserForm - missing component context" );
783 OSL_ENSURE( mxDocModel
.is(), "VbaUserForm::VbaUserForm - missing document model" );
786 void VbaUserForm::importForm( const Reference
< XNameContainer
>& rxDialogLib
,
787 StorageBase
& rVbaFormStrg
, const OUString
& rModuleName
, rtl_TextEncoding eTextEnc
)
789 OSL_ENSURE( rxDialogLib
.is(), "VbaUserForm::importForm - missing dialog library" );
790 if( !mxContext
.is() || !mxDocModel
.is() || !rxDialogLib
.is() )
793 // check that the '03VBFrame' stream exists, this is required for forms
794 BinaryXInputStream
aInStrm( rVbaFormStrg
.openInputStream( "\003VBFrame" ), true );
795 OSL_ENSURE( !aInStrm
.isEof(), "VbaUserForm::importForm - missing \\003VBFrame stream" );
796 if( aInStrm
.isEof() )
799 // scan for the line 'Begin {GUID} <FormName>'
800 TextInputStream
aFrameTextStrm( mxContext
, aInStrm
, eTextEnc
);
801 const OUString aBegin
= "Begin";
803 bool bBeginFound
= false;
804 while( !bBeginFound
&& !aFrameTextStrm
.isEof() )
806 aLine
= aFrameTextStrm
.readLine().trim();
807 bBeginFound
= lclEatKeyword( aLine
, aBegin
);
809 // check for the specific GUID that represents VBA forms
810 if( !bBeginFound
|| !lclEatKeyword( aLine
, "{C62A69F0-16DC-11CE-9E98-00AA00574A4F}" ) )
813 // remaining line is the form name
814 OUString aFormName
= aLine
.trim();
815 OSL_ENSURE( !aFormName
.isEmpty(), "VbaUserForm::importForm - missing form name" );
816 OSL_ENSURE( rModuleName
.equalsIgnoreAsciiCase( aFormName
), "VbaUserForm::importFrameStream - form and module name mismatch" );
817 if( aFormName
.isEmpty() )
818 aFormName
= rModuleName
;
819 if( aFormName
.isEmpty() )
821 mxSiteModel
.reset( new VbaSiteModel
);
822 mxSiteModel
->importProperty( XML_Name
, aFormName
);
824 // read the form properties (caption is contained in this '03VBFrame' stream, not in the 'f' stream)
825 mxCtrlModel
.reset( new AxUserFormModel
);
826 OUString aKey
, aValue
;
827 bool bExitLoop
= false;
828 while( !bExitLoop
&& !aFrameTextStrm
.isEof() )
830 aLine
= aFrameTextStrm
.readLine().trim();
831 bExitLoop
= aLine
.equalsIgnoreAsciiCase( "End" );
832 if( !bExitLoop
&& VbaHelper::extractKeyValue( aKey
, aValue
, aLine
) )
834 if( aKey
.equalsIgnoreAsciiCase( "Caption" ) )
835 mxCtrlModel
->importProperty( XML_Caption
, lclGetQuotedString( aValue
) );
836 else if( aKey
.equalsIgnoreAsciiCase( "Tag" ) )
837 mxSiteModel
->importProperty( XML_Tag
, lclGetQuotedString( aValue
) );
841 // use generic container control functionality to import the embedded controls
842 importStorage( rVbaFormStrg
, AxClassTable() );
846 // create the dialog model
847 OUString aServiceName
= mxCtrlModel
->getServiceName();
848 Reference
< XMultiServiceFactory
> xFactory( mxContext
->getServiceManager(), UNO_QUERY_THROW
);
849 Reference
< XControlModel
> xDialogModel( xFactory
->createInstance( aServiceName
), UNO_QUERY_THROW
);
850 Reference
< XNameContainer
> xDialogNC( xDialogModel
, UNO_QUERY_THROW
);
852 // convert properties and embedded controls
853 if( convertProperties( xDialogModel
, maConverter
, 0 ) )
855 // export the dialog to XML and insert it into the dialog library
856 Reference
< XInputStreamProvider
> xDialogSource( ::xmlscript::exportDialogModel( xDialogNC
, mxContext
, mxDocModel
), UNO_SET_THROW
);
857 OSL_ENSURE( !rxDialogLib
->hasByName( aFormName
), "VbaUserForm::importForm - multiple dialogs with equal name" );
858 ContainerHelper::insertByName( rxDialogLib
, aFormName
, Any( xDialogSource
) );
861 catch(const Exception
& )
869 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */