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 .
20 #include "dlgevtatt.hxx"
22 #include "dlgprov.hxx"
24 #include <sfx2/strings.hrc>
25 #include <sfx2/sfxresid.hxx>
27 #include <vcl/svapp.hxx>
28 #include <vcl/weld.hxx>
29 #include <comphelper/diagnose_ex.hxx>
31 #include <com/sun/star/awt/XControl.hpp>
32 #include <com/sun/star/awt/XControlContainer.hpp>
33 #include <com/sun/star/awt/XDialogEventHandler.hpp>
34 #include <com/sun/star/awt/XContainerWindowEventHandler.hpp>
35 #include <com/sun/star/beans/XPropertySet.hpp>
36 #include <com/sun/star/script/ScriptEventDescriptor.hpp>
37 #include <com/sun/star/script/XScriptEventsSupplier.hpp>
38 #include <com/sun/star/script/provider/XScriptProvider.hpp>
39 #include <com/sun/star/script/provider/theMasterScriptProviderFactory.hpp>
40 #include <com/sun/star/script/provider/XScriptProviderSupplier.hpp>
41 #include <com/sun/star/script/vba/XVBACompatibility.hpp>
42 #include <com/sun/star/lang/ServiceNotRegisteredException.hpp>
43 #include <com/sun/star/reflection/XIdlMethod.hpp>
44 #include <com/sun/star/beans/MethodConcept.hpp>
45 #include <com/sun/star/beans/XMaterialHolder.hpp>
47 #include <ooo/vba/XVBAToOOEventDescGen.hpp>
49 using namespace ::com::sun::star
;
50 using namespace ::com::sun::star::awt
;
51 using namespace ::com::sun::star::beans
;
52 using namespace ::com::sun::star::lang
;
53 using namespace ::com::sun::star::script
;
54 using namespace ::com::sun::star::uno
;
55 using namespace ::com::sun::star::reflection
;
62 class DialogSFScriptListenerImpl
: public DialogScriptListenerImpl
65 Reference
< frame::XModel
> m_xModel
;
66 virtual void firing_impl( const script::ScriptEvent
& aScriptEvent
, uno::Any
* pRet
) override
;
68 DialogSFScriptListenerImpl( const Reference
< XComponentContext
>& rxContext
, const Reference
< frame::XModel
>& rxModel
) : DialogScriptListenerImpl( rxContext
), m_xModel( rxModel
) {}
71 class DialogLegacyScriptListenerImpl
: public DialogSFScriptListenerImpl
74 virtual void firing_impl( const script::ScriptEvent
& aScriptEvent
, uno::Any
* pRet
) override
;
76 DialogLegacyScriptListenerImpl( const Reference
< XComponentContext
>& rxContext
, const Reference
< frame::XModel
>& rxModel
) : DialogSFScriptListenerImpl( rxContext
, rxModel
){}
79 class DialogUnoScriptListenerImpl
: public DialogSFScriptListenerImpl
81 Reference
< awt::XControl
> m_xControl
;
82 Reference
< XInterface
> m_xHandler
;
83 Reference
< beans::XIntrospectionAccess
> m_xIntrospectionAccess
;
84 bool m_bDialogProviderMode
;
86 virtual void firing_impl( const script::ScriptEvent
& aScriptEvent
, uno::Any
* pRet
) override
;
89 DialogUnoScriptListenerImpl( const Reference
< XComponentContext
>& rxContext
,
90 const Reference
< frame::XModel
>& rxModel
,
91 const Reference
< awt::XControl
>& rxControl
,
92 const Reference
< XInterface
>& rxHandler
,
93 const Reference
< beans::XIntrospectionAccess
>& rxIntrospectionAccess
,
94 bool bDialogProviderMode
); // false: ContainerWindowProvider mode
98 class DialogVBAScriptListenerImpl
: public DialogScriptListenerImpl
101 OUString msDialogCodeName
;
102 OUString msDialogLibName
;
103 Reference
< script::XScriptListener
> mxListener
;
104 virtual void firing_impl( const script::ScriptEvent
& aScriptEvent
, uno::Any
* pRet
) override
;
106 DialogVBAScriptListenerImpl( const Reference
< XComponentContext
>& rxContext
, const Reference
< awt::XControl
>& rxControl
, const Reference
< frame::XModel
>& xModel
, OUString sDialogLibName
);
111 DialogVBAScriptListenerImpl::DialogVBAScriptListenerImpl( const Reference
< XComponentContext
>& rxContext
, const Reference
< awt::XControl
>& rxControl
, const Reference
< frame::XModel
>& xModel
, OUString sDialogLibName
) : DialogScriptListenerImpl( rxContext
), msDialogLibName(std::move( sDialogLibName
))
113 Reference
< XMultiComponentFactory
> xSMgr( m_xContext
->getServiceManager() );
114 Sequence
< Any
> args(1);
117 args
.getArray()[0] <<= xModel
;
118 mxListener
.set( xSMgr
->createInstanceWithArgumentsAndContext( u
"ooo.vba.EventListener"_ustr
, args
, m_xContext
), UNO_QUERY
);
120 if ( !rxControl
.is() )
125 Reference
< XPropertySet
> xProps( rxControl
->getModel(), UNO_QUERY_THROW
);
126 xProps
->getPropertyValue(u
"Name"_ustr
) >>= msDialogCodeName
;
127 xProps
.set( mxListener
, UNO_QUERY_THROW
);
128 xProps
->setPropertyValue(u
"Model"_ustr
, args
[ 0 ] );
130 catch( const Exception
& )
132 DBG_UNHANDLED_EXCEPTION("scripting");
137 void DialogVBAScriptListenerImpl::firing_impl( const script::ScriptEvent
& aScriptEvent
, uno::Any
* )
139 if ( !(aScriptEvent
.ScriptType
== "VBAInterop" && mxListener
.is()) )
142 ScriptEvent
aScriptEventCopy( aScriptEvent
);
143 aScriptEventCopy
.ScriptCode
= msDialogLibName
+ "." + msDialogCodeName
;
146 mxListener
->firing( aScriptEventCopy
);
148 catch( const Exception
& )
150 DBG_UNHANDLED_EXCEPTION("scripting");
155 // DialogEventsAttacherImpl
158 DialogEventsAttacherImpl::DialogEventsAttacherImpl( const Reference
< XComponentContext
>& rxContext
, const Reference
< frame::XModel
>& rxModel
, const Reference
< awt::XControl
>& rxControl
, const Reference
< XInterface
>& rxHandler
, const Reference
< beans::XIntrospectionAccess
>& rxIntrospect
, bool bProviderMode
, const Reference
< script::XScriptListener
>& rxRTLListener
, const OUString
& sDialogLibName
)
159 :mbUseFakeVBAEvents( false ), m_xContext( rxContext
)
161 // key listeners by protocol when ScriptType = 'Script'
162 // otherwise key is the ScriptType e.g. StarBasic
163 if ( rxRTLListener
.is() ) // set up handler for RTL_BASIC
164 listenersForTypes
[ u
"StarBasic"_ustr
] = rxRTLListener
;
166 listenersForTypes
[ u
"StarBasic"_ustr
] = new DialogLegacyScriptListenerImpl( rxContext
, rxModel
);
167 // handler for Script & OUString("vnd.sun.star.UNO:")
168 listenersForTypes
[ u
"vnd.sun.star.UNO"_ustr
] = new DialogUnoScriptListenerImpl( rxContext
, rxModel
, rxControl
, rxHandler
, rxIntrospect
, bProviderMode
);
169 listenersForTypes
[ u
"vnd.sun.star.script"_ustr
] = new DialogSFScriptListenerImpl( rxContext
, rxModel
);
171 // determine the VBA compatibility mode from the Basic library container
174 uno::Reference
< beans::XPropertySet
> xModelProps( rxModel
, uno::UNO_QUERY_THROW
);
175 uno::Reference
< script::vba::XVBACompatibility
> xVBACompat(
176 xModelProps
->getPropertyValue(u
"BasicLibraries"_ustr
), uno::UNO_QUERY_THROW
);
177 mbUseFakeVBAEvents
= xVBACompat
->getVBACompatibilityMode();
179 catch( uno::Exception
& )
182 if ( mbUseFakeVBAEvents
)
183 listenersForTypes
[ u
"VBAInterop"_ustr
] = new DialogVBAScriptListenerImpl( rxContext
, rxControl
, rxModel
, sDialogLibName
);
187 DialogEventsAttacherImpl::~DialogEventsAttacherImpl()
192 Reference
< script::XScriptListener
> const &
193 DialogEventsAttacherImpl::getScriptListenerForKey( const OUString
& sKey
)
195 ListenerHash::iterator it
= listenersForTypes
.find( sKey
);
196 if ( it
== listenersForTypes
.end() )
197 throw RuntimeException(); // more text info here please
200 Reference
< XScriptEventsSupplier
> DialogEventsAttacherImpl::getFakeVbaEventsSupplier( const Reference
< XControl
>& xControl
, OUString
const & sControlName
)
202 Reference
< XScriptEventsSupplier
> xEventsSupplier
;
203 Reference
< XMultiComponentFactory
> xSMgr( m_xContext
->getServiceManager() );
206 Reference
< ooo::vba::XVBAToOOEventDescGen
> xVBAToOOEvtDesc( xSMgr
->createInstanceWithContext(u
"ooo.vba.VBAToOOEventDesc"_ustr
, m_xContext
), UNO_QUERY
);
207 if ( xVBAToOOEvtDesc
.is() )
208 xEventsSupplier
= xVBAToOOEvtDesc
->getEventSupplier( xControl
, sControlName
);
211 return xEventsSupplier
;
215 void DialogEventsAttacherImpl::attachEventsToControl( const Reference
< XControl
>& xControl
, const Reference
< XScriptEventsSupplier
>& xEventsSupplier
, const Any
& Helper
)
217 if ( !xEventsSupplier
.is() )
220 Reference
< container::XNameContainer
> xEventCont
= xEventsSupplier
->getEvents();
222 Reference
< XControlModel
> xControlModel
= xControl
->getModel();
223 if ( !xEventCont
.is() )
226 const Sequence
< OUString
> aNames
= xEventCont
->getElementNames();
228 for ( const OUString
& rName
: aNames
)
230 ScriptEventDescriptor aDesc
;
232 Any aElement
= xEventCont
->getByName( rName
);
234 OUString sKey
= aDesc
.ScriptType
;
235 if ( aDesc
.ScriptType
== "Script" || aDesc
.ScriptType
== "UNO" )
237 sal_Int32 nIndex
= aDesc
.ScriptCode
.indexOf( ':' );
238 sKey
= aDesc
.ScriptCode
.copy( 0, nIndex
);
240 Reference
< XAllListener
> xAllListener
=
241 new DialogAllListenerImpl( getScriptListenerForKey( sKey
), aDesc
.ScriptType
, aDesc
.ScriptCode
);
243 // try first to attach event to the ControlModel
244 bool bSuccess
= false;
247 Reference
< XEventListener
> xListener_
= m_xEventAttacher
->attachSingleEventListener(
248 xControlModel
, xAllListener
, Helper
, aDesc
.ListenerType
,
249 aDesc
.AddListenerParam
, aDesc
.EventMethod
);
251 if ( xListener_
.is() )
254 catch ( const Exception
& )
256 DBG_UNHANDLED_EXCEPTION("scripting");
261 // if we had no success, try to attach to the control
264 m_xEventAttacher
->attachSingleEventListener(
265 xControl
, xAllListener
, Helper
, aDesc
.ListenerType
,
266 aDesc
.AddListenerParam
, aDesc
.EventMethod
);
269 catch ( const Exception
& )
271 DBG_UNHANDLED_EXCEPTION("scripting");
277 void DialogEventsAttacherImpl::nestedAttachEvents( const Sequence
< Reference
< XInterface
> >& Objects
, const Any
& Helper
, OUString
& sDialogCodeName
)
279 for ( const Reference
< XInterface
>& rObject
: Objects
)
281 // We know that we have to do with instances of XControl.
282 // Otherwise this is not the right implementation for
283 // XScriptEventsAttacher and we have to give up.
284 nestedAttachEvents(rObject
.query
<XControl
>(), Helper
, sDialogCodeName
);
288 void DialogEventsAttacherImpl::nestedAttachEvents(
289 const css::uno::Reference
<css::awt::XControl
>& xControl
, const css::uno::Any
& Helper
,
290 OUString
& sDialogCodeName
)
293 throw IllegalArgumentException();
294 Reference
<XControlContainer
> xControlContainer(xControl
, UNO_QUERY
);
295 Reference
<XDialog
> xDialog(xControl
, UNO_QUERY
);
297 // get XEventsSupplier from control model
298 Reference
<XControlModel
> xControlModel
= xControl
->getModel();
299 Reference
<XScriptEventsSupplier
> xEventsSupplier(xControlModel
, UNO_QUERY
);
300 attachEventsToControl(xControl
, xEventsSupplier
, Helper
);
301 if (mbUseFakeVBAEvents
)
303 xEventsSupplier
.set(getFakeVbaEventsSupplier(xControl
, sDialogCodeName
));
304 Any
newHelper(xControl
);
305 attachEventsToControl(xControl
, xEventsSupplier
, newHelper
);
307 if (xControlContainer
.is() && !xDialog
.is())
309 for (auto& xChildControl
: xControlContainer
->getControls())
310 nestedAttachEvents(xChildControl
, Helper
, sDialogCodeName
);
315 // XScriptEventsAttacher
318 void SAL_CALL
DialogEventsAttacherImpl::attachEvents( const Sequence
< Reference
< XInterface
> >& Objects
,
319 const css::uno::Reference
<css::script::XScriptListener
>&,
324 ::osl::MutexGuard
aGuard( getMutex() );
326 if ( !m_xEventAttacher
.is() )
328 Reference
< XMultiComponentFactory
> xSMgr( m_xContext
->getServiceManager() );
330 throw RuntimeException();
332 m_xEventAttacher
.set( xSMgr
->createInstanceWithContext(
333 u
"com.sun.star.script.EventAttacher"_ustr
, m_xContext
), UNO_QUERY
);
335 if ( !m_xEventAttacher
.is() )
336 throw ServiceNotRegisteredException();
339 OUString sDialogCodeName
;
340 sal_Int32 nObjCount
= Objects
.getLength();
341 Reference
< awt::XControl
> xDlgControl( Objects
[ nObjCount
- 1 ], uno::UNO_QUERY
); // last object is the dialog
342 if ( xDlgControl
.is() )
344 Reference
< XPropertySet
> xProps( xDlgControl
->getModel(), UNO_QUERY
);
347 xProps
->getPropertyValue(u
"Name"_ustr
) >>= sDialogCodeName
;
349 catch( Exception
& ){}
351 // go over all objects
352 nestedAttachEvents( Objects
, Helper
, sDialogCodeName
);
356 // DialogAllListenerImpl
359 DialogAllListenerImpl::DialogAllListenerImpl( const Reference
< XScriptListener
>& rxListener
,
360 OUString sScriptType
, OUString sScriptCode
)
361 :m_xScriptListener( rxListener
)
362 ,m_sScriptType(std::move( sScriptType
))
363 ,m_sScriptCode(std::move( sScriptCode
))
368 DialogAllListenerImpl::~DialogAllListenerImpl()
373 void DialogAllListenerImpl::firing_impl( const AllEventObject
& Event
, Any
* pRet
)
375 ScriptEvent aScriptEvent
;
376 aScriptEvent
.Source
= getXWeak(); // get correct XInterface
377 aScriptEvent
.ListenerType
= Event
.ListenerType
;
378 aScriptEvent
.MethodName
= Event
.MethodName
;
379 aScriptEvent
.Arguments
= Event
.Arguments
;
380 aScriptEvent
.Helper
= Event
.Helper
;
381 aScriptEvent
.ScriptType
= m_sScriptType
;
382 aScriptEvent
.ScriptCode
= m_sScriptCode
;
384 if ( m_xScriptListener
.is() )
387 *pRet
= m_xScriptListener
->approveFiring( aScriptEvent
);
389 m_xScriptListener
->firing( aScriptEvent
);
397 void DialogAllListenerImpl::disposing(const EventObject
& )
405 void DialogAllListenerImpl::firing( const AllEventObject
& Event
)
407 //::osl::MutexGuard aGuard( getMutex() );
409 firing_impl( Event
, nullptr );
413 Any
DialogAllListenerImpl::approveFiring( const AllEventObject
& Event
)
415 //::osl::MutexGuard aGuard( getMutex() );
418 firing_impl( Event
, &aReturn
);
423 // DialogScriptListenerImpl
426 DialogUnoScriptListenerImpl::DialogUnoScriptListenerImpl( const Reference
< XComponentContext
>& rxContext
,
427 const Reference
< css::frame::XModel
>& rxModel
,
428 const Reference
< css::awt::XControl
>& rxControl
,
429 const Reference
< css::uno::XInterface
>& rxHandler
,
430 const Reference
< css::beans::XIntrospectionAccess
>& rxIntrospectionAccess
,
431 bool bDialogProviderMode
)
432 : DialogSFScriptListenerImpl( rxContext
, rxModel
)
433 ,m_xControl( rxControl
)
434 ,m_xHandler( rxHandler
)
435 ,m_xIntrospectionAccess( rxIntrospectionAccess
)
436 ,m_bDialogProviderMode( bDialogProviderMode
)
441 DialogScriptListenerImpl::~DialogScriptListenerImpl()
446 void DialogSFScriptListenerImpl::firing_impl( const ScriptEvent
& aScriptEvent
, Any
* pRet
)
450 Reference
< provider::XScriptProvider
> xScriptProvider
;
453 Reference
< provider::XScriptProviderSupplier
> xSupplier( m_xModel
, UNO_QUERY
);
454 OSL_ENSURE( xSupplier
.is(), "DialogScriptListenerImpl::firing_impl: failed to get script provider supplier" );
455 if ( xSupplier
.is() )
456 xScriptProvider
.set( xSupplier
->getScriptProvider() );
460 OSL_ASSERT( m_xContext
.is() );
461 if ( m_xContext
.is() )
463 Reference
< provider::XScriptProviderFactory
> xFactory
=
464 provider::theMasterScriptProviderFactory::get( m_xContext
);
467 aCtx
<<= u
"user"_ustr
;
468 xScriptProvider
= xFactory
->createScriptProvider( aCtx
);
472 OSL_ENSURE( xScriptProvider
.is(), "DialogScriptListenerImpl::firing_impl: failed to get script provider" );
474 if ( xScriptProvider
.is() )
476 Reference
< provider::XScript
> xScript
= xScriptProvider
->getScript( aScriptEvent
.ScriptCode
);
477 OSL_ENSURE( xScript
.is(), "DialogScriptListenerImpl::firing_impl: failed to get script" );
481 Sequence
< Any
> aInParams
;
482 Sequence
< sal_Int16
> aOutParamsIndex
;
483 Sequence
< Any
> aOutParams
;
485 // get arguments for script
486 aInParams
= aScriptEvent
.Arguments
;
488 Any aResult
= xScript
->invoke( aInParams
, aOutParamsIndex
, aOutParams
);
490 *pRet
= std::move(aResult
);
494 catch ( const Exception
& )
496 DBG_UNHANDLED_EXCEPTION("scripting");
500 void DialogLegacyScriptListenerImpl::firing_impl( const ScriptEvent
& aScriptEvent
, Any
* pRet
)
503 OUString
sScriptCode( aScriptEvent
.ScriptCode
);
505 if ( aScriptEvent
.ScriptType
!= "StarBasic" )
508 // StarBasic script: convert ScriptCode to scriptURL
509 sal_Int32 nIndex
= sScriptCode
.indexOf( ':' );
510 if ( nIndex
>= 0 && nIndex
< sScriptCode
.getLength() )
512 sScriptURL
= OUString::Concat("vnd.sun.star.script:") +
513 sScriptCode
.subView( nIndex
+ 1 ) +
514 "?language=Basic&location=" +
515 sScriptCode
.subView( 0, nIndex
);
517 ScriptEvent
aSFScriptEvent( aScriptEvent
);
518 aSFScriptEvent
.ScriptCode
= sScriptURL
;
519 DialogSFScriptListenerImpl::firing_impl( aSFScriptEvent
, pRet
);
522 void DialogUnoScriptListenerImpl::firing_impl( const ScriptEvent
& aScriptEvent
, Any
* pRet
)
524 OUString aMethodName
= aScriptEvent
.ScriptCode
.copy( strlen("vnd.sun.star.UNO:") );
526 Any aEventObject
= aScriptEvent
.Arguments
[0];
528 bool bHandled
= false;
529 if( m_xHandler
.is() )
531 if( m_bDialogProviderMode
)
533 Reference
< XDialogEventHandler
> xDialogEventHandler( m_xHandler
, UNO_QUERY
);
534 if( xDialogEventHandler
.is() )
536 Reference
< XDialog
> xDialog( m_xControl
, UNO_QUERY
);
537 bHandled
= xDialogEventHandler
->callHandlerMethod( xDialog
, aEventObject
, aMethodName
);
542 Reference
< XContainerWindowEventHandler
> xContainerWindowEventHandler( m_xHandler
, UNO_QUERY
);
543 if( xContainerWindowEventHandler
.is() )
545 Reference
< XWindow
> xWindow( m_xControl
, UNO_QUERY
);
546 bHandled
= xContainerWindowEventHandler
->callHandlerMethod( xWindow
, aEventObject
, aMethodName
);
552 if( !bHandled
&& m_xIntrospectionAccess
.is() )
557 const Reference
< XIdlMethod
> xMethod
= m_xIntrospectionAccess
->
558 getMethod( aMethodName
, MethodConcept::ALL
- MethodConcept::DANGEROUS
);
560 Reference
< XMaterialHolder
> xMaterialHolder
=
561 Reference
< XMaterialHolder
>::query( m_xIntrospectionAccess
);
562 Any aHandlerObject
= xMaterialHolder
->getMaterial();
564 Sequence
< Reference
< XIdlClass
> > aParamTypeSeq
= xMethod
->getParameterTypes();
565 sal_Int32 nParamCount
= aParamTypeSeq
.getLength();
566 if( nParamCount
== 0 )
569 xMethod
->invoke( aHandlerObject
, args
);
572 else if( nParamCount
== 2 )
574 // Signature check automatically done by reflection
575 Sequence
<Any
> Args(2);
576 Any
* pArgs
= Args
.getArray();
577 if( m_bDialogProviderMode
)
579 Reference
< XDialog
> xDialog( m_xControl
, UNO_QUERY
);
580 pArgs
[0] <<= xDialog
;
584 Reference
< XWindow
> xWindow( m_xControl
, UNO_QUERY
);
585 pArgs
[0] <<= xWindow
;
587 pArgs
[1] = std::move(aEventObject
);
588 aRet
= xMethod
->invoke( aHandlerObject
, Args
);
592 catch( const Exception
& )
594 DBG_UNHANDLED_EXCEPTION("scripting");
601 *pRet
= std::move(aRet
);
605 OUString
aRes(SfxResId(STR_ERRUNOEVENTBINDUNG
));
606 OUString
aQuoteChar( u
"\""_ustr
);
608 sal_Int32 nIndex
= aRes
.indexOf( '%' );
611 aRes
.subView( 0, nIndex
) +
612 aQuoteChar
+ aMethodName
+ aQuoteChar
+
613 aRes
.subView( nIndex
+ 2 );
615 std::unique_ptr
<weld::MessageDialog
> xBox(Application::CreateMessageDialog(nullptr,
616 VclMessageType::Warning
, VclButtonsType::Ok
, aOUFinal
));
625 void DialogScriptListenerImpl::disposing(const EventObject
& )
633 void DialogScriptListenerImpl::firing( const ScriptEvent
& aScriptEvent
)
635 //::osl::MutexGuard aGuard( getMutex() );
637 firing_impl( aScriptEvent
, nullptr );
641 Any
DialogScriptListenerImpl::approveFiring( const ScriptEvent
& aScriptEvent
)
643 //::osl::MutexGuard aGuard( getMutex() );
646 firing_impl( aScriptEvent
, &aReturn
);
651 } // namespace dlgprov
654 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */