1 /**************************************************************
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 *************************************************************/
21
22
23
24 #include "oox/ole/vbacontrol.hxx"
25
26 #include <algorithm>
27 #include <set>
28 #include <com/sun/star/awt/XControlModel.hpp>
29 #include <com/sun/star/container/XNameContainer.hpp>
30 #include <com/sun/star/io/XInputStreamProvider.hpp>
31 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
32 #include <com/sun/star/uno/XComponentContext.hpp>
33 #include <rtl/ustrbuf.hxx>
34 #include <xmlscript/xmldlg_imexp.hxx>
35 #include "oox/helper/attributelist.hxx"
36 #include "oox/helper/binaryinputstream.hxx"
37 #include "oox/helper/containerhelper.hxx"
38 #include "oox/helper/propertymap.hxx"
39 #include "oox/helper/propertyset.hxx"
40 #include "oox/helper/storagebase.hxx"
41 #include "oox/helper/textinputstream.hxx"
42 #include "oox/ole/vbahelper.hxx"
43
44 namespace oox {
45 namespace ole {
46
47 // ============================================================================
48
49 using namespace ::com::sun::star::awt;
50 using namespace ::com::sun::star::container;
51 using namespace ::com::sun::star::frame;
52 using namespace ::com::sun::star::io;
53 using namespace ::com::sun::star::lang;
54 using namespace ::com::sun::star::uno;
55
56 using ::rtl::OUString;
57 using ::rtl::OUStringBuffer;
58
59 // ============================================================================
60
61 namespace {
62
63 const sal_uInt16 VBA_SITE_CLASSIDINDEX = 0x8000;
64 const sal_uInt16 VBA_SITE_INDEXMASK = 0x7FFF;
65 const sal_uInt16 VBA_SITE_FORM = 7;
66 const sal_uInt16 VBA_SITE_IMAGE = 12;
67 const sal_uInt16 VBA_SITE_FRAME = 14;
68 const sal_uInt16 VBA_SITE_SPINBUTTON = 16;
69 const sal_uInt16 VBA_SITE_COMMANDBUTTON = 17;
70 const sal_uInt16 VBA_SITE_TABSTRIP = 18;
71 const sal_uInt16 VBA_SITE_LABEL = 21;
72 const sal_uInt16 VBA_SITE_TEXTBOX = 23;
73 const sal_uInt16 VBA_SITE_LISTBOX = 24;
74 const sal_uInt16 VBA_SITE_COMBOBOX = 25;
75 const sal_uInt16 VBA_SITE_CHECKBOX = 26;
76 const sal_uInt16 VBA_SITE_OPTIONBUTTON = 27;
77 const sal_uInt16 VBA_SITE_TOGGLEBUTTON = 28;
78 const sal_uInt16 VBA_SITE_SCROLLBAR = 47;
79 const sal_uInt16 VBA_SITE_MULTIPAGE = 57;
80 const sal_uInt16 VBA_SITE_UNKNOWN = 0x7FFF;
81
82 const sal_uInt32 VBA_SITE_TABSTOP = 0x00000001;
83 const sal_uInt32 VBA_SITE_VISIBLE = 0x00000002;
84 const sal_uInt32 VBA_SITE_DEFAULTBUTTON = 0x00000004;
85 const sal_uInt32 VBA_SITE_CANCELBUTTON = 0x00000008;
86 const sal_uInt32 VBA_SITE_OSTREAM = 0x00000010;
87 const sal_uInt32 VBA_SITE_DEFFLAGS = 0x00000033;
88
89 const sal_uInt8 VBA_SITEINFO_COUNT = 0x80;
90 const sal_uInt8 VBA_SITEINFO_MASK = 0x7F;
91
92 // ----------------------------------------------------------------------------
93
94 /** Collects names of all controls in a user form or container control. Allows
95 to generate unused names for dummy controls separating option groups.
96 */
97 class VbaControlNamesSet
98 {
99 public:
100 explicit VbaControlNamesSet();
101
102 /** Inserts the name of the passed control. */
103 void insertName( const VbaFormControl& rControl );
104 /** Returns a name that is not contained in this set. */
105 OUString generateDummyName();
106
107 private:
108 typedef ::std::set< OUString > OUStringSet;
109 OUStringSet maCtrlNames;
110 const OUString maDummyBaseName;
111 sal_Int32 mnIndex;
112 };
113
VbaControlNamesSet()114 VbaControlNamesSet::VbaControlNamesSet() :
115 maDummyBaseName( CREATE_OUSTRING( "DummyGroupSep" ) ),
116 mnIndex( 0 )
117 {
118 }
119
insertName(const VbaFormControl & rControl)120 void VbaControlNamesSet::insertName( const VbaFormControl& rControl )
121 {
122 OUString aName = rControl.getControlName();
123 if( aName.getLength() > 0 )
124 maCtrlNames.insert( aName );
125 }
126
generateDummyName()127 OUString VbaControlNamesSet::generateDummyName()
128 {
129 OUString aCtrlName;
130 do
131 {
132 aCtrlName = OUStringBuffer( maDummyBaseName ).append( ++mnIndex ).makeStringAndClear();
133 }
134 while( maCtrlNames.count( aCtrlName ) > 0 );
135 maCtrlNames.insert( aCtrlName );
136 return aCtrlName;
137 }
138
139 // ----------------------------------------------------------------------------
140
141 /** Functor that inserts the name of a control into a VbaControlNamesSet. */
142 struct VbaControlNameInserter
143 {
144 public:
145 VbaControlNamesSet& mrCtrlNames;
VbaControlNameInserteroox::ole::__anoneb805c5d0111::VbaControlNameInserter146 inline explicit VbaControlNameInserter( VbaControlNamesSet& rCtrlNames ) : mrCtrlNames( rCtrlNames ) {}
operator ()oox::ole::__anoneb805c5d0111::VbaControlNameInserter147 inline void operator()( const VbaFormControl& rControl ) { mrCtrlNames.insertName( rControl ); }
148 };
149
150 // ----------------------------------------------------------------------------
151
152 /** A dummy invisible form control (fixed label without text) that is used to
153 separate two groups of option buttons.
154 */
155 class VbaDummyFormControl : public VbaFormControl
156 {
157 public:
158 explicit VbaDummyFormControl( const OUString& rName );
159 };
160
VbaDummyFormControl(const OUString & rName)161 VbaDummyFormControl::VbaDummyFormControl( const OUString& rName )
162 {
163 mxSiteModel.reset( new VbaSiteModel );
164 mxSiteModel->importProperty( XML_Name, rName );
165 mxSiteModel->importProperty( XML_VariousPropertyBits, OUString( sal_Unicode( '0' ) ) );
166
167 mxCtrlModel.reset( new AxLabelModel );
168 mxCtrlModel->setAwtModelMode();
169 mxCtrlModel->importProperty( XML_Size, CREATE_OUSTRING( "10;10" ) );
170 }
171
172 } // namespace
173
174 // ============================================================================
175
VbaSiteModel()176 VbaSiteModel::VbaSiteModel() :
177 maPos( 0, 0 ),
178 mnId( 0 ),
179 mnHelpContextId( 0 ),
180 mnFlags( VBA_SITE_DEFFLAGS ),
181 mnStreamLen( 0 ),
182 mnTabIndex( -1 ),
183 mnClassIdOrCache( VBA_SITE_UNKNOWN ),
184 mnGroupId( 0 )
185 {
186 }
187
~VbaSiteModel()188 VbaSiteModel::~VbaSiteModel()
189 {
190 }
191
importProperty(sal_Int32 nPropId,const OUString & rValue)192 void VbaSiteModel::importProperty( sal_Int32 nPropId, const OUString& rValue )
193 {
194 switch( nPropId )
195 {
196 case XML_Name: maName = rValue; break;
197 case XML_Tag: maTag = rValue; break;
198 case XML_VariousPropertyBits: mnFlags = AttributeConversion::decodeUnsigned( rValue ); break;
199 }
200 }
201
importBinaryModel(BinaryInputStream & rInStrm)202 bool VbaSiteModel::importBinaryModel( BinaryInputStream& rInStrm )
203 {
204 AxBinaryPropertyReader aReader( rInStrm );
205 aReader.readStringProperty( maName );
206 aReader.readStringProperty( maTag );
207 aReader.readIntProperty< sal_Int32 >( mnId );
208 aReader.readIntProperty< sal_Int32 >( mnHelpContextId );
209 aReader.readIntProperty< sal_uInt32 >( mnFlags );
210 aReader.readIntProperty< sal_uInt32 >( mnStreamLen );
211 aReader.readIntProperty< sal_Int16 >( mnTabIndex );
212 aReader.readIntProperty< sal_uInt16 >( mnClassIdOrCache );
213 aReader.readPairProperty( maPos );
214 aReader.readIntProperty< sal_uInt16 >( mnGroupId );
215 aReader.skipUndefinedProperty();
216 aReader.readStringProperty( maToolTip );
217 aReader.skipStringProperty(); // license key
218 aReader.readStringProperty( maControlSource );
219 aReader.readStringProperty( maRowSource );
220 return aReader.finalizeImport();
221 }
222
moveRelative(const AxPairData & rDistance)223 void VbaSiteModel::moveRelative( const AxPairData& rDistance )
224 {
225 maPos.first += rDistance.first;
226 maPos.second += rDistance.second;
227 }
228
isVisible() const229 bool VbaSiteModel::isVisible() const
230 {
231 return getFlag( mnFlags, VBA_SITE_VISIBLE );
232 }
233
isContainer() const234 bool VbaSiteModel::isContainer() const
235 {
236 return !getFlag( mnFlags, VBA_SITE_OSTREAM );
237 }
238
getStreamLength() const239 sal_uInt32 VbaSiteModel::getStreamLength() const
240 {
241 return isContainer() ? 0 : mnStreamLen;
242 }
243
getSubStorageName() const244 OUString VbaSiteModel::getSubStorageName() const
245 {
246 if( mnId >= 0 )
247 {
248 OUStringBuffer aBuffer;
249 aBuffer.append( sal_Unicode( 'i' ) );
250 if( mnId < 10 )
251 aBuffer.append( sal_Unicode( '0' ) );
252 aBuffer.append( mnId );
253 return aBuffer.makeStringAndClear();
254 }
255 return OUString();
256 }
257
createControlModel(const AxClassTable & rClassTable) const258 ControlModelRef VbaSiteModel::createControlModel( const AxClassTable& rClassTable ) const
259 {
260 ControlModelRef xCtrlModel;
261
262 sal_Int32 nTypeIndex = static_cast< sal_Int32 >( mnClassIdOrCache & VBA_SITE_INDEXMASK );
263 if( !getFlag( mnClassIdOrCache, VBA_SITE_CLASSIDINDEX ) )
264 {
265 switch( nTypeIndex )
266 {
267 case VBA_SITE_COMMANDBUTTON: xCtrlModel.reset( new AxCommandButtonModel ); break;
268 case VBA_SITE_LABEL: xCtrlModel.reset( new AxLabelModel ); break;
269 case VBA_SITE_IMAGE: xCtrlModel.reset( new AxImageModel ); break;
270 case VBA_SITE_TOGGLEBUTTON: xCtrlModel.reset( new AxToggleButtonModel ); break;
271 case VBA_SITE_CHECKBOX: xCtrlModel.reset( new AxCheckBoxModel ); break;
272 case VBA_SITE_OPTIONBUTTON: xCtrlModel.reset( new AxOptionButtonModel ); break;
273 case VBA_SITE_TEXTBOX: xCtrlModel.reset( new AxTextBoxModel ); break;
274 case VBA_SITE_LISTBOX: xCtrlModel.reset( new AxListBoxModel ); break;
275 case VBA_SITE_COMBOBOX: xCtrlModel.reset( new AxComboBoxModel ); break;
276 case VBA_SITE_SPINBUTTON: /*xCtrlModel.reset( new AxSpinButtonModel );*/ break; // not supported (?)
277 case VBA_SITE_SCROLLBAR: xCtrlModel.reset( new AxScrollBarModel ); break;
278 case VBA_SITE_TABSTRIP: break; // not supported
279 case VBA_SITE_FRAME: xCtrlModel.reset( new AxFrameModel ); break;
280 case VBA_SITE_MULTIPAGE: break; // not supported
281 case VBA_SITE_FORM: break; // not supported
282 default: OSL_ENSURE( false, "VbaSiteModel::createControlModel - unknown type index" );
283 }
284 }
285 else
286 {
287 const OUString* pGuid = ContainerHelper::getVectorElement( rClassTable, nTypeIndex );
288 OSL_ENSURE( pGuid, "VbaSiteModel::createControlModel - invalid class table index" );
289 if( pGuid )
290 {
291 if( pGuid->equalsAscii( COMCTL_GUID_SCROLLBAR_60 ) )
292 xCtrlModel.reset( new ComCtlScrollBarModel( 6 ) );
293 else if( pGuid->equalsAscii( COMCTL_GUID_PROGRESSBAR_50 ) )
294 xCtrlModel.reset( new ComCtlProgressBarModel( 5 ) );
295 else if( pGuid->equalsAscii( COMCTL_GUID_PROGRESSBAR_60 ) )
296 xCtrlModel.reset( new ComCtlProgressBarModel( 6 ) );
297 }
298 }
299
300 if( xCtrlModel.get() )
301 {
302 // user form controls are AWT models
303 xCtrlModel->setAwtModelMode();
304
305 // check that container model matches container flag in site data
306 bool bModelIsContainer = dynamic_cast< const AxContainerModelBase* >( xCtrlModel.get() ) != 0;
307 bool bTypeMatch = bModelIsContainer == isContainer();
308 OSL_ENSURE( bTypeMatch, "VbaSiteModel::createControlModel - container type does not match container flag" );
309 if( !bTypeMatch )
310 xCtrlModel.reset();
311 }
312 return xCtrlModel;
313 }
314
convertProperties(PropertyMap & rPropMap,const ControlConverter & rConv,ApiControlType eCtrlType,sal_Int32 nCtrlIndex) const315 void VbaSiteModel::convertProperties( PropertyMap& rPropMap,
316 const ControlConverter& rConv, ApiControlType eCtrlType, sal_Int32 nCtrlIndex ) const
317 {
318 rPropMap.setProperty( PROP_Name, maName );
319 rPropMap.setProperty( PROP_Tag, maTag );
320
321 if( eCtrlType != API_CONTROL_DIALOG )
322 {
323 rPropMap.setProperty( PROP_HelpText, maToolTip );
324 rPropMap.setProperty( PROP_EnableVisible, getFlag( mnFlags, VBA_SITE_VISIBLE ) );
325 // we need to set the passed control index to make option button groups work
326 if( (0 <= nCtrlIndex) && (nCtrlIndex <= SAL_MAX_INT16) )
327 rPropMap.setProperty( PROP_TabIndex, static_cast< sal_Int16 >( nCtrlIndex ) );
328 // progress bar and group box support TabIndex, but not Tabstop...
329 if( (eCtrlType != API_CONTROL_PROGRESSBAR) && (eCtrlType != API_CONTROL_GROUPBOX) && (eCtrlType != API_CONTROL_FRAME) && (eCtrlType != API_CONTROL_PAGE) )
330 rPropMap.setProperty( PROP_Tabstop, getFlag( mnFlags, VBA_SITE_TABSTOP ) );
331 rConv.convertPosition( rPropMap, maPos );
332 }
333 }
334
bindToSources(const Reference<XControlModel> & rxCtrlModel,const ControlConverter & rConv) const335 void VbaSiteModel::bindToSources( const Reference< XControlModel >& rxCtrlModel, const ControlConverter& rConv ) const
336 {
337 rConv.bindToSources( rxCtrlModel, maControlSource, maRowSource );
338 }
339
340 // ============================================================================
341
VbaFormControl()342 VbaFormControl::VbaFormControl()
343 {
344 }
345
~VbaFormControl()346 VbaFormControl::~VbaFormControl()
347 {
348 }
349
importModelOrStorage(BinaryInputStream & rInStrm,StorageBase & rStrg,const AxClassTable & rClassTable)350 void VbaFormControl::importModelOrStorage( BinaryInputStream& rInStrm, StorageBase& rStrg, const AxClassTable& rClassTable )
351 {
352 if( mxSiteModel.get() )
353 {
354 if( mxSiteModel->isContainer() )
355 {
356 StorageRef xSubStrg = rStrg.openSubStorage( mxSiteModel->getSubStorageName(), false );
357 OSL_ENSURE( xSubStrg.get(), "VbaFormControl::importModelOrStorage - cannot find storage for embedded control" );
358 if( xSubStrg.get() )
359 importStorage( *xSubStrg, rClassTable );
360 }
361 else if( !rInStrm.isEof() )
362 {
363 sal_Int64 nNextStrmPos = rInStrm.tell() + mxSiteModel->getStreamLength();
364 importControlModel( rInStrm, rClassTable );
365 rInStrm.seek( nNextStrmPos );
366 }
367 }
368 }
369
getControlName() const370 OUString VbaFormControl::getControlName() const
371 {
372 return mxSiteModel.get() ? mxSiteModel->getName() : OUString();
373 }
374
getControlId() const375 sal_Int32 VbaFormControl::getControlId() const
376 {
377 return mxSiteModel.get() ? mxSiteModel->getId() : -1;
378 }
379
createAndConvert(sal_Int32 nCtrlIndex,const Reference<XNameContainer> & rxParentNC,const ControlConverter & rConv) const380 void VbaFormControl::createAndConvert( sal_Int32 nCtrlIndex,
381 const Reference< XNameContainer >& rxParentNC, const ControlConverter& rConv ) const
382 {
383 if( rxParentNC.is() && mxSiteModel.get() && mxCtrlModel.get() ) try
384 {
385 // create the control model
386 OUString aServiceName = mxCtrlModel->getServiceName();
387 Reference< XMultiServiceFactory > xModelFactory( rxParentNC, UNO_QUERY_THROW );
388 Reference< XControlModel > xCtrlModel( xModelFactory->createInstance( aServiceName ), UNO_QUERY_THROW );
389
390 // convert all properties and embedded controls
391 if( convertProperties( xCtrlModel, rConv, nCtrlIndex ) )
392 {
393 // insert into parent container
394 const OUString& rCtrlName = mxSiteModel->getName();
395 OSL_ENSURE( !rxParentNC->hasByName( rCtrlName ), "VbaFormControl::createAndConvert - multiple controls with equal name" );
396 ContainerHelper::insertByName( rxParentNC, rCtrlName, Any( xCtrlModel ) );
397 }
398 }
399 catch( Exception& )
400 {
401 }
402 }
403
404 // protected ------------------------------------------------------------------
405
importControlModel(BinaryInputStream & rInStrm,const AxClassTable & rClassTable)406 void VbaFormControl::importControlModel( BinaryInputStream& rInStrm, const AxClassTable& rClassTable )
407 {
408 createControlModel( rClassTable );
409 if( mxCtrlModel.get() )
410 mxCtrlModel->importBinaryModel( rInStrm );
411 }
412
importStorage(StorageBase & rStrg,const AxClassTable & rClassTable)413 void VbaFormControl::importStorage( StorageBase& rStrg, const AxClassTable& rClassTable )
414 {
415 createControlModel( rClassTable );
416 AxContainerModelBase* pContainerModel = dynamic_cast< AxContainerModelBase* >( mxCtrlModel.get() );
417 OSL_ENSURE( pContainerModel, "VbaFormControl::importStorage - missing container control model" );
418 if( pContainerModel )
419 {
420 /* Open the 'f' stream containing the model of this control and a list
421 of site models for all child controls. */
422 BinaryXInputStream aFStrm( rStrg.openInputStream( CREATE_OUSTRING( "f" ) ), true );
423 OSL_ENSURE( !aFStrm.isEof(), "VbaFormControl::importStorage - missing 'f' stream" );
424
425 /* Read the properties of this container control and the class table
426 (into the maClassTable vector) containing a list of GUIDs for
427 exotic embedded controls. */
428 if( !aFStrm.isEof() && pContainerModel->importBinaryModel( aFStrm ) && pContainerModel->importClassTable( aFStrm, maClassTable ) )
429 {
430 /* Read the site models of all embedded controls (this fills the
431 maControls vector). Ignore failure of importSiteModels() but
432 try to import as much controls as possible. */
433 importEmbeddedSiteModels( aFStrm );
434
435 /* Open the 'o' stream containing models of embedded simple
436 controls. Stream may be empty or missing, if this control
437 contains no controls or only container controls. */
438 BinaryXInputStream aOStrm( rStrg.openInputStream( CREATE_OUSTRING( "o" ) ), true );
439
440 /* Iterate over all embedded controls, import model from 'o'
441 stream (for embedded simple controls) or from the substorage
442 (for embedded container controls). */
443 maControls.forEachMem( &VbaFormControl::importModelOrStorage,
444 ::boost::ref( aOStrm ), ::boost::ref( rStrg ), ::boost::cref( maClassTable ) );
445
446 /* Reorder the controls (sorts all option buttons of an option
447 group together), and move all children of all embedded frames
448 (group boxes) to this control (UNO group boxes cannot contain
449 other controls). */
450 finalizeEmbeddedControls();
451 }
452 }
453 }
454
convertProperties(const Reference<XControlModel> & rxCtrlModel,const ControlConverter & rConv,sal_Int32 nCtrlIndex) const455 bool VbaFormControl::convertProperties( const Reference< XControlModel >& rxCtrlModel,
456 const ControlConverter& rConv, sal_Int32 nCtrlIndex ) const
457 {
458 if( rxCtrlModel.is() && mxSiteModel.get() && mxCtrlModel.get() )
459 {
460 const OUString& rCtrlName = mxSiteModel->getName();
461 OSL_ENSURE( rCtrlName.getLength() > 0, "VbaFormControl::convertProperties - control without name" );
462 if( rCtrlName.getLength() > 0 )
463 {
464 // convert all properties
465 PropertyMap aPropMap;
466 mxSiteModel->convertProperties( aPropMap, rConv, mxCtrlModel->getControlType(), nCtrlIndex );
467 mxCtrlModel->convertProperties( aPropMap, rConv );
468 mxCtrlModel->convertSize( aPropMap, rConv );
469 PropertySet aPropSet( rxCtrlModel );
470 aPropSet.setProperties( aPropMap );
471
472 // create and convert all embedded controls
473 if( !maControls.empty() ) try
474 {
475 Reference< XNameContainer > xCtrlModelNC( rxCtrlModel, UNO_QUERY_THROW );
476 /* Call conversion for all controls. Pass vector index as new
477 tab order to make option button groups work correctly. */
478 maControls.forEachMemWithIndex( &VbaFormControl::createAndConvert,
479 ::boost::cref( xCtrlModelNC ), ::boost::cref( rConv ) );
480 }
481 catch( Exception& )
482 {
483 OSL_ENSURE( false, "VbaFormControl::convertProperties - cannot get control container interface" );
484 }
485
486 return true;
487 }
488 }
489 return false;
490 }
491
492 // private --------------------------------------------------------------------
493
createControlModel(const AxClassTable & rClassTable)494 void VbaFormControl::createControlModel( const AxClassTable& rClassTable )
495 {
496 // derived classes may have created their own control model
497 if( !mxCtrlModel && mxSiteModel.get() )
498 mxCtrlModel = mxSiteModel->createControlModel( rClassTable );
499 }
500
importSiteModel(BinaryInputStream & rInStrm)501 bool VbaFormControl::importSiteModel( BinaryInputStream& rInStrm )
502 {
503 mxSiteModel.reset( new VbaSiteModel );
504 return mxSiteModel->importBinaryModel( rInStrm );
505 }
506
importEmbeddedSiteModels(BinaryInputStream & rInStrm)507 bool VbaFormControl::importEmbeddedSiteModels( BinaryInputStream& rInStrm )
508 {
509 sal_uInt64 nAnchorPos = rInStrm.tell();
510 sal_uInt32 nSiteCount, nSiteDataSize;
511 rInStrm >> nSiteCount >> nSiteDataSize;
512 sal_Int64 nSiteEndPos = rInStrm.tell() + nSiteDataSize;
513
514 // skip the site info structure
515 sal_uInt32 nSiteIndex = 0;
516 while( !rInStrm.isEof() && (nSiteIndex < nSiteCount) )
517 {
518 rInStrm.skip( 1 ); // site depth
519 sal_uInt8 nTypeCount = rInStrm.readuInt8(); // 'type-or-count' byte
520 if( getFlag( nTypeCount, VBA_SITEINFO_COUNT ) )
521 {
522 /* Count flag is set: the 'type-or-count' byte contains the number
523 of controls in the lower bits, the type specifier follows in
524 the next byte. The type specifier should always be 1 according
525 to the specification. */
526 rInStrm.skip( 1 );
527 nSiteIndex += (nTypeCount & VBA_SITEINFO_MASK);
528 }
529 else
530 {
531 /* Count flag is not set: the 'type-or-count' byte contains the
532 type specifier of *one* control in the lower bits (this type
533 should be 1, see above). */
534 ++nSiteIndex;
535 }
536 }
537 // align the stream to 32bit, relative to start of entire site info
538 rInStrm.alignToBlock( 4, nAnchorPos );
539
540 // import the site models for all embedded controls
541 maControls.clear();
542 bool bValid = !rInStrm.isEof();
543 for( nSiteIndex = 0; bValid && (nSiteIndex < nSiteCount); ++nSiteIndex )
544 {
545 VbaFormControlRef xControl( new VbaFormControl );
546 maControls.push_back( xControl );
547 bValid = xControl->importSiteModel( rInStrm );
548 }
549
550 rInStrm.seek( nSiteEndPos );
551 return bValid;
552 }
553
finalizeEmbeddedControls()554 void VbaFormControl::finalizeEmbeddedControls()
555 {
556 /* This function performs two tasks:
557
558 1) Reorder the controls appropriately (sort all option buttons of an
559 option group together to make grouping work).
560 2) Move all children of all embedded frames (group boxes) to this
561 control (UNO group boxes cannot contain other controls).
562 */
563
564 // first, sort all controls by original tab index
565 ::std::sort( maControls.begin(), maControls.end(), &compareByTabIndex );
566
567 /* Collect the programmatical names of all embedded controls (needed to be
568 able to set unused names to new dummy controls created below). Also
569 collect the names of all children of embedded frames (group boxes).
570 Luckily, names of controls must be unique in the entire form, not just
571 in the current container. */
572 VbaControlNamesSet aControlNames;
573 VbaControlNameInserter aInserter( aControlNames );
574 maControls.forEach( aInserter );
575 for( VbaFormControlVector::iterator aIt = maControls.begin(), aEnd = maControls.end(); aIt != aEnd; ++aIt )
576 if( (*aIt)->mxCtrlModel.get() && ((*aIt)->mxCtrlModel->getControlType() == API_CONTROL_GROUPBOX) )
577 (*aIt)->maControls.forEach( aInserter );
578
579 /* Reprocess the sorted list and collect all option button controls that
580 are part of the same option group (determined by group name). All
581 controls will be stored in a vector of vectors, that collects every
582 option button group in one vector element, and other controls between
583 these option groups (or leading or trailing controls) in other vector
584 elements. If an option button group follows another group, a dummy
585 separator control has to be inserted. */
586 typedef RefVector< VbaFormControlVector > VbaFormControlVectorVector;
587 VbaFormControlVectorVector aControlGroups;
588
589 typedef RefMap< OUString, VbaFormControlVector > VbaFormControlVectorMap;
590 VbaFormControlVectorMap aOptionGroups;
591
592 typedef VbaFormControlVectorMap::mapped_type VbaFormControlVectorRef;
593 bool bLastWasOptionButton = false;
594 for( VbaFormControlVector::iterator aIt = maControls.begin(), aEnd = maControls.end(); aIt != aEnd; ++aIt )
595 {
596 VbaFormControlRef xControl = *aIt;
597 const ControlModelBase* pCtrlModel = xControl->mxCtrlModel.get();
598
599 if( const AxOptionButtonModel* pOptButtonModel = dynamic_cast< const AxOptionButtonModel* >( pCtrlModel ) )
600 {
601 // check if a new option group needs to be created
602 const OUString& rGroupName = pOptButtonModel->getGroupName();
603 VbaFormControlVectorRef& rxOptionGroup = aOptionGroups[ rGroupName ];
604 if( !rxOptionGroup )
605 {
606 /* If last control was an option button too, we have two
607 option groups following each other, so a dummy separator
608 control is needed. */
609 if( bLastWasOptionButton )
610 {
611 VbaFormControlVectorRef xDummyGroup( new VbaFormControlVector );
612 aControlGroups.push_back( xDummyGroup );
613 OUString aName = aControlNames.generateDummyName();
614 VbaFormControlRef xDummyControl( new VbaDummyFormControl( aName ) );
615 xDummyGroup->push_back( xDummyControl );
616 }
617 rxOptionGroup.reset( new VbaFormControlVector );
618 aControlGroups.push_back( rxOptionGroup );
619 }
620 /* Append the option button to the control group (which is now
621 referred by the vector aControlGroups and by the map
622 aOptionGroups). */
623 rxOptionGroup->push_back( xControl );
624 bLastWasOptionButton = true;
625 }
626 else
627 {
628 // open a new control group, if the last group is an option group
629 if( bLastWasOptionButton || aControlGroups.empty() )
630 {
631 VbaFormControlVectorRef xControlGroup( new VbaFormControlVector );
632 aControlGroups.push_back( xControlGroup );
633 }
634 // append the control to the last control group
635 VbaFormControlVector& rLastGroup = *aControlGroups.back();
636 rLastGroup.push_back( xControl );
637 bLastWasOptionButton = false;
638
639 // if control is a group box, move all its children to this control
640 if( pCtrlModel && (pCtrlModel->getControlType() == API_CONTROL_GROUPBOX) )
641 {
642 /* Move all embedded controls of the group box relative to the
643 position of the group box. */
644 xControl->moveEmbeddedToAbsoluteParent();
645 /* Insert all children of the group box into the last control
646 group (following the group box). */
647 rLastGroup.insert( rLastGroup.end(), xControl->maControls.begin(), xControl->maControls.end() );
648 xControl->maControls.clear();
649 // check if last control of the group box is an option button
650 bLastWasOptionButton = dynamic_cast< const AxOptionButtonModel* >( rLastGroup.back()->mxCtrlModel.get() ) != 0;
651 }
652 }
653 }
654
655 // flatten the vector of vectors of form controls to a single vector
656 maControls.clear();
657 for( VbaFormControlVectorVector::iterator aIt = aControlGroups.begin(), aEnd = aControlGroups.end(); aIt != aEnd; ++aIt )
658 maControls.insert( maControls.end(), (*aIt)->begin(), (*aIt)->end() );
659 }
660
moveRelative(const AxPairData & rDistance)661 void VbaFormControl::moveRelative( const AxPairData& rDistance )
662 {
663 if( mxSiteModel.get() )
664 mxSiteModel->moveRelative( rDistance );
665 }
666
moveEmbeddedToAbsoluteParent()667 void VbaFormControl::moveEmbeddedToAbsoluteParent()
668 {
669 if( mxSiteModel.get() && !maControls.empty() )
670 {
671 // distance to move is equal to position of this control in its parent
672 AxPairData aDistance = mxSiteModel->getPosition();
673
674 /* For group boxes: add half of the font height to Y position (VBA
675 positions relative to frame border line, not to 'top' of frame). */
676 const AxFontDataModel* pFontModel = dynamic_cast< const AxFontDataModel* >( mxCtrlModel.get() );
677 if( pFontModel && (pFontModel->getControlType() == API_CONTROL_GROUPBOX) )
678 {
679 // convert points to 1/100 mm (1 pt = 1/72 inch = 2.54/72 cm = 2540/72 1/100 mm)
680 sal_Int32 nFontHeight = static_cast< sal_Int32 >( pFontModel->getFontHeight() * 2540 / 72 );
681 aDistance.second += nFontHeight / 2;
682 }
683
684 // move the embedded controls
685 maControls.forEachMem( &VbaFormControl::moveRelative, ::boost::cref( aDistance ) );
686 }
687 }
688
compareByTabIndex(const VbaFormControlRef & rxLeft,const VbaFormControlRef & rxRight)689 /*static*/ bool VbaFormControl::compareByTabIndex( const VbaFormControlRef& rxLeft, const VbaFormControlRef& rxRight )
690 {
691 // sort controls without model to the end
692 sal_Int32 nLeftTabIndex = rxLeft->mxSiteModel.get() ? rxLeft->mxSiteModel->getTabIndex() : SAL_MAX_INT32;
693 sal_Int32 nRightTabIndex = rxRight->mxSiteModel.get() ? rxRight->mxSiteModel->getTabIndex() : SAL_MAX_INT32;
694 return nLeftTabIndex < nRightTabIndex;
695 }
696
697 // ============================================================================
698
699 namespace {
700
lclGetQuotedString(const OUString & rCodeLine)701 OUString lclGetQuotedString( const OUString& rCodeLine )
702 {
703 OUStringBuffer aBuffer;
704 sal_Int32 nLen = rCodeLine.getLength();
705 if( (nLen > 0) && (rCodeLine[ 0 ] == '"') )
706 {
707 bool bExitLoop = false;
708 for( sal_Int32 nIndex = 1; !bExitLoop && (nIndex < nLen); ++nIndex )
709 {
710 sal_Unicode cChar = rCodeLine[ nIndex ];
711 // exit on closing quote char (but check on double quote chars)
712 bExitLoop = (cChar == '"') && ((nIndex + 1 == nLen) || (rCodeLine[ nIndex + 1 ] != '"'));
713 if( !bExitLoop )
714 {
715 aBuffer.append( cChar );
716 // skip second quote char
717 if( cChar == '"' )
718 ++nIndex;
719 }
720 }
721 }
722 return aBuffer.makeStringAndClear();
723 }
724
lclEatWhitespace(OUString & rCodeLine)725 bool lclEatWhitespace( OUString& rCodeLine )
726 {
727 sal_Int32 nIndex = 0;
728 while( (nIndex < rCodeLine.getLength()) && ((rCodeLine[ nIndex ] == ' ') || (rCodeLine[ nIndex ] == '\t')) )
729 ++nIndex;
730 if( nIndex > 0 )
731 {
732 rCodeLine = rCodeLine.copy( nIndex );
733 return true;
734 }
735 return false;
736 }
737
lclEatKeyword(OUString & rCodeLine,const OUString & rKeyword)738 bool lclEatKeyword( OUString& rCodeLine, const OUString& rKeyword )
739 {
740 if( rCodeLine.matchIgnoreAsciiCase( rKeyword ) )
741 {
742 rCodeLine = rCodeLine.copy( rKeyword.getLength() );
743 // success, if code line ends after keyword, or if whitespace follows
744 return (rCodeLine.getLength() == 0) || lclEatWhitespace( rCodeLine );
745 }
746 return false;
747 }
748
749 } // namespace
750
751 // ----------------------------------------------------------------------------
752
VbaUserForm(const Reference<XComponentContext> & rxContext,const Reference<XModel> & rxDocModel,const GraphicHelper & rGraphicHelper,bool bDefaultColorBgr)753 VbaUserForm::VbaUserForm( const Reference< XComponentContext >& rxContext,
754 const Reference< XModel >& rxDocModel, const GraphicHelper& rGraphicHelper, bool bDefaultColorBgr ) :
755 mxContext( rxContext ),
756 mxDocModel( rxDocModel ),
757 maConverter( rxDocModel, rGraphicHelper, bDefaultColorBgr )
758 {
759 OSL_ENSURE( mxContext.is(), "VbaUserForm::VbaUserForm - missing component context" );
760 OSL_ENSURE( mxDocModel.is(), "VbaUserForm::VbaUserForm - missing document model" );
761 }
762
importForm(const Reference<XNameContainer> & rxDialogLib,StorageBase & rVbaFormStrg,const OUString & rModuleName,rtl_TextEncoding eTextEnc)763 void VbaUserForm::importForm( const Reference< XNameContainer >& rxDialogLib,
764 StorageBase& rVbaFormStrg, const OUString& rModuleName, rtl_TextEncoding eTextEnc )
765 {
766 OSL_ENSURE( rxDialogLib.is(), "VbaUserForm::importForm - missing dialog library" );
767 if( !mxContext.is() || !mxDocModel.is() || !rxDialogLib.is() )
768 return;
769
770 // check that the '03VBFrame' stream exists, this is required for forms
771 BinaryXInputStream aInStrm( rVbaFormStrg.openInputStream( CREATE_OUSTRING( "\003VBFrame" ) ), true );
772 OSL_ENSURE( !aInStrm.isEof(), "VbaUserForm::importForm - missing \\003VBFrame stream" );
773 if( aInStrm.isEof() )
774 return;
775
776 // scan for the line 'Begin {GUID} <FormName>'
777 TextInputStream aFrameTextStrm( mxContext, aInStrm, eTextEnc );
778 const OUString aBegin = CREATE_OUSTRING( "Begin" );
779 OUString aLine;
780 bool bBeginFound = false;
781 while( !bBeginFound && !aFrameTextStrm.isEof() )
782 {
783 aLine = aFrameTextStrm.readLine().trim();
784 bBeginFound = lclEatKeyword( aLine, aBegin );
785 }
786 // check for the specific GUID that represents VBA forms
787 if( !bBeginFound || !lclEatKeyword( aLine, CREATE_OUSTRING( "{C62A69F0-16DC-11CE-9E98-00AA00574A4F}" ) ) )
788 return;
789
790 // remaining line is the form name
791 OUString aFormName = aLine.trim();
792 OSL_ENSURE( aFormName.getLength() > 0, "VbaUserForm::importForm - missing form name" );
793 OSL_ENSURE( rModuleName.equalsIgnoreAsciiCase( aFormName ), "VbaUserForm::importFrameStream - form and module name mismatch" );
794 if( aFormName.getLength() == 0 )
795 aFormName = rModuleName;
796 if( aFormName.getLength() == 0 )
797 return;
798 mxSiteModel.reset( new VbaSiteModel );
799 mxSiteModel->importProperty( XML_Name, aFormName );
800
801 // read the form properties (caption is contained in this '03VBFrame' stream, not in the 'f' stream)
802 mxCtrlModel.reset( new AxUserFormModel );
803 OUString aKey, aValue;
804 bool bExitLoop = false;
805 while( !bExitLoop && !aFrameTextStrm.isEof() )
806 {
807 aLine = aFrameTextStrm.readLine().trim();
808 bExitLoop = aLine.equalsIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "End" ) );
809 if( !bExitLoop && VbaHelper::extractKeyValue( aKey, aValue, aLine ) )
810 {
811 if( aKey.equalsIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "Caption" ) ) )
812 mxCtrlModel->importProperty( XML_Caption, lclGetQuotedString( aValue ) );
813 else if( aKey.equalsIgnoreAsciiCaseAsciiL( RTL_CONSTASCII_STRINGPARAM( "Tag" ) ) )
814 mxSiteModel->importProperty( XML_Tag, lclGetQuotedString( aValue ) );
815 }
816 }
817
818 // use generic container control functionality to import the embedded controls
819 importStorage( rVbaFormStrg, AxClassTable() );
820
821 try
822 {
823 // create the dialog model
824 OUString aServiceName = mxCtrlModel->getServiceName();
825 Reference< XMultiServiceFactory > xFactory( mxContext->getServiceManager(), UNO_QUERY_THROW );
826 Reference< XControlModel > xDialogModel( xFactory->createInstance( aServiceName ), UNO_QUERY_THROW );
827 Reference< XNameContainer > xDialogNC( xDialogModel, UNO_QUERY_THROW );
828
829 // convert properties and embedded controls
830 if( convertProperties( xDialogModel, maConverter, 0 ) )
831 {
832 // export the dialog to XML and insert it into the dialog library
833 Reference< XInputStreamProvider > xDialogSource( ::xmlscript::exportDialogModel( xDialogNC, mxContext ), UNO_SET_THROW );
834 OSL_ENSURE( !rxDialogLib->hasByName( aFormName ), "VbaUserForm::importForm - multiple dialogs with equal name" );
835 ContainerHelper::insertByName( rxDialogLib, aFormName, Any( xDialogSource ) );
836 }
837 }
838 catch( Exception& )
839 {
840 }
841 }
842
843 // ============================================================================
844
845 } // namespace ole
846 } // namespace oox
847