bump product version to 4.1.6.2
[LibreOffice.git] / sw / source / ui / index / cntex.cxx
blob33c1d906f2b83385436cfaafeab4947c8d99098c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 <rsc/rscsfx.hxx>
21 #include <com/sun/star/text/XDocumentIndexesSupplier.hpp>
22 #include <com/sun/star/text/XTextSectionsSupplier.hpp>
23 #include <com/sun/star/style/BreakType.hpp>
24 #include <com/sun/star/text/XTextFieldsSupplier.hpp>
25 #include <com/sun/star/text/XDependentTextField.hpp>
26 #include <com/sun/star/text/XParagraphCursor.hpp>
27 #include <com/sun/star/text/XDocumentIndex.hpp>
28 #include <com/sun/star/text/ChapterFormat.hpp>
29 #include <com/sun/star/text/XTextSection.hpp>
30 #include <com/sun/star/text/ControlCharacter.hpp>
31 #include <com/sun/star/beans/PropertyValues.hpp>
32 #include <com/sun/star/text/TextContentAnchorType.hpp>
33 #include <com/sun/star/ucb/XCommandEnvironment.hpp>
34 #include <com/sun/star/ui/dialogs/XFilePicker.hpp>
35 #include <com/sun/star/ui/dialogs/XFilterManager.hpp>
36 #include <comphelper/string.hxx>
37 #include <wrtsh.hxx>
38 #include <view.hxx>
39 #include <cnttab.hxx>
40 #include <poolfmt.hxx>
41 #include <unoprnms.hxx>
42 #include <unotools.hxx>
43 #include <unotxdoc.hxx>
44 #include <docsh.hxx>
45 #include <swmodule.hxx>
47 #include <cmdid.h>
48 #include <utlui.hrc>
49 #include <index.hrc>
50 #include <cnttab.hrc>
51 #include <globals.hrc>
52 #include <SwStyleNameMapper.hxx>
53 #include <swuicnttab.hxx>
54 #include <unomid.h>
57 using namespace ::com::sun::star;
58 using namespace ::com::sun::star::text;
59 using namespace ::com::sun::star::beans;
60 using namespace ::com::sun::star::container;
61 using namespace ::com::sun::star::lang;
62 using namespace ::com::sun::star::ucb;
63 using namespace ::com::sun::star::uno;
64 using namespace com::sun::star::ui::dialogs;
67 #ifdef SW_PROP_NAME_STR
68 #undef SW_PROP_NAME_STR
69 #endif
70 #define SW_PROP_NAME_STR(nId) SwGetPropName((nId)).pName
72 static void lcl_SetProp( uno::Reference< XPropertySetInfo > & xInfo,
73 uno::Reference< XPropertySet > & xProps,
74 const char* pPropName, const String& rValue)
76 OUString uPropName(OUString::createFromAscii(pPropName));
77 if(xInfo->hasPropertyByName(uPropName))
79 uno::Any aValue;
80 aValue <<= OUString(rValue);
81 xProps->setPropertyValue(uPropName, aValue);
85 static void lcl_SetProp( uno::Reference< XPropertySetInfo > & xInfo,
86 uno::Reference< XPropertySet > & xProps,
87 sal_uInt16 nId, const String& rValue)
89 lcl_SetProp( xInfo, xProps, SW_PROP_NAME_STR(nId), rValue);
92 static void lcl_SetProp( uno::Reference< XPropertySetInfo > & xInfo,
93 uno::Reference< XPropertySet > & xProps,
94 sal_uInt16 nId, sal_Int16 nValue )
96 OUString uPropName(OUString::createFromAscii(SW_PROP_NAME_STR(nId)));
97 if(xInfo->hasPropertyByName(uPropName))
99 uno::Any aValue;
100 aValue <<= nValue;
101 xProps->setPropertyValue(uPropName, aValue);
105 static void lcl_SetBOOLProp(
106 uno::Reference< beans::XPropertySetInfo > & xInfo,
107 uno::Reference< beans::XPropertySet > & xProps,
108 sal_uInt16 nId, sal_Bool bValue )
110 OUString uPropName(OUString::createFromAscii(SW_PROP_NAME_STR(nId)));
111 if(xInfo->hasPropertyByName(uPropName))
113 uno::Any aValue;
114 aValue.setValue(&bValue, ::getCppuBooleanType());
115 xProps->setPropertyValue(uPropName, aValue);
119 IMPL_LINK_NOARG(SwMultiTOXTabDialog, CreateExample_Hdl)
123 uno::Reference< frame::XModel > & xModel = pExampleFrame->GetModel();
124 uno::Reference< lang::XUnoTunnel > xDocTunnel(xModel, uno::UNO_QUERY);
125 SwXTextDocument* pDoc = reinterpret_cast<SwXTextDocument*>(xDocTunnel->getSomething(SwXTextDocument::getUnoTunnelId()));
127 if( pDoc )
128 pDoc->GetDocShell()->_LoadStyles( *rSh.GetView().GetDocShell(), sal_True );
130 uno::Reference< lang::XMultiServiceFactory > xFact(
131 xModel, uno::UNO_QUERY);
133 uno::Reference< text::XTextSectionsSupplier > xSectionSupplier(
134 xModel, uno::UNO_QUERY);
135 uno::Reference< container::XNameAccess > xSections =
136 xSectionSupplier->getTextSections();
138 OUString sSectionName("IndexSection_");
139 for(int i = 0; i < 7; ++i )
141 String sTmp( sSectionName ); sTmp += OUString::number(i);
142 uno::Any aSection = xSections->getByName( sTmp );
143 aSection >>= pxIndexSectionsArr[i]->xContainerSection;
145 uno::Reference< text::XDocumentIndexesSupplier > xIdxSupp(xModel, uno::UNO_QUERY);
146 uno::Reference< container::XIndexAccess > xIdxs = xIdxSupp->getDocumentIndexes();
147 int n = xIdxs->getCount();
148 while(n)
150 n--;
151 uno::Any aIdx = xIdxs->getByIndex(n);
152 uno::Reference< text::XDocumentIndex > xIdx;
153 aIdx >>= xIdx;
154 xIdx->dispose();
156 CreateOrUpdateExample(eCurrentTOXType.eType);
158 catch (const Exception&)
160 OSL_FAIL("::CreateExample() - exception caught");
162 return 0;
165 void SwMultiTOXTabDialog::CreateOrUpdateExample(
166 TOXTypes nTOXIndex, sal_uInt16 nPage, sal_uInt16 nCurrentLevel)
168 if(!pExampleFrame || !pExampleFrame->IsInitialized())
169 return;
171 const char* IndexServiceNames[] =
173 "com.sun.star.text.DocumentIndex",
174 "com.sun.star.text.UserIndex",
175 "com.sun.star.text.ContentIndex",
176 "com.sun.star.text.IllustrationsIndex",
177 "com.sun.star.text.ObjectIndex",
178 "com.sun.star.text.TableIndex",
179 "com.sun.star.text.Bibliography"
184 OSL_ENSURE(pxIndexSectionsArr[nTOXIndex] &&
185 pxIndexSectionsArr[nTOXIndex]->xContainerSection.is(),
186 "Section not created");
187 uno::Reference< frame::XModel > & xModel = pExampleFrame->GetModel();
188 sal_Bool bInitialCreate = sal_True;
189 if(!pxIndexSectionsArr[nTOXIndex]->xDocumentIndex.is())
191 bInitialCreate = sal_True;
192 if(!pxIndexSectionsArr[nTOXIndex]->xContainerSection.is())
193 throw uno::RuntimeException();
194 uno::Reference< text::XTextRange > xAnchor = pxIndexSectionsArr[nTOXIndex]->xContainerSection->getAnchor();
195 xAnchor = xAnchor->getStart();
196 uno::Reference< text::XTextCursor > xCrsr = xAnchor->getText()->createTextCursorByRange(xAnchor);
198 uno::Reference< lang::XMultiServiceFactory > xFact(xModel, uno::UNO_QUERY);
200 String sIndexTypeName;
201 sIndexTypeName.AssignAscii( IndexServiceNames[
202 nTOXIndex <= TOX_AUTHORITIES ? nTOXIndex : TOX_USER] );
203 pxIndexSectionsArr[nTOXIndex]->xDocumentIndex = uno::Reference< text::XDocumentIndex > (xFact->createInstance(
204 sIndexTypeName), uno::UNO_QUERY);
205 uno::Reference< text::XTextContent > xContent(pxIndexSectionsArr[nTOXIndex]->xDocumentIndex, uno::UNO_QUERY);
206 uno::Reference< text::XTextRange > xRg(xCrsr, uno::UNO_QUERY);
207 xCrsr->getText()->insertTextContent(xRg, xContent, sal_False);
209 OUString uIsVisible(OUString::createFromAscii(SW_PROP_NAME_STR(UNO_NAME_IS_VISIBLE)));
210 for(sal_uInt16 i = 0 ; i <= TOX_AUTHORITIES; i++)
212 uno::Reference< beans::XPropertySet > xSectPr(pxIndexSectionsArr[i]->xContainerSection, uno::UNO_QUERY);
213 uno::Any aVal;
215 if(xSectPr.is())
217 sal_Bool bTemp = i == nTOXIndex;
218 aVal.setValue(&bTemp, ::getBooleanCppuType());
219 xSectPr->setPropertyValue(uIsVisible, aVal);
222 // set properties
223 uno::Reference< beans::XPropertySet > xIdxProps(pxIndexSectionsArr[nTOXIndex]->xDocumentIndex, uno::UNO_QUERY);
224 uno::Reference< beans::XPropertySetInfo > xInfo = xIdxProps->getPropertySetInfo();
225 SwTOXDescription& rDesc = GetTOXDescription(eCurrentTOXType);
226 sal_uInt16 nIdxOptions = rDesc.GetIndexOptions();
227 if(bInitialCreate || !nPage || nPage == TOX_PAGE_SELECT)
229 //title
230 if(rDesc.GetTitle())
231 lcl_SetProp(xInfo, xIdxProps, UNO_NAME_TITLE, *rDesc.GetTitle());
233 //stylenames
234 sal_uInt16 nContentOptions = rDesc.GetContentOptions();
235 if(xInfo->hasPropertyByName(OUString::createFromAscii(SW_PROP_NAME_STR(UNO_NAME_LEVEL_PARAGRAPH_STYLES))))
237 sal_Bool bOn = 0!=(nContentOptions&nsSwTOXElement::TOX_TEMPLATE );
238 uno::Any aStyleNames(xIdxProps->getPropertyValue(OUString::createFromAscii(SW_PROP_NAME_STR(UNO_NAME_LEVEL_PARAGRAPH_STYLES))));
239 uno::Reference< container::XIndexReplace > xAcc;
240 aStyleNames >>= xAcc;
242 for(sal_uInt16 i = 0; i < MAXLEVEL; i++)
244 String sLevel;
245 if(bOn)
246 sLevel = rDesc.GetStyleNames(i);
247 sal_uInt16 nStyles = comphelper::string::getTokenCount(sLevel, TOX_STYLE_DELIMITER);
248 uno::Sequence<OUString> aStyles(nStyles);
249 OUString* pArr = aStyles.getArray();
250 for(sal_uInt16 nStyle = 0; nStyle < nStyles; nStyle++)
251 pArr[nStyle] = sLevel.GetToken(nStyle, TOX_STYLE_DELIMITER);
252 uno::Any aAny(&aStyles, ::getCppuType((uno::Sequence<OUString>*)0));
253 xAcc->replaceByIndex(i, aAny);
256 lcl_SetProp(xInfo, xIdxProps, UNO_NAME_LEVEL, (sal_Int16)rDesc.GetLevel());
257 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_CREATE_FROM_MARKS, 0!=(nContentOptions&nsSwTOXElement::TOX_MARK ));
258 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_CREATE_FROM_OUTLINE, 0!=(nContentOptions&nsSwTOXElement::TOX_OUTLINELEVEL));
259 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_CREATE_FROM_EMBEDDED_OBJECTS,0!=(nContentOptions&nsSwTOXElement::TOX_OLE ));
260 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_CREATE_FROM_TABLES , 0!=(nContentOptions&nsSwTOXElement::TOX_TABLE ));
261 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_CREATE_FROM_GRAPHIC_OBJECTS, 0!=(nContentOptions&nsSwTOXElement::TOX_GRAPHIC ));
262 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_CREATE_FROM_TEXT_FRAMES, 0!=(nContentOptions&nsSwTOXElement::TOX_FRAME ));
263 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_CREATE_FROM_LABELS, 0!=(nContentOptions&nsSwTOXElement::TOX_SEQUENCE ));
265 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_CREATE_FROM_CHAPTER, rDesc.IsFromChapter());
266 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_IS_PROTECTED, rDesc.IsReadonly());
268 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_USE_COMBINED_ENTRIES, 0 != (nIdxOptions&nsSwTOIOptions::TOI_SAME_ENTRY ));
269 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_USE_P_P, 0 != (nIdxOptions&nsSwTOIOptions::TOI_FF ));
270 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_IS_CASE_SENSITIVE, 0 != (nIdxOptions&nsSwTOIOptions::TOI_CASE_SENSITIVE ));
271 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_USE_KEY_AS_ENTRY, 0 != (nIdxOptions&nsSwTOIOptions::TOI_KEY_AS_ENTRY ));
272 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_USE_ALPHABETICAL_SEPARATORS, 0 != (nIdxOptions&nsSwTOIOptions::TOI_ALPHA_DELIMITTER));
273 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_USE_DASH, 0 != (nIdxOptions&nsSwTOIOptions::TOI_DASH ));
274 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_USE_UPPER_CASE, 0 != (nIdxOptions&nsSwTOIOptions::TOI_INITIAL_CAPS ));
276 String aTmpName( SwStyleNameMapper::GetSpecialExtraProgName( rDesc.GetSequenceName() ) );
277 lcl_SetProp(xInfo, xIdxProps, UNO_NAME_LABEL_CATEGORY, aTmpName );
278 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_CREATE_FROM_LABELS, !rDesc.IsCreateFromObjectNames());
280 sal_Int16 nSet = text::ChapterFormat::NAME_NUMBER;
281 switch (rDesc.GetCaptionDisplay())
283 case CAPTION_COMPLETE: nSet = text::ChapterFormat::NAME_NUMBER;break;
284 case CAPTION_NUMBER : nSet = text::ChapterFormat::NUMBER; break;
285 case CAPTION_TEXT : nSet = text::ChapterFormat::NAME; break;
287 lcl_SetProp(xInfo, xIdxProps, UNO_NAME_LABEL_DISPLAY_TYPE, nSet);
289 sal_uInt16 nOLEOptions = rDesc.GetOLEOptions();
290 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_CREATE_FROM_STAR_MATH, 0 != (nsSwTOOElements::TOO_MATH &nOLEOptions ));
291 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_CREATE_FROM_STAR_CHART, 0 != (nsSwTOOElements::TOO_CHART &nOLEOptions ));
292 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_CREATE_FROM_STAR_CALC, 0 != (nsSwTOOElements::TOO_CALC &nOLEOptions ));
293 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_CREATE_FROM_STAR_DRAW, 0 != (nsSwTOOElements::TOO_DRAW_IMPRESS&nOLEOptions));
294 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_CREATE_FROM_OTHER_EMBEDDED_OBJECTS, 0 != (nsSwTOOElements::TOO_OTHER & nOLEOptions));
296 const SwForm* pForm = GetForm(eCurrentTOXType);
297 if(bInitialCreate || !nPage || nPage == TOX_PAGE_ENTRY)
299 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_IS_COMMA_SEPARATED, pForm->IsCommaSeparated());
300 lcl_SetBOOLProp(xInfo, xIdxProps, UNO_NAME_USE_ALPHABETICAL_SEPARATORS, 0 != (nIdxOptions&nsSwTOIOptions::TOI_ALPHA_DELIMITTER));
301 sal_uInt16 nStartLevel = USHRT_MAX;
302 sal_uInt16 nEndLevel = USHRT_MAX;
303 if(nCurrentLevel < pForm->GetFormMax())
304 nStartLevel = nEndLevel = nCurrentLevel;
305 else
307 nStartLevel = 0;
308 nEndLevel = pForm->GetFormMax() - 1;
311 if(xInfo->hasPropertyByName(OUString::createFromAscii(SW_PROP_NAME_STR(UNO_NAME_LEVEL_FORMAT))))
313 for(sal_uInt16 nCurrLevel = nStartLevel; nCurrLevel <= nEndLevel; nCurrLevel++)
315 String sTokenType;
316 uno::Sequence< beans::PropertyValues> aSequPropVals(10);
317 long nTokenIndex = 0;
318 long nParamCount = 2;
320 // #i24377#
321 SwFormTokens aPattern = pForm->GetPattern(nCurrLevel);
322 SwFormTokens::iterator aIt = aPattern.begin();
324 while(aIt != aPattern.end())
326 if( aSequPropVals.getLength() <= nTokenIndex)
327 aSequPropVals.realloc(nTokenIndex + 10);
329 SwFormToken aToken = *aIt; // #i24377#
330 switch(aToken.eTokenType)
332 case TOKEN_ENTRY_NO :
333 sTokenType.AssignAscii(RTL_CONSTASCII_STRINGPARAM(
334 "TokenEntryNumber"));
335 // numbering for content index
336 break;
337 case TOKEN_ENTRY_TEXT :
338 case TOKEN_ENTRY :
339 sTokenType.AssignAscii(RTL_CONSTASCII_STRINGPARAM(
340 "TokenEntryText"));
341 break;
342 case TOKEN_TAB_STOP :
343 nParamCount += 3;
344 sTokenType.AssignAscii(RTL_CONSTASCII_STRINGPARAM(
345 "TokenTabStop"));
346 break;
347 case TOKEN_TEXT :
348 sTokenType.AssignAscii(RTL_CONSTASCII_STRINGPARAM(
349 "TokenText"));
350 nParamCount += 1;
351 break;
352 case TOKEN_PAGE_NUMS :
353 sTokenType.AssignAscii(RTL_CONSTASCII_STRINGPARAM(
354 "TokenPageNumber"));
355 break;
356 case TOKEN_CHAPTER_INFO :
357 sTokenType.AssignAscii(RTL_CONSTASCII_STRINGPARAM(
358 "TokenChapterInfo"));
359 break;
360 case TOKEN_LINK_START :
361 sTokenType.AssignAscii(RTL_CONSTASCII_STRINGPARAM(
362 "TokenHyperlinkStart"));
363 break;
364 case TOKEN_LINK_END :
365 sTokenType.AssignAscii(RTL_CONSTASCII_STRINGPARAM(
366 "TokenHyperlinkEnd"));
367 break;
368 case TOKEN_AUTHORITY :
370 sTokenType.AssignAscii(RTL_CONSTASCII_STRINGPARAM(
371 "TokenBibliographyDataField"));
373 break;
374 default:; //prevent warning
376 beans::PropertyValues aPropVals(nParamCount);
377 beans::PropertyValue* pPropValArr = aPropVals.getArray();
378 pPropValArr[0].Name = "TokenType";
379 pPropValArr[0].Value <<= OUString(sTokenType);
380 pPropValArr[1].Name = "CharacterStyleName";
381 pPropValArr[1].Value <<= OUString(aToken.sCharStyleName);
382 if(TOKEN_TAB_STOP == aToken.eTokenType)
384 pPropValArr[2].Name = "TabStopRightAligned";
385 sal_Bool bTemp = SVX_TAB_ADJUST_END == aToken.eTabAlign;
386 pPropValArr[2].Value.setValue(&bTemp, ::getBooleanCppuType());
387 pPropValArr[3].Name = "TabStopFillCharacter";
388 pPropValArr[3].Value <<= OUString(aToken.cTabFillChar);
389 pPropValArr[4].Name = "TabStopPosition";
390 SwTwips nTempPos = aToken.nTabStopPosition >= 0 ?
391 aToken.nTabStopPosition : 0;
392 nTempPos = TWIP_TO_MM100(nTempPos);
393 pPropValArr[4].Value <<= (sal_Int32)nTempPos;
395 else if(TOKEN_TEXT == aToken.eTokenType)
397 pPropValArr[2].Name = "Text";
398 pPropValArr[2].Value <<= OUString(aToken.sText);
400 beans::PropertyValues* pValues = aSequPropVals.getArray();
401 pValues[nTokenIndex] = aPropVals;
402 nTokenIndex++;
404 ++aIt; // #i24377#
406 aSequPropVals.realloc(nTokenIndex);
408 uno::Any aFormatAccess = xIdxProps->getPropertyValue(OUString::createFromAscii(SW_PROP_NAME_STR(UNO_NAME_LEVEL_FORMAT)));
409 OSL_ENSURE(aFormatAccess.getValueType() == ::getCppuType((uno::Reference<container::XIndexReplace>*)0),
410 "wrong property type");
413 uno::Reference< container::XIndexReplace > xFormatAccess;
414 aFormatAccess >>= xFormatAccess;
415 uno::Any aLevelProp(&aSequPropVals, ::getCppuType((uno::Sequence<beans::PropertyValues>*)0));
416 xFormatAccess->replaceByIndex(nCurrLevel, aLevelProp);
420 if(bInitialCreate || !nPage || nPage == TOX_PAGE_STYLES)
422 lcl_SetProp(xInfo, xIdxProps, "ParaStyleHeading", pForm->GetTemplate(0));
423 sal_uInt16 nOffset = 0;
424 sal_uInt16 nEndLevel = 2;
425 switch(eCurrentTOXType.eType)
427 case TOX_INDEX:
429 nOffset = 1;
430 nEndLevel = 4;
431 lcl_SetProp(xInfo, xIdxProps, "ParaStyleSeparator", pForm->GetTemplate(1));
433 break;
434 case TOX_CONTENT :
435 nEndLevel = 11;
436 break;
437 default:; //prevent warning
439 for(sal_uInt16 i = 1; i < nEndLevel; i++)
441 String sPropName(OUString("ParaStyleLevel"));
442 sPropName += OUString::number( i );
443 lcl_SetProp(xInfo,
444 xIdxProps,
445 OUStringToOString(sPropName, RTL_TEXTENCODING_ASCII_US).getStr(),
446 pForm->GetTemplate(i + nOffset));
449 pxIndexSectionsArr[nTOXIndex]->xDocumentIndex->update();
452 catch (const Exception&)
454 OSL_FAIL("::CreateExample() - exception caught");
458 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */