1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 .
22 #include "Columns.hxx"
23 #include "property.hrc"
24 #include "property.hxx"
25 #include "componenttools.hxx"
27 #include "findpos.hxx"
28 #include <com/sun/star/io/XPersistObject.hpp>
29 #include <com/sun/star/io/XObjectOutputStream.hpp>
30 #include <com/sun/star/io/XObjectInputStream.hpp>
31 #include <com/sun/star/io/XMarkableStream.hpp>
32 #include <com/sun/star/form/XFormComponent.hpp>
33 #include <com/sun/star/lang/XServiceInfo.hpp>
34 #include <com/sun/star/form/binding/XBindableValue.hpp>
35 #include <com/sun/star/beans/XPropertyContainer.hpp>
36 #include <com/sun/star/text/XText.hpp>
37 #include <comphelper/sequence.hxx>
38 #include <comphelper/property.hxx>
39 #include <comphelper/basicio.hxx>
40 #include <comphelper/types.hxx>
41 #include <comphelper/servicehelper.hxx>
42 #include "services.hxx"
43 #include "frm_resource.hrc"
44 #include <tools/debug.hxx>
46 //.........................................................................
49 //.........................................................................
50 using namespace ::com::sun::star::uno
;
51 using namespace ::com::sun::star::beans
;
52 using namespace ::com::sun::star::container
;
53 using namespace ::com::sun::star::form
;
54 using namespace ::com::sun::star::awt
;
55 using namespace ::com::sun::star::io
;
56 using namespace ::com::sun::star::lang
;
57 using namespace ::com::sun::star::util
;
58 using namespace ::com::sun::star::text
;
59 using namespace ::com::sun::star::form::binding
;
61 const sal_uInt16 WIDTH
= 0x0001;
62 const sal_uInt16 ALIGN
= 0x0002;
63 const sal_uInt16 OLD_HIDDEN
= 0x0004;
64 const sal_uInt16 COMPATIBLE_HIDDEN
= 0x0008;
66 //------------------------------------------------------------------------------
67 const StringSequence
& getColumnTypes()
69 static StringSequence
aColumnTypes(10);
70 if (aColumnTypes
.getConstArray()[0].isEmpty())
72 OUString
* pNames
= aColumnTypes
.getArray();
73 pNames
[TYPE_CHECKBOX
] = OUString( "CheckBox" );
74 pNames
[TYPE_COMBOBOX
] = OUString( "ComboBox" );
75 pNames
[TYPE_CURRENCYFIELD
] = OUString( "CurrencyField" );
76 pNames
[TYPE_DATEFIELD
] = OUString( "DateField" );
77 pNames
[TYPE_FORMATTEDFIELD
] = OUString( "FormattedField" );
78 pNames
[TYPE_LISTBOX
] = OUString( "ListBox" );
79 pNames
[TYPE_NUMERICFIELD
] = OUString( "NumericField" );
80 pNames
[TYPE_PATTERNFIELD
] = OUString( "PatternField" );
81 pNames
[TYPE_TEXTFIELD
] = OUString( "TextField" );
82 pNames
[TYPE_TIMEFIELD
] = OUString( "TimeField" );
87 //------------------------------------------------------------------------------
88 sal_Int32
getColumnTypeByModelName(const OUString
& aModelName
)
90 const OUString
aModelPrefix ("com.sun.star.form.component.");
91 const OUString
aCompatibleModelPrefix ("stardiv.one.form.component.");
93 sal_Int32 nTypeId
= -1;
94 if (aModelName
== FRM_COMPONENT_EDIT
)
95 nTypeId
= TYPE_TEXTFIELD
;
98 sal_Int32 nPrefixPos
= aModelName
.indexOf(aModelPrefix
);
100 sal_Int32 nCompatiblePrefixPos
= aModelName
.indexOf(aCompatibleModelPrefix
);
102 DBG_ASSERT( (nPrefixPos
!= -1) || (nCompatiblePrefixPos
!= -1),
103 "::getColumnTypeByModelName() : wrong servivce !");
105 OUString aColumnType
= (nPrefixPos
!= -1)
106 ? aModelName
.copy(aModelPrefix
.getLength())
107 : aModelName
.copy(aCompatibleModelPrefix
.getLength());
109 const StringSequence
& rColumnTypes
= getColumnTypes();
110 nTypeId
= ::detail::findPos(aColumnType
, rColumnTypes
);
115 /*************************************************************************/
119 class theOGridColumnImplementationId
: public rtl::Static
< UnoTunnelIdInit
, theOGridColumnImplementationId
> {};
122 const Sequence
<sal_Int8
>& OGridColumn::getUnoTunnelImplementationId()
124 return theOGridColumnImplementationId::get().getSeq();
127 //------------------------------------------------------------------
128 sal_Int64 SAL_CALL
OGridColumn::getSomething( const Sequence
<sal_Int8
>& _rIdentifier
) throw(RuntimeException
)
130 sal_Int64
nReturn(0);
132 if ( (_rIdentifier
.getLength() == 16)
133 && (0 == memcmp( getUnoTunnelImplementationId().getConstArray(), _rIdentifier
.getConstArray(), 16 ))
136 nReturn
= reinterpret_cast<sal_Int64
>(this);
140 Reference
< XUnoTunnel
> xAggTunnel
;
141 if ( query_aggregation( m_xAggregate
, xAggTunnel
) )
142 return xAggTunnel
->getSomething( _rIdentifier
);
147 //------------------------------------------------------------------
148 Sequence
<sal_Int8
> SAL_CALL
OGridColumn::getImplementationId() throw(RuntimeException
)
150 return OImplementationIds::getImplementationId(getTypes());
153 //------------------------------------------------------------------
154 Sequence
<Type
> SAL_CALL
OGridColumn::getTypes() throw(RuntimeException
)
156 TypeBag
aTypes( OGridColumn_BASE::getTypes() );
157 // erase the types which we do not support
158 aTypes
.removeType( XFormComponent::static_type() );
159 aTypes
.removeType( XServiceInfo::static_type() );
160 aTypes
.removeType( XBindableValue::static_type() );
161 aTypes
.removeType( XPropertyContainer::static_type() );
163 // but re-add their base class(es)
164 aTypes
.addType( XChild::static_type() );
166 Reference
< XTypeProvider
> xProv
;
167 if ( query_aggregation( m_xAggregate
, xProv
))
168 aTypes
.addTypes( xProv
->getTypes() );
170 aTypes
.removeType( XTextRange::static_type() );
171 aTypes
.removeType( XSimpleText::static_type() );
172 aTypes
.removeType( XText::static_type() );
174 return aTypes
.getTypes();
177 //------------------------------------------------------------------
178 Any SAL_CALL
OGridColumn::queryAggregation( const Type
& _rType
) throw (RuntimeException
)
181 // some functionality at our aggregate cannot be reasonably fullfilled here.
182 if ( _rType
.equals(::getCppuType(static_cast< Reference
< XFormComponent
>* >(NULL
)))
183 || _rType
.equals(::getCppuType(static_cast< Reference
< XServiceInfo
>* >(NULL
)))
184 || _rType
.equals(::getCppuType(static_cast< Reference
< XBindableValue
>* >(NULL
)))
185 || _rType
.equals(::getCppuType(static_cast< Reference
< XPropertyContainer
>* >(NULL
)))
186 || comphelper::isAssignableFrom(::getCppuType(static_cast< Reference
< XTextRange
>* >(NULL
)),_rType
)
190 aReturn
= OGridColumn_BASE::queryAggregation(_rType
);
191 if (!aReturn
.hasValue())
193 aReturn
= OPropertySetAggregationHelper::queryInterface(_rType
);
194 if (!aReturn
.hasValue() && m_xAggregate
.is())
195 aReturn
= m_xAggregate
->queryAggregation(_rType
);
201 DBG_NAME(OGridColumn
);
202 //------------------------------------------------------------------------------
203 OGridColumn::OGridColumn( const comphelper::ComponentContext
& _rContext
, const OUString
& _sModelName
)
204 :OGridColumn_BASE(m_aMutex
)
205 ,OPropertySetAggregationHelper(OGridColumn_BASE::rBHelper
)
206 ,m_aHidden( makeAny( sal_False
) )
207 ,m_aContext( _rContext
)
208 ,m_aModelName(_sModelName
)
210 DBG_CTOR(OGridColumn
,NULL
);
212 // Create the UnoControlModel
213 if ( !m_aModelName
.isEmpty() ) // is there a to-be-aggregated model?
215 increment( m_refCount
);
218 m_xAggregate
.set( m_aContext
.createComponent( m_aModelName
), UNO_QUERY
);
219 setAggregation( m_xAggregate
);
222 if ( m_xAggregate
.is() )
223 { // don't omit those brackets - they ensure that the following temporary is properly deleted
224 m_xAggregate
->setDelegator( static_cast< ::cppu::OWeakObject
* >( this ) );
227 // Set refcount back to zero
228 decrement( m_refCount
);
232 //------------------------------------------------------------------------------
233 OGridColumn::OGridColumn( const OGridColumn
* _pOriginal
)
234 :OGridColumn_BASE( m_aMutex
)
235 ,OPropertySetAggregationHelper( OGridColumn_BASE::rBHelper
)
236 ,m_aContext( _pOriginal
->m_aContext
)
238 DBG_CTOR(OGridColumn
,NULL
);
240 m_aWidth
= _pOriginal
->m_aWidth
;
241 m_aAlign
= _pOriginal
->m_aAlign
;
242 m_aHidden
= _pOriginal
->m_aHidden
;
243 m_aModelName
= _pOriginal
->m_aModelName
;
244 m_aLabel
= _pOriginal
->m_aLabel
;
246 increment( m_refCount
);
249 m_xAggregate
= createAggregateClone( _pOriginal
);
250 setAggregation( m_xAggregate
);
253 if ( m_xAggregate
.is() )
254 { // don't omit this brackets - they ensure that the following temporary is properly deleted
255 m_xAggregate
->setDelegator( static_cast< ::cppu::OWeakObject
* >( this ) );
258 decrement( m_refCount
);
261 //------------------------------------------------------------------------------
262 OGridColumn::~OGridColumn()
264 if (!OGridColumn_BASE::rBHelper
.bDisposed
)
270 // Free the aggregate
271 if (m_xAggregate
.is())
274 m_xAggregate
->setDelegator(xIface
);
277 DBG_DTOR(OGridColumn
,NULL
);
281 //------------------------------------------------------------------------------
282 void SAL_CALL
OGridColumn::disposing(const EventObject
& _rSource
) throw(RuntimeException
)
284 OPropertySetAggregationHelper::disposing(_rSource
);
286 Reference
<XEventListener
> xEvtLstner
;
287 if (query_aggregation(m_xAggregate
, xEvtLstner
))
288 xEvtLstner
->disposing(_rSource
);
292 //-----------------------------------------------------------------------------
293 void OGridColumn::disposing()
295 OGridColumn_BASE::disposing();
296 OPropertySetAggregationHelper::disposing();
298 Reference
<XComponent
> xComp
;
299 if (query_aggregation(m_xAggregate
, xComp
))
303 //------------------------------------------------------------------------------
304 void OGridColumn::clearAggregateProperties( Sequence
< Property
>& _rProps
, sal_Bool bAllowDropDown
)
306 // some properties are not to be exposed to the outer world
307 ::std::set
< OUString
> aForbiddenProperties
;
308 aForbiddenProperties
.insert( PROPERTY_ALIGN
);
309 aForbiddenProperties
.insert( PROPERTY_AUTOCOMPLETE
);
310 aForbiddenProperties
.insert( PROPERTY_BACKGROUNDCOLOR
);
311 aForbiddenProperties
.insert( PROPERTY_BORDER
);
312 aForbiddenProperties
.insert( PROPERTY_BORDERCOLOR
);
313 aForbiddenProperties
.insert( PROPERTY_ECHO_CHAR
);
314 aForbiddenProperties
.insert( PROPERTY_FILLCOLOR
);
315 aForbiddenProperties
.insert( PROPERTY_FONT
);
316 aForbiddenProperties
.insert( PROPERTY_FONT_NAME
);
317 aForbiddenProperties
.insert( PROPERTY_FONT_STYLENAME
);
318 aForbiddenProperties
.insert( PROPERTY_FONT_FAMILY
);
319 aForbiddenProperties
.insert( PROPERTY_FONT_CHARSET
);
320 aForbiddenProperties
.insert( PROPERTY_FONT_HEIGHT
);
321 aForbiddenProperties
.insert( PROPERTY_FONT_WEIGHT
);
322 aForbiddenProperties
.insert( PROPERTY_FONT_SLANT
);
323 aForbiddenProperties
.insert( PROPERTY_FONT_UNDERLINE
);
324 aForbiddenProperties
.insert( PROPERTY_FONT_STRIKEOUT
);
325 aForbiddenProperties
.insert( PROPERTY_FONT_WORDLINEMODE
);
326 aForbiddenProperties
.insert( PROPERTY_TEXTLINECOLOR
);
327 aForbiddenProperties
.insert( PROPERTY_FONTEMPHASISMARK
);
328 aForbiddenProperties
.insert( PROPERTY_FONTRELIEF
);
329 aForbiddenProperties
.insert( PROPERTY_HARDLINEBREAKS
);
330 aForbiddenProperties
.insert( PROPERTY_HSCROLL
);
331 aForbiddenProperties
.insert( PROPERTY_LABEL
);
332 aForbiddenProperties
.insert( PROPERTY_LINECOLOR
);
333 aForbiddenProperties
.insert( PROPERTY_MULTISELECTION
);
334 aForbiddenProperties
.insert( PROPERTY_PRINTABLE
);
335 aForbiddenProperties
.insert( PROPERTY_TABINDEX
);
336 aForbiddenProperties
.insert( PROPERTY_TABSTOP
);
337 aForbiddenProperties
.insert( PROPERTY_TEXTCOLOR
);
338 aForbiddenProperties
.insert( PROPERTY_VSCROLL
);
339 aForbiddenProperties
.insert( PROPERTY_CONTROLLABEL
);
340 aForbiddenProperties
.insert( PROPERTY_RICH_TEXT
);
341 aForbiddenProperties
.insert( PROPERTY_VERTICAL_ALIGN
);
342 aForbiddenProperties
.insert( PROPERTY_IMAGE_URL
);
343 aForbiddenProperties
.insert( PROPERTY_IMAGE_POSITION
);
344 aForbiddenProperties
.insert( OUString( "EnableVisible" ) );
345 if ( !bAllowDropDown
)
346 aForbiddenProperties
.insert( PROPERTY_DROPDOWN
);
348 Sequence
< Property
> aNewProps( _rProps
.getLength() );
349 Property
* pNewProps
= aNewProps
.getArray();
351 const Property
* pProps
= _rProps
.getConstArray();
352 const Property
* pPropsEnd
= pProps
+ _rProps
.getLength();
353 for ( ; pProps
!= pPropsEnd
; ++pProps
)
355 if ( aForbiddenProperties
.find( pProps
->Name
) == aForbiddenProperties
.end() )
356 *pNewProps
++ = *pProps
;
359 aNewProps
.realloc( pNewProps
- aNewProps
.getArray() );
363 //------------------------------------------------------------------------------
364 void OGridColumn::setOwnProperties(Sequence
<Property
>& aDescriptor
)
366 aDescriptor
.realloc(5);
367 Property
* pProperties
= aDescriptor
.getArray();
368 DECL_PROP1(LABEL
, OUString
, BOUND
);
369 DECL_PROP3(WIDTH
, sal_Int32
, BOUND
, MAYBEVOID
, MAYBEDEFAULT
);
370 DECL_PROP3(ALIGN
, sal_Int16
, BOUND
, MAYBEVOID
, MAYBEDEFAULT
);
371 DECL_BOOL_PROP2(HIDDEN
, BOUND
, MAYBEDEFAULT
);
372 DECL_PROP1(COLUMNSERVICENAME
, OUString
, READONLY
);
375 // Reference<XPropertySet>
376 //------------------------------------------------------------------------------
377 void OGridColumn::getFastPropertyValue(Any
& rValue
, sal_Int32 nHandle
) const
381 case PROPERTY_ID_COLUMNSERVICENAME
:
382 rValue
<<= m_aModelName
;
384 case PROPERTY_ID_LABEL
:
387 case PROPERTY_ID_WIDTH
:
390 case PROPERTY_ID_ALIGN
:
393 case PROPERTY_ID_HIDDEN
:
397 OPropertySetAggregationHelper::getFastPropertyValue(rValue
, nHandle
);
401 //------------------------------------------------------------------------------
402 sal_Bool
OGridColumn::convertFastPropertyValue( Any
& rConvertedValue
, Any
& rOldValue
,
403 sal_Int32 nHandle
, const Any
& rValue
)throw( IllegalArgumentException
)
405 sal_Bool
bModified(sal_False
);
408 case PROPERTY_ID_LABEL
:
409 bModified
= tryPropertyValue(rConvertedValue
, rOldValue
, rValue
, m_aLabel
);
411 case PROPERTY_ID_WIDTH
:
412 bModified
= tryPropertyValue(rConvertedValue
, rOldValue
, rValue
, m_aWidth
, ::getCppuType((const sal_Int32
*)NULL
));
414 case PROPERTY_ID_ALIGN
:
415 bModified
= tryPropertyValue( rConvertedValue
, rOldValue
, rValue
, m_aAlign
, ::getCppuType( (const sal_Int32
*)NULL
) );
416 // strange enough, css.awt.TextAlign is a 32-bit integer, while the Align property (both here for grid controls
417 // and for ordinary toolkit controls) is a 16-bit integer. So, allow for 32 bit, but normalize it to 16 bit
420 sal_Int32
nAlign( 0 );
421 if ( rConvertedValue
>>= nAlign
)
422 rConvertedValue
<<= (sal_Int16
)nAlign
;
425 case PROPERTY_ID_HIDDEN
:
426 bModified
= tryPropertyValue(rConvertedValue
, rOldValue
, rValue
, getBOOL(m_aHidden
));
432 //------------------------------------------------------------------------------
433 void OGridColumn::setFastPropertyValue_NoBroadcast( sal_Int32 nHandle
, const Any
& rValue
) throw (::com::sun::star::uno::Exception
)
437 case PROPERTY_ID_LABEL
:
438 DBG_ASSERT(rValue
.getValueType().getTypeClass() == TypeClass_STRING
, "invalid type" );
441 case PROPERTY_ID_WIDTH
:
444 case PROPERTY_ID_ALIGN
:
447 case PROPERTY_ID_HIDDEN
:
455 //------------------------------------------------------------------------------
456 Any
OGridColumn::getPropertyDefaultByHandle( sal_Int32 nHandle
) const
460 case PROPERTY_ID_WIDTH
:
461 case PROPERTY_ID_ALIGN
:
463 case PROPERTY_ID_HIDDEN
:
464 return makeAny((sal_Bool
)sal_False
);
466 return OPropertySetAggregationHelper::getPropertyDefaultByHandle(nHandle
);
471 //------------------------------------------------------------------------------
472 Reference
< XCloneable
> SAL_CALL
OGridColumn::createClone( ) throw (RuntimeException
)
474 OGridColumn
* pNewColumn
= createCloneColumn();
479 //------------------------------------------------------------------------------
480 void SAL_CALL
OGridColumn::write(const Reference
<XObjectOutputStream
>& _rxOutStream
)
482 // 1. Write the UnoControl
483 Reference
<XMarkableStream
> xMark(_rxOutStream
, UNO_QUERY
);
484 sal_Int32 nMark
= xMark
->createMark();
487 _rxOutStream
->writeLong(nLen
);
489 Reference
<XPersistObject
> xPersist
;
490 if (query_aggregation(m_xAggregate
, xPersist
))
491 xPersist
->write(_rxOutStream
);
493 // Calculate the length
494 nLen
= xMark
->offsetToMark(nMark
) - 4;
495 xMark
->jumpToMark(nMark
);
496 _rxOutStream
->writeLong(nLen
);
497 xMark
->jumpToFurthest();
498 xMark
->deleteMark(nMark
);
500 // 2. Write a version number
501 _rxOutStream
->writeShort(0x0002);
503 sal_uInt16 nAnyMask
= 0;
504 if (m_aWidth
.getValueType().getTypeClass() == TypeClass_LONG
)
507 if (m_aAlign
.getValueTypeClass() == TypeClass_SHORT
)
510 nAnyMask
|= COMPATIBLE_HIDDEN
;
512 _rxOutStream
->writeShort(nAnyMask
);
514 if (nAnyMask
& WIDTH
)
515 _rxOutStream
->writeLong(getINT32(m_aWidth
));
516 if (nAnyMask
& ALIGN
)
517 _rxOutStream
->writeShort(getINT16(m_aAlign
));
520 _rxOutStream
<< m_aLabel
;
522 // the new place for the hidden flag : after m_aLabel, so older office version read the correct label, too
523 if (nAnyMask
& COMPATIBLE_HIDDEN
)
524 _rxOutStream
->writeBoolean(getBOOL(m_aHidden
));
527 //------------------------------------------------------------------------------
528 void SAL_CALL
OGridColumn::read(const Reference
<XObjectInputStream
>& _rxInStream
)
530 // 1. Read the UnoControl
531 sal_Int32 nLen
= _rxInStream
->readLong();
534 Reference
<XMarkableStream
> xMark(_rxInStream
, UNO_QUERY
);
535 sal_Int32 nMark
= xMark
->createMark();
536 Reference
<XPersistObject
> xPersist
;
537 if (query_aggregation(m_xAggregate
, xPersist
))
538 xPersist
->read(_rxInStream
);
540 xMark
->jumpToMark(nMark
);
541 _rxInStream
->skipBytes(nLen
);
542 xMark
->deleteMark(nMark
);
545 // 2. Write a version number
546 sal_uInt16 nVersion
= _rxInStream
->readShort(); (void)nVersion
;
547 sal_uInt16 nAnyMask
= _rxInStream
->readShort();
549 if (nAnyMask
& WIDTH
)
551 sal_Int32 nValue
= _rxInStream
->readLong();
552 m_aWidth
<<= (sal_Int32
)nValue
;
555 if (nAnyMask
& ALIGN
)
557 sal_Int16 nValue
= _rxInStream
->readShort();
560 if (nAnyMask
& OLD_HIDDEN
)
562 sal_Bool bValue
= _rxInStream
->readBoolean();
563 m_aHidden
<<= (sal_Bool
)bValue
;
567 _rxInStream
>> m_aLabel
;
569 if (nAnyMask
& COMPATIBLE_HIDDEN
)
571 sal_Bool bValue
= _rxInStream
->readBoolean();
572 m_aHidden
<<= (sal_Bool
)bValue
;
576 //------------------------------------------------------------------------------
577 IMPL_COLUMN(TextFieldColumn
, FRM_SUN_COMPONENT_TEXTFIELD
, sal_False
);
578 IMPL_COLUMN(PatternFieldColumn
, FRM_SUN_COMPONENT_PATTERNFIELD
, sal_False
);
579 IMPL_COLUMN(DateFieldColumn
, FRM_SUN_COMPONENT_DATEFIELD
, sal_True
);
580 IMPL_COLUMN(TimeFieldColumn
, FRM_SUN_COMPONENT_TIMEFIELD
, sal_False
);
581 IMPL_COLUMN(NumericFieldColumn
, FRM_SUN_COMPONENT_NUMERICFIELD
, sal_False
);
582 IMPL_COLUMN(CurrencyFieldColumn
, FRM_SUN_COMPONENT_CURRENCYFIELD
, sal_False
);
583 IMPL_COLUMN(CheckBoxColumn
, FRM_SUN_COMPONENT_CHECKBOX
, sal_False
);
584 IMPL_COLUMN(ComboBoxColumn
, FRM_SUN_COMPONENT_COMBOBOX
, sal_False
);
585 IMPL_COLUMN(ListBoxColumn
, FRM_SUN_COMPONENT_LISTBOX
, sal_False
);
586 IMPL_COLUMN(FormattedFieldColumn
, FRM_SUN_COMPONENT_FORMATTEDFIELD
, sal_False
);
588 //.........................................................................
590 //.........................................................................
592 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */