nss: upgrade to release 3.73
[LibreOffice.git] / oox / source / ole / vbacontrol.cxx
blob1b85c27dd6f6f94b9b12af768efd3797f96f90e7
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 <sal/log.hxx>
33 #include <xmlscript/xmldlg_imexp.hxx>
34 #include <oox/helper/attributelist.hxx>
35 #include <oox/helper/binaryinputstream.hxx>
36 #include <oox/helper/containerhelper.hxx>
37 #include <oox/helper/propertymap.hxx>
38 #include <oox/helper/propertyset.hxx>
39 #include <oox/helper/storagebase.hxx>
40 #include <oox/helper/textinputstream.hxx>
41 #include <oox/ole/vbahelper.hxx>
42 #include <oox/token/properties.hxx>
43 #include <oox/token/tokens.hxx>
44 #include <unordered_map>
46 namespace oox::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 ::std::set< OUString >
99 maCtrlNames;
100 sal_Int32 mnIndex;
103 constexpr OUStringLiteral gaDummyBaseName( u"DummyGroupSep" );
105 VbaControlNamesSet::VbaControlNamesSet() :
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 = gaDummyBaseName + OUString::number( ++mnIndex );
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 = std::make_shared<VbaSiteModel>();
150 mxSiteModel->importProperty( XML_Name, rName );
151 mxSiteModel->importProperty( XML_VariousPropertyBits, OUString( '0' ) );
153 mxCtrlModel = std::make_shared<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= std::make_shared<AxCommandButtonModel>(); break;
247 case VBA_SITE_LABEL: xCtrlModel= std::make_shared<AxLabelModel>(); break;
248 case VBA_SITE_IMAGE: xCtrlModel= std::make_shared<AxImageModel>(); break;
249 case VBA_SITE_TOGGLEBUTTON: xCtrlModel= std::make_shared<AxToggleButtonModel>(); break;
250 case VBA_SITE_CHECKBOX: xCtrlModel= std::make_shared<AxCheckBoxModel>(); break;
251 case VBA_SITE_OPTIONBUTTON: xCtrlModel= std::make_shared<AxOptionButtonModel>(); break;
252 case VBA_SITE_TEXTBOX: xCtrlModel= std::make_shared<AxTextBoxModel>(); break;
253 case VBA_SITE_LISTBOX: xCtrlModel= std::make_shared<AxListBoxModel>(); break;
254 case VBA_SITE_COMBOBOX: xCtrlModel= std::make_shared<AxComboBoxModel>(); break;
255 case VBA_SITE_SPINBUTTON: xCtrlModel= std::make_shared<AxSpinButtonModel>(); break;
256 case VBA_SITE_SCROLLBAR: xCtrlModel= std::make_shared<AxScrollBarModel>(); break;
257 case VBA_SITE_TABSTRIP: xCtrlModel= std::make_shared<AxTabStripModel>();
258 break;
259 case VBA_SITE_FRAME: xCtrlModel= std::make_shared<AxFrameModel>(); break;
260 case VBA_SITE_MULTIPAGE: xCtrlModel= std::make_shared<AxMultiPageModel>();
261 break;
262 case VBA_SITE_FORM: xCtrlModel= std::make_shared<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 = std::make_shared<ComCtlScrollBarModel>( 6 );
275 else if( *pGuid == COMCTL_GUID_PROGRESSBAR_50 )
276 xCtrlModel = std::make_shared<ComCtlProgressBarModel>( 5 );
277 else if( *pGuid == COMCTL_GUID_PROGRESSBAR_60 )
278 xCtrlModel = std::make_shared<ComCtlProgressBarModel>( 6 );
282 if( xCtrlModel )
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 )
328 return;
330 if( mxSiteModel->isContainer() )
332 StorageRef xSubStrg = rStrg.openSubStorage( mxSiteModel->getSubStorageName(), false );
333 OSL_ENSURE( xSubStrg, "VbaFormControl::importModelOrStorage - cannot find storage for embedded control" );
334 if( xSubStrg )
335 importStorage( *xSubStrg, rClassTable );
337 else if( !rInStrm.isEof() )
339 sal_Int64 nNextStrmPos = rInStrm.tell() + mxSiteModel->getStreamLength();
340 importControlModel( rInStrm, rClassTable );
341 rInStrm.seek( nNextStrmPos );
345 OUString VbaFormControl::getControlName() const
347 return mxSiteModel ? mxSiteModel->getName() : OUString();
350 void VbaFormControl::createAndConvert( sal_Int32 nCtrlIndex,
351 const Reference< XNameContainer >& rxParentNC, const ControlConverter& rConv ) const
353 if( !(rxParentNC.is() && mxSiteModel && mxCtrlModel) )
354 return;
358 // create the control model
359 OUString aServiceName = mxCtrlModel->getServiceName();
360 Reference< XMultiServiceFactory > xModelFactory( rxParentNC, UNO_QUERY_THROW );
361 Reference< XControlModel > xCtrlModel( xModelFactory->createInstance( aServiceName ), UNO_QUERY_THROW );
363 // convert all properties and embedded controls
364 if( convertProperties( xCtrlModel, rConv, nCtrlIndex ) )
366 // insert into parent container
367 const OUString& rCtrlName = mxSiteModel->getName();
368 OSL_ENSURE( !rxParentNC->hasByName( rCtrlName ), "VbaFormControl::createAndConvert - multiple controls with equal name" );
369 ContainerHelper::insertByName( rxParentNC, rCtrlName, Any( xCtrlModel ) );
372 catch(const Exception& )
377 // protected ------------------------------------------------------------------
379 void VbaFormControl::importControlModel( BinaryInputStream& rInStrm, const AxClassTable& rClassTable )
381 createControlModel( rClassTable );
382 if( mxCtrlModel )
383 mxCtrlModel->importBinaryModel( rInStrm );
386 void VbaFormControl::importStorage( StorageBase& rStrg, const AxClassTable& rClassTable )
388 createControlModel( rClassTable );
389 AxContainerModelBase* pContainerModel = dynamic_cast< AxContainerModelBase* >( mxCtrlModel.get() );
390 OSL_ENSURE( pContainerModel, "VbaFormControl::importStorage - missing container control model" );
391 if( !pContainerModel )
392 return;
394 /* Open the 'f' stream containing the model of this control and a list
395 of site models for all child controls. */
396 BinaryXInputStream aFStrm( rStrg.openInputStream( "f" ), true );
397 OSL_ENSURE( !aFStrm.isEof(), "VbaFormControl::importStorage - missing 'f' stream" );
399 /* Read the properties of this container control and the class table
400 (into the maClassTable vector) containing a list of GUIDs for
401 exotic embedded controls. */
402 if( !(!aFStrm.isEof() && pContainerModel->importBinaryModel( aFStrm ) && pContainerModel->importClassTable( aFStrm, maClassTable )) )
403 return;
405 /* Read the site models of all embedded controls (this fills the
406 maControls vector). Ignore failure of importSiteModels() but
407 try to import as much controls as possible. */
408 importEmbeddedSiteModels( aFStrm );
409 /* Open the 'o' stream containing models of embedded simple
410 controls. Stream may be empty or missing, if this control
411 contains no controls or only container controls. */
412 BinaryXInputStream aOStrm( rStrg.openInputStream( "o" ), true );
414 /* Iterate over all embedded controls, import model from 'o'
415 stream (for embedded simple controls) or from the substorage
416 (for embedded container controls). */
417 maControls.forEachMem( &VbaFormControl::importModelOrStorage,
418 ::std::ref( aOStrm ), ::std::ref( rStrg ), ::std::cref( maClassTable ) );
420 // Special handling for multi-page which has non-standard
421 // containment and additionally needs to re-order Page children
422 if ( pContainerModel->getControlType() == API_CONTROL_MULTIPAGE )
424 AxMultiPageModel* pMultiPage = dynamic_cast< AxMultiPageModel* >( pContainerModel );
425 assert(pMultiPage);
427 BinaryXInputStream aXStrm( rStrg.openInputStream( "x" ), true );
428 pMultiPage->importPageAndMultiPageProperties( aXStrm, maControls.size() );
430 typedef std::unordered_map< sal_uInt32, std::shared_ptr< VbaFormControl > > IdToPageMap;
431 IdToPageMap idToPage;
432 AxArrayString sCaptions;
434 for (auto const& control : maControls)
436 auto& elem = control->mxCtrlModel;
437 if (!elem)
439 SAL_WARN("oox", "empty control model");
440 continue;
442 if (elem->getControlType() == API_CONTROL_PAGE)
444 VbaSiteModelRef xPageSiteRef = control->mxSiteModel;
445 if ( xPageSiteRef )
446 idToPage[ xPageSiteRef->getId() ] = control;
448 else
450 AxTabStripModel* pTabStrip = static_cast<AxTabStripModel*>(elem.get());
451 sCaptions = pTabStrip->maItems;
452 pMultiPage->mnActiveTab = pTabStrip->mnListIndex;
453 pMultiPage->mnTabStyle = pTabStrip->mnTabStyle;
456 // apply caption/titles to pages
458 maControls.clear();
459 // need to sort the controls according to the order of the ids
460 if ( sCaptions.size() == idToPage.size() )
462 AxArrayString::iterator itCaption = sCaptions.begin();
463 for ( const auto& rCtrlId : pMultiPage->mnIDs )
465 IdToPageMap::iterator iter = idToPage.find( rCtrlId );
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 );
473 ++itCaption;
477 /* Reorder the controls (sorts all option buttons of an option
478 group together), and move all children of all embedded frames
479 (group boxes) to this control (UNO group boxes cannot contain
480 other controls). */
481 finalizeEmbeddedControls();
484 bool VbaFormControl::convertProperties( const Reference< XControlModel >& rxCtrlModel,
485 const ControlConverter& rConv, sal_Int32 nCtrlIndex ) const
487 if( rxCtrlModel.is() && mxSiteModel && mxCtrlModel )
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 ::std::cref( xCtrlModelNC ), ::std::cref( rConv ) );
511 catch(const Exception& )
513 OSL_FAIL( "VbaFormControl::convertProperties - cannot get control container interface" );
516 return true;
519 return false;
522 // private --------------------------------------------------------------------
524 void VbaFormControl::createControlModel( const AxClassTable& rClassTable )
526 // derived classes may have created their own control model
527 if( !mxCtrlModel && mxSiteModel )
528 mxCtrlModel = mxSiteModel->createControlModel( rClassTable );
531 bool VbaFormControl::importSiteModel( BinaryInputStream& rInStrm )
533 mxSiteModel = std::make_shared<VbaSiteModel>();
534 return mxSiteModel->importBinaryModel( rInStrm );
537 void VbaFormControl::importEmbeddedSiteModels( BinaryInputStream& rInStrm )
539 sal_uInt64 nAnchorPos = rInStrm.tell();
540 sal_uInt32 nSiteCount, nSiteDataSize;
541 nSiteCount = rInStrm.readuInt32();
542 nSiteDataSize = rInStrm.readuInt32();
543 sal_Int64 nSiteEndPos = rInStrm.tell() + nSiteDataSize;
545 // skip the site info structure
546 sal_uInt32 nSiteIndex = 0;
547 while( !rInStrm.isEof() && (nSiteIndex < nSiteCount) )
549 rInStrm.skip( 1 ); // site depth
550 sal_uInt8 nTypeCount = rInStrm.readuInt8(); // 'type-or-count' byte
551 if( getFlag( nTypeCount, VBA_SITEINFO_COUNT ) )
553 /* Count flag is set: the 'type-or-count' byte contains the number
554 of controls in the lower bits, the type specifier follows in
555 the next byte. The type specifier should always be 1 according
556 to the specification. */
557 rInStrm.skip( 1 );
558 nSiteIndex += (nTypeCount & VBA_SITEINFO_MASK);
560 else
562 /* Count flag is not set: the 'type-or-count' byte contains the
563 type specifier of *one* control in the lower bits (this type
564 should be 1, see above). */
565 ++nSiteIndex;
568 // align the stream to 32bit, relative to start of entire site info
569 rInStrm.alignToBlock( 4, nAnchorPos );
571 // import the site models for all embedded controls
572 maControls.clear();
573 bool bValid = !rInStrm.isEof();
574 for( nSiteIndex = 0; bValid && (nSiteIndex < nSiteCount); ++nSiteIndex )
576 VbaFormControlRef xControl = std::make_shared<VbaFormControl>();
577 maControls.push_back( xControl );
578 bValid = xControl->importSiteModel( rInStrm );
581 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 (auto const& control : maControls)
606 if( control->mxCtrlModel && (control->mxCtrlModel->getControlType() == API_CONTROL_GROUPBOX) )
607 control->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 (auto const& control : maControls)
626 const ControlModelBase* pCtrlModel = control->mxCtrlModel.get();
628 if( const AxOptionButtonModel* pOptButtonModel = dynamic_cast< const AxOptionButtonModel* >( pCtrlModel ) )
630 // check if a new option group needs to be created
631 const OUString& rGroupName = pOptButtonModel->getGroupName();
632 VbaFormControlVectorRef& rxOptionGroup = aOptionGroups[ rGroupName ];
633 if( !rxOptionGroup )
635 /* If last control was an option button too, we have two
636 option groups following each other, so a dummy separator
637 control is needed. */
638 if( bLastWasOptionButton )
640 VbaFormControlVectorRef xDummyGroup = std::make_shared<VbaFormControlVector>();
641 aControlGroups.push_back( xDummyGroup );
642 OUString aName = aControlNames.generateDummyName();
643 VbaFormControlRef xDummyControl = std::make_shared<VbaDummyFormControl>( aName );
644 xDummyGroup->push_back( xDummyControl );
646 rxOptionGroup = std::make_shared<VbaFormControlVector>();
647 aControlGroups.push_back( rxOptionGroup );
649 /* Append the option button to the control group (which is now
650 referred by the vector aControlGroups and by the map
651 aOptionGroups). */
652 rxOptionGroup->push_back(control);
653 bLastWasOptionButton = true;
655 else
657 // open a new control group, if the last group is an option group
658 if( bLastWasOptionButton || aControlGroups.empty() )
660 VbaFormControlVectorRef xControlGroup = std::make_shared<VbaFormControlVector>();
661 aControlGroups.push_back( xControlGroup );
663 // append the control to the last control group
664 VbaFormControlVector& rLastGroup = *aControlGroups.back();
665 rLastGroup.push_back(control);
666 bLastWasOptionButton = false;
668 // if control is a group box, move all its children to this control
669 if( pCtrlModel && (pCtrlModel->getControlType() == API_CONTROL_GROUPBOX) )
671 /* Move all embedded controls of the group box relative to the
672 position of the group box. */
673 control->moveEmbeddedToAbsoluteParent();
674 /* Insert all children of the group box into the last control
675 group (following the group box). */
676 rLastGroup.insert( rLastGroup.end(), control->maControls.begin(), control->maControls.end() );
677 control->maControls.clear();
678 // check if last control of the group box is an option button
679 bLastWasOptionButton = dynamic_cast< const AxOptionButtonModel* >( rLastGroup.back()->mxCtrlModel.get() ) != nullptr;
684 // flatten the vector of vectors of form controls to a single vector
685 maControls.clear();
686 for (auto const& controlGroup : aControlGroups)
687 maControls.insert( maControls.end(), controlGroup->begin(), controlGroup->end() );
690 void VbaFormControl::moveRelative( const AxPairData& rDistance )
692 if( mxSiteModel )
693 mxSiteModel->moveRelative( rDistance );
696 void VbaFormControl::moveEmbeddedToAbsoluteParent()
698 if( !mxSiteModel || maControls.empty() )
699 return;
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, ::std::cref( aDistance ) );
718 bool VbaFormControl::compareByTabIndex( const VbaFormControlRef& rxLeft, const VbaFormControlRef& rxRight )
720 // sort controls without model to the end
721 sal_Int32 nLeftTabIndex = rxLeft->mxSiteModel ? rxLeft->mxSiteModel->getTabIndex() : SAL_MAX_INT32;
722 sal_Int32 nRightTabIndex = rxRight->mxSiteModel ? rxRight->mxSiteModel->getTabIndex() : SAL_MAX_INT32;
723 return nLeftTabIndex < nRightTabIndex;
726 namespace {
728 OUString lclGetQuotedString( const OUString& rCodeLine )
730 OUStringBuffer aBuffer;
731 sal_Int32 nLen = rCodeLine.getLength();
732 if( (nLen > 0) && (rCodeLine[ 0 ] == '"') )
734 bool bExitLoop = false;
735 for( sal_Int32 nIndex = 1; !bExitLoop && (nIndex < nLen); ++nIndex )
737 sal_Unicode cChar = rCodeLine[ nIndex ];
738 // exit on closing quote char (but check on double quote chars)
739 bExitLoop = (cChar == '"') && ((nIndex + 1 == nLen) || (rCodeLine[ nIndex + 1 ] != '"'));
740 if( !bExitLoop )
742 aBuffer.append( cChar );
743 // skip second quote char
744 if( cChar == '"' )
745 ++nIndex;
749 return aBuffer.makeStringAndClear();
752 bool lclEatWhitespace( OUString& rCodeLine )
754 sal_Int32 nIndex = 0;
755 while( (nIndex < rCodeLine.getLength()) && ((rCodeLine[ nIndex ] == ' ') || (rCodeLine[ nIndex ] == '\t')) )
756 ++nIndex;
757 if( nIndex > 0 )
759 rCodeLine = rCodeLine.copy( nIndex );
760 return true;
762 return false;
765 bool lclEatKeyword( OUString& rCodeLine, const OUString& rKeyword )
767 if( rCodeLine.matchIgnoreAsciiCase( rKeyword ) )
769 rCodeLine = rCodeLine.copy( rKeyword.getLength() );
770 // success, if code line ends after keyword, or if whitespace follows
771 return rCodeLine.isEmpty() || lclEatWhitespace( rCodeLine );
773 return false;
776 } // namespace
778 VbaUserForm::VbaUserForm( const Reference< XComponentContext >& rxContext,
779 const Reference< XModel >& rxDocModel, const GraphicHelper& rGraphicHelper, bool bDefaultColorBgr ) :
780 mxContext( rxContext ),
781 mxDocModel( rxDocModel ),
782 maConverter( rxDocModel, rGraphicHelper, bDefaultColorBgr )
784 OSL_ENSURE( mxContext.is(), "VbaUserForm::VbaUserForm - missing component context" );
785 OSL_ENSURE( mxDocModel.is(), "VbaUserForm::VbaUserForm - missing document model" );
788 void VbaUserForm::importForm( const Reference< XNameContainer >& rxDialogLib,
789 StorageBase& rVbaFormStrg, const OUString& rModuleName, rtl_TextEncoding eTextEnc )
791 OSL_ENSURE( rxDialogLib.is(), "VbaUserForm::importForm - missing dialog library" );
792 if( !mxContext.is() || !mxDocModel.is() || !rxDialogLib.is() )
793 return;
795 // check that the '03VBFrame' stream exists, this is required for forms
796 BinaryXInputStream aInStrm( rVbaFormStrg.openInputStream( "\003VBFrame" ), true );
797 OSL_ENSURE( !aInStrm.isEof(), "VbaUserForm::importForm - missing \\003VBFrame stream" );
798 if( aInStrm.isEof() )
799 return;
801 // scan for the line 'Begin {GUID} <FormName>'
802 TextInputStream aFrameTextStrm( mxContext, aInStrm, eTextEnc );
803 const OUString aBegin = "Begin";
804 OUString aLine;
805 bool bBeginFound = false;
806 while( !bBeginFound && !aFrameTextStrm.isEof() )
808 aLine = aFrameTextStrm.readLine().trim();
809 bBeginFound = lclEatKeyword( aLine, aBegin );
811 // check for the specific GUID that represents VBA forms
812 if( !bBeginFound || !lclEatKeyword( aLine, "{C62A69F0-16DC-11CE-9E98-00AA00574A4F}" ) )
813 return;
815 // remaining line is the form name
816 OUString aFormName = aLine.trim();
817 OSL_ENSURE( !aFormName.isEmpty(), "VbaUserForm::importForm - missing form name" );
818 OSL_ENSURE( rModuleName.equalsIgnoreAsciiCase( aFormName ), "VbaUserForm::importFrameStream - form and module name mismatch" );
819 if( aFormName.isEmpty() )
820 aFormName = rModuleName;
821 if( aFormName.isEmpty() )
822 return;
823 mxSiteModel = std::make_shared<VbaSiteModel>();
824 mxSiteModel->importProperty( XML_Name, aFormName );
826 // read the form properties (caption is contained in this '03VBFrame' stream, not in the 'f' stream)
827 mxCtrlModel = std::make_shared<AxUserFormModel>();
828 OUString aKey, aValue;
829 bool bExitLoop = false;
830 while( !bExitLoop && !aFrameTextStrm.isEof() )
832 aLine = aFrameTextStrm.readLine().trim();
833 bExitLoop = aLine.equalsIgnoreAsciiCase( "End" );
834 if( !bExitLoop && VbaHelper::extractKeyValue( aKey, aValue, aLine ) )
836 if( aKey.equalsIgnoreAsciiCase( "Caption" ) )
837 mxCtrlModel->importProperty( XML_Caption, lclGetQuotedString( aValue ) );
838 else if( aKey.equalsIgnoreAsciiCase( "Tag" ) )
839 mxSiteModel->importProperty( XML_Tag, lclGetQuotedString( aValue ) );
843 // use generic container control functionality to import the embedded controls
844 importStorage( rVbaFormStrg, AxClassTable() );
848 // create the dialog model
849 OUString aServiceName = mxCtrlModel->getServiceName();
850 Reference< XMultiServiceFactory > xFactory( mxContext->getServiceManager(), UNO_QUERY_THROW );
851 Reference< XControlModel > xDialogModel( xFactory->createInstance( aServiceName ), UNO_QUERY_THROW );
852 Reference< XNameContainer > xDialogNC( xDialogModel, UNO_QUERY_THROW );
854 // convert properties and embedded controls
855 if( convertProperties( xDialogModel, maConverter, 0 ) )
857 // export the dialog to XML and insert it into the dialog library
858 Reference< XInputStreamProvider > xDialogSource( ::xmlscript::exportDialogModel( xDialogNC, mxContext, mxDocModel ), UNO_SET_THROW );
859 OSL_ENSURE( !rxDialogLib->hasByName( aFormName ), "VbaUserForm::importForm - multiple dialogs with equal name" );
860 ContainerHelper::insertByName( rxDialogLib, aFormName, Any( xDialogSource ) );
863 catch(const Exception& )
868 } // namespace oox
870 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */