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 <hintids.hxx>
22 #include <editeng/paperinf.hxx>
23 #include <editeng/tstpitem.hxx>
24 #include <editeng/lrspitem.hxx>
25 #include <editeng/brushitem.hxx>
26 #include <vcl/msgbox.hxx>
27 #include <vcl/menu.hxx>
31 #include <swtypes.hxx>
38 #include "swuipardlg.hxx"
39 #include <pattern.hxx>
40 #include <poolfmt.hxx>
41 #include <uiborder.hxx>
49 #include "swabstdlg.hxx"
53 /// Converts a ranges array to a list containing one entry for each
54 /// element covered by the ranges.
55 /// @param aRanges An array containing zero or more range specifications and
56 /// terminated by one or more zero entries. A range
57 /// specification is two consecutive entries that specify
58 /// the start and end points of the range.
59 /// @returns A vector containing one element for each item covered by the
60 /// ranges. This is not gauranteed to be sorted and may contain
61 /// duplicates if the original ranges contained overlaps.
62 static std::vector
<sal_uInt16
> lcl_convertRangesToList(const sal_uInt16 aRanges
[]) {
63 std::vector
<sal_uInt16
> aVec
;
67 for (sal_uInt16 n
= aRanges
[i
]; n
<= aRanges
[i
+1]; ++n
)
76 /// Converts a list of elements to a ranges array.
77 /// @param rElements Vector of the initial elements, this need not be sorted,
78 /// and may contain duplicate items. The vector is sorted
79 /// on exit from this function but may still contain duplicates.
80 /// @returns An array containing zero or more range specifications and
81 /// terminated by one or more zero entries. A range specification
82 /// is two consecutive entries that specify the start and end
83 /// points of the range. This list will be sorted and will not
84 /// contain any overlapping ranges.
85 static sal_uInt16
* lcl_convertListToRanges(std::vector
<sal_uInt16
> &rElements
) {
86 std::sort(rElements
.begin(), rElements
.end());
87 std::vector
<sal_uInt16
> aRanges
;
89 for (i
= 0; i
< rElements
.size(); ++i
)
91 //Push the start of the this range.
92 aRanges
.push_back(rElements
[i
]);
93 //Seek to the end of this range.
94 while (i
+ 1 < rElements
.size() && rElements
[i
+1] - rElements
[i
] <= 1)
98 //Push the end of this range (may be the same as the start).
99 aRanges
.push_back( rElements
[i
] );
102 // Convert the vector to an array with terminating zero
103 sal_uInt16
*pNewRanges
= new sal_uInt16
[aRanges
.size() + 1];
104 for (i
= 0; i
< aRanges
.size(); ++i
)
106 pNewRanges
[i
] = aRanges
[i
];
116 SwAbstractDialogFactory
* GetFactory();
119 static PopupMenu
*pMenu
;
120 static long lUserW
= 5669; // 10 cm
121 static long lUserH
= 5669; // 10 cm
123 SwEnvFmtPage::SwEnvFmtPage(Window
* pParent
, const SfxItemSet
& rSet
) :
125 SfxTabPage(pParent
, SW_RES(TP_ENV_FMT
), rSet
),
127 aAddrFL (this, SW_RES( FL_ADDRESSEE
)),
128 aAddrPosInfo (this, SW_RES( TXT_ADDR_POS
)),
129 aAddrLeftText (this, SW_RES( TXT_ADDR_LEFT
)),
130 aAddrLeftField (this, SW_RES( FLD_ADDR_LEFT
)),
131 aAddrTopText (this, SW_RES( TXT_ADDR_TOP
)),
132 aAddrTopField (this, SW_RES( FLD_ADDR_TOP
)),
133 aAddrFormatInfo (this, SW_RES( TXT_ADDR_FORMAT
)),
134 aAddrEditButton (this, SW_RES( BTN_ADDR_EDIT
)),
135 aSendFL (this, SW_RES( FL_SENDER
)),
136 aSendPosInfo (this, SW_RES( TXT_SEND_POS
)),
137 aSendLeftText (this, SW_RES( TXT_SEND_LEFT
)),
138 aSendLeftField (this, SW_RES( FLD_SEND_LEFT
)),
139 aSendTopText (this, SW_RES( TXT_SEND_TOP
)),
140 aSendTopField (this, SW_RES( FLD_SEND_TOP
)),
141 aSendFormatInfo (this, SW_RES( TXT_SEND_FORMAT
)),
142 aSendEditButton (this, SW_RES( BTN_SEND_EDIT
)),
143 aSizeFL (this, SW_RES( FL_SIZE
)),
144 aSizeFormatText (this, SW_RES( TXT_SIZE_FORMAT
)),
145 aSizeFormatBox (this, SW_RES( BOX_SIZE_FORMAT
)),
146 aSizeWidthText (this, SW_RES( TXT_SIZE_WIDTH
)),
147 aSizeWidthField (this, SW_RES( FLD_SIZE_WIDTH
)),
148 aSizeHeightText (this, SW_RES( TXT_SIZE_HEIGHT
)),
149 aSizeHeightField (this, SW_RES( FLD_SIZE_HEIGHT
)),
150 aPreview (this, SW_RES( WIN_PREVIEW
))
154 SetExchangeSupport();
157 FieldUnit aMetric
= ::GetDfltMetric(sal_False
);
158 SetMetric(aAddrLeftField
, aMetric
);
159 SetMetric(aAddrTopField
, aMetric
);
160 SetMetric(aSendLeftField
, aMetric
);
161 SetMetric(aSendTopField
, aMetric
);
162 SetMetric(aSizeWidthField
, aMetric
);
163 SetMetric(aSizeHeightField
, aMetric
);
166 ::pMenu
= new PopupMenu(SW_RES(MNU_EDIT
));
167 aAddrEditButton
.SetPopupMenu(::pMenu
);
168 aSendEditButton
.SetPopupMenu(::pMenu
);
171 Link aLk
= LINK(this, SwEnvFmtPage
, ModifyHdl
);
172 aAddrLeftField
.SetUpHdl( aLk
);
173 aAddrTopField
.SetUpHdl( aLk
);
174 aSendLeftField
.SetUpHdl( aLk
);
175 aSendTopField
.SetUpHdl( aLk
);
176 aSizeWidthField
.SetUpHdl( aLk
);
177 aSizeHeightField
.SetUpHdl( aLk
);
179 aAddrLeftField
.SetDownHdl( aLk
);
180 aAddrTopField
.SetDownHdl( aLk
);
181 aSendLeftField
.SetDownHdl( aLk
);
182 aSendTopField
.SetDownHdl( aLk
);
183 aSizeWidthField
.SetDownHdl( aLk
);
184 aSizeHeightField
.SetDownHdl( aLk
);
186 aAddrLeftField
.SetLoseFocusHdl( aLk
);
187 aAddrTopField
.SetLoseFocusHdl( aLk
);
188 aSendLeftField
.SetLoseFocusHdl( aLk
);
189 aSendTopField
.SetLoseFocusHdl( aLk
);
190 aSizeWidthField
.SetLoseFocusHdl( aLk
);
191 aSizeHeightField
.SetLoseFocusHdl( aLk
);
193 aLk
= LINK(this, SwEnvFmtPage
, EditHdl
);
194 aAddrEditButton
.SetSelectHdl( aLk
);
195 aSendEditButton
.SetSelectHdl( aLk
);
197 aPreview
.SetBorderStyle( WINDOW_BORDER_MONO
);
199 aSizeFormatBox
.SetSelectHdl(LINK(this, SwEnvFmtPage
, FormatHdl
));
202 for (sal_uInt16 i
= PAPER_A3
; i
<= PAPER_KAI32BIG
; i
++)
206 String aPaperName
= SvxPaperInfo::GetName((Paper
) i
),
211 while (nPos
< aSizeFormatBox
.GetEntryCount() && !bFound
)
213 aEntryName
= aSizeFormatBox
.GetEntry(i
);
214 if (aEntryName
< aPaperName
)
219 aSizeFormatBox
.InsertEntry(aPaperName
, nPos
);
220 aIDs
.insert( aIDs
.begin() + nPos
, (sal_uInt16
) i
);
223 aSizeFormatBox
.InsertEntry(SvxPaperInfo::GetName(PAPER_USER
));
224 aIDs
.push_back( (sal_uInt16
) PAPER_USER
);
228 SwEnvFmtPage::~SwEnvFmtPage()
230 aAddrEditButton
.SetPopupMenu(0);
231 aSendEditButton
.SetPopupMenu(0);
235 IMPL_LINK_INLINE_START( SwEnvFmtPage
, ModifyHdl
, Edit
*, pEdit
)
237 long lWVal
= static_cast< long >(GetFldVal(aSizeWidthField
));
238 long lHVal
= static_cast< long >(GetFldVal(aSizeHeightField
));
240 long lWidth
= std::max(lWVal
, lHVal
);
241 long lHeight
= std::min(lWVal
, lHVal
);
243 if (pEdit
== &aSizeWidthField
|| pEdit
== &aSizeHeightField
)
245 Paper ePaper
= SvxPaperInfo::GetSvxPaper(
246 Size(lHeight
, lWidth
), MAP_TWIP
, sal_True
);
247 for (sal_uInt16 i
= 0; i
< (sal_uInt16
)aIDs
.size(); i
++)
248 if (aIDs
[i
] == (sal_uInt16
)ePaper
)
249 aSizeFormatBox
.SelectEntryPos(i
);
251 // remember user size
252 if (aIDs
[aSizeFormatBox
.GetSelectEntryPos()] == (sal_uInt16
)PAPER_USER
)
258 aSizeFormatBox
.GetSelectHdl().Call(&aSizeFormatBox
);
262 FillItem(GetParentSwEnvDlg()->aEnvItem
);
264 aPreview
.Invalidate();
268 IMPL_LINK_INLINE_END( SwEnvFmtPage
, ModifyHdl
, Edit
*, pEdit
)
270 IMPL_LINK( SwEnvFmtPage
, EditHdl
, MenuButton
*, pButton
)
272 SwWrtShell
* pSh
= GetParentSwEnvDlg()->pSh
;
273 OSL_ENSURE(pSh
, "Shell missing");
275 // determine collection-ptr
276 bool bSender
= pButton
!= &aAddrEditButton
;
278 SwTxtFmtColl
* pColl
= pSh
->GetTxtCollFromPool( static_cast< sal_uInt16
>(
279 bSender
? RES_POOLCOLL_SENDADRESS
: RES_POOLCOLL_JAKETADRESS
));
280 OSL_ENSURE(pColl
, "Text collection missing");
282 switch (pButton
->GetCurItemId())
286 SfxItemSet
*pCollSet
= GetCollItemSet(pColl
, bSender
);
288 // In order for the background color not to get ironed over:
289 SfxAllItemSet
aTmpSet(*pCollSet
);
291 // The CHRATR_BACKGROUND attribute gets transformed into a
292 // RES_BACKGROUND for the dialog and back again ...
293 const SfxPoolItem
*pTmpBrush
;
295 if( SFX_ITEM_SET
== aTmpSet
.GetItemState( RES_CHRATR_BACKGROUND
,
296 sal_True
, &pTmpBrush
) )
298 SvxBrushItem
aTmpBrush( *((SvxBrushItem
*)pTmpBrush
) );
299 aTmpBrush
.SetWhich( RES_BACKGROUND
);
300 aTmpSet
.Put( aTmpBrush
);
303 aTmpSet
.ClearItem( RES_BACKGROUND
);
305 SwAbstractDialogFactory
* pFact
= swui::GetFactory();
306 OSL_ENSURE(pFact
, "SwAbstractDialogFactory fail!");
308 SfxAbstractTabDialog
* pDlg
= pFact
->CreateSwCharDlg(GetParentSwEnvDlg(), pSh
->GetView(), aTmpSet
, &pColl
->GetName());
309 OSL_ENSURE(pDlg
, "Dialogdiet fail!");
310 if (pDlg
->Execute() == RET_OK
)
312 SfxItemSet
aOutputSet( *pDlg
->GetOutputItemSet() );
313 if( SFX_ITEM_SET
== aOutputSet
.GetItemState( RES_BACKGROUND
,
314 sal_False
, &pTmpBrush
) )
316 SvxBrushItem
aTmpBrush( *((SvxBrushItem
*)pTmpBrush
) );
317 aTmpBrush
.SetWhich( RES_CHRATR_BACKGROUND
);
318 pCollSet
->Put( aTmpBrush
);
320 aOutputSet
.ClearItem( RES_BACKGROUND
);
321 //pColl->SetAttr( aTmpSet );
322 pCollSet
->Put(aOutputSet
);
330 SfxItemSet
*pCollSet
= GetCollItemSet(pColl
, bSender
);
332 // In order for the tabulators not to get ironed over:
333 SfxAllItemSet
aTmpSet(*pCollSet
);
335 // Insert tabs, default tabs into ItemSet
336 const SvxTabStopItem
& rDefTabs
= (const SvxTabStopItem
&)
337 pSh
->GetView().GetCurShell()->GetPool().GetDefaultItem(RES_PARATR_TABSTOP
);
339 sal_uInt16 nDefDist
= ::GetTabDist( rDefTabs
);
340 SfxUInt16Item
aDefDistItem( SID_ATTR_TABSTOP_DEFAULTS
, nDefDist
);
341 aTmpSet
.Put( aDefDistItem
);
344 SfxUInt16Item
aTabPos( SID_ATTR_TABSTOP_POS
, 0 );
345 aTmpSet
.Put( aTabPos
);
347 // left border as offset
348 const long nOff
= ((SvxLRSpaceItem
&)aTmpSet
.Get( RES_LR_SPACE
)).
350 SfxInt32Item
aOff( SID_ATTR_TABSTOP_OFFSET
, nOff
);
354 ::PrepareBoxInfo( aTmpSet
, *pSh
);
356 SwParaDlg
*pDlg
= new SwParaDlg(GetParentSwEnvDlg(), pSh
->GetView(), aTmpSet
, DLG_ENVELOP
, &pColl
->GetName());
358 if ( pDlg
->Execute() == RET_OK
)
360 // maybe relocate defaults
361 const SfxPoolItem
* pItem
= 0;
362 SfxItemSet
* pOutputSet
= (SfxItemSet
*)pDlg
->GetOutputItemSet();
365 if( SFX_ITEM_SET
== pOutputSet
->GetItemState( SID_ATTR_TABSTOP_DEFAULTS
,
366 sal_False
, &pItem
) &&
367 nDefDist
!= (nNewDist
= ((SfxUInt16Item
*)pItem
)->GetValue()) )
369 SvxTabStopItem
aDefTabs( 0, 0, SVX_TAB_ADJUST_DEFAULT
, RES_PARATR_TABSTOP
);
370 MakeDefTabs( nNewDist
, aDefTabs
);
371 pSh
->SetDefault( aDefTabs
);
372 pOutputSet
->ClearItem( SID_ATTR_TABSTOP_DEFAULTS
);
374 if( pOutputSet
->Count() )
376 pCollSet
->Put(*pOutputSet
);
386 /*------------------------------------------------------------------------
387 Description: A temporary Itemset that gets discarded at abort
388 ------------------------------------------------------------------------*/
390 SfxItemSet
*SwEnvFmtPage::GetCollItemSet(SwTxtFmtColl
* pColl
, bool bSender
)
392 SfxItemSet
*&pAddrSet
= bSender
? GetParentSwEnvDlg()->pSenderSet
: GetParentSwEnvDlg()->pAddresseeSet
;
396 // determine range (merge both Itemsets' ranges)
397 const sal_uInt16
*pRanges
= pColl
->GetAttrSet().GetRanges();
399 static sal_uInt16
const aRanges
[] =
401 RES_PARATR_BEGIN
, RES_PARATR_ADJUST
,
402 RES_PARATR_TABSTOP
, RES_PARATR_END
-1,
403 RES_LR_SPACE
, RES_UL_SPACE
,
404 RES_BACKGROUND
, RES_SHADOW
,
405 SID_ATTR_TABSTOP_POS
, SID_ATTR_TABSTOP_POS
,
406 SID_ATTR_TABSTOP_DEFAULTS
, SID_ATTR_TABSTOP_DEFAULTS
,
407 SID_ATTR_TABSTOP_OFFSET
, SID_ATTR_TABSTOP_OFFSET
,
408 SID_ATTR_BORDER_INNER
, SID_ATTR_BORDER_INNER
,
412 // BruteForce merge because MergeRange in SvTools is buggy:
413 std::vector
<sal_uInt16
> pVec
= ::lcl_convertRangesToList(pRanges
);
414 std::vector
<sal_uInt16
> aVec
= ::lcl_convertRangesToList(aRanges
);
415 pVec
.insert(pVec
.end(), aVec
.begin(), aVec
.end());
416 sal_uInt16
*pNewRanges
= ::lcl_convertListToRanges(pVec
);
418 pAddrSet
= new SfxItemSet(GetParentSwEnvDlg()->pSh
->GetView().GetCurShell()->GetPool(),
420 pAddrSet
->Put(pColl
->GetAttrSet());
427 IMPL_LINK_NOARG(SwEnvFmtPage
, FormatHdl
)
436 sal_uInt16 nPaper
= aIDs
[aSizeFormatBox
.GetSelectEntryPos()];
437 if (nPaper
!= (sal_uInt16
)PAPER_USER
)
439 Size aSz
= SvxPaperInfo::GetPaperSize((Paper
)nPaper
);
440 lWidth
= std::max(aSz
.Width(), aSz
.Height());
441 lHeight
= std::min(aSz
.Width(), aSz
.Height());
449 lSendFromLeft
= 566; // 1cm
450 lSendFromTop
= 566; // 1cm
451 lAddrFromLeft
= lWidth
/ 2;
452 lAddrFromTop
= lHeight
/ 2;
454 SetFldVal(aAddrLeftField
, lAddrFromLeft
);
455 SetFldVal(aAddrTopField
, lAddrFromTop
);
456 SetFldVal(aSendLeftField
, lSendFromLeft
);
457 SetFldVal(aSendTopField
, lSendFromTop
);
459 SetFldVal(aSizeWidthField
, lWidth
);
460 SetFldVal(aSizeHeightField
, lHeight
);
464 FillItem(GetParentSwEnvDlg()->aEnvItem
);
465 aPreview
.Invalidate();
469 void SwEnvFmtPage::SetMinMax()
471 long lWVal
= static_cast< long >(GetFldVal(aSizeWidthField
));
472 long lHVal
= static_cast< long >(GetFldVal(aSizeHeightField
));
474 long lWidth
= std::max(lWVal
, lHVal
),
475 lHeight
= std::min(lWVal
, lHVal
);
478 aAddrLeftField
.SetMin((long) 100 * (GetFldVal(aSendLeftField
) + 566), FUNIT_TWIP
);
479 aAddrLeftField
.SetMax((long) 100 * (lWidth
- 2 * 566), FUNIT_TWIP
);
480 aAddrTopField
.SetMin((long) 100 * (GetFldVal(aSendTopField
) + 2 * 566), FUNIT_TWIP
);
481 aAddrTopField
.SetMax((long) 100 * (lHeight
- 2 * 566), FUNIT_TWIP
);
482 aSendLeftField
.SetMin((long) 100 * (566), FUNIT_TWIP
);
483 aSendLeftField
.SetMax((long) 100 * (GetFldVal(aAddrLeftField
) - 566), FUNIT_TWIP
);
484 aSendTopField
.SetMin((long) 100 * (566), FUNIT_TWIP
);
485 aSendTopField
.SetMax((long) 100 * (GetFldVal(aAddrTopField
) - 2 * 566), FUNIT_TWIP
);
488 aAddrLeftField
.SetFirst(aAddrLeftField
.GetMin());
489 aAddrLeftField
.SetLast (aAddrLeftField
.GetMax());
490 aAddrTopField
.SetFirst(aAddrTopField
.GetMin());
491 aAddrTopField
.SetLast (aAddrTopField
.GetMax());
492 aSendLeftField
.SetFirst(aSendLeftField
.GetMin());
493 aSendLeftField
.SetLast (aSendLeftField
.GetMax());
494 aSendTopField
.SetFirst(aSendTopField
.GetMin());
495 aSendTopField
.SetLast (aSendTopField
.GetMax());
498 aAddrLeftField
.Reformat();
499 aAddrTopField
.Reformat();
500 aSendLeftField
.Reformat();
501 aSendTopField
.Reformat();
502 aSizeWidthField
.Reformat();
503 aSizeHeightField
.Reformat();
506 SfxTabPage
* SwEnvFmtPage::Create(Window
* pParent
, const SfxItemSet
& rSet
)
508 return new SwEnvFmtPage(pParent
, rSet
);
511 void SwEnvFmtPage::ActivatePage(const SfxItemSet
& rSet
)
513 SfxItemSet
aSet(rSet
);
514 aSet
.Put(GetParentSwEnvDlg()->aEnvItem
);
518 int SwEnvFmtPage::DeactivatePage(SfxItemSet
* _pSet
)
522 return SfxTabPage::LEAVE_PAGE
;
525 void SwEnvFmtPage::FillItem(SwEnvItem
& rItem
)
527 rItem
.lAddrFromLeft
= static_cast< sal_Int32
>(GetFldVal(aAddrLeftField
));
528 rItem
.lAddrFromTop
= static_cast< sal_Int32
>(GetFldVal(aAddrTopField
));
529 rItem
.lSendFromLeft
= static_cast< sal_Int32
>(GetFldVal(aSendLeftField
));
530 rItem
.lSendFromTop
= static_cast< sal_Int32
>(GetFldVal(aSendTopField
));
532 sal_uInt16 nPaper
= aIDs
[aSizeFormatBox
.GetSelectEntryPos()];
533 if (nPaper
== (sal_uInt16
)PAPER_USER
)
535 long lWVal
= static_cast< long >(GetFldVal(aSizeWidthField
));
536 long lHVal
= static_cast< long >(GetFldVal(aSizeHeightField
));
537 rItem
.lWidth
= std::max(lWVal
, lHVal
);
538 rItem
.lHeight
= std::min(lWVal
, lHVal
);
542 long lWVal
= SvxPaperInfo::GetPaperSize((Paper
)nPaper
).Width ();
543 long lHVal
= SvxPaperInfo::GetPaperSize((Paper
)nPaper
).Height();
544 rItem
.lWidth
= std::max(lWVal
, lHVal
);
545 rItem
.lHeight
= std::min(lWVal
, lHVal
);
549 sal_Bool
SwEnvFmtPage::FillItemSet(SfxItemSet
& rSet
)
551 FillItem(GetParentSwEnvDlg()->aEnvItem
);
552 rSet
.Put(GetParentSwEnvDlg()->aEnvItem
);
556 void SwEnvFmtPage::Reset(const SfxItemSet
& rSet
)
558 const SwEnvItem
& rItem
= (const SwEnvItem
&) rSet
.Get(FN_ENVELOP
);
560 Paper ePaper
= SvxPaperInfo::GetSvxPaper(
561 Size( std::min(rItem
.lWidth
, rItem
.lHeight
),
562 std::max(rItem
.lWidth
, rItem
.lHeight
)), MAP_TWIP
, sal_True
);
563 for (sal_uInt16 i
= 0; i
< (sal_uInt16
) aIDs
.size(); i
++)
564 if (aIDs
[i
] == (sal_uInt16
)ePaper
)
565 aSizeFormatBox
.SelectEntryPos(i
);
568 SetFldVal(aAddrLeftField
, rItem
.lAddrFromLeft
);
569 SetFldVal(aAddrTopField
, rItem
.lAddrFromTop
);
570 SetFldVal(aSendLeftField
, rItem
.lSendFromLeft
);
571 SetFldVal(aSendTopField
, rItem
.lSendFromTop
);
572 SetFldVal(aSizeWidthField
, std::max(rItem
.lWidth
, rItem
.lHeight
));
573 SetFldVal(aSizeHeightField
, std::min(rItem
.lWidth
, rItem
.lHeight
));
576 DELETEZ(GetParentSwEnvDlg()->pSenderSet
);
577 DELETEZ(GetParentSwEnvDlg()->pAddresseeSet
);
582 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */