Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
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/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
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 .
18 : */
19 :
20 : #include "cellbindinghandler.hxx"
21 : #include "formstrings.hxx"
22 : #include "formmetadata.hxx"
23 : #include "cellbindinghelper.hxx"
24 :
25 : #include <com/sun/star/form/binding/XValueBinding.hpp>
26 : #include <com/sun/star/table/CellAddress.hpp>
27 : #include <com/sun/star/inspection/XObjectInspectorUI.hpp>
28 : #include <tools/debug.hxx>
29 :
30 : //------------------------------------------------------------------------
31 0 : extern "C" void SAL_CALL createRegistryInfo_CellBindingPropertyHandler()
32 : {
33 0 : ::pcr::CellBindingPropertyHandler::registerImplementation();
34 0 : }
35 :
36 : //........................................................................
37 : namespace pcr
38 : {
39 : //........................................................................
40 :
41 : using namespace ::com::sun::star::uno;
42 : using namespace ::com::sun::star::table;
43 : using namespace ::com::sun::star::lang;
44 : using namespace ::com::sun::star::beans;
45 : using namespace ::com::sun::star::script;
46 : using namespace ::com::sun::star::frame;
47 : using namespace ::com::sun::star::inspection;
48 : using namespace ::com::sun::star::form::binding;
49 : using namespace ::comphelper;
50 :
51 : //====================================================================
52 : //= CellBindingPropertyHandler
53 : //====================================================================
54 : DBG_NAME( CellBindingPropertyHandler )
55 : //--------------------------------------------------------------------
56 0 : CellBindingPropertyHandler::CellBindingPropertyHandler( const Reference< XComponentContext >& _rxContext )
57 : :CellBindingPropertyHandler_Base( _rxContext )
58 0 : ,m_pCellExchangeConverter( new DefaultEnumRepresentation( *m_pInfoService, ::getCppuType( static_cast< sal_Int16* >( NULL ) ), PROPERTY_ID_CELL_EXCHANGE_TYPE ) )
59 : {
60 : DBG_CTOR( CellBindingPropertyHandler, NULL );
61 0 : }
62 :
63 : //--------------------------------------------------------------------
64 0 : ::rtl::OUString SAL_CALL CellBindingPropertyHandler::getImplementationName_static( ) throw (RuntimeException)
65 : {
66 0 : return ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.comp.extensions.CellBindingPropertyHandler" ) );
67 : }
68 :
69 : //--------------------------------------------------------------------
70 0 : Sequence< ::rtl::OUString > SAL_CALL CellBindingPropertyHandler::getSupportedServiceNames_static( ) throw (RuntimeException)
71 : {
72 0 : Sequence< ::rtl::OUString > aSupported( 1 );
73 0 : aSupported[0] = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.form.inspection.CellBindingPropertyHandler" ) );
74 0 : return aSupported;
75 : }
76 :
77 : //--------------------------------------------------------------------
78 0 : void CellBindingPropertyHandler::onNewComponent()
79 : {
80 0 : PropertyHandlerComponent::onNewComponent();
81 :
82 0 : Reference< XModel > xDocument( impl_getContextDocument_nothrow() );
83 : DBG_ASSERT( xDocument.is(), "CellBindingPropertyHandler::onNewComponent: no document!" );
84 0 : if ( CellBindingHelper::isSpreadsheetDocument( xDocument ) )
85 0 : m_pHelper.reset( new CellBindingHelper( m_xComponent, xDocument ) );
86 0 : }
87 :
88 : //--------------------------------------------------------------------
89 0 : CellBindingPropertyHandler::~CellBindingPropertyHandler( )
90 : {
91 : DBG_DTOR( CellBindingPropertyHandler, NULL );
92 0 : }
93 :
94 : //--------------------------------------------------------------------
95 0 : Sequence< ::rtl::OUString > SAL_CALL CellBindingPropertyHandler::getActuatingProperties( ) throw (RuntimeException)
96 : {
97 0 : Sequence< ::rtl::OUString > aInterestingProperties( 3 );
98 0 : aInterestingProperties[0] = PROPERTY_LIST_CELL_RANGE;
99 0 : aInterestingProperties[1] = PROPERTY_BOUND_CELL;
100 0 : aInterestingProperties[2] = PROPERTY_CONTROLSOURCE;
101 0 : return aInterestingProperties;
102 : }
103 :
104 : //--------------------------------------------------------------------
105 0 : void SAL_CALL CellBindingPropertyHandler::actuatingPropertyChanged( const ::rtl::OUString& _rActuatingPropertyName, const Any& _rNewValue, const Any& /*_rOldValue*/, const Reference< XObjectInspectorUI >& _rxInspectorUI, sal_Bool _bFirstTimeInit ) throw (NullPointerException, RuntimeException)
106 : {
107 0 : ::osl::MutexGuard aGuard( m_aMutex );
108 0 : PropertyId nActuatingPropId( impl_getPropertyId_throw( _rActuatingPropertyName ) );
109 : OSL_PRECOND( m_pHelper.get(), "CellBindingPropertyHandler::actuatingPropertyChanged: inconsistentcy!" );
110 : // if we survived impl_getPropertyId_throw, we should have a helper, since no helper implies no properties
111 :
112 : OSL_PRECOND( _rxInspectorUI.is(), "FormComponentPropertyHandler::actuatingPropertyChanged: no access to the UI!" );
113 0 : if ( !_rxInspectorUI.is() )
114 0 : throw NullPointerException();
115 :
116 0 : ::std::vector< PropertyId > aDependentProperties;
117 :
118 0 : switch ( nActuatingPropId )
119 : {
120 : // ----- BoundCell -----
121 : case PROPERTY_ID_BOUND_CELL:
122 : {
123 : // the SQL-data-binding related properties need to be enabled if and only if
124 : // there is *no* valid cell binding
125 0 : Reference< XValueBinding > xBinding;
126 0 : _rNewValue >>= xBinding;
127 :
128 0 : if ( impl_isSupportedProperty_nothrow( PROPERTY_ID_CELL_EXCHANGE_TYPE ) )
129 0 : _rxInspectorUI->enablePropertyUI( PROPERTY_CELL_EXCHANGE_TYPE, xBinding.is() );
130 0 : if ( impl_componentHasProperty_throw( PROPERTY_CONTROLSOURCE ) )
131 0 : _rxInspectorUI->enablePropertyUI( PROPERTY_CONTROLSOURCE, !xBinding.is() );
132 :
133 0 : if ( impl_isSupportedProperty_nothrow( PROPERTY_ID_FILTERPROPOSAL ) )
134 0 : _rxInspectorUI->enablePropertyUI( PROPERTY_FILTERPROPOSAL, !xBinding.is() );
135 0 : if ( impl_isSupportedProperty_nothrow( PROPERTY_ID_EMPTY_IS_NULL ) )
136 0 : _rxInspectorUI->enablePropertyUI( PROPERTY_EMPTY_IS_NULL, !xBinding.is() );
137 :
138 0 : aDependentProperties.push_back( PROPERTY_ID_BOUNDCOLUMN );
139 :
140 0 : if ( !xBinding.is() && m_pHelper->getCurrentBinding().is() )
141 : {
142 : // ensure that the "transfer selection as" property is reset. Since we can't remember
143 : // it at the object itself, but derive it from the binding only, we have to normalize
144 : // it now that there *is* no binding anymore.
145 0 : setPropertyValue( PROPERTY_CELL_EXCHANGE_TYPE, makeAny( (sal_Int16) 0 ) );
146 0 : }
147 : }
148 0 : break;
149 :
150 : // ----- CellRange -----
151 : case PROPERTY_ID_LIST_CELL_RANGE:
152 : {
153 : // the list source related properties need to be enabled if and only if
154 : // there is *no* valid external list source for the control
155 0 : Reference< XListEntrySource > xSource;
156 0 : _rNewValue >>= xSource;
157 :
158 0 : _rxInspectorUI->enablePropertyUI( PROPERTY_STRINGITEMLIST, !xSource.is() );
159 0 : _rxInspectorUI->enablePropertyUI( PROPERTY_LISTSOURCE, !xSource.is() );
160 0 : _rxInspectorUI->enablePropertyUI( PROPERTY_LISTSOURCETYPE, !xSource.is() );
161 :
162 0 : aDependentProperties.push_back( PROPERTY_ID_BOUNDCOLUMN );
163 :
164 : // also reset the list entries if the cell range is reset
165 : // #i28319#
166 0 : if ( !_bFirstTimeInit )
167 : {
168 : try
169 : {
170 0 : if ( !xSource.is() )
171 0 : setPropertyValue( PROPERTY_STRINGITEMLIST, makeAny( Sequence< ::rtl::OUString >() ) );
172 : }
173 0 : catch( const Exception& )
174 : {
175 : OSL_FAIL( "OPropertyBrowserController::actuatingPropertyChanged( ListCellRange ): caught an exception while resetting the string items!" );
176 : }
177 0 : }
178 : }
179 0 : break; // case PROPERTY_ID_LIST_CELL_RANGE
180 :
181 : // ----- DataField -----
182 : case PROPERTY_ID_CONTROLSOURCE:
183 : {
184 0 : ::rtl::OUString sControlSource;
185 0 : _rNewValue >>= sControlSource;
186 0 : if ( impl_isSupportedProperty_nothrow( PROPERTY_ID_BOUND_CELL ) )
187 0 : _rxInspectorUI->enablePropertyUI( PROPERTY_BOUND_CELL, sControlSource.isEmpty() );
188 : }
189 0 : break; // case PROPERTY_ID_CONTROLSOURCE
190 :
191 : default:
192 : OSL_FAIL( "CellBindingPropertyHandler::actuatingPropertyChanged: did not register for this property!" );
193 : }
194 :
195 0 : for ( ::std::vector< PropertyId >::const_iterator loopAffected = aDependentProperties.begin();
196 0 : loopAffected != aDependentProperties.end();
197 : ++loopAffected
198 : )
199 : {
200 0 : impl_updateDependentProperty_nothrow( *loopAffected, _rxInspectorUI );
201 0 : }
202 0 : }
203 :
204 : //--------------------------------------------------------------------
205 0 : void CellBindingPropertyHandler::impl_updateDependentProperty_nothrow( PropertyId _nPropId, const Reference< XObjectInspectorUI >& _rxInspectorUI ) const
206 : {
207 : try
208 : {
209 0 : switch ( _nPropId )
210 : {
211 : // ----- BoundColumn -----
212 : case PROPERTY_ID_BOUNDCOLUMN:
213 : {
214 0 : CellBindingPropertyHandler* pNonConstThis = const_cast< CellBindingPropertyHandler* >( this );
215 0 : Reference< XValueBinding > xBinding( pNonConstThis->getPropertyValue( PROPERTY_BOUND_CELL ), UNO_QUERY );
216 0 : Reference< XListEntrySource > xListSource( pNonConstThis->getPropertyValue( PROPERTY_LIST_CELL_RANGE ), UNO_QUERY );
217 :
218 0 : if ( impl_isSupportedProperty_nothrow( PROPERTY_ID_BOUNDCOLUMN ) )
219 0 : _rxInspectorUI->enablePropertyUI( PROPERTY_BOUNDCOLUMN, !xBinding.is() && !xListSource.is() );
220 : }
221 0 : break; // case PROPERTY_ID_BOUNDCOLUMN
222 :
223 : } // switch
224 :
225 : }
226 0 : catch( const Exception& )
227 : {
228 : OSL_FAIL( "CellBindingPropertyHandler::impl_updateDependentProperty_nothrow: caught an exception!" );
229 : }
230 0 : }
231 :
232 : //--------------------------------------------------------------------
233 0 : Any SAL_CALL CellBindingPropertyHandler::getPropertyValue( const ::rtl::OUString& _rPropertyName ) throw (UnknownPropertyException, RuntimeException)
234 : {
235 0 : ::osl::MutexGuard aGuard( m_aMutex );
236 0 : PropertyId nPropId( impl_getPropertyId_throw( _rPropertyName ) );
237 :
238 : OSL_ENSURE( m_pHelper.get(), "CellBindingPropertyHandler::getPropertyValue: inconsistency!" );
239 : // if we survived impl_getPropertyId_throw, we should have a helper, since no helper implies no properties
240 :
241 0 : Any aReturn;
242 0 : switch ( nPropId )
243 : {
244 : case PROPERTY_ID_BOUND_CELL:
245 : {
246 0 : Reference< XValueBinding > xBinding( m_pHelper->getCurrentBinding() );
247 0 : if ( !m_pHelper->isCellBinding( xBinding ) )
248 0 : xBinding.clear();
249 :
250 0 : aReturn <<= xBinding;
251 : }
252 0 : break;
253 :
254 : case PROPERTY_ID_LIST_CELL_RANGE:
255 : {
256 0 : Reference< XListEntrySource > xSource( m_pHelper->getCurrentListSource() );
257 0 : if ( !m_pHelper->isCellRangeListSource( xSource ) )
258 0 : xSource.clear();
259 :
260 0 : aReturn <<= xSource;
261 : }
262 0 : break;
263 :
264 : case PROPERTY_ID_CELL_EXCHANGE_TYPE:
265 : {
266 0 : Reference< XValueBinding > xBinding( m_pHelper->getCurrentBinding() );
267 0 : aReturn <<= (sal_Int16)( m_pHelper->isCellIntegerBinding( xBinding ) ? 1 : 0 );
268 : }
269 0 : break;
270 :
271 : default:
272 : OSL_FAIL( "CellBindingPropertyHandler::getPropertyValue: cannot handle this!" );
273 0 : break;
274 : }
275 0 : return aReturn;
276 : }
277 :
278 : //--------------------------------------------------------------------
279 0 : void SAL_CALL CellBindingPropertyHandler::setPropertyValue( const ::rtl::OUString& _rPropertyName, const Any& _rValue ) throw (UnknownPropertyException, RuntimeException)
280 : {
281 0 : ::osl::MutexGuard aGuard( m_aMutex );
282 0 : PropertyId nPropId( impl_getPropertyId_throw( _rPropertyName ) );
283 :
284 : OSL_ENSURE( m_pHelper.get(), "CellBindingPropertyHandler::setPropertyValue: inconsistency!" );
285 : // if we survived impl_getPropertyId_throw, we should have a helper, since no helper implies no properties
286 :
287 : try
288 : {
289 0 : Any aOldValue = getPropertyValue( _rPropertyName );
290 :
291 0 : switch ( nPropId )
292 : {
293 : case PROPERTY_ID_BOUND_CELL:
294 : {
295 0 : Reference< XValueBinding > xBinding;
296 0 : _rValue >>= xBinding;
297 0 : m_pHelper->setBinding( xBinding );
298 : }
299 0 : break;
300 :
301 : case PROPERTY_ID_LIST_CELL_RANGE:
302 : {
303 0 : Reference< XListEntrySource > xSource;
304 0 : _rValue >>= xSource;
305 0 : m_pHelper->setListSource( xSource );
306 : }
307 0 : break;
308 :
309 : case PROPERTY_ID_CELL_EXCHANGE_TYPE:
310 : {
311 0 : sal_Int16 nExchangeType = 0;
312 0 : OSL_VERIFY( _rValue >>= nExchangeType );
313 :
314 0 : Reference< XValueBinding > xBinding = m_pHelper->getCurrentBinding( );
315 0 : if ( xBinding.is() )
316 : {
317 0 : sal_Bool bNeedIntegerBinding = ( nExchangeType == 1 );
318 0 : if ( (bool)bNeedIntegerBinding != m_pHelper->isCellIntegerBinding( xBinding ) )
319 : {
320 0 : CellAddress aAddress;
321 0 : if ( m_pHelper->getAddressFromCellBinding( xBinding, aAddress ) )
322 : {
323 0 : xBinding = m_pHelper->createCellBindingFromAddress( aAddress, bNeedIntegerBinding );
324 0 : m_pHelper->setBinding( xBinding );
325 : }
326 : }
327 0 : }
328 : }
329 0 : break;
330 :
331 : default:
332 : OSL_FAIL( "CellBindingPropertyHandler::setPropertyValue: cannot handle this!" );
333 0 : break;
334 : }
335 :
336 0 : impl_setContextDocumentModified_nothrow();
337 :
338 0 : Any aNewValue( getPropertyValue( _rPropertyName ) );
339 0 : firePropertyChange( _rPropertyName, nPropId, aOldValue, aNewValue );
340 : // TODO/UNOize: can't we make this a part of the base class, for all those "virtual"
341 : // properties? Base class'es |setPropertyValue| could call some |doSetPropertyValue|,
342 : // and handle the listener notification itself
343 : }
344 0 : catch( const Exception& )
345 : {
346 : OSL_FAIL( "CellBindingPropertyHandler::setPropertyValue: caught an exception!" );
347 0 : }
348 0 : }
349 :
350 : //--------------------------------------------------------------------
351 0 : Any SAL_CALL CellBindingPropertyHandler::convertToPropertyValue( const ::rtl::OUString& _rPropertyName, const Any& _rControlValue ) throw (UnknownPropertyException, RuntimeException)
352 : {
353 0 : ::osl::MutexGuard aGuard( m_aMutex );
354 0 : Any aPropertyValue;
355 :
356 : OSL_ENSURE( m_pHelper.get(), "CellBindingPropertyHandler::convertToPropertyValue: we have no SupportedProperties!" );
357 0 : if ( !m_pHelper.get() )
358 : return aPropertyValue;
359 :
360 0 : PropertyId nPropId( m_pInfoService->getPropertyId( _rPropertyName ) );
361 :
362 0 : ::rtl::OUString sControlValue;
363 0 : OSL_VERIFY( _rControlValue >>= sControlValue );
364 0 : switch( nPropId )
365 : {
366 : case PROPERTY_ID_LIST_CELL_RANGE:
367 0 : aPropertyValue <<= m_pHelper->createCellListSourceFromStringAddress( sControlValue );
368 0 : break;
369 :
370 : case PROPERTY_ID_BOUND_CELL:
371 : {
372 : // if we have the possibility of an integer binding, then we must preserve
373 : // this property's value (e.g. if the current binding is an integer binding, then
374 : // the newly created one must be, too)
375 0 : bool bIntegerBinding = false;
376 0 : if ( m_pHelper->isCellIntegerBindingAllowed() )
377 : {
378 0 : sal_Int16 nCurrentBindingType = 0;
379 0 : getPropertyValue( PROPERTY_CELL_EXCHANGE_TYPE ) >>= nCurrentBindingType;
380 0 : bIntegerBinding = ( nCurrentBindingType != 0 );
381 : }
382 0 : aPropertyValue <<= m_pHelper->createCellBindingFromStringAddress( sControlValue, bIntegerBinding );
383 : }
384 0 : break;
385 :
386 : case PROPERTY_ID_CELL_EXCHANGE_TYPE:
387 0 : m_pCellExchangeConverter->getValueFromDescription( sControlValue, aPropertyValue );
388 0 : break;
389 :
390 : default:
391 : OSL_FAIL( "CellBindingPropertyHandler::convertToPropertyValue: cannot handle this!" );
392 0 : break;
393 : }
394 :
395 0 : return aPropertyValue;
396 : }
397 :
398 : //--------------------------------------------------------------------
399 0 : Any SAL_CALL CellBindingPropertyHandler::convertToControlValue( const ::rtl::OUString& _rPropertyName,
400 : const Any& _rPropertyValue, const Type& /*_rControlValueType*/ ) throw (UnknownPropertyException, RuntimeException)
401 : {
402 0 : ::osl::MutexGuard aGuard( m_aMutex );
403 0 : Any aControlValue;
404 :
405 : OSL_ENSURE( m_pHelper.get(), "CellBindingPropertyHandler::convertToControlValue: we have no SupportedProperties!" );
406 0 : if ( !m_pHelper.get() )
407 : return aControlValue;
408 :
409 0 : PropertyId nPropId( m_pInfoService->getPropertyId( _rPropertyName ) );
410 :
411 0 : switch ( nPropId )
412 : {
413 : case PROPERTY_ID_BOUND_CELL:
414 : {
415 0 : Reference< XValueBinding > xBinding;
416 : #if OSL_DEBUG_LEVEL > 0
417 : sal_Bool bSuccess =
418 : #endif
419 0 : _rPropertyValue >>= xBinding;
420 : OSL_ENSURE( bSuccess, "CellBindingPropertyHandler::convertToControlValue: invalid value (1)!" );
421 :
422 : // the only value binding we support so far is linking to spreadsheet cells
423 0 : aControlValue <<= m_pHelper->getStringAddressFromCellBinding( xBinding );
424 : }
425 0 : break;
426 :
427 : case PROPERTY_ID_LIST_CELL_RANGE:
428 : {
429 0 : Reference< XListEntrySource > xSource;
430 : #if OSL_DEBUG_LEVEL > 0
431 : sal_Bool bSuccess =
432 : #endif
433 0 : _rPropertyValue >>= xSource;
434 : OSL_ENSURE( bSuccess, "CellBindingPropertyHandler::convertToControlValue: invalid value (2)!" );
435 :
436 : // the only value binding we support so far is linking to spreadsheet cells
437 0 : aControlValue <<= m_pHelper->getStringAddressFromCellListSource( xSource );
438 : }
439 0 : break;
440 :
441 : case PROPERTY_ID_CELL_EXCHANGE_TYPE:
442 0 : aControlValue <<= m_pCellExchangeConverter->getDescriptionForValue( _rPropertyValue );
443 0 : break;
444 :
445 : default:
446 : OSL_FAIL( "CellBindingPropertyHandler::convertToControlValue: cannot handle this!" );
447 0 : break;
448 : }
449 :
450 0 : return aControlValue;
451 : }
452 :
453 : //--------------------------------------------------------------------
454 0 : Sequence< Property > SAL_CALL CellBindingPropertyHandler::doDescribeSupportedProperties() const
455 : {
456 0 : ::std::vector< Property > aProperties;
457 :
458 0 : bool bAllowCellLinking = m_pHelper.get() && m_pHelper->isCellBindingAllowed();
459 0 : bool bAllowCellIntLinking = m_pHelper.get() && m_pHelper->isCellIntegerBindingAllowed();
460 0 : bool bAllowListCellRange = m_pHelper.get() && m_pHelper->isListCellRangeAllowed();
461 0 : if ( bAllowCellLinking || bAllowListCellRange || bAllowCellIntLinking )
462 : {
463 : sal_Int32 nPos = ( bAllowCellLinking ? 1 : 0 )
464 : + ( bAllowListCellRange ? 1 : 0 )
465 0 : + ( bAllowCellIntLinking ? 1 : 0 );
466 0 : aProperties.resize( nPos );
467 :
468 0 : if ( bAllowCellLinking )
469 : {
470 0 : aProperties[ --nPos ] = Property( PROPERTY_BOUND_CELL, PROPERTY_ID_BOUND_CELL,
471 0 : ::getCppuType( static_cast< ::rtl::OUString* >( NULL ) ), 0 );
472 : }
473 0 : if ( bAllowCellIntLinking )
474 : {
475 0 : aProperties[ --nPos ] = Property( PROPERTY_CELL_EXCHANGE_TYPE, PROPERTY_ID_CELL_EXCHANGE_TYPE,
476 0 : ::getCppuType( static_cast< sal_Int16* >( NULL ) ), 0 );
477 : }
478 0 : if ( bAllowListCellRange )
479 : {
480 0 : aProperties[ --nPos ] = Property( PROPERTY_LIST_CELL_RANGE, PROPERTY_ID_LIST_CELL_RANGE,
481 0 : ::getCppuType( static_cast< ::rtl::OUString* >( NULL ) ), 0 );
482 : }
483 : }
484 :
485 0 : if ( aProperties.empty() )
486 0 : return Sequence< Property >();
487 0 : return Sequence< Property >( &(*aProperties.begin()), aProperties.size() );
488 : }
489 :
490 : //........................................................................
491 : } // namespace pcr
492 : //........................................................................
493 :
494 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|