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 .
24 #include <vcl/svapp.hxx>
25 #include <sal/log.hxx>
27 #include <win/wincomp.hxx>
28 #include <win/saldata.hxx>
29 #include <win/salinst.h>
30 #include <win/salframe.h>
31 #include <win/salobj.h>
33 #include <comphelper/windowserrorstring.hxx>
35 static bool ImplIsSysWindowOrChild( HWND hWndParent
, HWND hWndChild
)
37 if ( hWndParent
== hWndChild
)
40 HWND hTempWnd
= ::GetParent( hWndChild
);
43 // stop searching if not a child window
44 if ( !(GetWindowStyle( hTempWnd
) & WS_CHILD
) )
46 if ( hTempWnd
== hWndParent
)
48 hTempWnd
= ::GetParent( hTempWnd
);
54 WinSalObject
* ImplFindSalObject( HWND hWndChild
)
56 SalData
* pSalData
= GetSalData();
57 WinSalObject
* pObject
= pSalData
->mpFirstObject
;
60 if ( ImplIsSysWindowOrChild( pObject
->mhWndChild
, hWndChild
) )
63 pObject
= pObject
->mpNextObject
;
69 static WinSalFrame
* ImplFindSalObjectFrame( HWND hWnd
)
71 WinSalFrame
* pFrame
= nullptr;
72 WinSalObject
* pObject
= ImplFindSalObject( hWnd
);
75 // find matching frame
76 HWND hWnd2
= ::GetParent( pObject
->mhWnd
);
77 pFrame
= GetSalData()->mpFirstFrame
;
80 if ( pFrame
->mhWnd
== hWnd2
)
83 pFrame
= pFrame
->mpNextFrame
;
90 static LRESULT CALLBACK
SalSysMsgProc( int nCode
, WPARAM wParam
, LPARAM lParam
)
92 // Used for Unicode and none Unicode
93 SalData
* pSalData
= GetSalData();
95 if ( (nCode
>= 0) && lParam
)
97 CWPSTRUCT
* pData
= reinterpret_cast<CWPSTRUCT
*>(lParam
);
98 if ( (pData
->message
!= WM_KEYDOWN
) &&
99 (pData
->message
!= WM_KEYUP
) )
100 pSalData
->mnSalObjWantKeyEvt
= 0;
103 // check if we need to process data for a SalObject-window
104 WinSalObject
* pObject
;
105 if ( pData
->message
== WM_SETFOCUS
)
107 pObject
= ImplFindSalObject( pData
->hwnd
);
110 pObject
->mhLastFocusWnd
= pData
->hwnd
;
111 if ( ImplSalYieldMutexTryToAcquire() )
113 pObject
->CallCallback( SalObjEvent::GetFocus
);
114 ImplSalYieldMutexRelease();
118 bool const ret
= PostMessageW(pObject
->mhWnd
, SALOBJ_MSG_POSTFOCUS
, 0, 0);
119 SAL_WARN_IF(!ret
, "vcl", "ERROR: PostMessage() failed!");
123 else if ( pData
->message
== WM_KILLFOCUS
)
125 pObject
= ImplFindSalObject( pData
->hwnd
);
126 if ( pObject
&& !ImplFindSalObject( reinterpret_cast<HWND
>(pData
->wParam
) ) )
128 // only call LoseFocus, if truly no child window gets the focus
129 if ( !pData
->wParam
|| !ImplFindSalObject( reinterpret_cast<HWND
>(pData
->wParam
) ) )
131 if ( ImplSalYieldMutexTryToAcquire() )
133 pObject
->CallCallback( SalObjEvent::LoseFocus
);
134 ImplSalYieldMutexRelease();
138 bool const ret
= PostMessageW(pObject
->mhWnd
, SALOBJ_MSG_POSTFOCUS
, 0, 0);
139 SAL_WARN_IF(!ret
, "vcl", "ERROR: PostMessage() failed!");
143 pObject
->mhLastFocusWnd
= reinterpret_cast<HWND
>(pData
->wParam
);
148 return CallNextHookEx( pSalData
->mhSalObjMsgHook
, nCode
, wParam
, lParam
);
151 bool ImplSalPreDispatchMsg( const MSG
* pMsg
)
153 // Used for Unicode and none Unicode
154 SalData
* pSalData
= GetSalData();
155 WinSalObject
* pObject
;
157 if ( (pMsg
->message
== WM_LBUTTONDOWN
) ||
158 (pMsg
->message
== WM_RBUTTONDOWN
) ||
159 (pMsg
->message
== WM_MBUTTONDOWN
) )
161 ImplSalYieldMutexAcquireWithWait();
162 pObject
= ImplFindSalObject( pMsg
->hwnd
);
163 if ( pObject
&& !pObject
->IsMouseTransparent() )
165 bool const ret
= PostMessageW(pObject
->mhWnd
, SALOBJ_MSG_TOTOP
, 0, 0);
166 SAL_WARN_IF(!ret
, "vcl", "ERROR: PostMessage() failed!");
168 ImplSalYieldMutexRelease();
171 if ( (pMsg
->message
== WM_KEYDOWN
) ||
172 (pMsg
->message
== WM_KEYUP
) )
174 // process KeyEvents even if the control does not process them itself
175 // SysKeys are processed as WM_SYSCOMMAND
176 // Char-Events are not processed, as they are not accelerator-relevant
177 bool bWantedKeyCode
= false;
178 // A-Z, 0-9 only when combined with the Control-key
179 if ( ((pMsg
->wParam
>= 65) && (pMsg
->wParam
<= 90)) ||
180 ((pMsg
->wParam
>= 48) && (pMsg
->wParam
<= 57)) )
182 if ( GetKeyState( VK_CONTROL
) & 0x8000 )
183 bWantedKeyCode
= true;
185 else if ( ((pMsg
->wParam
>= VK_F1
) && (pMsg
->wParam
<= VK_F24
)) ||
186 ((pMsg
->wParam
>= VK_SPACE
) && (pMsg
->wParam
<= VK_HELP
)) ||
187 (pMsg
->wParam
== VK_BACK
) || (pMsg
->wParam
== VK_TAB
) ||
188 (pMsg
->wParam
== VK_CLEAR
) || (pMsg
->wParam
== VK_RETURN
) ||
189 (pMsg
->wParam
== VK_ESCAPE
) )
190 bWantedKeyCode
= true;
191 if ( bWantedKeyCode
)
193 ImplSalYieldMutexAcquireWithWait();
194 pObject
= ImplFindSalObject( pMsg
->hwnd
);
196 pSalData
->mnSalObjWantKeyEvt
= pMsg
->wParam
;
197 ImplSalYieldMutexRelease();
200 // check WM_SYSCHAR, to activate menu with Alt key
201 else if ( pMsg
->message
== WM_SYSCHAR
)
203 pSalData
->mnSalObjWantKeyEvt
= 0;
205 sal_uInt16 nKeyCode
= LOWORD( pMsg
->wParam
);
207 if ( ((nKeyCode
>= 48) && (nKeyCode
<= 57)) ||
208 ((nKeyCode
>= 65) && (nKeyCode
<= 90)) ||
209 ((nKeyCode
>= 97) && (nKeyCode
<= 122)) )
212 ImplSalYieldMutexAcquireWithWait();
213 pObject
= ImplFindSalObject( pMsg
->hwnd
);
216 if ( pMsg
->hwnd
== ::GetFocus() )
218 WinSalFrame
* pFrame
= ImplFindSalObjectFrame( pMsg
->hwnd
);
221 if ( ImplHandleSalObjSysCharMsg( pFrame
->mhWnd
, pMsg
->wParam
, pMsg
->lParam
) )
226 ImplSalYieldMutexRelease();
232 pSalData
->mnSalObjWantKeyEvt
= 0;
237 void ImplSalPostDispatchMsg( const MSG
* pMsg
)
239 // Used for Unicode and none Unicode
240 SalData
*pSalData
= GetSalData();
242 if ( (pMsg
->message
== WM_KEYDOWN
) || (pMsg
->message
== WM_KEYUP
) )
244 if ( pSalData
->mnSalObjWantKeyEvt
== pMsg
->wParam
)
246 pSalData
->mnSalObjWantKeyEvt
= 0;
247 if ( pMsg
->hwnd
== ::GetFocus() )
249 ImplSalYieldMutexAcquireWithWait();
250 WinSalFrame
* pFrame
= ImplFindSalObjectFrame( pMsg
->hwnd
);
252 ImplHandleSalObjKeyMsg( pFrame
->mhWnd
, pMsg
->message
, pMsg
->wParam
, pMsg
->lParam
);
253 ImplSalYieldMutexRelease();
258 pSalData
->mnSalObjWantKeyEvt
= 0;
261 static LRESULT CALLBACK
SalSysObjWndProcW(HWND hWnd
, UINT nMsg
, WPARAM wParam
, LPARAM lParam
)
270 BeginPaint( hWnd
, &aPs
);
271 EndPaint( hWnd
, &aPs
);
275 case WM_PARENTNOTIFY
:
276 if (UINT nNotifyMsg
= LOWORD(wParam
);
277 (nNotifyMsg
== WM_LBUTTONDOWN
) ||
278 (nNotifyMsg
== WM_RBUTTONDOWN
) ||
279 (nNotifyMsg
== WM_MBUTTONDOWN
) )
281 ImplSalYieldMutexAcquireWithWait();
282 WinSalObject
* pSysObj
= GetSalObjWindowPtr(hWnd
);
283 if ( pSysObj
&& !pSysObj
->IsMouseTransparent() )
284 pSysObj
->CallCallback( SalObjEvent::ToTop
);
285 ImplSalYieldMutexRelease();
289 case WM_MOUSEACTIVATE
:
290 ImplSalYieldMutexAcquireWithWait();
291 if (WinSalObject
* pSysObj
= GetSalObjWindowPtr(hWnd
);
292 pSysObj
&& !pSysObj
->IsMouseTransparent())
294 bool const ret
= PostMessageW( hWnd
, SALOBJ_MSG_TOTOP
, 0, 0 );
295 SAL_WARN_IF(!ret
, "vcl", "ERROR: PostMessage() failed!");
297 ImplSalYieldMutexRelease();
300 case SALOBJ_MSG_TOTOP
:
301 if ( ImplSalYieldMutexTryToAcquire() )
303 if (WinSalObject
* pSysObj
= GetSalObjWindowPtr(hWnd
))
304 pSysObj
->CallCallback(SalObjEvent::ToTop
);
305 ImplSalYieldMutexRelease();
310 bool const ret
= PostMessageW( hWnd
, SALOBJ_MSG_TOTOP
, 0, 0 );
311 SAL_WARN_IF(!ret
, "vcl", "ERROR: PostMessage() failed!");
315 case SALOBJ_MSG_POSTFOCUS
:
316 if ( ImplSalYieldMutexTryToAcquire() )
318 WinSalObject
* pSysObj
= GetSalObjWindowPtr(hWnd
);
319 HWND hFocusWnd
= ::GetFocus();
321 if ( hFocusWnd
&& ImplIsSysWindowOrChild( hWnd
, hFocusWnd
) )
322 nEvent
= SalObjEvent::GetFocus
;
324 nEvent
= SalObjEvent::LoseFocus
;
325 pSysObj
->CallCallback( nEvent
);
326 ImplSalYieldMutexRelease();
330 bool const ret
= PostMessageW(hWnd
, SALOBJ_MSG_POSTFOCUS
, 0, 0);
331 SAL_WARN_IF(!ret
, "vcl", "ERROR: PostMessage() failed!");
336 if (HWND hWndChild
= GetWindow(hWnd
, GW_CHILD
))
338 SetWindowPos( hWndChild
,
339 nullptr, 0, 0, static_cast<int>(LOWORD( lParam
)), static_cast<int>(HIWORD( lParam
)),
340 SWP_NOZORDER
| SWP_NOACTIVATE
);
346 // Save the window instance at the window handle.
347 CREATESTRUCTW
* pStruct
= reinterpret_cast<CREATESTRUCTW
*>(lParam
);
348 WinSalObject
* pSysObj
= static_cast<WinSalObject
*>(pStruct
->lpCreateParams
);
349 SetSalObjWindowPtr( hWnd
, pSysObj
);
350 // set HWND already here,
351 // as instance data might be used during CreateWindow() events
352 pSysObj
->mhWnd
= hWnd
;
357 return DefWindowProcW(hWnd
, nMsg
, wParam
, lParam
);
360 static LRESULT CALLBACK
SalSysObjChildWndProcW(HWND hWnd
, UINT nMsg
, WPARAM wParam
, LPARAM lParam
)
364 // clear background for plugins
366 if (WinSalObject
* pSysObj
= GetSalObjWindowPtr(GetParent(hWnd
));
367 pSysObj
&& !pSysObj
->IsEraseBackgroundEnabled())
369 // do not erase background
377 BeginPaint( hWnd
, &aPs
);
378 EndPaint( hWnd
, &aPs
);
389 if (WinSalObject
* pSysObj
= GetSalObjWindowPtr(GetParent(hWnd
));
390 pSysObj
&& pSysObj
->IsMouseTransparent())
392 // forward mouse events to parent frame
393 HWND hWndParent
= GetParent(pSysObj
->mhWnd
);
395 // transform coordinates
397 pt
.x
= static_cast<tools::Long
>(LOWORD(lParam
));
398 pt
.y
= static_cast<tools::Long
>(HIWORD(lParam
));
399 MapWindowPoints(hWnd
, hWndParent
, &pt
, 1);
400 lParam
= MAKELPARAM(static_cast<WORD
>(pt
.x
), static_cast<WORD
>(pt
.y
));
402 return SendMessageW(hWndParent
, nMsg
, wParam
, lParam
);
407 return DefWindowProcW(hWnd
, nMsg
, wParam
, lParam
);
410 SalObject
* ImplSalCreateObject( WinSalInstance
* pInst
, WinSalFrame
* pParent
)
412 SalData
* pSalData
= GetSalData();
414 // install hook, if it is the first SalObject
415 if ( !pSalData
->mpFirstObject
)
417 pSalData
->mhSalObjMsgHook
= SetWindowsHookExW( WH_CALLWNDPROC
,
420 pSalData
->mnAppThreadId
);
423 if ( !pSalData
->mbObjClassInit
)
425 WNDCLASSEXW aWndClassEx
;
426 aWndClassEx
.cbSize
= sizeof( aWndClassEx
);
427 aWndClassEx
.style
= 0;
428 aWndClassEx
.lpfnWndProc
= SalSysObjWndProcW
;
429 aWndClassEx
.cbClsExtra
= 0;
430 aWndClassEx
.cbWndExtra
= SAL_OBJECT_WNDEXTRA
;
431 aWndClassEx
.hInstance
= pSalData
->mhInst
;
432 aWndClassEx
.hIcon
= nullptr;
433 aWndClassEx
.hIconSm
= nullptr;
434 aWndClassEx
.hCursor
= LoadCursor( nullptr, IDC_ARROW
);
435 aWndClassEx
.hbrBackground
= nullptr;
436 aWndClassEx
.lpszMenuName
= nullptr;
437 aWndClassEx
.lpszClassName
= SAL_OBJECT_CLASSNAMEW
;
438 if ( RegisterClassExW( &aWndClassEx
) )
440 // Clean background first because of plugins.
441 aWndClassEx
.cbWndExtra
= 0;
442 aWndClassEx
.hbrBackground
= reinterpret_cast<HBRUSH
>(COLOR_WINDOW
+1);
443 aWndClassEx
.lpfnWndProc
= SalSysObjChildWndProcW
;
444 aWndClassEx
.lpszClassName
= SAL_OBJECT_CHILDCLASSNAMEW
;
445 if ( RegisterClassExW( &aWndClassEx
) )
446 pSalData
->mbObjClassInit
= true;
450 if ( pSalData
->mbObjClassInit
)
452 WinSalObject
* pObject
= new WinSalObject
;
454 // #135235# Clip siblings of this
455 // SystemChildWindow. Otherwise, DXCanvas (using a hidden
456 // SystemChildWindow) clobbers applets/plugins during
458 HWND hWnd
= CreateWindowExW( 0, SAL_OBJECT_CLASSNAMEW
, L
"",
459 WS_CHILD
| WS_CLIPSIBLINGS
, 0, 0, 0, 0,
460 pParent
->mhWnd
, nullptr,
461 pInst
->mhInst
, pObject
);
463 HWND hWndChild
= nullptr;
466 // #135235# Explicitly stack SystemChildWindows in
467 // the order they're created - since there's no notion
469 SetWindowPos(hWnd
,HWND_TOP
,0,0,0,0,
470 SWP_NOACTIVATE
|SWP_NOMOVE
|SWP_NOREDRAW
|SWP_NOSIZE
);
471 hWndChild
= CreateWindowExW( 0, SAL_OBJECT_CHILDCLASSNAMEW
, L
"",
472 WS_CHILD
| WS_CLIPCHILDREN
| WS_CLIPSIBLINGS
| WS_VISIBLE
,
475 pInst
->mhInst
, nullptr );
480 SAL_WARN("vcl", "CreateWindowExW failed: " << WindowsErrorString(GetLastError()));
488 pObject
->mhWnd
= hWnd
;
489 pObject
->mhWndChild
= hWndChild
;
490 pObject
->maSysData
.hWnd
= hWndChild
;
498 WinSalObject::WinSalObject()
500 SalData
* pSalData
= GetSalData();
503 mhWndChild
= nullptr;
504 mhLastFocusWnd
= nullptr;
505 mpStdClipRgnData
= nullptr;
507 // Insert object in objectlist
508 mpNextObject
= pSalData
->mpFirstObject
;
509 pSalData
->mpFirstObject
= this;
512 WinSalObject::~WinSalObject()
514 SalData
* pSalData
= GetSalData();
516 // remove frame from framelist
517 if ( this == pSalData
->mpFirstObject
)
519 pSalData
->mpFirstObject
= mpNextObject
;
521 // remove hook, if it is the last SalObject
522 if ( !pSalData
->mpFirstObject
)
523 UnhookWindowsHookEx( pSalData
->mhSalObjMsgHook
);
527 WinSalObject
* pTempObject
= pSalData
->mpFirstObject
;
528 while ( pTempObject
->mpNextObject
!= this )
529 pTempObject
= pTempObject
->mpNextObject
;
531 pTempObject
->mpNextObject
= mpNextObject
;
534 // destroy cache data
535 delete [] reinterpret_cast<BYTE
*>(mpStdClipRgnData
);
537 HWND hWndParent
= ::GetParent( mhWnd
);
540 DestroyWindow( mhWndChild
);
542 DestroyWindow( mhWnd
);
544 // reset palette, if no external child window is left,
545 // as they might have overwritten our palette
547 ::GetActiveWindow() == hWndParent
&&
548 !GetWindow( hWndParent
, GW_CHILD
) )
549 SendMessageW( hWndParent
, SAL_MSG_FORCEPALETTE
, 0, 0 );
552 void WinSalObject::ResetClipRegion()
554 SetWindowRgn( mhWnd
, nullptr, TRUE
);
557 void WinSalObject::BeginSetClipRegion( sal_uInt32 nRectCount
)
559 sal_uLong nRectBufSize
= sizeof(RECT
)*nRectCount
;
560 if ( nRectCount
< SAL_CLIPRECT_COUNT
)
562 if ( !mpStdClipRgnData
)
563 mpStdClipRgnData
= reinterpret_cast<RGNDATA
*>(new BYTE
[sizeof(RGNDATA
)-1+(SAL_CLIPRECT_COUNT
*sizeof(RECT
))]);
564 mpClipRgnData
= mpStdClipRgnData
;
567 mpClipRgnData
= reinterpret_cast<RGNDATA
*>(new BYTE
[sizeof(RGNDATA
)-1+nRectBufSize
]);
568 mpClipRgnData
->rdh
.dwSize
= sizeof( RGNDATAHEADER
);
569 mpClipRgnData
->rdh
.iType
= RDH_RECTANGLES
;
570 mpClipRgnData
->rdh
.nCount
= nRectCount
;
571 mpClipRgnData
->rdh
.nRgnSize
= nRectBufSize
;
572 SetRectEmpty( &(mpClipRgnData
->rdh
.rcBound
) );
573 mpNextClipRect
= reinterpret_cast<RECT
*>(&(mpClipRgnData
->Buffer
));
574 mbFirstClipRect
= true;
577 void WinSalObject::UnionClipRegion( tools::Long nX
, tools::Long nY
, tools::Long nWidth
, tools::Long nHeight
)
579 RECT
* pRect
= mpNextClipRect
;
580 RECT
* pBoundRect
= &(mpClipRgnData
->rdh
.rcBound
);
581 tools::Long nRight
= nX
+ nWidth
;
582 tools::Long nBottom
= nY
+ nHeight
;
584 if ( mbFirstClipRect
)
586 pBoundRect
->left
= nX
;
587 pBoundRect
->top
= nY
;
588 pBoundRect
->right
= nRight
;
589 pBoundRect
->bottom
= nBottom
;
590 mbFirstClipRect
= false;
594 if ( nX
< pBoundRect
->left
)
595 pBoundRect
->left
= static_cast<int>(nX
);
597 if ( nY
< pBoundRect
->top
)
598 pBoundRect
->top
= static_cast<int>(nY
);
600 if ( nRight
> pBoundRect
->right
)
601 pBoundRect
->right
= static_cast<int>(nRight
);
603 if ( nBottom
> pBoundRect
->bottom
)
604 pBoundRect
->bottom
= static_cast<int>(nBottom
);
607 pRect
->left
= static_cast<int>(nX
);
608 pRect
->top
= static_cast<int>(nY
);
609 pRect
->right
= static_cast<int>(nRight
);
610 pRect
->bottom
= static_cast<int>(nBottom
);
614 void WinSalObject::EndSetClipRegion()
618 // create a ClipRegion from the vcl::Region data
619 if ( mpClipRgnData
->rdh
.nCount
== 1 )
621 RECT
* pRect
= &(mpClipRgnData
->rdh
.rcBound
);
622 hRegion
= CreateRectRgn( pRect
->left
, pRect
->top
,
623 pRect
->right
, pRect
->bottom
);
627 sal_uLong nSize
= mpClipRgnData
->rdh
.nRgnSize
+sizeof(RGNDATAHEADER
);
628 hRegion
= ExtCreateRegion( nullptr, nSize
, mpClipRgnData
);
629 if ( mpClipRgnData
!= mpStdClipRgnData
)
630 delete [] reinterpret_cast<BYTE
*>(mpClipRgnData
);
633 SAL_WARN_IF( !hRegion
, "vcl", "SalObject::EndSetClipRegion() - Can't create ClipRegion" );
634 SetWindowRgn( mhWnd
, hRegion
, TRUE
);
637 void WinSalObject::SetPosSize( tools::Long nX
, tools::Long nY
, tools::Long nWidth
, tools::Long nHeight
)
639 sal_uLong nStyle
= 0;
640 bool bVisible
= (GetWindowStyle( mhWnd
) & WS_VISIBLE
) != 0;
643 ShowWindow( mhWnd
, SW_HIDE
);
644 nStyle
|= SWP_SHOWWINDOW
;
646 SetWindowPos( mhWnd
, nullptr,
647 static_cast<int>(nX
), static_cast<int>(nY
), static_cast<int>(nWidth
), static_cast<int>(nHeight
),
648 SWP_NOZORDER
| SWP_NOACTIVATE
| nStyle
);
651 void WinSalObject::Show( bool bVisible
)
654 ShowWindow( mhWnd
, SW_SHOWNORMAL
);
656 ShowWindow( mhWnd
, SW_HIDE
);
659 void WinSalObject::Enable( bool bEnable
)
661 EnableWindow( mhWnd
, bEnable
);
664 void WinSalObject::GrabFocus()
666 if ( mhLastFocusWnd
&&
667 IsWindow( mhLastFocusWnd
) &&
668 ImplIsSysWindowOrChild( mhWndChild
, mhLastFocusWnd
) )
669 ::SetFocus( mhLastFocusWnd
);
671 ::SetFocus( mhWndChild
);
674 const SystemEnvData
* WinSalObject::GetSystemData() const
679 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */