Version 5.4.3.2, tag libreoffice-5.4.3.2
[LibreOffice.git] / oox / source / ole / vbacontrol.cxx
blobeb7a54b94b9c227c90a0f07bd0c318d835960b2a
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/beans/XPropertySet.hpp>
26 #include <com/sun/star/container/XNameContainer.hpp>
27 #include <com/sun/star/io/XInputStreamProvider.hpp>
28 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
29 #include <com/sun/star/uno/XComponentContext.hpp>
30 #include <osl/diagnose.h>
31 #include <rtl/ustrbuf.hxx>
32 #include <xmlscript/xmldlg_imexp.hxx>
33 #include "oox/helper/attributelist.hxx"
34 #include "oox/helper/binaryinputstream.hxx"
35 #include "oox/helper/containerhelper.hxx"
36 #include "oox/helper/propertymap.hxx"
37 #include "oox/helper/propertyset.hxx"
38 #include "oox/helper/storagebase.hxx"
39 #include "oox/helper/textinputstream.hxx"
40 #include "oox/ole/vbahelper.hxx"
41 #include <oox/token/properties.hxx>
42 #include <oox/token/tokens.hxx>
43 #include <unordered_map>
45 namespace oox {
46 namespace ole {
48 using namespace ::com::sun::star::awt;
49 using namespace ::com::sun::star::container;
50 using namespace ::com::sun::star::frame;
51 using namespace ::com::sun::star::io;
52 using namespace ::com::sun::star::lang;
53 using namespace ::com::sun::star::uno;
55 namespace {
57 const sal_uInt16 VBA_SITE_CLASSIDINDEX = 0x8000;
58 const sal_uInt16 VBA_SITE_INDEXMASK = 0x7FFF;
59 const sal_uInt16 VBA_SITE_FORM = 7;
60 const sal_uInt16 VBA_SITE_IMAGE = 12;
61 const sal_uInt16 VBA_SITE_FRAME = 14;
62 const sal_uInt16 VBA_SITE_SPINBUTTON = 16;
63 const sal_uInt16 VBA_SITE_COMMANDBUTTON = 17;
64 const sal_uInt16 VBA_SITE_TABSTRIP = 18;
65 const sal_uInt16 VBA_SITE_LABEL = 21;
66 const sal_uInt16 VBA_SITE_TEXTBOX = 23;
67 const sal_uInt16 VBA_SITE_LISTBOX = 24;
68 const sal_uInt16 VBA_SITE_COMBOBOX = 25;
69 const sal_uInt16 VBA_SITE_CHECKBOX = 26;
70 const sal_uInt16 VBA_SITE_OPTIONBUTTON = 27;
71 const sal_uInt16 VBA_SITE_TOGGLEBUTTON = 28;
72 const sal_uInt16 VBA_SITE_SCROLLBAR = 47;
73 const sal_uInt16 VBA_SITE_MULTIPAGE = 57;
74 const sal_uInt16 VBA_SITE_UNKNOWN = 0x7FFF;
76 const sal_uInt32 VBA_SITE_TABSTOP = 0x00000001;
77 const sal_uInt32 VBA_SITE_VISIBLE = 0x00000002;
78 const sal_uInt32 VBA_SITE_OSTREAM = 0x00000010;
79 const sal_uInt32 VBA_SITE_DEFFLAGS = 0x00000033;
81 const sal_uInt8 VBA_SITEINFO_COUNT = 0x80;
82 const sal_uInt8 VBA_SITEINFO_MASK = 0x7F;
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
89 public:
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();
97 private:
98 typedef ::std::set< OUString > OUStringSet;
99 OUStringSet maCtrlNames;
100 const OUString maDummyBaseName;
101 sal_Int32 mnIndex;
104 VbaControlNamesSet::VbaControlNamesSet() :
105 maDummyBaseName( "DummyGroupSep" ),
106 mnIndex( 0 )
110 void VbaControlNamesSet::insertName( const VbaFormControl& rControl )
112 OUString aName = rControl.getControlName();
113 if( !aName.isEmpty() )
114 maCtrlNames.insert( aName );
117 OUString VbaControlNamesSet::generateDummyName()
119 OUString aCtrlName;
122 aCtrlName = OUStringBuffer( maDummyBaseName ).append( ++mnIndex ).makeStringAndClear();
124 while( maCtrlNames.count( aCtrlName ) > 0 );
125 maCtrlNames.insert( aCtrlName );
126 return aCtrlName;
129 /** Functor that inserts the name of a control into a VbaControlNamesSet. */
130 struct VbaControlNameInserter
132 public:
133 VbaControlNamesSet& mrCtrlNames;
134 explicit VbaControlNameInserter( VbaControlNamesSet& rCtrlNames ) : mrCtrlNames( rCtrlNames ) {}
135 void operator()( const VbaFormControl& rControl ) { mrCtrlNames.insertName( rControl ); }
138 /** A dummy invisible form control (fixed label without text) that is used to
139 separate two groups of option buttons.
141 class VbaDummyFormControl : public VbaFormControl
143 public:
144 explicit VbaDummyFormControl( const OUString& rName );
147 VbaDummyFormControl::VbaDummyFormControl( const OUString& rName )
149 mxSiteModel.reset( new VbaSiteModel );
150 mxSiteModel->importProperty( XML_Name, rName );
151 mxSiteModel->importProperty( XML_VariousPropertyBits, OUString( '0' ) );
153 mxCtrlModel.reset( new AxLabelModel );
154 mxCtrlModel->setAwtModelMode();
155 mxCtrlModel->importProperty( XML_Size, "10;10" );
158 } // namespace
160 VbaSiteModel::VbaSiteModel() :
161 maPos( 0, 0 ),
162 mnId( 0 ),
163 mnHelpContextId( 0 ),
164 mnFlags( VBA_SITE_DEFFLAGS ),
165 mnStreamLen( 0 ),
166 mnTabIndex( -1 ),
167 mnClassIdOrCache( VBA_SITE_UNKNOWN ),
168 mnGroupId( 0 )
172 VbaSiteModel::~VbaSiteModel()
176 void VbaSiteModel::importProperty( sal_Int32 nPropId, const OUString& rValue )
178 switch( nPropId )
180 case XML_Name: maName = rValue; break;
181 case XML_Tag: maTag = rValue; break;
182 case XML_VariousPropertyBits: mnFlags = AttributeConversion::decodeUnsigned( rValue ); break;
186 bool VbaSiteModel::importBinaryModel( BinaryInputStream& rInStrm )
188 AxBinaryPropertyReader aReader( rInStrm );
189 aReader.readStringProperty( maName );
190 aReader.readStringProperty( maTag );
191 aReader.readIntProperty< sal_Int32 >( mnId );
192 aReader.readIntProperty< sal_Int32 >( mnHelpContextId );
193 aReader.readIntProperty< sal_uInt32 >( mnFlags );
194 aReader.readIntProperty< sal_uInt32 >( mnStreamLen );
195 aReader.readIntProperty< sal_Int16 >( mnTabIndex );
196 aReader.readIntProperty< sal_uInt16 >( mnClassIdOrCache );
197 aReader.readPairProperty( maPos );
198 aReader.readIntProperty< sal_uInt16 >( mnGroupId );
199 aReader.skipUndefinedProperty();
200 aReader.readStringProperty( maToolTip );
201 aReader.skipStringProperty(); // license key
202 aReader.readStringProperty( maControlSource );
203 aReader.readStringProperty( maRowSource );
204 return aReader.finalizeImport();
207 void VbaSiteModel::moveRelative( const AxPairData& rDistance )
209 maPos.first += rDistance.first;
210 maPos.second += rDistance.second;
213 bool VbaSiteModel::isContainer() const
215 return !getFlag( mnFlags, VBA_SITE_OSTREAM );
218 sal_uInt32 VbaSiteModel::getStreamLength() const
220 return isContainer() ? 0 : mnStreamLen;
223 OUString VbaSiteModel::getSubStorageName() const
225 if( mnId >= 0 )
227 OUStringBuffer aBuffer;
228 aBuffer.append( 'i' );
229 if( mnId < 10 )
230 aBuffer.append( '0' );
231 aBuffer.append( mnId );
232 return aBuffer.makeStringAndClear();
234 return OUString();
237 ControlModelRef VbaSiteModel::createControlModel( const AxClassTable& rClassTable ) const
239 ControlModelRef xCtrlModel;
241 sal_Int32 nTypeIndex = static_cast< sal_Int32 >( mnClassIdOrCache & VBA_SITE_INDEXMASK );
242 if( !getFlag( mnClassIdOrCache, VBA_SITE_CLASSIDINDEX ) )
244 switch( nTypeIndex )
246 case VBA_SITE_COMMANDBUTTON: xCtrlModel.reset( new AxCommandButtonModel ); break;
247 case VBA_SITE_LABEL: xCtrlModel.reset( new AxLabelModel ); break;
248 case VBA_SITE_IMAGE: xCtrlModel.reset( new AxImageModel ); break;
249 case VBA_SITE_TOGGLEBUTTON: xCtrlModel.reset( new AxToggleButtonModel ); break;
250 case VBA_SITE_CHECKBOX: xCtrlModel.reset( new AxCheckBoxModel ); break;
251 case VBA_SITE_OPTIONBUTTON: xCtrlModel.reset( new AxOptionButtonModel ); break;
252 case VBA_SITE_TEXTBOX: xCtrlModel.reset( new AxTextBoxModel ); break;
253 case VBA_SITE_LISTBOX: xCtrlModel.reset( new AxListBoxModel ); break;
254 case VBA_SITE_COMBOBOX: xCtrlModel.reset( new AxComboBoxModel ); break;
255 case VBA_SITE_SPINBUTTON: xCtrlModel.reset( new AxSpinButtonModel ); break;
256 case VBA_SITE_SCROLLBAR: xCtrlModel.reset( new AxScrollBarModel ); break;
257 case VBA_SITE_TABSTRIP: xCtrlModel.reset( new AxTabStripModel );
258 break;
259 case VBA_SITE_FRAME: xCtrlModel.reset( new AxFrameModel ); break;
260 case VBA_SITE_MULTIPAGE: xCtrlModel.reset( new AxMultiPageModel );
261 break;
262 case VBA_SITE_FORM: xCtrlModel.reset( new AxPageModel );
263 break;
264 default: OSL_FAIL( "VbaSiteModel::createControlModel - unknown type index" );
267 else
269 const OUString* pGuid = ContainerHelper::getVectorElement( rClassTable, nTypeIndex );
270 OSL_ENSURE( pGuid, "VbaSiteModel::createControlModel - invalid class table index" );
271 if( pGuid )
273 if( *pGuid == COMCTL_GUID_SCROLLBAR_60 )
274 xCtrlModel.reset( new ComCtlScrollBarModel( 6 ) );
275 else if( *pGuid == COMCTL_GUID_PROGRESSBAR_50 )
276 xCtrlModel.reset( new ComCtlProgressBarModel( 5 ) );
277 else if( *pGuid == COMCTL_GUID_PROGRESSBAR_60 )
278 xCtrlModel.reset( new ComCtlProgressBarModel( 6 ) );
282 if( xCtrlModel.get() )
284 // user form controls are AWT models
285 xCtrlModel->setAwtModelMode();
287 // check that container model matches container flag in site data
288 bool bModelIsContainer = dynamic_cast< const AxContainerModelBase* >( xCtrlModel.get() ) != nullptr;
289 bool bTypeMatch = bModelIsContainer == isContainer();
290 OSL_ENSURE( bTypeMatch, "VbaSiteModel::createControlModel - container type does not match container flag" );
291 if( !bTypeMatch )
292 xCtrlModel.reset();
294 return xCtrlModel;
297 void VbaSiteModel::convertProperties( PropertyMap& rPropMap,
298 const ControlConverter& rConv, ApiControlType eCtrlType, sal_Int32 nCtrlIndex ) const
300 rPropMap.setProperty( PROP_Name, maName );
301 rPropMap.setProperty( PROP_Tag, maTag );
303 if( eCtrlType != API_CONTROL_DIALOG )
305 rPropMap.setProperty( PROP_HelpText, maToolTip );
306 rPropMap.setProperty( PROP_EnableVisible, getFlag( mnFlags, VBA_SITE_VISIBLE ) );
307 // we need to set the passed control index to make option button groups work
308 if( (0 <= nCtrlIndex) && (nCtrlIndex <= SAL_MAX_INT16) )
309 rPropMap.setProperty( PROP_TabIndex, static_cast< sal_Int16 >( nCtrlIndex ) );
310 // progress bar and group box support TabIndex, but not Tabstop...
311 if( (eCtrlType != API_CONTROL_PROGRESSBAR) && (eCtrlType != API_CONTROL_GROUPBOX) && (eCtrlType != API_CONTROL_FRAME) && (eCtrlType != API_CONTROL_PAGE) )
312 rPropMap.setProperty( PROP_Tabstop, getFlag( mnFlags, VBA_SITE_TABSTOP ) );
313 rConv.convertPosition( rPropMap, maPos );
317 VbaFormControl::VbaFormControl()
321 VbaFormControl::~VbaFormControl()
325 void VbaFormControl::importModelOrStorage( BinaryInputStream& rInStrm, StorageBase& rStrg, const AxClassTable& rClassTable )
327 if( mxSiteModel.get() )
329 if( mxSiteModel->isContainer() )
331 StorageRef xSubStrg = rStrg.openSubStorage( mxSiteModel->getSubStorageName(), false );
332 OSL_ENSURE( xSubStrg.get(), "VbaFormControl::importModelOrStorage - cannot find storage for embedded control" );
333 if( xSubStrg.get() )
334 importStorage( *xSubStrg, rClassTable );
336 else if( !rInStrm.isEof() )
338 sal_Int64 nNextStrmPos = rInStrm.tell() + mxSiteModel->getStreamLength();
339 importControlModel( rInStrm, rClassTable );
340 rInStrm.seek( nNextStrmPos );
345 OUString VbaFormControl::getControlName() const
347 return mxSiteModel.get() ? mxSiteModel->getName() : OUString();
350 void VbaFormControl::createAndConvert( sal_Int32 nCtrlIndex,
351 const Reference< XNameContainer >& rxParentNC, const ControlConverter& rConv ) const
353 if( rxParentNC.is() && mxSiteModel.get() && mxCtrlModel.get() ) try
355 // create the control model
356 OUString aServiceName = mxCtrlModel->getServiceName();
357 Reference< XMultiServiceFactory > xModelFactory( rxParentNC, UNO_QUERY_THROW );
358 Reference< XControlModel > xCtrlModel( xModelFactory->createInstance( aServiceName ), UNO_QUERY_THROW );
360 // convert all properties and embedded controls
361 if( convertProperties( xCtrlModel, rConv, nCtrlIndex ) )
363 // insert into parent container
364 const OUString& rCtrlName = mxSiteModel->getName();
365 OSL_ENSURE( !rxParentNC->hasByName( rCtrlName ), "VbaFormControl::createAndConvert - multiple controls with equal name" );
366 ContainerHelper::insertByName( rxParentNC, rCtrlName, Any( xCtrlModel ) );
369 catch(const Exception& )
374 // protected ------------------------------------------------------------------
376 void VbaFormControl::importControlModel( BinaryInputStream& rInStrm, const AxClassTable& rClassTable )
378 createControlModel( rClassTable );
379 if( mxCtrlModel.get() )
380 mxCtrlModel->importBinaryModel( rInStrm );
383 void VbaFormControl::importStorage( StorageBase& rStrg, const AxClassTable& rClassTable )
385 createControlModel( rClassTable );
386 AxContainerModelBase* pContainerModel = dynamic_cast< AxContainerModelBase* >( mxCtrlModel.get() );
387 OSL_ENSURE( pContainerModel, "VbaFormControl::importStorage - missing container control model" );
388 if( pContainerModel )
390 /* Open the 'f' stream containing the model of this control and a list
391 of site models for all child controls. */
392 BinaryXInputStream aFStrm( rStrg.openInputStream( "f" ), true );
393 OSL_ENSURE( !aFStrm.isEof(), "VbaFormControl::importStorage - missing 'f' stream" );
395 /* Read the properties of this container control and the class table
396 (into the maClassTable vector) containing a list of GUIDs for
397 exotic embedded controls. */
398 if( !aFStrm.isEof() && pContainerModel->importBinaryModel( aFStrm ) && pContainerModel->importClassTable( aFStrm, maClassTable ) )
400 /* Read the site models of all embedded controls (this fills the
401 maControls vector). Ignore failure of importSiteModels() but
402 try to import as much controls as possible. */
403 importEmbeddedSiteModels( aFStrm );
404 /* Open the 'o' stream containing models of embedded simple
405 controls. Stream may be empty or missing, if this control
406 contains no controls or only container controls. */
407 BinaryXInputStream aOStrm( rStrg.openInputStream( "o" ), true );
409 /* Iterate over all embedded controls, import model from 'o'
410 stream (for embedded simple controls) or from the substorage
411 (for embedded container controls). */
412 maControls.forEachMem( &VbaFormControl::importModelOrStorage,
413 ::std::ref( aOStrm ), ::std::ref( rStrg ), ::std::cref( maClassTable ) );
415 // Special handling for multi-page which has non-standard
416 // containment and additionally needs to re-order Page children
417 if ( pContainerModel->getControlType() == API_CONTROL_MULTIPAGE )
419 AxMultiPageModel* pMultiPage = dynamic_cast< AxMultiPageModel* >( pContainerModel );
420 if ( pMultiPage )
422 BinaryXInputStream aXStrm( rStrg.openInputStream( "x" ), true );
423 pMultiPage->importPageAndMultiPageProperties( aXStrm, maControls.size() );
425 typedef std::unordered_map< sal_uInt32, std::shared_ptr< VbaFormControl > > IdToPageMap;
426 IdToPageMap idToPage;
427 VbaFormControlVector::iterator it = maControls.begin();
428 VbaFormControlVector::iterator it_end = maControls.end();
429 typedef std::vector< sal_uInt32 > UInt32Array;
430 AxArrayString sCaptions;
432 for ( ; it != it_end; ++it )
434 if ( (*it)->mxCtrlModel->getControlType() == API_CONTROL_PAGE )
436 VbaSiteModelRef xPageSiteRef = (*it)->mxSiteModel;
437 if ( xPageSiteRef.get() )
438 idToPage[ xPageSiteRef->getId() ] = (*it);
440 else
442 AxTabStripModel* pTabStrip = static_cast<AxTabStripModel*> ( (*it)->mxCtrlModel.get() );
443 sCaptions = pTabStrip->maItems;
444 pMultiPage->mnActiveTab = pTabStrip->mnListIndex;
445 pMultiPage->mnTabStyle = pTabStrip->mnTabStyle;
448 // apply caption/titles to pages
449 UInt32Array::iterator itCtrlId = pMultiPage->mnIDs.begin();
450 UInt32Array::iterator itCtrlId_end = pMultiPage->mnIDs.end();
451 AxArrayString::iterator itCaption = sCaptions.begin();
453 maControls.clear();
454 // need to sort the controls according to the order of the ids
455 for ( sal_Int32 index = 1 ; ( sCaptions.size() == idToPage.size() ) && itCtrlId != itCtrlId_end; ++itCtrlId, ++itCaption, ++index )
457 IdToPageMap::iterator iter = idToPage.find( *itCtrlId );
458 if ( iter != idToPage.end() )
460 AxPageModel* pPage = static_cast<AxPageModel*> ( iter->second->mxCtrlModel.get() );
462 pPage->importProperty( XML_Caption, *itCaption );
463 maControls.push_back( iter->second );
467 /* Reorder the controls (sorts all option buttons of an option
468 group together), and move all children of all embedded frames
469 (group boxes) to this control (UNO group boxes cannot contain
470 other controls). */
471 finalizeEmbeddedControls();
476 bool VbaFormControl::convertProperties( const Reference< XControlModel >& rxCtrlModel,
477 const ControlConverter& rConv, sal_Int32 nCtrlIndex ) const
479 if( rxCtrlModel.is() && mxSiteModel.get() && mxCtrlModel.get() )
481 const OUString& rCtrlName = mxSiteModel->getName();
482 OSL_ENSURE( !rCtrlName.isEmpty(), "VbaFormControl::convertProperties - control without name" );
483 if( !rCtrlName.isEmpty() )
485 // convert all properties
486 PropertyMap aPropMap;
487 mxSiteModel->convertProperties( aPropMap, rConv, mxCtrlModel->getControlType(), nCtrlIndex );
488 rConv.bindToSources( rxCtrlModel, mxSiteModel->getControlSource(), mxSiteModel->getRowSource() );
489 mxCtrlModel->convertProperties( aPropMap, rConv );
490 mxCtrlModel->convertSize( aPropMap, rConv );
491 PropertySet aPropSet( rxCtrlModel );
492 aPropSet.setProperties( aPropMap );
494 // create and convert all embedded controls
495 if( !maControls.empty() ) try
497 Reference< XNameContainer > xCtrlModelNC( rxCtrlModel, UNO_QUERY_THROW );
498 /* Call conversion for all controls. Pass vector index as new
499 tab order to make option button groups work correctly. */
500 maControls.forEachMemWithIndex( &VbaFormControl::createAndConvert,
501 ::std::cref( xCtrlModelNC ), ::std::cref( rConv ) );
503 catch(const Exception& )
505 OSL_FAIL( "VbaFormControl::convertProperties - cannot get control container interface" );
508 return true;
511 return false;
514 // private --------------------------------------------------------------------
516 void VbaFormControl::createControlModel( const AxClassTable& rClassTable )
518 // derived classes may have created their own control model
519 if( !mxCtrlModel && mxSiteModel.get() )
520 mxCtrlModel = mxSiteModel->createControlModel( rClassTable );
523 bool VbaFormControl::importSiteModel( BinaryInputStream& rInStrm )
525 mxSiteModel.reset( new VbaSiteModel );
526 return mxSiteModel->importBinaryModel( rInStrm );
529 void VbaFormControl::importEmbeddedSiteModels( BinaryInputStream& rInStrm )
531 sal_uInt64 nAnchorPos = rInStrm.tell();
532 sal_uInt32 nSiteCount, nSiteDataSize;
533 nSiteCount = rInStrm.readuInt32();
534 nSiteDataSize = rInStrm.readuInt32();
535 sal_Int64 nSiteEndPos = rInStrm.tell() + nSiteDataSize;
537 // skip the site info structure
538 sal_uInt32 nSiteIndex = 0;
539 while( !rInStrm.isEof() && (nSiteIndex < nSiteCount) )
541 rInStrm.skip( 1 ); // site depth
542 sal_uInt8 nTypeCount = rInStrm.readuInt8(); // 'type-or-count' byte
543 if( getFlag( nTypeCount, VBA_SITEINFO_COUNT ) )
545 /* Count flag is set: the 'type-or-count' byte contains the number
546 of controls in the lower bits, the type specifier follows in
547 the next byte. The type specifier should always be 1 according
548 to the specification. */
549 rInStrm.skip( 1 );
550 nSiteIndex += (nTypeCount & VBA_SITEINFO_MASK);
552 else
554 /* Count flag is not set: the 'type-or-count' byte contains the
555 type specifier of *one* control in the lower bits (this type
556 should be 1, see above). */
557 ++nSiteIndex;
560 // align the stream to 32bit, relative to start of entire site info
561 rInStrm.alignToBlock( 4, nAnchorPos );
563 // import the site models for all embedded controls
564 maControls.clear();
565 bool bValid = !rInStrm.isEof();
566 for( nSiteIndex = 0; bValid && (nSiteIndex < nSiteCount); ++nSiteIndex )
568 VbaFormControlRef xControl( new VbaFormControl );
569 maControls.push_back( xControl );
570 bValid = xControl->importSiteModel( rInStrm );
573 rInStrm.seek( nSiteEndPos );
576 void VbaFormControl::finalizeEmbeddedControls()
578 /* This function performs two tasks:
580 1) Reorder the controls appropriately (sort all option buttons of an
581 option group together to make grouping work).
582 2) Move all children of all embedded frames (group boxes) to this
583 control (UNO group boxes cannot contain other controls).
586 // first, sort all controls by original tab index
587 ::std::sort( maControls.begin(), maControls.end(), &compareByTabIndex );
589 /* Collect the programmatical names of all embedded controls (needed to be
590 able to set unused names to new dummy controls created below). Also
591 collect the names of all children of embedded frames (group boxes).
592 Luckily, names of controls must be unique in the entire form, not just
593 in the current container. */
594 VbaControlNamesSet aControlNames;
595 VbaControlNameInserter aInserter( aControlNames );
596 maControls.forEach( aInserter );
597 for( VbaFormControlVector::iterator aIt = maControls.begin(), aEnd = maControls.end(); aIt != aEnd; ++aIt )
598 if( (*aIt)->mxCtrlModel.get() && ((*aIt)->mxCtrlModel->getControlType() == API_CONTROL_GROUPBOX) )
599 (*aIt)->maControls.forEach( aInserter );
601 /* Reprocess the sorted list and collect all option button controls that
602 are part of the same option group (determined by group name). All
603 controls will be stored in a vector of vectors, that collects every
604 option button group in one vector element, and other controls between
605 these option groups (or leading or trailing controls) in other vector
606 elements. If an option button group follows another group, a dummy
607 separator control has to be inserted. */
608 typedef RefVector< VbaFormControlVector > VbaFormControlVectorVector;
609 VbaFormControlVectorVector aControlGroups;
611 typedef RefMap< OUString, VbaFormControlVector > VbaFormControlVectorMap;
612 VbaFormControlVectorMap aOptionGroups;
614 typedef VbaFormControlVectorMap::mapped_type VbaFormControlVectorRef;
615 bool bLastWasOptionButton = false;
616 for( VbaFormControlVector::iterator aIt = maControls.begin(), aEnd = maControls.end(); aIt != aEnd; ++aIt )
618 VbaFormControlRef xControl = *aIt;
619 const ControlModelBase* pCtrlModel = xControl->mxCtrlModel.get();
621 if( const AxOptionButtonModel* pOptButtonModel = dynamic_cast< const AxOptionButtonModel* >( pCtrlModel ) )
623 // check if a new option group needs to be created
624 const OUString& rGroupName = pOptButtonModel->getGroupName();
625 VbaFormControlVectorRef& rxOptionGroup = aOptionGroups[ rGroupName ];
626 if( !rxOptionGroup )
628 /* If last control was an option button too, we have two
629 option groups following each other, so a dummy separator
630 control is needed. */
631 if( bLastWasOptionButton )
633 VbaFormControlVectorRef xDummyGroup( new VbaFormControlVector );
634 aControlGroups.push_back( xDummyGroup );
635 OUString aName = aControlNames.generateDummyName();
636 VbaFormControlRef xDummyControl( new VbaDummyFormControl( aName ) );
637 xDummyGroup->push_back( xDummyControl );
639 rxOptionGroup.reset( new VbaFormControlVector );
640 aControlGroups.push_back( rxOptionGroup );
642 /* Append the option button to the control group (which is now
643 referred by the vector aControlGroups and by the map
644 aOptionGroups). */
645 rxOptionGroup->push_back( xControl );
646 bLastWasOptionButton = true;
648 else
650 // open a new control group, if the last group is an option group
651 if( bLastWasOptionButton || aControlGroups.empty() )
653 VbaFormControlVectorRef xControlGroup( new VbaFormControlVector );
654 aControlGroups.push_back( xControlGroup );
656 // append the control to the last control group
657 VbaFormControlVector& rLastGroup = *aControlGroups.back();
658 rLastGroup.push_back( xControl );
659 bLastWasOptionButton = false;
661 // if control is a group box, move all its children to this control
662 if( pCtrlModel && (pCtrlModel->getControlType() == API_CONTROL_GROUPBOX) )
664 /* Move all embedded controls of the group box relative to the
665 position of the group box. */
666 xControl->moveEmbeddedToAbsoluteParent();
667 /* Insert all children of the group box into the last control
668 group (following the group box). */
669 rLastGroup.insert( rLastGroup.end(), xControl->maControls.begin(), xControl->maControls.end() );
670 xControl->maControls.clear();
671 // check if last control of the group box is an option button
672 bLastWasOptionButton = dynamic_cast< const AxOptionButtonModel* >( rLastGroup.back()->mxCtrlModel.get() ) != nullptr;
677 // flatten the vector of vectors of form controls to a single vector
678 maControls.clear();
679 for( VbaFormControlVectorVector::iterator aIt = aControlGroups.begin(), aEnd = aControlGroups.end(); aIt != aEnd; ++aIt )
680 maControls.insert( maControls.end(), (*aIt)->begin(), (*aIt)->end() );
683 void VbaFormControl::moveRelative( const AxPairData& rDistance )
685 if( mxSiteModel.get() )
686 mxSiteModel->moveRelative( rDistance );
689 void VbaFormControl::moveEmbeddedToAbsoluteParent()
691 if( mxSiteModel.get() && !maControls.empty() )
693 // distance to move is equal to position of this control in its parent
694 AxPairData aDistance = mxSiteModel->getPosition();
696 /* For group boxes: add half of the font height to Y position (VBA
697 positions relative to frame border line, not to 'top' of frame). */
698 const AxFontDataModel* pFontModel = dynamic_cast< const AxFontDataModel* >( mxCtrlModel.get() );
699 if( pFontModel && (pFontModel->getControlType() == API_CONTROL_GROUPBOX) )
701 // convert points to 1/100 mm (1 pt = 1/72 inch = 2.54/72 cm = 2540/72 1/100 mm)
702 sal_Int32 nFontHeight = static_cast< sal_Int32 >( pFontModel->getFontHeight() * 2540 / 72 );
703 aDistance.second += nFontHeight / 2;
706 // move the embedded controls
707 maControls.forEachMem( &VbaFormControl::moveRelative, ::std::cref( aDistance ) );
711 bool VbaFormControl::compareByTabIndex( const VbaFormControlRef& rxLeft, const VbaFormControlRef& rxRight )
713 // sort controls without model to the end
714 sal_Int32 nLeftTabIndex = rxLeft->mxSiteModel.get() ? rxLeft->mxSiteModel->getTabIndex() : SAL_MAX_INT32;
715 sal_Int32 nRightTabIndex = rxRight->mxSiteModel.get() ? rxRight->mxSiteModel->getTabIndex() : SAL_MAX_INT32;
716 return nLeftTabIndex < nRightTabIndex;
719 namespace {
721 OUString lclGetQuotedString( const OUString& rCodeLine )
723 OUStringBuffer aBuffer;
724 sal_Int32 nLen = rCodeLine.getLength();
725 if( (nLen > 0) && (rCodeLine[ 0 ] == '"') )
727 bool bExitLoop = false;
728 for( sal_Int32 nIndex = 1; !bExitLoop && (nIndex < nLen); ++nIndex )
730 sal_Unicode cChar = rCodeLine[ nIndex ];
731 // exit on closing quote char (but check on double quote chars)
732 bExitLoop = (cChar == '"') && ((nIndex + 1 == nLen) || (rCodeLine[ nIndex + 1 ] != '"'));
733 if( !bExitLoop )
735 aBuffer.append( cChar );
736 // skip second quote char
737 if( cChar == '"' )
738 ++nIndex;
742 return aBuffer.makeStringAndClear();
745 bool lclEatWhitespace( OUString& rCodeLine )
747 sal_Int32 nIndex = 0;
748 while( (nIndex < rCodeLine.getLength()) && ((rCodeLine[ nIndex ] == ' ') || (rCodeLine[ nIndex ] == '\t')) )
749 ++nIndex;
750 if( nIndex > 0 )
752 rCodeLine = rCodeLine.copy( nIndex );
753 return true;
755 return false;
758 bool lclEatKeyword( OUString& rCodeLine, const OUString& rKeyword )
760 if( rCodeLine.matchIgnoreAsciiCase( rKeyword ) )
762 rCodeLine = rCodeLine.copy( rKeyword.getLength() );
763 // success, if code line ends after keyword, or if whitespace follows
764 return rCodeLine.isEmpty() || lclEatWhitespace( rCodeLine );
766 return false;
769 } // namespace
771 VbaUserForm::VbaUserForm( const Reference< XComponentContext >& rxContext,
772 const Reference< XModel >& rxDocModel, const GraphicHelper& rGraphicHelper, bool bDefaultColorBgr ) :
773 mxContext( rxContext ),
774 mxDocModel( rxDocModel ),
775 maConverter( rxDocModel, rGraphicHelper, bDefaultColorBgr )
777 OSL_ENSURE( mxContext.is(), "VbaUserForm::VbaUserForm - missing component context" );
778 OSL_ENSURE( mxDocModel.is(), "VbaUserForm::VbaUserForm - missing document model" );
781 void VbaUserForm::importForm( const Reference< XNameContainer >& rxDialogLib,
782 StorageBase& rVbaFormStrg, const OUString& rModuleName, rtl_TextEncoding eTextEnc )
784 OSL_ENSURE( rxDialogLib.is(), "VbaUserForm::importForm - missing dialog library" );
785 if( !mxContext.is() || !mxDocModel.is() || !rxDialogLib.is() )
786 return;
788 // check that the '03VBFrame' stream exists, this is required for forms
789 BinaryXInputStream aInStrm( rVbaFormStrg.openInputStream( "\003VBFrame" ), true );
790 OSL_ENSURE( !aInStrm.isEof(), "VbaUserForm::importForm - missing \\003VBFrame stream" );
791 if( aInStrm.isEof() )
792 return;
794 // scan for the line 'Begin {GUID} <FormName>'
795 TextInputStream aFrameTextStrm( mxContext, aInStrm, eTextEnc );
796 const OUString aBegin = "Begin";
797 OUString aLine;
798 bool bBeginFound = false;
799 while( !bBeginFound && !aFrameTextStrm.isEof() )
801 aLine = aFrameTextStrm.readLine().trim();
802 bBeginFound = lclEatKeyword( aLine, aBegin );
804 // check for the specific GUID that represents VBA forms
805 if( !bBeginFound || !lclEatKeyword( aLine, "{C62A69F0-16DC-11CE-9E98-00AA00574A4F}" ) )
806 return;
808 // remaining line is the form name
809 OUString aFormName = aLine.trim();
810 OSL_ENSURE( !aFormName.isEmpty(), "VbaUserForm::importForm - missing form name" );
811 OSL_ENSURE( rModuleName.equalsIgnoreAsciiCase( aFormName ), "VbaUserForm::importFrameStream - form and module name mismatch" );
812 if( aFormName.isEmpty() )
813 aFormName = rModuleName;
814 if( aFormName.isEmpty() )
815 return;
816 mxSiteModel.reset( new VbaSiteModel );
817 mxSiteModel->importProperty( XML_Name, aFormName );
819 // read the form properties (caption is contained in this '03VBFrame' stream, not in the 'f' stream)
820 mxCtrlModel.reset( new AxUserFormModel );
821 OUString aKey, aValue;
822 bool bExitLoop = false;
823 while( !bExitLoop && !aFrameTextStrm.isEof() )
825 aLine = aFrameTextStrm.readLine().trim();
826 bExitLoop = aLine.equalsIgnoreAsciiCase( "End" );
827 if( !bExitLoop && VbaHelper::extractKeyValue( aKey, aValue, aLine ) )
829 if( aKey.equalsIgnoreAsciiCase( "Caption" ) )
830 mxCtrlModel->importProperty( XML_Caption, lclGetQuotedString( aValue ) );
831 else if( aKey.equalsIgnoreAsciiCase( "Tag" ) )
832 mxSiteModel->importProperty( XML_Tag, lclGetQuotedString( aValue ) );
836 // use generic container control functionality to import the embedded controls
837 importStorage( rVbaFormStrg, AxClassTable() );
841 // create the dialog model
842 OUString aServiceName = mxCtrlModel->getServiceName();
843 Reference< XMultiServiceFactory > xFactory( mxContext->getServiceManager(), UNO_QUERY_THROW );
844 Reference< XControlModel > xDialogModel( xFactory->createInstance( aServiceName ), UNO_QUERY_THROW );
845 Reference< XNameContainer > xDialogNC( xDialogModel, UNO_QUERY_THROW );
847 // convert properties and embedded controls
848 if( convertProperties( xDialogModel, maConverter, 0 ) )
850 // export the dialog to XML and insert it into the dialog library
851 Reference< XInputStreamProvider > xDialogSource( ::xmlscript::exportDialogModel( xDialogNC, mxContext, mxDocModel ), UNO_SET_THROW );
852 OSL_ENSURE( !rxDialogLib->hasByName( aFormName ), "VbaUserForm::importForm - multiple dialogs with equal name" );
853 ContainerHelper::insertByName( rxDialogLib, aFormName, Any( xDialogSource ) );
856 catch(const Exception& )
861 } // namespace ole
862 } // namespace oox
864 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */