bump product version to 5.0.4.1
[LibreOffice.git] / oox / source / ole / vbacontrol.cxx
blob82bc27d9606fe56f352f38ffc6613b8e37e358ff
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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"
22 #include <algorithm>
23 #include <set>
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 <osl/diagnose.h>
30 #include <rtl/ustrbuf.hxx>
31 #include <xmlscript/xmldlg_imexp.hxx>
32 #include "oox/helper/attributelist.hxx"
33 #include "oox/helper/binaryinputstream.hxx"
34 #include "oox/helper/containerhelper.hxx"
35 #include "oox/helper/propertymap.hxx"
36 #include "oox/helper/propertyset.hxx"
37 #include "oox/helper/storagebase.hxx"
38 #include "oox/helper/textinputstream.hxx"
39 #include "oox/ole/vbahelper.hxx"
40 #include <unordered_map>
42 namespace oox {
43 namespace ole {
45 using namespace ::com::sun::star::awt;
46 using namespace ::com::sun::star::container;
47 using namespace ::com::sun::star::frame;
48 using namespace ::com::sun::star::io;
49 using namespace ::com::sun::star::lang;
50 using namespace ::com::sun::star::uno;
52 namespace {
54 const sal_uInt16 VBA_SITE_CLASSIDINDEX = 0x8000;
55 const sal_uInt16 VBA_SITE_INDEXMASK = 0x7FFF;
56 const sal_uInt16 VBA_SITE_FORM = 7;
57 const sal_uInt16 VBA_SITE_IMAGE = 12;
58 const sal_uInt16 VBA_SITE_FRAME = 14;
59 const sal_uInt16 VBA_SITE_SPINBUTTON = 16;
60 const sal_uInt16 VBA_SITE_COMMANDBUTTON = 17;
61 const sal_uInt16 VBA_SITE_TABSTRIP = 18;
62 const sal_uInt16 VBA_SITE_LABEL = 21;
63 const sal_uInt16 VBA_SITE_TEXTBOX = 23;
64 const sal_uInt16 VBA_SITE_LISTBOX = 24;
65 const sal_uInt16 VBA_SITE_COMBOBOX = 25;
66 const sal_uInt16 VBA_SITE_CHECKBOX = 26;
67 const sal_uInt16 VBA_SITE_OPTIONBUTTON = 27;
68 const sal_uInt16 VBA_SITE_TOGGLEBUTTON = 28;
69 const sal_uInt16 VBA_SITE_SCROLLBAR = 47;
70 const sal_uInt16 VBA_SITE_MULTIPAGE = 57;
71 const sal_uInt16 VBA_SITE_UNKNOWN = 0x7FFF;
73 const sal_uInt32 VBA_SITE_TABSTOP = 0x00000001;
74 const sal_uInt32 VBA_SITE_VISIBLE = 0x00000002;
75 const sal_uInt32 VBA_SITE_OSTREAM = 0x00000010;
76 const sal_uInt32 VBA_SITE_DEFFLAGS = 0x00000033;
78 const sal_uInt8 VBA_SITEINFO_COUNT = 0x80;
79 const sal_uInt8 VBA_SITEINFO_MASK = 0x7F;
81 /** Collects names of all controls in a user form or container control. Allows
82 to generate unused names for dummy controls separating option groups.
84 class VbaControlNamesSet
86 public:
87 explicit VbaControlNamesSet();
89 /** Inserts the name of the passed control. */
90 void insertName( const VbaFormControl& rControl );
91 /** Returns a name that is not contained in this set. */
92 OUString generateDummyName();
94 private:
95 typedef ::std::set< OUString > OUStringSet;
96 OUStringSet maCtrlNames;
97 const OUString maDummyBaseName;
98 sal_Int32 mnIndex;
101 VbaControlNamesSet::VbaControlNamesSet() :
102 maDummyBaseName( "DummyGroupSep" ),
103 mnIndex( 0 )
107 void VbaControlNamesSet::insertName( const VbaFormControl& rControl )
109 OUString aName = rControl.getControlName();
110 if( !aName.isEmpty() )
111 maCtrlNames.insert( aName );
114 OUString VbaControlNamesSet::generateDummyName()
116 OUString aCtrlName;
119 aCtrlName = OUStringBuffer( maDummyBaseName ).append( ++mnIndex ).makeStringAndClear();
121 while( maCtrlNames.count( aCtrlName ) > 0 );
122 maCtrlNames.insert( aCtrlName );
123 return aCtrlName;
126 /** Functor that inserts the name of a control into a VbaControlNamesSet. */
127 struct VbaControlNameInserter
129 public:
130 VbaControlNamesSet& mrCtrlNames;
131 inline explicit VbaControlNameInserter( VbaControlNamesSet& rCtrlNames ) : mrCtrlNames( rCtrlNames ) {}
132 inline void operator()( const VbaFormControl& rControl ) { mrCtrlNames.insertName( rControl ); }
135 /** A dummy invisible form control (fixed label without text) that is used to
136 separate two groups of option buttons.
138 class VbaDummyFormControl : public VbaFormControl
140 public:
141 explicit VbaDummyFormControl( const OUString& rName );
144 VbaDummyFormControl::VbaDummyFormControl( const OUString& rName )
146 mxSiteModel.reset( new VbaSiteModel );
147 mxSiteModel->importProperty( XML_Name, rName );
148 mxSiteModel->importProperty( XML_VariousPropertyBits, OUString( '0' ) );
150 mxCtrlModel.reset( new AxLabelModel );
151 mxCtrlModel->setAwtModelMode();
152 mxCtrlModel->importProperty( XML_Size, "10;10" );
155 } // namespace
157 VbaSiteModel::VbaSiteModel() :
158 maPos( 0, 0 ),
159 mnId( 0 ),
160 mnHelpContextId( 0 ),
161 mnFlags( VBA_SITE_DEFFLAGS ),
162 mnStreamLen( 0 ),
163 mnTabIndex( -1 ),
164 mnClassIdOrCache( VBA_SITE_UNKNOWN ),
165 mnGroupId( 0 )
169 VbaSiteModel::~VbaSiteModel()
173 void VbaSiteModel::importProperty( sal_Int32 nPropId, const OUString& rValue )
175 switch( nPropId )
177 case XML_Name: maName = rValue; break;
178 case XML_Tag: maTag = rValue; break;
179 case XML_VariousPropertyBits: mnFlags = AttributeConversion::decodeUnsigned( rValue ); break;
183 bool VbaSiteModel::importBinaryModel( BinaryInputStream& rInStrm )
185 AxBinaryPropertyReader aReader( rInStrm );
186 aReader.readStringProperty( maName );
187 aReader.readStringProperty( maTag );
188 aReader.readIntProperty< sal_Int32 >( mnId );
189 aReader.readIntProperty< sal_Int32 >( mnHelpContextId );
190 aReader.readIntProperty< sal_uInt32 >( mnFlags );
191 aReader.readIntProperty< sal_uInt32 >( mnStreamLen );
192 aReader.readIntProperty< sal_Int16 >( mnTabIndex );
193 aReader.readIntProperty< sal_uInt16 >( mnClassIdOrCache );
194 aReader.readPairProperty( maPos );
195 aReader.readIntProperty< sal_uInt16 >( mnGroupId );
196 aReader.skipUndefinedProperty();
197 aReader.readStringProperty( maToolTip );
198 aReader.skipStringProperty(); // license key
199 aReader.readStringProperty( maControlSource );
200 aReader.readStringProperty( maRowSource );
201 return aReader.finalizeImport();
204 void VbaSiteModel::moveRelative( const AxPairData& rDistance )
206 maPos.first += rDistance.first;
207 maPos.second += rDistance.second;
210 bool VbaSiteModel::isContainer() const
212 return !getFlag( mnFlags, VBA_SITE_OSTREAM );
215 sal_uInt32 VbaSiteModel::getStreamLength() const
217 return isContainer() ? 0 : mnStreamLen;
220 OUString VbaSiteModel::getSubStorageName() const
222 if( mnId >= 0 )
224 OUStringBuffer aBuffer;
225 aBuffer.append( 'i' );
226 if( mnId < 10 )
227 aBuffer.append( '0' );
228 aBuffer.append( mnId );
229 return aBuffer.makeStringAndClear();
231 return OUString();
234 ControlModelRef VbaSiteModel::createControlModel( const AxClassTable& rClassTable ) const
236 ControlModelRef xCtrlModel;
238 sal_Int32 nTypeIndex = static_cast< sal_Int32 >( mnClassIdOrCache & VBA_SITE_INDEXMASK );
239 if( !getFlag( mnClassIdOrCache, VBA_SITE_CLASSIDINDEX ) )
241 switch( nTypeIndex )
243 case VBA_SITE_COMMANDBUTTON: xCtrlModel.reset( new AxCommandButtonModel ); break;
244 case VBA_SITE_LABEL: xCtrlModel.reset( new AxLabelModel ); break;
245 case VBA_SITE_IMAGE: xCtrlModel.reset( new AxImageModel ); break;
246 case VBA_SITE_TOGGLEBUTTON: xCtrlModel.reset( new AxToggleButtonModel ); break;
247 case VBA_SITE_CHECKBOX: xCtrlModel.reset( new AxCheckBoxModel ); break;
248 case VBA_SITE_OPTIONBUTTON: xCtrlModel.reset( new AxOptionButtonModel ); break;
249 case VBA_SITE_TEXTBOX: xCtrlModel.reset( new AxTextBoxModel ); break;
250 case VBA_SITE_LISTBOX: xCtrlModel.reset( new AxListBoxModel ); break;
251 case VBA_SITE_COMBOBOX: xCtrlModel.reset( new AxComboBoxModel ); break;
252 case VBA_SITE_SPINBUTTON: xCtrlModel.reset( new AxSpinButtonModel ); break;
253 case VBA_SITE_SCROLLBAR: xCtrlModel.reset( new AxScrollBarModel ); break;
254 case VBA_SITE_TABSTRIP: xCtrlModel.reset( new AxTabStripModel );
255 break;
256 case VBA_SITE_FRAME: xCtrlModel.reset( new AxFrameModel ); break;
257 case VBA_SITE_MULTIPAGE: xCtrlModel.reset( new AxMultiPageModel );
258 break;
259 case VBA_SITE_FORM: xCtrlModel.reset( new AxPageModel );
260 break;
261 default: OSL_FAIL( "VbaSiteModel::createControlModel - unknown type index" );
264 else
266 const OUString* pGuid = ContainerHelper::getVectorElement( rClassTable, nTypeIndex );
267 OSL_ENSURE( pGuid, "VbaSiteModel::createControlModel - invalid class table index" );
268 if( pGuid )
270 if( *pGuid == COMCTL_GUID_SCROLLBAR_60 )
271 xCtrlModel.reset( new ComCtlScrollBarModel( 6 ) );
272 else if( *pGuid == COMCTL_GUID_PROGRESSBAR_50 )
273 xCtrlModel.reset( new ComCtlProgressBarModel( 5 ) );
274 else if( *pGuid == COMCTL_GUID_PROGRESSBAR_60 )
275 xCtrlModel.reset( new ComCtlProgressBarModel( 6 ) );
279 if( xCtrlModel.get() )
281 // user form controls are AWT models
282 xCtrlModel->setAwtModelMode();
284 // check that container model matches container flag in site data
285 bool bModelIsContainer = dynamic_cast< const AxContainerModelBase* >( xCtrlModel.get() ) != 0;
286 bool bTypeMatch = bModelIsContainer == isContainer();
287 OSL_ENSURE( bTypeMatch, "VbaSiteModel::createControlModel - container type does not match container flag" );
288 if( !bTypeMatch )
289 xCtrlModel.reset();
291 return xCtrlModel;
294 void VbaSiteModel::convertProperties( PropertyMap& rPropMap,
295 const ControlConverter& rConv, ApiControlType eCtrlType, sal_Int32 nCtrlIndex ) const
297 rPropMap.setProperty( PROP_Name, maName );
298 rPropMap.setProperty( PROP_Tag, maTag );
300 if( eCtrlType != API_CONTROL_DIALOG )
302 rPropMap.setProperty( PROP_HelpText, maToolTip );
303 rPropMap.setProperty( PROP_EnableVisible, getFlag( mnFlags, VBA_SITE_VISIBLE ) );
304 // we need to set the passed control index to make option button groups work
305 if( (0 <= nCtrlIndex) && (nCtrlIndex <= SAL_MAX_INT16) )
306 rPropMap.setProperty( PROP_TabIndex, static_cast< sal_Int16 >( nCtrlIndex ) );
307 // progress bar and group box support TabIndex, but not Tabstop...
308 if( (eCtrlType != API_CONTROL_PROGRESSBAR) && (eCtrlType != API_CONTROL_GROUPBOX) && (eCtrlType != API_CONTROL_FRAME) && (eCtrlType != API_CONTROL_PAGE) )
309 rPropMap.setProperty( PROP_Tabstop, getFlag( mnFlags, VBA_SITE_TABSTOP ) );
310 rConv.convertPosition( rPropMap, maPos );
314 VbaFormControl::VbaFormControl()
318 VbaFormControl::~VbaFormControl()
322 void VbaFormControl::importModelOrStorage( BinaryInputStream& rInStrm, StorageBase& rStrg, const AxClassTable& rClassTable )
324 if( mxSiteModel.get() )
326 if( mxSiteModel->isContainer() )
328 StorageRef xSubStrg = rStrg.openSubStorage( mxSiteModel->getSubStorageName(), false );
329 OSL_ENSURE( xSubStrg.get(), "VbaFormControl::importModelOrStorage - cannot find storage for embedded control" );
330 if( xSubStrg.get() )
331 importStorage( *xSubStrg, rClassTable );
333 else if( !rInStrm.isEof() )
335 sal_Int64 nNextStrmPos = rInStrm.tell() + mxSiteModel->getStreamLength();
336 importControlModel( rInStrm, rClassTable );
337 rInStrm.seek( nNextStrmPos );
342 OUString VbaFormControl::getControlName() const
344 return mxSiteModel.get() ? mxSiteModel->getName() : OUString();
347 void VbaFormControl::createAndConvert( sal_Int32 nCtrlIndex,
348 const Reference< XNameContainer >& rxParentNC, const ControlConverter& rConv ) const
350 if( rxParentNC.is() && mxSiteModel.get() && mxCtrlModel.get() ) try
352 // create the control model
353 OUString aServiceName = mxCtrlModel->getServiceName();
354 Reference< XMultiServiceFactory > xModelFactory( rxParentNC, UNO_QUERY_THROW );
355 Reference< XControlModel > xCtrlModel( xModelFactory->createInstance( aServiceName ), UNO_QUERY_THROW );
357 // convert all properties and embedded controls
358 if( convertProperties( xCtrlModel, rConv, nCtrlIndex ) )
360 // insert into parent container
361 const OUString& rCtrlName = mxSiteModel->getName();
362 OSL_ENSURE( !rxParentNC->hasByName( rCtrlName ), "VbaFormControl::createAndConvert - multiple controls with equal name" );
363 ContainerHelper::insertByName( rxParentNC, rCtrlName, Any( xCtrlModel ) );
366 catch(const Exception& )
371 // protected ------------------------------------------------------------------
373 void VbaFormControl::importControlModel( BinaryInputStream& rInStrm, const AxClassTable& rClassTable )
375 createControlModel( rClassTable );
376 if( mxCtrlModel.get() )
377 mxCtrlModel->importBinaryModel( rInStrm );
380 void VbaFormControl::importStorage( StorageBase& rStrg, const AxClassTable& rClassTable )
382 createControlModel( rClassTable );
383 AxContainerModelBase* pContainerModel = dynamic_cast< AxContainerModelBase* >( mxCtrlModel.get() );
384 OSL_ENSURE( pContainerModel, "VbaFormControl::importStorage - missing container control model" );
385 if( pContainerModel )
387 /* Open the 'f' stream containing the model of this control and a list
388 of site models for all child controls. */
389 BinaryXInputStream aFStrm( rStrg.openInputStream( "f" ), true );
390 OSL_ENSURE( !aFStrm.isEof(), "VbaFormControl::importStorage - missing 'f' stream" );
392 /* Read the properties of this container control and the class table
393 (into the maClassTable vector) containing a list of GUIDs for
394 exotic embedded controls. */
395 if( !aFStrm.isEof() && pContainerModel->importBinaryModel( aFStrm ) && pContainerModel->importClassTable( aFStrm, maClassTable ) )
397 /* Read the site models of all embedded controls (this fills the
398 maControls vector). Ignore failure of importSiteModels() but
399 try to import as much controls as possible. */
400 importEmbeddedSiteModels( aFStrm );
401 /* Open the 'o' stream containing models of embedded simple
402 controls. Stream may be empty or missing, if this control
403 contains no controls or only container controls. */
404 BinaryXInputStream aOStrm( rStrg.openInputStream( "o" ), true );
406 /* Iterate over all embedded controls, import model from 'o'
407 stream (for embedded simple controls) or from the substorage
408 (for embedded container controls). */
409 maControls.forEachMem( &VbaFormControl::importModelOrStorage,
410 ::boost::ref( aOStrm ), ::boost::ref( rStrg ), ::boost::cref( maClassTable ) );
412 // Special handling for multi-page which has non-standard
413 // containment and additionally needs to re-order Page children
414 if ( pContainerModel->getControlType() == API_CONTROL_MULTIPAGE )
416 AxMultiPageModel* pMultiPage = dynamic_cast< AxMultiPageModel* >( pContainerModel );
417 if ( pMultiPage )
419 BinaryXInputStream aXStrm( rStrg.openInputStream( "x" ), true );
420 pMultiPage->importPageAndMultiPageProperties( aXStrm, maControls.size() );
422 typedef std::unordered_map< sal_uInt32, std::shared_ptr< VbaFormControl > > IdToPageMap;
423 IdToPageMap idToPage;
424 VbaFormControlVector::iterator it = maControls.begin();
425 VbaFormControlVector::iterator it_end = maControls.end();
426 typedef std::vector< sal_uInt32 > UInt32Array;
427 AxArrayString sCaptions;
429 for ( ; it != it_end; ++it )
431 if ( (*it)->mxCtrlModel->getControlType() == API_CONTROL_PAGE )
433 VbaSiteModelRef xPageSiteRef = (*it)->mxSiteModel;
434 if ( xPageSiteRef.get() )
435 idToPage[ xPageSiteRef->getId() ] = (*it);
437 else
439 AxTabStripModel* pTabStrip = static_cast<AxTabStripModel*> ( (*it)->mxCtrlModel.get() );
440 sCaptions = pTabStrip->maItems;
441 pMultiPage->mnActiveTab = pTabStrip->mnListIndex;
442 pMultiPage->mnTabStyle = pTabStrip->mnTabStyle;
445 // apply caption/titles to pages
446 UInt32Array::iterator itCtrlId = pMultiPage->mnIDs.begin();
447 UInt32Array::iterator itCtrlId_end = pMultiPage->mnIDs.end();
448 AxArrayString::iterator itCaption = sCaptions.begin();
450 maControls.clear();
451 // need to sort the controls according to the order of the ids
452 for ( sal_Int32 index = 1 ; ( sCaptions.size() == idToPage.size() ) && itCtrlId != itCtrlId_end; ++itCtrlId, ++itCaption, ++index )
454 IdToPageMap::iterator iter = idToPage.find( *itCtrlId );
455 if ( iter != idToPage.end() )
457 AxPageModel* pPage = static_cast<AxPageModel*> ( iter->second->mxCtrlModel.get() );
459 pPage->importProperty( XML_Caption, *itCaption );
460 maControls.push_back( iter->second );
464 /* Reorder the controls (sorts all option buttons of an option
465 group together), and move all children of all embedded frames
466 (group boxes) to this control (UNO group boxes cannot contain
467 other controls). */
468 finalizeEmbeddedControls();
473 bool VbaFormControl::convertProperties( const Reference< XControlModel >& rxCtrlModel,
474 const ControlConverter& rConv, sal_Int32 nCtrlIndex ) const
476 if( rxCtrlModel.is() && mxSiteModel.get() && mxCtrlModel.get() )
478 const OUString& rCtrlName = mxSiteModel->getName();
479 OSL_ENSURE( !rCtrlName.isEmpty(), "VbaFormControl::convertProperties - control without name" );
480 if( !rCtrlName.isEmpty() )
482 // convert all properties
483 PropertyMap aPropMap;
484 mxSiteModel->convertProperties( aPropMap, rConv, mxCtrlModel->getControlType(), nCtrlIndex );
485 rConv.bindToSources( rxCtrlModel, mxSiteModel->getControlSource(), mxSiteModel->getRowSource() );
486 mxCtrlModel->convertProperties( aPropMap, rConv );
487 mxCtrlModel->convertSize( aPropMap, rConv );
488 PropertySet aPropSet( rxCtrlModel );
489 aPropSet.setProperties( aPropMap );
491 // create and convert all embedded controls
492 if( !maControls.empty() ) try
494 Reference< XNameContainer > xCtrlModelNC( rxCtrlModel, UNO_QUERY_THROW );
495 /* Call conversion for all controls. Pass vector index as new
496 tab order to make option button groups work correctly. */
497 maControls.forEachMemWithIndex( &VbaFormControl::createAndConvert,
498 ::boost::cref( xCtrlModelNC ), ::boost::cref( rConv ) );
500 catch(const Exception& )
502 OSL_FAIL( "VbaFormControl::convertProperties - cannot get control container interface" );
505 return true;
508 return false;
511 // private --------------------------------------------------------------------
513 void VbaFormControl::createControlModel( const AxClassTable& rClassTable )
515 // derived classes may have created their own control model
516 if( !mxCtrlModel && mxSiteModel.get() )
517 mxCtrlModel = mxSiteModel->createControlModel( rClassTable );
520 bool VbaFormControl::importSiteModel( BinaryInputStream& rInStrm )
522 mxSiteModel.reset( new VbaSiteModel );
523 return mxSiteModel->importBinaryModel( rInStrm );
526 bool VbaFormControl::importEmbeddedSiteModels( BinaryInputStream& rInStrm )
528 sal_uInt64 nAnchorPos = rInStrm.tell();
529 sal_uInt32 nSiteCount, nSiteDataSize;
530 nSiteCount = rInStrm.readuInt32();
531 nSiteDataSize = rInStrm.readuInt32();
532 sal_Int64 nSiteEndPos = rInStrm.tell() + nSiteDataSize;
534 // skip the site info structure
535 sal_uInt32 nSiteIndex = 0;
536 while( !rInStrm.isEof() && (nSiteIndex < nSiteCount) )
538 rInStrm.skip( 1 ); // site depth
539 sal_uInt8 nTypeCount = rInStrm.readuInt8(); // 'type-or-count' byte
540 if( getFlag( nTypeCount, VBA_SITEINFO_COUNT ) )
542 /* Count flag is set: the 'type-or-count' byte contains the number
543 of controls in the lower bits, the type specifier follows in
544 the next byte. The type specifier should always be 1 according
545 to the specification. */
546 rInStrm.skip( 1 );
547 nSiteIndex += (nTypeCount & VBA_SITEINFO_MASK);
549 else
551 /* Count flag is not set: the 'type-or-count' byte contains the
552 type specifier of *one* control in the lower bits (this type
553 should be 1, see above). */
554 ++nSiteIndex;
557 // align the stream to 32bit, relative to start of entire site info
558 rInStrm.alignToBlock( 4, nAnchorPos );
560 // import the site models for all embedded controls
561 maControls.clear();
562 bool bValid = !rInStrm.isEof();
563 for( nSiteIndex = 0; bValid && (nSiteIndex < nSiteCount); ++nSiteIndex )
565 VbaFormControlRef xControl( new VbaFormControl );
566 maControls.push_back( xControl );
567 bValid = xControl->importSiteModel( rInStrm );
570 rInStrm.seek( nSiteEndPos );
571 return bValid;
574 void VbaFormControl::finalizeEmbeddedControls()
576 /* This function performs two tasks:
578 1) Reorder the controls appropriately (sort all option buttons of an
579 option group together to make grouping work).
580 2) Move all children of all embedded frames (group boxes) to this
581 control (UNO group boxes cannot contain other controls).
584 // first, sort all controls by original tab index
585 ::std::sort( maControls.begin(), maControls.end(), &compareByTabIndex );
587 /* Collect the programmatical names of all embedded controls (needed to be
588 able to set unused names to new dummy controls created below). Also
589 collect the names of all children of embedded frames (group boxes).
590 Luckily, names of controls must be unique in the entire form, not just
591 in the current container. */
592 VbaControlNamesSet aControlNames;
593 VbaControlNameInserter aInserter( aControlNames );
594 maControls.forEach( aInserter );
595 for( VbaFormControlVector::iterator aIt = maControls.begin(), aEnd = maControls.end(); aIt != aEnd; ++aIt )
596 if( (*aIt)->mxCtrlModel.get() && ((*aIt)->mxCtrlModel->getControlType() == API_CONTROL_GROUPBOX) )
597 (*aIt)->maControls.forEach( aInserter );
599 /* Reprocess the sorted list and collect all option button controls that
600 are part of the same option group (determined by group name). All
601 controls will be stored in a vector of vectors, that collects every
602 option button group in one vector element, and other controls between
603 these option groups (or leading or trailing controls) in other vector
604 elements. If an option button group follows another group, a dummy
605 separator control has to be inserted. */
606 typedef RefVector< VbaFormControlVector > VbaFormControlVectorVector;
607 VbaFormControlVectorVector aControlGroups;
609 typedef RefMap< OUString, VbaFormControlVector > VbaFormControlVectorMap;
610 VbaFormControlVectorMap aOptionGroups;
612 typedef VbaFormControlVectorMap::mapped_type VbaFormControlVectorRef;
613 bool bLastWasOptionButton = false;
614 for( VbaFormControlVector::iterator aIt = maControls.begin(), aEnd = maControls.end(); aIt != aEnd; ++aIt )
616 VbaFormControlRef xControl = *aIt;
617 const ControlModelBase* pCtrlModel = xControl->mxCtrlModel.get();
619 if( const AxOptionButtonModel* pOptButtonModel = dynamic_cast< const AxOptionButtonModel* >( pCtrlModel ) )
621 // check if a new option group needs to be created
622 const OUString& rGroupName = pOptButtonModel->getGroupName();
623 VbaFormControlVectorRef& rxOptionGroup = aOptionGroups[ rGroupName ];
624 if( !rxOptionGroup )
626 /* If last control was an option button too, we have two
627 option groups following each other, so a dummy separator
628 control is needed. */
629 if( bLastWasOptionButton )
631 VbaFormControlVectorRef xDummyGroup( new VbaFormControlVector );
632 aControlGroups.push_back( xDummyGroup );
633 OUString aName = aControlNames.generateDummyName();
634 VbaFormControlRef xDummyControl( new VbaDummyFormControl( aName ) );
635 xDummyGroup->push_back( xDummyControl );
637 rxOptionGroup.reset( new VbaFormControlVector );
638 aControlGroups.push_back( rxOptionGroup );
640 /* Append the option button to the control group (which is now
641 referred by the vector aControlGroups and by the map
642 aOptionGroups). */
643 rxOptionGroup->push_back( xControl );
644 bLastWasOptionButton = true;
646 else
648 // open a new control group, if the last group is an option group
649 if( bLastWasOptionButton || aControlGroups.empty() )
651 VbaFormControlVectorRef xControlGroup( new VbaFormControlVector );
652 aControlGroups.push_back( xControlGroup );
654 // append the control to the last control group
655 VbaFormControlVector& rLastGroup = *aControlGroups.back();
656 rLastGroup.push_back( xControl );
657 bLastWasOptionButton = false;
659 // if control is a group box, move all its children to this control
660 if( pCtrlModel && (pCtrlModel->getControlType() == API_CONTROL_GROUPBOX) )
662 /* Move all embedded controls of the group box relative to the
663 position of the group box. */
664 xControl->moveEmbeddedToAbsoluteParent();
665 /* Insert all children of the group box into the last control
666 group (following the group box). */
667 rLastGroup.insert( rLastGroup.end(), xControl->maControls.begin(), xControl->maControls.end() );
668 xControl->maControls.clear();
669 // check if last control of the group box is an option button
670 bLastWasOptionButton = dynamic_cast< const AxOptionButtonModel* >( rLastGroup.back()->mxCtrlModel.get() ) != 0;
675 // flatten the vector of vectors of form controls to a single vector
676 maControls.clear();
677 for( VbaFormControlVectorVector::iterator aIt = aControlGroups.begin(), aEnd = aControlGroups.end(); aIt != aEnd; ++aIt )
678 maControls.insert( maControls.end(), (*aIt)->begin(), (*aIt)->end() );
681 void VbaFormControl::moveRelative( const AxPairData& rDistance )
683 if( mxSiteModel.get() )
684 mxSiteModel->moveRelative( rDistance );
687 void VbaFormControl::moveEmbeddedToAbsoluteParent()
689 if( mxSiteModel.get() && !maControls.empty() )
691 // distance to move is equal to position of this control in its parent
692 AxPairData aDistance = mxSiteModel->getPosition();
694 /* For group boxes: add half of the font height to Y position (VBA
695 positions relative to frame border line, not to 'top' of frame). */
696 const AxFontDataModel* pFontModel = dynamic_cast< const AxFontDataModel* >( mxCtrlModel.get() );
697 if( pFontModel && (pFontModel->getControlType() == API_CONTROL_GROUPBOX) )
699 // convert points to 1/100 mm (1 pt = 1/72 inch = 2.54/72 cm = 2540/72 1/100 mm)
700 sal_Int32 nFontHeight = static_cast< sal_Int32 >( pFontModel->getFontHeight() * 2540 / 72 );
701 aDistance.second += nFontHeight / 2;
704 // move the embedded controls
705 maControls.forEachMem( &VbaFormControl::moveRelative, ::boost::cref( aDistance ) );
709 bool VbaFormControl::compareByTabIndex( const VbaFormControlRef& rxLeft, const VbaFormControlRef& rxRight )
711 // sort controls without model to the end
712 sal_Int32 nLeftTabIndex = rxLeft->mxSiteModel.get() ? rxLeft->mxSiteModel->getTabIndex() : SAL_MAX_INT32;
713 sal_Int32 nRightTabIndex = rxRight->mxSiteModel.get() ? rxRight->mxSiteModel->getTabIndex() : SAL_MAX_INT32;
714 return nLeftTabIndex < nRightTabIndex;
717 namespace {
719 OUString lclGetQuotedString( const OUString& rCodeLine )
721 OUStringBuffer aBuffer;
722 sal_Int32 nLen = rCodeLine.getLength();
723 if( (nLen > 0) && (rCodeLine[ 0 ] == '"') )
725 bool bExitLoop = false;
726 for( sal_Int32 nIndex = 1; !bExitLoop && (nIndex < nLen); ++nIndex )
728 sal_Unicode cChar = rCodeLine[ nIndex ];
729 // exit on closing quote char (but check on double quote chars)
730 bExitLoop = (cChar == '"') && ((nIndex + 1 == nLen) || (rCodeLine[ nIndex + 1 ] != '"'));
731 if( !bExitLoop )
733 aBuffer.append( cChar );
734 // skip second quote char
735 if( cChar == '"' )
736 ++nIndex;
740 return aBuffer.makeStringAndClear();
743 bool lclEatWhitespace( OUString& rCodeLine )
745 sal_Int32 nIndex = 0;
746 while( (nIndex < rCodeLine.getLength()) && ((rCodeLine[ nIndex ] == ' ') || (rCodeLine[ nIndex ] == '\t')) )
747 ++nIndex;
748 if( nIndex > 0 )
750 rCodeLine = rCodeLine.copy( nIndex );
751 return true;
753 return false;
756 bool lclEatKeyword( OUString& rCodeLine, const OUString& rKeyword )
758 if( rCodeLine.matchIgnoreAsciiCase( rKeyword ) )
760 rCodeLine = rCodeLine.copy( rKeyword.getLength() );
761 // success, if code line ends after keyword, or if whitespace follows
762 return rCodeLine.isEmpty() || lclEatWhitespace( rCodeLine );
764 return false;
767 } // namespace
769 VbaUserForm::VbaUserForm( const Reference< XComponentContext >& rxContext,
770 const Reference< XModel >& rxDocModel, const GraphicHelper& rGraphicHelper, bool bDefaultColorBgr ) :
771 mxContext( rxContext ),
772 mxDocModel( rxDocModel ),
773 maConverter( rxDocModel, rGraphicHelper, bDefaultColorBgr )
775 OSL_ENSURE( mxContext.is(), "VbaUserForm::VbaUserForm - missing component context" );
776 OSL_ENSURE( mxDocModel.is(), "VbaUserForm::VbaUserForm - missing document model" );
779 void VbaUserForm::importForm( const Reference< XNameContainer >& rxDialogLib,
780 StorageBase& rVbaFormStrg, const OUString& rModuleName, rtl_TextEncoding eTextEnc )
782 OSL_ENSURE( rxDialogLib.is(), "VbaUserForm::importForm - missing dialog library" );
783 if( !mxContext.is() || !mxDocModel.is() || !rxDialogLib.is() )
784 return;
786 // check that the '03VBFrame' stream exists, this is required for forms
787 BinaryXInputStream aInStrm( rVbaFormStrg.openInputStream( "\003VBFrame" ), true );
788 OSL_ENSURE( !aInStrm.isEof(), "VbaUserForm::importForm - missing \\003VBFrame stream" );
789 if( aInStrm.isEof() )
790 return;
792 // scan for the line 'Begin {GUID} <FormName>'
793 TextInputStream aFrameTextStrm( mxContext, aInStrm, eTextEnc );
794 const OUString aBegin = "Begin";
795 OUString aLine;
796 bool bBeginFound = false;
797 while( !bBeginFound && !aFrameTextStrm.isEof() )
799 aLine = aFrameTextStrm.readLine().trim();
800 bBeginFound = lclEatKeyword( aLine, aBegin );
802 // check for the specific GUID that represents VBA forms
803 if( !bBeginFound || !lclEatKeyword( aLine, "{C62A69F0-16DC-11CE-9E98-00AA00574A4F}" ) )
804 return;
806 // remaining line is the form name
807 OUString aFormName = aLine.trim();
808 OSL_ENSURE( !aFormName.isEmpty(), "VbaUserForm::importForm - missing form name" );
809 OSL_ENSURE( rModuleName.equalsIgnoreAsciiCase( aFormName ), "VbaUserForm::importFrameStream - form and module name mismatch" );
810 if( aFormName.isEmpty() )
811 aFormName = rModuleName;
812 if( aFormName.isEmpty() )
813 return;
814 mxSiteModel.reset( new VbaSiteModel );
815 mxSiteModel->importProperty( XML_Name, aFormName );
817 // read the form properties (caption is contained in this '03VBFrame' stream, not in the 'f' stream)
818 mxCtrlModel.reset( new AxUserFormModel );
819 OUString aKey, aValue;
820 bool bExitLoop = false;
821 while( !bExitLoop && !aFrameTextStrm.isEof() )
823 aLine = aFrameTextStrm.readLine().trim();
824 bExitLoop = aLine.equalsIgnoreAsciiCase( "End" );
825 if( !bExitLoop && VbaHelper::extractKeyValue( aKey, aValue, aLine ) )
827 if( aKey.equalsIgnoreAsciiCase( "Caption" ) )
828 mxCtrlModel->importProperty( XML_Caption, lclGetQuotedString( aValue ) );
829 else if( aKey.equalsIgnoreAsciiCase( "Tag" ) )
830 mxSiteModel->importProperty( XML_Tag, lclGetQuotedString( aValue ) );
834 // use generic container control functionality to import the embedded controls
835 importStorage( rVbaFormStrg, AxClassTable() );
839 // create the dialog model
840 OUString aServiceName = mxCtrlModel->getServiceName();
841 Reference< XMultiServiceFactory > xFactory( mxContext->getServiceManager(), UNO_QUERY_THROW );
842 Reference< XControlModel > xDialogModel( xFactory->createInstance( aServiceName ), UNO_QUERY_THROW );
843 Reference< XNameContainer > xDialogNC( xDialogModel, UNO_QUERY_THROW );
845 // convert properties and embedded controls
846 if( convertProperties( xDialogModel, maConverter, 0 ) )
848 // export the dialog to XML and insert it into the dialog library
849 Reference< XInputStreamProvider > xDialogSource( ::xmlscript::exportDialogModel( xDialogNC, mxContext, mxDocModel ), UNO_SET_THROW );
850 OSL_ENSURE( !rxDialogLib->hasByName( aFormName ), "VbaUserForm::importForm - multiple dialogs with equal name" );
851 ContainerHelper::insertByName( rxDialogLib, aFormName, Any( xDialogSource ) );
854 catch(const Exception& )
859 } // namespace ole
860 } // namespace oox
862 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */