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_DEFAULTBUTTON
= 0x00000004;
75 const sal_uInt32 VBA_SITE_CANCELBUTTON
= 0x00000008;
76 const sal_uInt32 VBA_SITE_OSTREAM
= 0x00000010;
77 const sal_uInt32 VBA_SITE_DEFFLAGS
= 0x00000033;
79 const sal_uInt8 VBA_SITEINFO_COUNT
= 0x80;
80 const sal_uInt8 VBA_SITEINFO_MASK
= 0x7F;
82 // ----------------------------------------------------------------------------
84 /** Collects names of all controls in a user form or container control. Allows
85 to generate unused names for dummy controls separating option groups.
87 class VbaControlNamesSet
90 explicit VbaControlNamesSet();
92 /** Inserts the name of the passed control. */
93 void insertName( const VbaFormControl
& rControl
);
94 /** Returns a name that is not contained in this set. */
95 OUString
generateDummyName();
98 typedef ::std::set
< OUString
> OUStringSet
;
99 OUStringSet maCtrlNames
;
100 const OUString maDummyBaseName
;
104 VbaControlNamesSet::VbaControlNamesSet() :
105 maDummyBaseName( "DummyGroupSep" ),
110 void VbaControlNamesSet::insertName( const VbaFormControl
& rControl
)
112 OUString aName
= rControl
.getControlName();
113 if( !aName
.isEmpty() )
114 maCtrlNames
.insert( aName
);
117 OUString
VbaControlNamesSet::generateDummyName()
122 aCtrlName
= OUStringBuffer( maDummyBaseName
).append( ++mnIndex
).makeStringAndClear();
124 while( maCtrlNames
.count( aCtrlName
) > 0 );
125 maCtrlNames
.insert( aCtrlName
);
129 // ----------------------------------------------------------------------------
131 /** Functor that inserts the name of a control into a VbaControlNamesSet. */
132 struct VbaControlNameInserter
135 VbaControlNamesSet
& mrCtrlNames
;
136 inline explicit VbaControlNameInserter( VbaControlNamesSet
& rCtrlNames
) : mrCtrlNames( rCtrlNames
) {}
137 inline void operator()( const VbaFormControl
& rControl
) { mrCtrlNames
.insertName( rControl
); }
140 // ----------------------------------------------------------------------------
142 /** A dummy invisible form control (fixed label without text) that is used to
143 separate two groups of option buttons.
145 class VbaDummyFormControl
: public VbaFormControl
148 explicit VbaDummyFormControl( const OUString
& rName
);
151 VbaDummyFormControl::VbaDummyFormControl( const OUString
& rName
)
153 mxSiteModel
.reset( new VbaSiteModel
);
154 mxSiteModel
->importProperty( XML_Name
, rName
);
155 mxSiteModel
->importProperty( XML_VariousPropertyBits
, OUString( sal_Unicode( '0' ) ) );
157 mxCtrlModel
.reset( new AxLabelModel
);
158 mxCtrlModel
->setAwtModelMode();
159 mxCtrlModel
->importProperty( XML_Size
, "10;10" );
164 // ============================================================================
166 VbaSiteModel::VbaSiteModel() :
169 mnHelpContextId( 0 ),
170 mnFlags( VBA_SITE_DEFFLAGS
),
173 mnClassIdOrCache( VBA_SITE_UNKNOWN
),
178 VbaSiteModel::~VbaSiteModel()
182 void VbaSiteModel::importProperty( sal_Int32 nPropId
, const OUString
& rValue
)
186 case XML_Name
: maName
= rValue
; break;
187 case XML_Tag
: maTag
= rValue
; break;
188 case XML_VariousPropertyBits
: mnFlags
= AttributeConversion::decodeUnsigned( rValue
); break;
192 bool VbaSiteModel::importBinaryModel( BinaryInputStream
& rInStrm
)
194 AxBinaryPropertyReader
aReader( rInStrm
);
195 aReader
.readStringProperty( maName
);
196 aReader
.readStringProperty( maTag
);
197 aReader
.readIntProperty
< sal_Int32
>( mnId
);
198 aReader
.readIntProperty
< sal_Int32
>( mnHelpContextId
);
199 aReader
.readIntProperty
< sal_uInt32
>( mnFlags
);
200 aReader
.readIntProperty
< sal_uInt32
>( mnStreamLen
);
201 aReader
.readIntProperty
< sal_Int16
>( mnTabIndex
);
202 aReader
.readIntProperty
< sal_uInt16
>( mnClassIdOrCache
);
203 aReader
.readPairProperty( maPos
);
204 aReader
.readIntProperty
< sal_uInt16
>( mnGroupId
);
205 aReader
.skipUndefinedProperty();
206 aReader
.readStringProperty( maToolTip
);
207 aReader
.skipStringProperty(); // license key
208 aReader
.readStringProperty( maControlSource
);
209 aReader
.readStringProperty( maRowSource
);
210 return aReader
.finalizeImport();
213 void VbaSiteModel::moveRelative( const AxPairData
& rDistance
)
215 maPos
.first
+= rDistance
.first
;
216 maPos
.second
+= rDistance
.second
;
219 bool VbaSiteModel::isContainer() const
221 return !getFlag( mnFlags
, VBA_SITE_OSTREAM
);
224 sal_uInt32
VbaSiteModel::getStreamLength() const
226 return isContainer() ? 0 : mnStreamLen
;
229 OUString
VbaSiteModel::getSubStorageName() const
233 OUStringBuffer aBuffer
;
234 aBuffer
.append( sal_Unicode( 'i' ) );
236 aBuffer
.append( sal_Unicode( '0' ) );
237 aBuffer
.append( mnId
);
238 return aBuffer
.makeStringAndClear();
243 ControlModelRef
VbaSiteModel::createControlModel( const AxClassTable
& rClassTable
) const
245 ControlModelRef xCtrlModel
;
247 sal_Int32 nTypeIndex
= static_cast< sal_Int32
>( mnClassIdOrCache
& VBA_SITE_INDEXMASK
);
248 if( !getFlag( mnClassIdOrCache
, VBA_SITE_CLASSIDINDEX
) )
252 case VBA_SITE_COMMANDBUTTON
: xCtrlModel
.reset( new AxCommandButtonModel
); break;
253 case VBA_SITE_LABEL
: xCtrlModel
.reset( new AxLabelModel
); break;
254 case VBA_SITE_IMAGE
: xCtrlModel
.reset( new AxImageModel
); break;
255 case VBA_SITE_TOGGLEBUTTON
: xCtrlModel
.reset( new AxToggleButtonModel
); break;
256 case VBA_SITE_CHECKBOX
: xCtrlModel
.reset( new AxCheckBoxModel
); break;
257 case VBA_SITE_OPTIONBUTTON
: xCtrlModel
.reset( new AxOptionButtonModel
); break;
258 case VBA_SITE_TEXTBOX
: xCtrlModel
.reset( new AxTextBoxModel
); break;
259 case VBA_SITE_LISTBOX
: xCtrlModel
.reset( new AxListBoxModel
); break;
260 case VBA_SITE_COMBOBOX
: xCtrlModel
.reset( new AxComboBoxModel
); break;
261 case VBA_SITE_SPINBUTTON
: xCtrlModel
.reset( new AxSpinButtonModel
); break;
262 case VBA_SITE_SCROLLBAR
: xCtrlModel
.reset( new AxScrollBarModel
); break;
263 case VBA_SITE_TABSTRIP
: xCtrlModel
.reset( new AxTabStripModel
);
265 case VBA_SITE_FRAME
: xCtrlModel
.reset( new AxFrameModel
); break;
266 case VBA_SITE_MULTIPAGE
: xCtrlModel
.reset( new AxMultiPageModel
);
268 case VBA_SITE_FORM
: xCtrlModel
.reset( new AxPageModel
);
270 default: OSL_FAIL( "VbaSiteModel::createControlModel - unknown type index" );
275 const OUString
* pGuid
= ContainerHelper::getVectorElement( rClassTable
, nTypeIndex
);
276 OSL_ENSURE( pGuid
, "VbaSiteModel::createControlModel - invalid class table index" );
279 if( *pGuid
== COMCTL_GUID_SCROLLBAR_60
)
280 xCtrlModel
.reset( new ComCtlScrollBarModel( 6 ) );
281 else if( *pGuid
== COMCTL_GUID_PROGRESSBAR_50
)
282 xCtrlModel
.reset( new ComCtlProgressBarModel( 5 ) );
283 else if( *pGuid
== COMCTL_GUID_PROGRESSBAR_60
)
284 xCtrlModel
.reset( new ComCtlProgressBarModel( 6 ) );
288 if( xCtrlModel
.get() )
290 // user form controls are AWT models
291 xCtrlModel
->setAwtModelMode();
293 // check that container model matches container flag in site data
294 bool bModelIsContainer
= dynamic_cast< const AxContainerModelBase
* >( xCtrlModel
.get() ) != 0;
295 bool bTypeMatch
= bModelIsContainer
== isContainer();
296 OSL_ENSURE( bTypeMatch
, "VbaSiteModel::createControlModel - container type does not match container flag" );
303 void VbaSiteModel::convertProperties( PropertyMap
& rPropMap
,
304 const ControlConverter
& rConv
, ApiControlType eCtrlType
, sal_Int32 nCtrlIndex
) const
306 rPropMap
.setProperty( PROP_Name
, maName
);
307 rPropMap
.setProperty( PROP_Tag
, maTag
);
309 if( eCtrlType
!= API_CONTROL_DIALOG
)
311 rPropMap
.setProperty( PROP_HelpText
, maToolTip
);
312 rPropMap
.setProperty( PROP_EnableVisible
, getFlag( mnFlags
, VBA_SITE_VISIBLE
) );
313 // we need to set the passed control index to make option button groups work
314 if( (0 <= nCtrlIndex
) && (nCtrlIndex
<= SAL_MAX_INT16
) )
315 rPropMap
.setProperty( PROP_TabIndex
, static_cast< sal_Int16
>( nCtrlIndex
) );
316 // progress bar and group box support TabIndex, but not Tabstop...
317 if( (eCtrlType
!= API_CONTROL_PROGRESSBAR
) && (eCtrlType
!= API_CONTROL_GROUPBOX
) && (eCtrlType
!= API_CONTROL_FRAME
) && (eCtrlType
!= API_CONTROL_PAGE
) )
318 rPropMap
.setProperty( PROP_Tabstop
, getFlag( mnFlags
, VBA_SITE_TABSTOP
) );
319 rConv
.convertPosition( rPropMap
, maPos
);
323 // ============================================================================
325 VbaFormControl::VbaFormControl()
329 VbaFormControl::~VbaFormControl()
333 void VbaFormControl::importModelOrStorage( BinaryInputStream
& rInStrm
, StorageBase
& rStrg
, const AxClassTable
& rClassTable
)
335 if( mxSiteModel
.get() )
337 if( mxSiteModel
->isContainer() )
339 StorageRef xSubStrg
= rStrg
.openSubStorage( mxSiteModel
->getSubStorageName(), false );
340 OSL_ENSURE( xSubStrg
.get(), "VbaFormControl::importModelOrStorage - cannot find storage for embedded control" );
342 importStorage( *xSubStrg
, rClassTable
);
344 else if( !rInStrm
.isEof() )
346 sal_Int64 nNextStrmPos
= rInStrm
.tell() + mxSiteModel
->getStreamLength();
347 importControlModel( rInStrm
, rClassTable
);
348 rInStrm
.seek( nNextStrmPos
);
353 OUString
VbaFormControl::getControlName() const
355 return mxSiteModel
.get() ? mxSiteModel
->getName() : OUString();
358 void VbaFormControl::createAndConvert( sal_Int32 nCtrlIndex
,
359 const Reference
< XNameContainer
>& rxParentNC
, const ControlConverter
& rConv
) const
361 if( rxParentNC
.is() && mxSiteModel
.get() && mxCtrlModel
.get() ) try
363 // create the control model
364 OUString aServiceName
= mxCtrlModel
->getServiceName();
365 Reference
< XMultiServiceFactory
> xModelFactory( rxParentNC
, UNO_QUERY_THROW
);
366 Reference
< XControlModel
> xCtrlModel( xModelFactory
->createInstance( aServiceName
), UNO_QUERY_THROW
);
368 // convert all properties and embedded controls
369 if( convertProperties( xCtrlModel
, rConv
, nCtrlIndex
) )
371 // insert into parent container
372 const OUString
& rCtrlName
= mxSiteModel
->getName();
373 OSL_ENSURE( !rxParentNC
->hasByName( rCtrlName
), "VbaFormControl::createAndConvert - multiple controls with equal name" );
374 ContainerHelper::insertByName( rxParentNC
, rCtrlName
, Any( xCtrlModel
) );
377 catch(const Exception
& )
382 // protected ------------------------------------------------------------------
384 void VbaFormControl::importControlModel( BinaryInputStream
& rInStrm
, const AxClassTable
& rClassTable
)
386 createControlModel( rClassTable
);
387 if( mxCtrlModel
.get() )
388 mxCtrlModel
->importBinaryModel( rInStrm
);
391 void VbaFormControl::importStorage( StorageBase
& rStrg
, const AxClassTable
& rClassTable
)
393 createControlModel( rClassTable
);
394 AxContainerModelBase
* pContainerModel
= dynamic_cast< AxContainerModelBase
* >( mxCtrlModel
.get() );
395 OSL_ENSURE( pContainerModel
, "VbaFormControl::importStorage - missing container control model" );
396 if( pContainerModel
)
398 /* Open the 'f' stream containing the model of this control and a list
399 of site models for all child controls. */
400 BinaryXInputStream
aFStrm( rStrg
.openInputStream( "f" ), true );
401 OSL_ENSURE( !aFStrm
.isEof(), "VbaFormControl::importStorage - missing 'f' stream" );
403 /* Read the properties of this container control and the class table
404 (into the maClassTable vector) containing a list of GUIDs for
405 exotic embedded controls. */
406 if( !aFStrm
.isEof() && pContainerModel
->importBinaryModel( aFStrm
) && pContainerModel
->importClassTable( aFStrm
, maClassTable
) )
408 /* Read the site models of all embedded controls (this fills the
409 maControls vector). Ignore failure of importSiteModels() but
410 try to import as much controls as possible. */
411 importEmbeddedSiteModels( aFStrm
);
412 /* Open the 'o' stream containing models of embedded simple
413 controls. Stream may be empty or missing, if this control
414 contains no controls or only container controls. */
415 BinaryXInputStream
aOStrm( rStrg
.openInputStream( "o" ), true );
417 /* Iterate over all embedded controls, import model from 'o'
418 stream (for embedded simple controls) or from the substorage
419 (for embedded container controls). */
420 maControls
.forEachMem( &VbaFormControl::importModelOrStorage
,
421 ::boost::ref( aOStrm
), ::boost::ref( rStrg
), ::boost::cref( maClassTable
) );
423 // Special handling for multi-page which has non-standard
424 // containment and additionally needs to re-order Page children
425 if ( pContainerModel
->getControlType() == API_CONTROL_MULTIPAGE
)
427 AxMultiPageModel
* pMultiPage
= dynamic_cast< AxMultiPageModel
* >( pContainerModel
);
430 BinaryXInputStream
aXStrm( rStrg
.openInputStream( "x" ), true );
431 pMultiPage
->importPageAndMultiPageProperties( aXStrm
, maControls
.size() );
433 typedef boost::unordered_map
< sal_uInt32
, ::boost::shared_ptr
< VbaFormControl
> > IdToPageMap
;
434 IdToPageMap idToPage
;
435 VbaFormControlVector::iterator it
= maControls
.begin();
436 VbaFormControlVector::iterator it_end
= maControls
.end();
437 typedef std::vector
< sal_uInt32
> UInt32Array
;
438 AxArrayString sCaptions
;
440 for ( ; it
!= it_end
; ++it
)
442 if ( (*it
)->mxCtrlModel
->getControlType() == API_CONTROL_PAGE
)
444 VbaSiteModelRef xPageSiteRef
= (*it
)->mxSiteModel
;
445 if ( xPageSiteRef
.get() )
446 idToPage
[ xPageSiteRef
->getId() ] = (*it
);
450 AxTabStripModel
* pTabStrip
= static_cast<AxTabStripModel
*> ( (*it
)->mxCtrlModel
.get() );
451 sCaptions
= pTabStrip
->maItems
;
452 pMultiPage
->mnActiveTab
= pTabStrip
->mnListIndex
;
453 pMultiPage
->mnTabStyle
= pTabStrip
->mnTabStyle
;
456 // apply caption/titles to pages
457 UInt32Array::iterator itCtrlId
= pMultiPage
->mnIDs
.begin();
458 UInt32Array::iterator itCtrlId_end
= pMultiPage
->mnIDs
.end();
459 AxArrayString::iterator itCaption
= sCaptions
.begin();
462 // need to sort the controls according to the order of the ids
463 for ( sal_Int32 index
= 1 ; ( sCaptions
.size() == idToPage
.size() ) && itCtrlId
!= itCtrlId_end
; ++itCtrlId
, ++itCaption
, ++index
)
465 IdToPageMap::iterator iter
= idToPage
.find( *itCtrlId
);
466 if ( iter
!= idToPage
.end() )
468 AxPageModel
* pPage
= static_cast<AxPageModel
*> ( iter
->second
->mxCtrlModel
.get() );
470 pPage
->importProperty( XML_Caption
, *itCaption
);
471 maControls
.push_back( iter
->second
);
475 /* Reorder the controls (sorts all option buttons of an option
476 group together), and move all children of all embedded frames
477 (group boxes) to this control (UNO group boxes cannot contain
479 finalizeEmbeddedControls();
484 bool VbaFormControl::convertProperties( const Reference
< XControlModel
>& rxCtrlModel
,
485 const ControlConverter
& rConv
, sal_Int32 nCtrlIndex
) const
487 if( rxCtrlModel
.is() && mxSiteModel
.get() && mxCtrlModel
.get() )
489 const OUString
& rCtrlName
= mxSiteModel
->getName();
490 OSL_ENSURE( !rCtrlName
.isEmpty(), "VbaFormControl::convertProperties - control without name" );
491 if( !rCtrlName
.isEmpty() )
493 // convert all properties
494 PropertyMap aPropMap
;
495 mxSiteModel
->convertProperties( aPropMap
, rConv
, mxCtrlModel
->getControlType(), nCtrlIndex
);
496 rConv
.bindToSources( rxCtrlModel
, mxSiteModel
->getControlSource(), mxSiteModel
->getRowSource() );
497 mxCtrlModel
->convertProperties( aPropMap
, rConv
);
498 mxCtrlModel
->convertSize( aPropMap
, rConv
);
499 PropertySet
aPropSet( rxCtrlModel
);
500 aPropSet
.setProperties( aPropMap
);
502 // create and convert all embedded controls
503 if( !maControls
.empty() ) try
505 Reference
< XNameContainer
> xCtrlModelNC( rxCtrlModel
, UNO_QUERY_THROW
);
506 /* Call conversion for all controls. Pass vector index as new
507 tab order to make option button groups work correctly. */
508 maControls
.forEachMemWithIndex( &VbaFormControl::createAndConvert
,
509 ::boost::cref( xCtrlModelNC
), ::boost::cref( rConv
) );
511 catch(const Exception
& )
513 OSL_FAIL( "VbaFormControl::convertProperties - cannot get control container interface" );
522 // private --------------------------------------------------------------------
524 void VbaFormControl::createControlModel( const AxClassTable
& rClassTable
)
526 // derived classes may have created their own control model
527 if( !mxCtrlModel
&& mxSiteModel
.get() )
528 mxCtrlModel
= mxSiteModel
->createControlModel( rClassTable
);
531 bool VbaFormControl::importSiteModel( BinaryInputStream
& rInStrm
)
533 mxSiteModel
.reset( new VbaSiteModel
);
534 return mxSiteModel
->importBinaryModel( rInStrm
);
537 bool VbaFormControl::importEmbeddedSiteModels( BinaryInputStream
& rInStrm
)
539 sal_uInt64 nAnchorPos
= rInStrm
.tell();
540 sal_uInt32 nSiteCount
, nSiteDataSize
;
541 rInStrm
>> nSiteCount
>> nSiteDataSize
;
542 sal_Int64 nSiteEndPos
= rInStrm
.tell() + nSiteDataSize
;
544 // skip the site info structure
545 sal_uInt32 nSiteIndex
= 0;
546 while( !rInStrm
.isEof() && (nSiteIndex
< nSiteCount
) )
548 rInStrm
.skip( 1 ); // site depth
549 sal_uInt8 nTypeCount
= rInStrm
.readuInt8(); // 'type-or-count' byte
550 if( getFlag( nTypeCount
, VBA_SITEINFO_COUNT
) )
552 /* Count flag is set: the 'type-or-count' byte contains the number
553 of controls in the lower bits, the type specifier follows in
554 the next byte. The type specifier should always be 1 according
555 to the specification. */
557 nSiteIndex
+= (nTypeCount
& VBA_SITEINFO_MASK
);
561 /* Count flag is not set: the 'type-or-count' byte contains the
562 type specifier of *one* control in the lower bits (this type
563 should be 1, see above). */
567 // align the stream to 32bit, relative to start of entire site info
568 rInStrm
.alignToBlock( 4, nAnchorPos
);
570 // import the site models for all embedded controls
572 bool bValid
= !rInStrm
.isEof();
573 for( nSiteIndex
= 0; bValid
&& (nSiteIndex
< nSiteCount
); ++nSiteIndex
)
575 VbaFormControlRef
xControl( new VbaFormControl
);
576 maControls
.push_back( xControl
);
577 bValid
= xControl
->importSiteModel( rInStrm
);
580 rInStrm
.seek( nSiteEndPos
);
584 void VbaFormControl::finalizeEmbeddedControls()
586 /* This function performs two tasks:
588 1) Reorder the controls appropriately (sort all option buttons of an
589 option group together to make grouping work).
590 2) Move all children of all embedded frames (group boxes) to this
591 control (UNO group boxes cannot contain other controls).
594 // first, sort all controls by original tab index
595 ::std::sort( maControls
.begin(), maControls
.end(), &compareByTabIndex
);
597 /* Collect the programmatical names of all embedded controls (needed to be
598 able to set unused names to new dummy controls created below). Also
599 collect the names of all children of embedded frames (group boxes).
600 Luckily, names of controls must be unique in the entire form, not just
601 in the current container. */
602 VbaControlNamesSet aControlNames
;
603 VbaControlNameInserter
aInserter( aControlNames
);
604 maControls
.forEach( aInserter
);
605 for( VbaFormControlVector::iterator aIt
= maControls
.begin(), aEnd
= maControls
.end(); aIt
!= aEnd
; ++aIt
)
606 if( (*aIt
)->mxCtrlModel
.get() && ((*aIt
)->mxCtrlModel
->getControlType() == API_CONTROL_GROUPBOX
) )
607 (*aIt
)->maControls
.forEach( aInserter
);
609 /* Reprocess the sorted list and collect all option button controls that
610 are part of the same option group (determined by group name). All
611 controls will be stored in a vector of vectors, that collects every
612 option button group in one vector element, and other controls between
613 these option groups (or leading or trailing controls) in other vector
614 elements. If an option button group follows another group, a dummy
615 separator control has to be inserted. */
616 typedef RefVector
< VbaFormControlVector
> VbaFormControlVectorVector
;
617 VbaFormControlVectorVector aControlGroups
;
619 typedef RefMap
< OUString
, VbaFormControlVector
> VbaFormControlVectorMap
;
620 VbaFormControlVectorMap aOptionGroups
;
622 typedef VbaFormControlVectorMap::mapped_type VbaFormControlVectorRef
;
623 bool bLastWasOptionButton
= false;
624 for( VbaFormControlVector::iterator aIt
= maControls
.begin(), aEnd
= maControls
.end(); aIt
!= aEnd
; ++aIt
)
626 VbaFormControlRef xControl
= *aIt
;
627 const ControlModelBase
* pCtrlModel
= xControl
->mxCtrlModel
.get();
629 if( const AxOptionButtonModel
* pOptButtonModel
= dynamic_cast< const AxOptionButtonModel
* >( pCtrlModel
) )
631 // check if a new option group needs to be created
632 const OUString
& rGroupName
= pOptButtonModel
->getGroupName();
633 VbaFormControlVectorRef
& rxOptionGroup
= aOptionGroups
[ rGroupName
];
636 /* If last control was an option button too, we have two
637 option groups following each other, so a dummy separator
638 control is needed. */
639 if( bLastWasOptionButton
)
641 VbaFormControlVectorRef
xDummyGroup( new VbaFormControlVector
);
642 aControlGroups
.push_back( xDummyGroup
);
643 OUString aName
= aControlNames
.generateDummyName();
644 VbaFormControlRef
xDummyControl( new VbaDummyFormControl( aName
) );
645 xDummyGroup
->push_back( xDummyControl
);
647 rxOptionGroup
.reset( new VbaFormControlVector
);
648 aControlGroups
.push_back( rxOptionGroup
);
650 /* Append the option button to the control group (which is now
651 referred by the vector aControlGroups and by the map
653 rxOptionGroup
->push_back( xControl
);
654 bLastWasOptionButton
= true;
658 // open a new control group, if the last group is an option group
659 if( bLastWasOptionButton
|| aControlGroups
.empty() )
661 VbaFormControlVectorRef
xControlGroup( new VbaFormControlVector
);
662 aControlGroups
.push_back( xControlGroup
);
664 // append the control to the last control group
665 VbaFormControlVector
& rLastGroup
= *aControlGroups
.back();
666 rLastGroup
.push_back( xControl
);
667 bLastWasOptionButton
= false;
669 // if control is a group box, move all its children to this control
670 if( pCtrlModel
&& (pCtrlModel
->getControlType() == API_CONTROL_GROUPBOX
) )
672 /* Move all embedded controls of the group box relative to the
673 position of the group box. */
674 xControl
->moveEmbeddedToAbsoluteParent();
675 /* Insert all children of the group box into the last control
676 group (following the group box). */
677 rLastGroup
.insert( rLastGroup
.end(), xControl
->maControls
.begin(), xControl
->maControls
.end() );
678 xControl
->maControls
.clear();
679 // check if last control of the group box is an option button
680 bLastWasOptionButton
= dynamic_cast< const AxOptionButtonModel
* >( rLastGroup
.back()->mxCtrlModel
.get() ) != 0;
685 // flatten the vector of vectors of form controls to a single vector
687 for( VbaFormControlVectorVector::iterator aIt
= aControlGroups
.begin(), aEnd
= aControlGroups
.end(); aIt
!= aEnd
; ++aIt
)
688 maControls
.insert( maControls
.end(), (*aIt
)->begin(), (*aIt
)->end() );
691 void VbaFormControl::moveRelative( const AxPairData
& rDistance
)
693 if( mxSiteModel
.get() )
694 mxSiteModel
->moveRelative( rDistance
);
697 void VbaFormControl::moveEmbeddedToAbsoluteParent()
699 if( mxSiteModel
.get() && !maControls
.empty() )
701 // distance to move is equal to position of this control in its parent
702 AxPairData aDistance
= mxSiteModel
->getPosition();
704 /* For group boxes: add half of the font height to Y position (VBA
705 positions relative to frame border line, not to 'top' of frame). */
706 const AxFontDataModel
* pFontModel
= dynamic_cast< const AxFontDataModel
* >( mxCtrlModel
.get() );
707 if( pFontModel
&& (pFontModel
->getControlType() == API_CONTROL_GROUPBOX
) )
709 // convert points to 1/100 mm (1 pt = 1/72 inch = 2.54/72 cm = 2540/72 1/100 mm)
710 sal_Int32 nFontHeight
= static_cast< sal_Int32
>( pFontModel
->getFontHeight() * 2540 / 72 );
711 aDistance
.second
+= nFontHeight
/ 2;
714 // move the embedded controls
715 maControls
.forEachMem( &VbaFormControl::moveRelative
, ::boost::cref( aDistance
) );
719 bool VbaFormControl::compareByTabIndex( const VbaFormControlRef
& rxLeft
, const VbaFormControlRef
& rxRight
)
721 // sort controls without model to the end
722 sal_Int32 nLeftTabIndex
= rxLeft
->mxSiteModel
.get() ? rxLeft
->mxSiteModel
->getTabIndex() : SAL_MAX_INT32
;
723 sal_Int32 nRightTabIndex
= rxRight
->mxSiteModel
.get() ? rxRight
->mxSiteModel
->getTabIndex() : SAL_MAX_INT32
;
724 return nLeftTabIndex
< nRightTabIndex
;
727 // ============================================================================
731 OUString
lclGetQuotedString( const OUString
& rCodeLine
)
733 OUStringBuffer aBuffer
;
734 sal_Int32 nLen
= rCodeLine
.getLength();
735 if( (nLen
> 0) && (rCodeLine
[ 0 ] == '"') )
737 bool bExitLoop
= false;
738 for( sal_Int32 nIndex
= 1; !bExitLoop
&& (nIndex
< nLen
); ++nIndex
)
740 sal_Unicode cChar
= rCodeLine
[ nIndex
];
741 // exit on closing quote char (but check on double quote chars)
742 bExitLoop
= (cChar
== '"') && ((nIndex
+ 1 == nLen
) || (rCodeLine
[ nIndex
+ 1 ] != '"'));
745 aBuffer
.append( cChar
);
746 // skip second quote char
752 return aBuffer
.makeStringAndClear();
755 bool lclEatWhitespace( OUString
& rCodeLine
)
757 sal_Int32 nIndex
= 0;
758 while( (nIndex
< rCodeLine
.getLength()) && ((rCodeLine
[ nIndex
] == ' ') || (rCodeLine
[ nIndex
] == '\t')) )
762 rCodeLine
= rCodeLine
.copy( nIndex
);
768 bool lclEatKeyword( OUString
& rCodeLine
, const OUString
& rKeyword
)
770 if( rCodeLine
.matchIgnoreAsciiCase( rKeyword
) )
772 rCodeLine
= rCodeLine
.copy( rKeyword
.getLength() );
773 // success, if code line ends after keyword, or if whitespace follows
774 return rCodeLine
.isEmpty() || lclEatWhitespace( rCodeLine
);
781 // ----------------------------------------------------------------------------
783 VbaUserForm::VbaUserForm( const Reference
< XComponentContext
>& rxContext
,
784 const Reference
< XModel
>& rxDocModel
, const GraphicHelper
& rGraphicHelper
, bool bDefaultColorBgr
) :
785 mxContext( rxContext
),
786 mxDocModel( rxDocModel
),
787 maConverter( rxDocModel
, rGraphicHelper
, bDefaultColorBgr
)
789 OSL_ENSURE( mxContext
.is(), "VbaUserForm::VbaUserForm - missing component context" );
790 OSL_ENSURE( mxDocModel
.is(), "VbaUserForm::VbaUserForm - missing document model" );
793 void VbaUserForm::importForm( const Reference
< XNameContainer
>& rxDialogLib
,
794 StorageBase
& rVbaFormStrg
, const OUString
& rModuleName
, rtl_TextEncoding eTextEnc
)
796 OSL_ENSURE( rxDialogLib
.is(), "VbaUserForm::importForm - missing dialog library" );
797 if( !mxContext
.is() || !mxDocModel
.is() || !rxDialogLib
.is() )
800 // check that the '03VBFrame' stream exists, this is required for forms
801 BinaryXInputStream
aInStrm( rVbaFormStrg
.openInputStream( "\003VBFrame" ), true );
802 OSL_ENSURE( !aInStrm
.isEof(), "VbaUserForm::importForm - missing \\003VBFrame stream" );
803 if( aInStrm
.isEof() )
806 // scan for the line 'Begin {GUID} <FormName>'
807 TextInputStream
aFrameTextStrm( mxContext
, aInStrm
, eTextEnc
);
808 const OUString aBegin
= "Begin";
810 bool bBeginFound
= false;
811 while( !bBeginFound
&& !aFrameTextStrm
.isEof() )
813 aLine
= aFrameTextStrm
.readLine().trim();
814 bBeginFound
= lclEatKeyword( aLine
, aBegin
);
816 // check for the specific GUID that represents VBA forms
817 if( !bBeginFound
|| !lclEatKeyword( aLine
, "{C62A69F0-16DC-11CE-9E98-00AA00574A4F}" ) )
820 // remaining line is the form name
821 OUString aFormName
= aLine
.trim();
822 OSL_ENSURE( !aFormName
.isEmpty(), "VbaUserForm::importForm - missing form name" );
823 OSL_ENSURE( rModuleName
.equalsIgnoreAsciiCase( aFormName
), "VbaUserForm::importFrameStream - form and module name mismatch" );
824 if( aFormName
.isEmpty() )
825 aFormName
= rModuleName
;
826 if( aFormName
.isEmpty() )
828 mxSiteModel
.reset( new VbaSiteModel
);
829 mxSiteModel
->importProperty( XML_Name
, aFormName
);
831 // read the form properties (caption is contained in this '03VBFrame' stream, not in the 'f' stream)
832 mxCtrlModel
.reset( new AxUserFormModel
);
833 OUString aKey
, aValue
;
834 bool bExitLoop
= false;
835 while( !bExitLoop
&& !aFrameTextStrm
.isEof() )
837 aLine
= aFrameTextStrm
.readLine().trim();
838 bExitLoop
= aLine
.equalsIgnoreAsciiCase( "End" );
839 if( !bExitLoop
&& VbaHelper::extractKeyValue( aKey
, aValue
, aLine
) )
841 if( aKey
.equalsIgnoreAsciiCase( "Caption" ) )
842 mxCtrlModel
->importProperty( XML_Caption
, lclGetQuotedString( aValue
) );
843 else if( aKey
.equalsIgnoreAsciiCase( "Tag" ) )
844 mxSiteModel
->importProperty( XML_Tag
, lclGetQuotedString( aValue
) );
848 // use generic container control functionality to import the embedded controls
849 importStorage( rVbaFormStrg
, AxClassTable() );
853 // create the dialog model
854 OUString aServiceName
= mxCtrlModel
->getServiceName();
855 Reference
< XMultiServiceFactory
> xFactory( mxContext
->getServiceManager(), UNO_QUERY_THROW
);
856 Reference
< XControlModel
> xDialogModel( xFactory
->createInstance( aServiceName
), UNO_QUERY_THROW
);
857 Reference
< XNameContainer
> xDialogNC( xDialogModel
, UNO_QUERY_THROW
);
859 // convert properties and embedded controls
860 if( convertProperties( xDialogModel
, maConverter
, 0 ) )
862 // export the dialog to XML and insert it into the dialog library
863 Reference
< XInputStreamProvider
> xDialogSource( ::xmlscript::exportDialogModel( xDialogNC
, mxContext
, mxDocModel
), UNO_SET_THROW
);
864 OSL_ENSURE( !rxDialogLib
->hasByName( aFormName
), "VbaUserForm::importForm - multiple dialogs with equal name" );
865 ContainerHelper::insertByName( rxDialogLib
, aFormName
, Any( xDialogSource
) );
868 catch(const Exception
& )
873 // ============================================================================
878 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */