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/container/XNameContainer.hpp>
26 #include <com/sun/star/io/XInputStreamProvider.hpp>
27 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
28 #include <com/sun/star/uno/XComponentContext.hpp>
29 #include <rtl/ustrbuf.hxx>
30 #include <xmlscript/xmldlg_imexp.hxx>
31 #include "oox/helper/attributelist.hxx"
32 #include "oox/helper/binaryinputstream.hxx"
33 #include "oox/helper/containerhelper.hxx"
34 #include "oox/helper/propertymap.hxx"
35 #include "oox/helper/propertyset.hxx"
36 #include "oox/helper/storagebase.hxx"
37 #include "oox/helper/textinputstream.hxx"
38 #include "oox/ole/vbahelper.hxx"
39 #include <boost/unordered_map.hpp>
44 using namespace ::com::sun::star::awt
;
45 using namespace ::com::sun::star::container
;
46 using namespace ::com::sun::star::frame
;
47 using namespace ::com::sun::star::io
;
48 using namespace ::com::sun::star::lang
;
49 using namespace ::com::sun::star::uno
;
53 const sal_uInt16 VBA_SITE_CLASSIDINDEX
= 0x8000;
54 const sal_uInt16 VBA_SITE_INDEXMASK
= 0x7FFF;
55 const sal_uInt16 VBA_SITE_FORM
= 7;
56 const sal_uInt16 VBA_SITE_IMAGE
= 12;
57 const sal_uInt16 VBA_SITE_FRAME
= 14;
58 const sal_uInt16 VBA_SITE_SPINBUTTON
= 16;
59 const sal_uInt16 VBA_SITE_COMMANDBUTTON
= 17;
60 const sal_uInt16 VBA_SITE_TABSTRIP
= 18;
61 const sal_uInt16 VBA_SITE_LABEL
= 21;
62 const sal_uInt16 VBA_SITE_TEXTBOX
= 23;
63 const sal_uInt16 VBA_SITE_LISTBOX
= 24;
64 const sal_uInt16 VBA_SITE_COMBOBOX
= 25;
65 const sal_uInt16 VBA_SITE_CHECKBOX
= 26;
66 const sal_uInt16 VBA_SITE_OPTIONBUTTON
= 27;
67 const sal_uInt16 VBA_SITE_TOGGLEBUTTON
= 28;
68 const sal_uInt16 VBA_SITE_SCROLLBAR
= 47;
69 const sal_uInt16 VBA_SITE_MULTIPAGE
= 57;
70 const sal_uInt16 VBA_SITE_UNKNOWN
= 0x7FFF;
72 const sal_uInt32 VBA_SITE_TABSTOP
= 0x00000001;
73 const sal_uInt32 VBA_SITE_VISIBLE
= 0x00000002;
74 const sal_uInt32 VBA_SITE_OSTREAM
= 0x00000010;
75 const sal_uInt32 VBA_SITE_DEFFLAGS
= 0x00000033;
77 const sal_uInt8 VBA_SITEINFO_COUNT
= 0x80;
78 const sal_uInt8 VBA_SITEINFO_MASK
= 0x7F;
80 // ----------------------------------------------------------------------------
82 /** Collects names of all controls in a user form or container control. Allows
83 to generate unused names for dummy controls separating option groups.
85 class VbaControlNamesSet
88 explicit VbaControlNamesSet();
90 /** Inserts the name of the passed control. */
91 void insertName( const VbaFormControl
& rControl
);
92 /** Returns a name that is not contained in this set. */
93 OUString
generateDummyName();
96 typedef ::std::set
< OUString
> OUStringSet
;
97 OUStringSet maCtrlNames
;
98 const OUString maDummyBaseName
;
102 VbaControlNamesSet::VbaControlNamesSet() :
103 maDummyBaseName( "DummyGroupSep" ),
108 void VbaControlNamesSet::insertName( const VbaFormControl
& rControl
)
110 OUString aName
= rControl
.getControlName();
111 if( !aName
.isEmpty() )
112 maCtrlNames
.insert( aName
);
115 OUString
VbaControlNamesSet::generateDummyName()
120 aCtrlName
= OUStringBuffer( maDummyBaseName
).append( ++mnIndex
).makeStringAndClear();
122 while( maCtrlNames
.count( aCtrlName
) > 0 );
123 maCtrlNames
.insert( aCtrlName
);
127 // ----------------------------------------------------------------------------
129 /** Functor that inserts the name of a control into a VbaControlNamesSet. */
130 struct VbaControlNameInserter
133 VbaControlNamesSet
& mrCtrlNames
;
134 inline explicit VbaControlNameInserter( VbaControlNamesSet
& rCtrlNames
) : mrCtrlNames( rCtrlNames
) {}
135 inline void operator()( const VbaFormControl
& rControl
) { mrCtrlNames
.insertName( rControl
); }
138 // ----------------------------------------------------------------------------
140 /** A dummy invisible form control (fixed label without text) that is used to
141 separate two groups of option buttons.
143 class VbaDummyFormControl
: public VbaFormControl
146 explicit VbaDummyFormControl( const OUString
& rName
);
149 VbaDummyFormControl::VbaDummyFormControl( const OUString
& rName
)
151 mxSiteModel
.reset( new VbaSiteModel
);
152 mxSiteModel
->importProperty( XML_Name
, rName
);
153 mxSiteModel
->importProperty( XML_VariousPropertyBits
, OUString( '0' ) );
155 mxCtrlModel
.reset( new AxLabelModel
);
156 mxCtrlModel
->setAwtModelMode();
157 mxCtrlModel
->importProperty( XML_Size
, "10;10" );
162 // ============================================================================
164 VbaSiteModel::VbaSiteModel() :
167 mnHelpContextId( 0 ),
168 mnFlags( VBA_SITE_DEFFLAGS
),
171 mnClassIdOrCache( VBA_SITE_UNKNOWN
),
176 VbaSiteModel::~VbaSiteModel()
180 void VbaSiteModel::importProperty( sal_Int32 nPropId
, const OUString
& rValue
)
184 case XML_Name
: maName
= rValue
; break;
185 case XML_Tag
: maTag
= rValue
; break;
186 case XML_VariousPropertyBits
: mnFlags
= AttributeConversion::decodeUnsigned( rValue
); break;
190 bool VbaSiteModel::importBinaryModel( BinaryInputStream
& rInStrm
)
192 AxBinaryPropertyReader
aReader( rInStrm
);
193 aReader
.readStringProperty( maName
);
194 aReader
.readStringProperty( maTag
);
195 aReader
.readIntProperty
< sal_Int32
>( mnId
);
196 aReader
.readIntProperty
< sal_Int32
>( mnHelpContextId
);
197 aReader
.readIntProperty
< sal_uInt32
>( mnFlags
);
198 aReader
.readIntProperty
< sal_uInt32
>( mnStreamLen
);
199 aReader
.readIntProperty
< sal_Int16
>( mnTabIndex
);
200 aReader
.readIntProperty
< sal_uInt16
>( mnClassIdOrCache
);
201 aReader
.readPairProperty( maPos
);
202 aReader
.readIntProperty
< sal_uInt16
>( mnGroupId
);
203 aReader
.skipUndefinedProperty();
204 aReader
.readStringProperty( maToolTip
);
205 aReader
.skipStringProperty(); // license key
206 aReader
.readStringProperty( maControlSource
);
207 aReader
.readStringProperty( maRowSource
);
208 return aReader
.finalizeImport();
211 void VbaSiteModel::moveRelative( const AxPairData
& rDistance
)
213 maPos
.first
+= rDistance
.first
;
214 maPos
.second
+= rDistance
.second
;
217 bool VbaSiteModel::isContainer() const
219 return !getFlag( mnFlags
, VBA_SITE_OSTREAM
);
222 sal_uInt32
VbaSiteModel::getStreamLength() const
224 return isContainer() ? 0 : mnStreamLen
;
227 OUString
VbaSiteModel::getSubStorageName() const
231 OUStringBuffer aBuffer
;
232 aBuffer
.append( 'i' );
234 aBuffer
.append( '0' );
235 aBuffer
.append( mnId
);
236 return aBuffer
.makeStringAndClear();
241 ControlModelRef
VbaSiteModel::createControlModel( const AxClassTable
& rClassTable
) const
243 ControlModelRef xCtrlModel
;
245 sal_Int32 nTypeIndex
= static_cast< sal_Int32
>( mnClassIdOrCache
& VBA_SITE_INDEXMASK
);
246 if( !getFlag( mnClassIdOrCache
, VBA_SITE_CLASSIDINDEX
) )
250 case VBA_SITE_COMMANDBUTTON
: xCtrlModel
.reset( new AxCommandButtonModel
); break;
251 case VBA_SITE_LABEL
: xCtrlModel
.reset( new AxLabelModel
); break;
252 case VBA_SITE_IMAGE
: xCtrlModel
.reset( new AxImageModel
); break;
253 case VBA_SITE_TOGGLEBUTTON
: xCtrlModel
.reset( new AxToggleButtonModel
); break;
254 case VBA_SITE_CHECKBOX
: xCtrlModel
.reset( new AxCheckBoxModel
); break;
255 case VBA_SITE_OPTIONBUTTON
: xCtrlModel
.reset( new AxOptionButtonModel
); break;
256 case VBA_SITE_TEXTBOX
: xCtrlModel
.reset( new AxTextBoxModel
); break;
257 case VBA_SITE_LISTBOX
: xCtrlModel
.reset( new AxListBoxModel
); break;
258 case VBA_SITE_COMBOBOX
: xCtrlModel
.reset( new AxComboBoxModel
); break;
259 case VBA_SITE_SPINBUTTON
: xCtrlModel
.reset( new AxSpinButtonModel
); break;
260 case VBA_SITE_SCROLLBAR
: xCtrlModel
.reset( new AxScrollBarModel
); break;
261 case VBA_SITE_TABSTRIP
: xCtrlModel
.reset( new AxTabStripModel
);
263 case VBA_SITE_FRAME
: xCtrlModel
.reset( new AxFrameModel
); break;
264 case VBA_SITE_MULTIPAGE
: xCtrlModel
.reset( new AxMultiPageModel
);
266 case VBA_SITE_FORM
: xCtrlModel
.reset( new AxPageModel
);
268 default: OSL_FAIL( "VbaSiteModel::createControlModel - unknown type index" );
273 const OUString
* pGuid
= ContainerHelper::getVectorElement( rClassTable
, nTypeIndex
);
274 OSL_ENSURE( pGuid
, "VbaSiteModel::createControlModel - invalid class table index" );
277 if( *pGuid
== COMCTL_GUID_SCROLLBAR_60
)
278 xCtrlModel
.reset( new ComCtlScrollBarModel( 6 ) );
279 else if( *pGuid
== COMCTL_GUID_PROGRESSBAR_50
)
280 xCtrlModel
.reset( new ComCtlProgressBarModel( 5 ) );
281 else if( *pGuid
== COMCTL_GUID_PROGRESSBAR_60
)
282 xCtrlModel
.reset( new ComCtlProgressBarModel( 6 ) );
286 if( xCtrlModel
.get() )
288 // user form controls are AWT models
289 xCtrlModel
->setAwtModelMode();
291 // check that container model matches container flag in site data
292 bool bModelIsContainer
= dynamic_cast< const AxContainerModelBase
* >( xCtrlModel
.get() ) != 0;
293 bool bTypeMatch
= bModelIsContainer
== isContainer();
294 OSL_ENSURE( bTypeMatch
, "VbaSiteModel::createControlModel - container type does not match container flag" );
301 void VbaSiteModel::convertProperties( PropertyMap
& rPropMap
,
302 const ControlConverter
& rConv
, ApiControlType eCtrlType
, sal_Int32 nCtrlIndex
) const
304 rPropMap
.setProperty( PROP_Name
, maName
);
305 rPropMap
.setProperty( PROP_Tag
, maTag
);
307 if( eCtrlType
!= API_CONTROL_DIALOG
)
309 rPropMap
.setProperty( PROP_HelpText
, maToolTip
);
310 rPropMap
.setProperty( PROP_EnableVisible
, getFlag( mnFlags
, VBA_SITE_VISIBLE
) );
311 // we need to set the passed control index to make option button groups work
312 if( (0 <= nCtrlIndex
) && (nCtrlIndex
<= SAL_MAX_INT16
) )
313 rPropMap
.setProperty( PROP_TabIndex
, static_cast< sal_Int16
>( nCtrlIndex
) );
314 // progress bar and group box support TabIndex, but not Tabstop...
315 if( (eCtrlType
!= API_CONTROL_PROGRESSBAR
) && (eCtrlType
!= API_CONTROL_GROUPBOX
) && (eCtrlType
!= API_CONTROL_FRAME
) && (eCtrlType
!= API_CONTROL_PAGE
) )
316 rPropMap
.setProperty( PROP_Tabstop
, getFlag( mnFlags
, VBA_SITE_TABSTOP
) );
317 rConv
.convertPosition( rPropMap
, maPos
);
321 // ============================================================================
323 VbaFormControl::VbaFormControl()
327 VbaFormControl::~VbaFormControl()
331 void VbaFormControl::importModelOrStorage( BinaryInputStream
& rInStrm
, StorageBase
& rStrg
, const AxClassTable
& rClassTable
)
333 if( mxSiteModel
.get() )
335 if( mxSiteModel
->isContainer() )
337 StorageRef xSubStrg
= rStrg
.openSubStorage( mxSiteModel
->getSubStorageName(), false );
338 OSL_ENSURE( xSubStrg
.get(), "VbaFormControl::importModelOrStorage - cannot find storage for embedded control" );
340 importStorage( *xSubStrg
, rClassTable
);
342 else if( !rInStrm
.isEof() )
344 sal_Int64 nNextStrmPos
= rInStrm
.tell() + mxSiteModel
->getStreamLength();
345 importControlModel( rInStrm
, rClassTable
);
346 rInStrm
.seek( nNextStrmPos
);
351 OUString
VbaFormControl::getControlName() const
353 return mxSiteModel
.get() ? mxSiteModel
->getName() : OUString();
356 void VbaFormControl::createAndConvert( sal_Int32 nCtrlIndex
,
357 const Reference
< XNameContainer
>& rxParentNC
, const ControlConverter
& rConv
) const
359 if( rxParentNC
.is() && mxSiteModel
.get() && mxCtrlModel
.get() ) try
361 // create the control model
362 OUString aServiceName
= mxCtrlModel
->getServiceName();
363 Reference
< XMultiServiceFactory
> xModelFactory( rxParentNC
, UNO_QUERY_THROW
);
364 Reference
< XControlModel
> xCtrlModel( xModelFactory
->createInstance( aServiceName
), UNO_QUERY_THROW
);
366 // convert all properties and embedded controls
367 if( convertProperties( xCtrlModel
, rConv
, nCtrlIndex
) )
369 // insert into parent container
370 const OUString
& rCtrlName
= mxSiteModel
->getName();
371 OSL_ENSURE( !rxParentNC
->hasByName( rCtrlName
), "VbaFormControl::createAndConvert - multiple controls with equal name" );
372 ContainerHelper::insertByName( rxParentNC
, rCtrlName
, Any( xCtrlModel
) );
375 catch(const Exception
& )
380 // protected ------------------------------------------------------------------
382 void VbaFormControl::importControlModel( BinaryInputStream
& rInStrm
, const AxClassTable
& rClassTable
)
384 createControlModel( rClassTable
);
385 if( mxCtrlModel
.get() )
386 mxCtrlModel
->importBinaryModel( rInStrm
);
389 void VbaFormControl::importStorage( StorageBase
& rStrg
, const AxClassTable
& rClassTable
)
391 createControlModel( rClassTable
);
392 AxContainerModelBase
* pContainerModel
= dynamic_cast< AxContainerModelBase
* >( mxCtrlModel
.get() );
393 OSL_ENSURE( pContainerModel
, "VbaFormControl::importStorage - missing container control model" );
394 if( pContainerModel
)
396 /* Open the 'f' stream containing the model of this control and a list
397 of site models for all child controls. */
398 BinaryXInputStream
aFStrm( rStrg
.openInputStream( "f" ), true );
399 OSL_ENSURE( !aFStrm
.isEof(), "VbaFormControl::importStorage - missing 'f' stream" );
401 /* Read the properties of this container control and the class table
402 (into the maClassTable vector) containing a list of GUIDs for
403 exotic embedded controls. */
404 if( !aFStrm
.isEof() && pContainerModel
->importBinaryModel( aFStrm
) && pContainerModel
->importClassTable( aFStrm
, maClassTable
) )
406 /* Read the site models of all embedded controls (this fills the
407 maControls vector). Ignore failure of importSiteModels() but
408 try to import as much controls as possible. */
409 importEmbeddedSiteModels( aFStrm
);
410 /* Open the 'o' stream containing models of embedded simple
411 controls. Stream may be empty or missing, if this control
412 contains no controls or only container controls. */
413 BinaryXInputStream
aOStrm( rStrg
.openInputStream( "o" ), true );
415 /* Iterate over all embedded controls, import model from 'o'
416 stream (for embedded simple controls) or from the substorage
417 (for embedded container controls). */
418 maControls
.forEachMem( &VbaFormControl::importModelOrStorage
,
419 ::boost::ref( aOStrm
), ::boost::ref( rStrg
), ::boost::cref( maClassTable
) );
421 // Special handling for multi-page which has non-standard
422 // containment and additionally needs to re-order Page children
423 if ( pContainerModel
->getControlType() == API_CONTROL_MULTIPAGE
)
425 AxMultiPageModel
* pMultiPage
= dynamic_cast< AxMultiPageModel
* >( pContainerModel
);
428 BinaryXInputStream
aXStrm( rStrg
.openInputStream( "x" ), true );
429 pMultiPage
->importPageAndMultiPageProperties( aXStrm
, maControls
.size() );
431 typedef boost::unordered_map
< sal_uInt32
, ::boost::shared_ptr
< VbaFormControl
> > IdToPageMap
;
432 IdToPageMap idToPage
;
433 VbaFormControlVector::iterator it
= maControls
.begin();
434 VbaFormControlVector::iterator it_end
= maControls
.end();
435 typedef std::vector
< sal_uInt32
> UInt32Array
;
436 AxArrayString sCaptions
;
438 for ( ; it
!= it_end
; ++it
)
440 if ( (*it
)->mxCtrlModel
->getControlType() == API_CONTROL_PAGE
)
442 VbaSiteModelRef xPageSiteRef
= (*it
)->mxSiteModel
;
443 if ( xPageSiteRef
.get() )
444 idToPage
[ xPageSiteRef
->getId() ] = (*it
);
448 AxTabStripModel
* pTabStrip
= static_cast<AxTabStripModel
*> ( (*it
)->mxCtrlModel
.get() );
449 sCaptions
= pTabStrip
->maItems
;
450 pMultiPage
->mnActiveTab
= pTabStrip
->mnListIndex
;
451 pMultiPage
->mnTabStyle
= pTabStrip
->mnTabStyle
;
454 // apply caption/titles to pages
455 UInt32Array::iterator itCtrlId
= pMultiPage
->mnIDs
.begin();
456 UInt32Array::iterator itCtrlId_end
= pMultiPage
->mnIDs
.end();
457 AxArrayString::iterator itCaption
= sCaptions
.begin();
460 // need to sort the controls according to the order of the ids
461 for ( sal_Int32 index
= 1 ; ( sCaptions
.size() == idToPage
.size() ) && itCtrlId
!= itCtrlId_end
; ++itCtrlId
, ++itCaption
, ++index
)
463 IdToPageMap::iterator iter
= idToPage
.find( *itCtrlId
);
464 if ( iter
!= idToPage
.end() )
466 AxPageModel
* pPage
= static_cast<AxPageModel
*> ( iter
->second
->mxCtrlModel
.get() );
468 pPage
->importProperty( XML_Caption
, *itCaption
);
469 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 ::boost::cref( xCtrlModelNC
), ::boost::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 bool VbaFormControl::importEmbeddedSiteModels( BinaryInputStream
& rInStrm
)
537 sal_uInt64 nAnchorPos
= rInStrm
.tell();
538 sal_uInt32 nSiteCount
, nSiteDataSize
;
539 rInStrm
>> nSiteCount
>> nSiteDataSize
;
540 sal_Int64 nSiteEndPos
= rInStrm
.tell() + nSiteDataSize
;
542 // skip the site info structure
543 sal_uInt32 nSiteIndex
= 0;
544 while( !rInStrm
.isEof() && (nSiteIndex
< nSiteCount
) )
546 rInStrm
.skip( 1 ); // site depth
547 sal_uInt8 nTypeCount
= rInStrm
.readuInt8(); // 'type-or-count' byte
548 if( getFlag( nTypeCount
, VBA_SITEINFO_COUNT
) )
550 /* Count flag is set: the 'type-or-count' byte contains the number
551 of controls in the lower bits, the type specifier follows in
552 the next byte. The type specifier should always be 1 according
553 to the specification. */
555 nSiteIndex
+= (nTypeCount
& VBA_SITEINFO_MASK
);
559 /* Count flag is not set: the 'type-or-count' byte contains the
560 type specifier of *one* control in the lower bits (this type
561 should be 1, see above). */
565 // align the stream to 32bit, relative to start of entire site info
566 rInStrm
.alignToBlock( 4, nAnchorPos
);
568 // import the site models for all embedded controls
570 bool bValid
= !rInStrm
.isEof();
571 for( nSiteIndex
= 0; bValid
&& (nSiteIndex
< nSiteCount
); ++nSiteIndex
)
573 VbaFormControlRef
xControl( new VbaFormControl
);
574 maControls
.push_back( xControl
);
575 bValid
= xControl
->importSiteModel( rInStrm
);
578 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( VbaFormControlVector::iterator aIt
= maControls
.begin(), aEnd
= maControls
.end(); aIt
!= aEnd
; ++aIt
)
604 if( (*aIt
)->mxCtrlModel
.get() && ((*aIt
)->mxCtrlModel
->getControlType() == API_CONTROL_GROUPBOX
) )
605 (*aIt
)->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( VbaFormControlVector::iterator aIt
= maControls
.begin(), aEnd
= maControls
.end(); aIt
!= aEnd
; ++aIt
)
624 VbaFormControlRef xControl
= *aIt
;
625 const ControlModelBase
* pCtrlModel
= xControl
->mxCtrlModel
.get();
627 if( const AxOptionButtonModel
* pOptButtonModel
= dynamic_cast< const AxOptionButtonModel
* >( pCtrlModel
) )
629 // check if a new option group needs to be created
630 const OUString
& rGroupName
= pOptButtonModel
->getGroupName();
631 VbaFormControlVectorRef
& rxOptionGroup
= aOptionGroups
[ rGroupName
];
634 /* If last control was an option button too, we have two
635 option groups following each other, so a dummy separator
636 control is needed. */
637 if( bLastWasOptionButton
)
639 VbaFormControlVectorRef
xDummyGroup( new VbaFormControlVector
);
640 aControlGroups
.push_back( xDummyGroup
);
641 OUString aName
= aControlNames
.generateDummyName();
642 VbaFormControlRef
xDummyControl( new VbaDummyFormControl( aName
) );
643 xDummyGroup
->push_back( xDummyControl
);
645 rxOptionGroup
.reset( new VbaFormControlVector
);
646 aControlGroups
.push_back( rxOptionGroup
);
648 /* Append the option button to the control group (which is now
649 referred by the vector aControlGroups and by the map
651 rxOptionGroup
->push_back( xControl
);
652 bLastWasOptionButton
= true;
656 // open a new control group, if the last group is an option group
657 if( bLastWasOptionButton
|| aControlGroups
.empty() )
659 VbaFormControlVectorRef
xControlGroup( new VbaFormControlVector
);
660 aControlGroups
.push_back( xControlGroup
);
662 // append the control to the last control group
663 VbaFormControlVector
& rLastGroup
= *aControlGroups
.back();
664 rLastGroup
.push_back( xControl
);
665 bLastWasOptionButton
= false;
667 // if control is a group box, move all its children to this control
668 if( pCtrlModel
&& (pCtrlModel
->getControlType() == API_CONTROL_GROUPBOX
) )
670 /* Move all embedded controls of the group box relative to the
671 position of the group box. */
672 xControl
->moveEmbeddedToAbsoluteParent();
673 /* Insert all children of the group box into the last control
674 group (following the group box). */
675 rLastGroup
.insert( rLastGroup
.end(), xControl
->maControls
.begin(), xControl
->maControls
.end() );
676 xControl
->maControls
.clear();
677 // check if last control of the group box is an option button
678 bLastWasOptionButton
= dynamic_cast< const AxOptionButtonModel
* >( rLastGroup
.back()->mxCtrlModel
.get() ) != 0;
683 // flatten the vector of vectors of form controls to a single vector
685 for( VbaFormControlVectorVector::iterator aIt
= aControlGroups
.begin(), aEnd
= aControlGroups
.end(); aIt
!= aEnd
; ++aIt
)
686 maControls
.insert( maControls
.end(), (*aIt
)->begin(), (*aIt
)->end() );
689 void VbaFormControl::moveRelative( const AxPairData
& rDistance
)
691 if( mxSiteModel
.get() )
692 mxSiteModel
->moveRelative( rDistance
);
695 void VbaFormControl::moveEmbeddedToAbsoluteParent()
697 if( mxSiteModel
.get() && !maControls
.empty() )
699 // distance to move is equal to position of this control in its parent
700 AxPairData aDistance
= mxSiteModel
->getPosition();
702 /* For group boxes: add half of the font height to Y position (VBA
703 positions relative to frame border line, not to 'top' of frame). */
704 const AxFontDataModel
* pFontModel
= dynamic_cast< const AxFontDataModel
* >( mxCtrlModel
.get() );
705 if( pFontModel
&& (pFontModel
->getControlType() == API_CONTROL_GROUPBOX
) )
707 // convert points to 1/100 mm (1 pt = 1/72 inch = 2.54/72 cm = 2540/72 1/100 mm)
708 sal_Int32 nFontHeight
= static_cast< sal_Int32
>( pFontModel
->getFontHeight() * 2540 / 72 );
709 aDistance
.second
+= nFontHeight
/ 2;
712 // move the embedded controls
713 maControls
.forEachMem( &VbaFormControl::moveRelative
, ::boost::cref( aDistance
) );
717 bool VbaFormControl::compareByTabIndex( const VbaFormControlRef
& rxLeft
, const VbaFormControlRef
& rxRight
)
719 // sort controls without model to the end
720 sal_Int32 nLeftTabIndex
= rxLeft
->mxSiteModel
.get() ? rxLeft
->mxSiteModel
->getTabIndex() : SAL_MAX_INT32
;
721 sal_Int32 nRightTabIndex
= rxRight
->mxSiteModel
.get() ? rxRight
->mxSiteModel
->getTabIndex() : SAL_MAX_INT32
;
722 return nLeftTabIndex
< nRightTabIndex
;
725 // ============================================================================
729 OUString
lclGetQuotedString( const OUString
& rCodeLine
)
731 OUStringBuffer aBuffer
;
732 sal_Int32 nLen
= rCodeLine
.getLength();
733 if( (nLen
> 0) && (rCodeLine
[ 0 ] == '"') )
735 bool bExitLoop
= false;
736 for( sal_Int32 nIndex
= 1; !bExitLoop
&& (nIndex
< nLen
); ++nIndex
)
738 sal_Unicode cChar
= rCodeLine
[ nIndex
];
739 // exit on closing quote char (but check on double quote chars)
740 bExitLoop
= (cChar
== '"') && ((nIndex
+ 1 == nLen
) || (rCodeLine
[ nIndex
+ 1 ] != '"'));
743 aBuffer
.append( cChar
);
744 // skip second quote char
750 return aBuffer
.makeStringAndClear();
753 bool lclEatWhitespace( OUString
& rCodeLine
)
755 sal_Int32 nIndex
= 0;
756 while( (nIndex
< rCodeLine
.getLength()) && ((rCodeLine
[ nIndex
] == ' ') || (rCodeLine
[ nIndex
] == '\t')) )
760 rCodeLine
= rCodeLine
.copy( nIndex
);
766 bool lclEatKeyword( OUString
& rCodeLine
, const OUString
& rKeyword
)
768 if( rCodeLine
.matchIgnoreAsciiCase( rKeyword
) )
770 rCodeLine
= rCodeLine
.copy( rKeyword
.getLength() );
771 // success, if code line ends after keyword, or if whitespace follows
772 return rCodeLine
.isEmpty() || lclEatWhitespace( rCodeLine
);
779 // ----------------------------------------------------------------------------
781 VbaUserForm::VbaUserForm( const Reference
< XComponentContext
>& rxContext
,
782 const Reference
< XModel
>& rxDocModel
, const GraphicHelper
& rGraphicHelper
, bool bDefaultColorBgr
) :
783 mxContext( rxContext
),
784 mxDocModel( rxDocModel
),
785 maConverter( rxDocModel
, rGraphicHelper
, bDefaultColorBgr
)
787 OSL_ENSURE( mxContext
.is(), "VbaUserForm::VbaUserForm - missing component context" );
788 OSL_ENSURE( mxDocModel
.is(), "VbaUserForm::VbaUserForm - missing document model" );
791 void VbaUserForm::importForm( const Reference
< XNameContainer
>& rxDialogLib
,
792 StorageBase
& rVbaFormStrg
, const OUString
& rModuleName
, rtl_TextEncoding eTextEnc
)
794 OSL_ENSURE( rxDialogLib
.is(), "VbaUserForm::importForm - missing dialog library" );
795 if( !mxContext
.is() || !mxDocModel
.is() || !rxDialogLib
.is() )
798 // check that the '03VBFrame' stream exists, this is required for forms
799 BinaryXInputStream
aInStrm( rVbaFormStrg
.openInputStream( "\003VBFrame" ), true );
800 OSL_ENSURE( !aInStrm
.isEof(), "VbaUserForm::importForm - missing \\003VBFrame stream" );
801 if( aInStrm
.isEof() )
804 // scan for the line 'Begin {GUID} <FormName>'
805 TextInputStream
aFrameTextStrm( mxContext
, aInStrm
, eTextEnc
);
806 const OUString aBegin
= "Begin";
808 bool bBeginFound
= false;
809 while( !bBeginFound
&& !aFrameTextStrm
.isEof() )
811 aLine
= aFrameTextStrm
.readLine().trim();
812 bBeginFound
= lclEatKeyword( aLine
, aBegin
);
814 // check for the specific GUID that represents VBA forms
815 if( !bBeginFound
|| !lclEatKeyword( aLine
, "{C62A69F0-16DC-11CE-9E98-00AA00574A4F}" ) )
818 // remaining line is the form name
819 OUString aFormName
= aLine
.trim();
820 OSL_ENSURE( !aFormName
.isEmpty(), "VbaUserForm::importForm - missing form name" );
821 OSL_ENSURE( rModuleName
.equalsIgnoreAsciiCase( aFormName
), "VbaUserForm::importFrameStream - form and module name mismatch" );
822 if( aFormName
.isEmpty() )
823 aFormName
= rModuleName
;
824 if( aFormName
.isEmpty() )
826 mxSiteModel
.reset( new VbaSiteModel
);
827 mxSiteModel
->importProperty( XML_Name
, aFormName
);
829 // read the form properties (caption is contained in this '03VBFrame' stream, not in the 'f' stream)
830 mxCtrlModel
.reset( new AxUserFormModel
);
831 OUString aKey
, aValue
;
832 bool bExitLoop
= false;
833 while( !bExitLoop
&& !aFrameTextStrm
.isEof() )
835 aLine
= aFrameTextStrm
.readLine().trim();
836 bExitLoop
= aLine
.equalsIgnoreAsciiCase( "End" );
837 if( !bExitLoop
&& VbaHelper::extractKeyValue( aKey
, aValue
, aLine
) )
839 if( aKey
.equalsIgnoreAsciiCase( "Caption" ) )
840 mxCtrlModel
->importProperty( XML_Caption
, lclGetQuotedString( aValue
) );
841 else if( aKey
.equalsIgnoreAsciiCase( "Tag" ) )
842 mxSiteModel
->importProperty( XML_Tag
, lclGetQuotedString( aValue
) );
846 // use generic container control functionality to import the embedded controls
847 importStorage( rVbaFormStrg
, AxClassTable() );
851 // create the dialog model
852 OUString aServiceName
= mxCtrlModel
->getServiceName();
853 Reference
< XMultiServiceFactory
> xFactory( mxContext
->getServiceManager(), UNO_QUERY_THROW
);
854 Reference
< XControlModel
> xDialogModel( xFactory
->createInstance( aServiceName
), UNO_QUERY_THROW
);
855 Reference
< XNameContainer
> xDialogNC( xDialogModel
, UNO_QUERY_THROW
);
857 // convert properties and embedded controls
858 if( convertProperties( xDialogModel
, maConverter
, 0 ) )
860 // export the dialog to XML and insert it into the dialog library
861 Reference
< XInputStreamProvider
> xDialogSource( ::xmlscript::exportDialogModel( xDialogNC
, mxContext
, mxDocModel
), UNO_SET_THROW
);
862 OSL_ENSURE( !rxDialogLib
->hasByName( aFormName
), "VbaUserForm::importForm - multiple dialogs with equal name" );
863 ContainerHelper::insertByName( rxDialogLib
, aFormName
, Any( xDialogSource
) );
866 catch(const Exception
& )
871 // ============================================================================
876 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */