Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
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/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
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 .
18 : */
19 :
20 : #include "groupboxwiz.hxx"
21 : #include "commonpagesdbp.hxx"
22 : #include <tools/debug.hxx>
23 : #include <vcl/svapp.hxx>
24 : #include <vcl/msgbox.hxx>
25 : #include "optiongrouplayouter.hxx"
26 : #include "dbpilots.hrc"
27 : #include <comphelper/processfactory.hxx>
28 :
29 : #define GBW_STATE_OPTIONLIST 0
30 : #define GBW_STATE_DEFAULTOPTION 1
31 : #define GBW_STATE_OPTIONVALUES 2
32 : #define GBW_STATE_DBFIELD 3
33 : #define GBW_STATE_FINALIZE 4
34 :
35 : //.........................................................................
36 : namespace dbp
37 : {
38 : //.........................................................................
39 :
40 : using namespace ::com::sun::star::uno;
41 : using namespace ::com::sun::star::lang;
42 : using namespace ::com::sun::star::beans;
43 : using namespace ::com::sun::star::form;
44 : using namespace ::svt;
45 :
46 : //=====================================================================
47 : //= OGroupBoxWizard
48 : //=====================================================================
49 : //---------------------------------------------------------------------
50 0 : OGroupBoxWizard::OGroupBoxWizard( Window* _pParent,
51 : const Reference< XPropertySet >& _rxObjectModel, const Reference< XComponentContext >& _rxContext )
52 : :OControlWizard(_pParent, ModuleRes(RID_DLG_GROUPBOXWIZARD), _rxObjectModel, _rxContext)
53 : ,m_bVisitedDefault(sal_False)
54 0 : ,m_bVisitedDB(sal_False)
55 : {
56 0 : initControlSettings(&m_aSettings);
57 :
58 0 : m_pPrevPage->SetHelpId(HID_GROUPWIZARD_PREVIOUS);
59 0 : m_pNextPage->SetHelpId(HID_GROUPWIZARD_NEXT);
60 0 : m_pCancel->SetHelpId(HID_GROUPWIZARD_CANCEL);
61 0 : m_pFinish->SetHelpId(HID_GROUPWIZARD_FINISH);
62 0 : }
63 :
64 : //---------------------------------------------------------------------
65 0 : sal_Bool OGroupBoxWizard::approveControl(sal_Int16 _nClassId)
66 : {
67 0 : return FormComponentType::GROUPBOX == _nClassId;
68 : }
69 :
70 : //---------------------------------------------------------------------
71 0 : OWizardPage* OGroupBoxWizard::createPage(::svt::WizardTypes::WizardState _nState)
72 : {
73 0 : switch (_nState)
74 : {
75 : case GBW_STATE_OPTIONLIST:
76 0 : return new ORadioSelectionPage(this);
77 :
78 : case GBW_STATE_DEFAULTOPTION:
79 0 : return new ODefaultFieldSelectionPage(this);
80 :
81 : case GBW_STATE_OPTIONVALUES:
82 0 : return new OOptionValuesPage(this);
83 :
84 : case GBW_STATE_DBFIELD:
85 0 : return new OOptionDBFieldPage(this);
86 :
87 : case GBW_STATE_FINALIZE:
88 0 : return new OFinalizeGBWPage(this);
89 : }
90 :
91 0 : return NULL;
92 : }
93 :
94 : //---------------------------------------------------------------------
95 0 : WizardTypes::WizardState OGroupBoxWizard::determineNextState( ::svt::WizardTypes::WizardState _nCurrentState ) const
96 : {
97 0 : switch (_nCurrentState)
98 : {
99 : case GBW_STATE_OPTIONLIST:
100 0 : return GBW_STATE_DEFAULTOPTION;
101 :
102 : case GBW_STATE_DEFAULTOPTION:
103 0 : return GBW_STATE_OPTIONVALUES;
104 :
105 : case GBW_STATE_OPTIONVALUES:
106 0 : if (getContext().aFieldNames.getLength())
107 0 : return GBW_STATE_DBFIELD;
108 : else
109 0 : return GBW_STATE_FINALIZE;
110 :
111 : case GBW_STATE_DBFIELD:
112 0 : return GBW_STATE_FINALIZE;
113 : }
114 :
115 0 : return WZS_INVALID_STATE;
116 : }
117 :
118 : //---------------------------------------------------------------------
119 0 : void OGroupBoxWizard::enterState(::svt::WizardTypes::WizardState _nState)
120 : {
121 : // some stuff to do before calling the base class (modifying our settings)
122 0 : switch (_nState)
123 : {
124 : case GBW_STATE_DEFAULTOPTION:
125 0 : if (!m_bVisitedDefault)
126 : { // assume that the first of the radio buttons should be selected
127 : DBG_ASSERT(m_aSettings.aLabels.size(), "OGroupBoxWizard::enterState: should never have reached this state!");
128 0 : m_aSettings.sDefaultField = m_aSettings.aLabels[0];
129 : }
130 0 : m_bVisitedDefault = sal_True;
131 0 : break;
132 :
133 : case GBW_STATE_DBFIELD:
134 0 : if (!m_bVisitedDB)
135 : { // try to generate a default for the DB field
136 : // (simply use the first field in the DB names collection)
137 0 : if (getContext().aFieldNames.getLength())
138 0 : m_aSettings.sDBField = getContext().aFieldNames[0];
139 : }
140 0 : m_bVisitedDB = sal_True;
141 0 : break;
142 : }
143 :
144 : // setting the def button .... to be done before the base class is called, too, 'cause the base class
145 : // calls the pages, which are allowed to override our def button behaviour
146 0 : defaultButton(GBW_STATE_FINALIZE == _nState ? WZB_FINISH : WZB_NEXT);
147 :
148 : // allow "finish" on the last page only
149 0 : enableButtons(WZB_FINISH, GBW_STATE_FINALIZE == _nState);
150 : // allow previous on all pages but the first one
151 0 : enableButtons(WZB_PREVIOUS, GBW_STATE_OPTIONLIST != _nState);
152 : // allow next on all pages but the last one
153 0 : enableButtons(WZB_NEXT, GBW_STATE_FINALIZE != _nState);
154 :
155 0 : OControlWizard::enterState(_nState);
156 0 : }
157 :
158 : //---------------------------------------------------------------------
159 0 : void OGroupBoxWizard::createRadios()
160 : {
161 : try
162 : {
163 0 : OOptionGroupLayouter aLayouter( getComponentContext() );
164 0 : aLayouter.doLayout(getContext(), getSettings());
165 : }
166 0 : catch(const Exception&)
167 : {
168 : OSL_FAIL("OGroupBoxWizard::createRadios: caught an exception while creating the radio shapes!");
169 : }
170 0 : }
171 :
172 : //---------------------------------------------------------------------
173 0 : sal_Bool OGroupBoxWizard::onFinish()
174 : {
175 : // commit the basic control setttings
176 0 : commitControlSettings(&m_aSettings);
177 :
178 : // create the radio buttons
179 0 : createRadios();
180 :
181 0 : return OControlWizard::onFinish();
182 : }
183 :
184 : //=====================================================================
185 : //= ORadioSelectionPage
186 : //=====================================================================
187 : //---------------------------------------------------------------------
188 0 : ORadioSelectionPage::ORadioSelectionPage( OControlWizard* _pParent )
189 : :OGBWPage(_pParent, ModuleRes(RID_PAGE_GROUPRADIOSELECTION))
190 : ,m_aFrame (this, ModuleRes(FL_DATA))
191 : ,m_aRadioNameLabel (this, ModuleRes(FT_RADIOLABELS))
192 : ,m_aRadioName (this, ModuleRes(ET_RADIOLABELS))
193 : ,m_aMoveRight (this, ModuleRes(PB_MOVETORIGHT))
194 : ,m_aMoveLeft (this, ModuleRes(PB_MOVETOLEFT))
195 : ,m_aExistingRadiosLabel (this, ModuleRes(FT_RADIOBUTTONS))
196 0 : ,m_aExistingRadios (this, ModuleRes(LB_RADIOBUTTONS))
197 : {
198 0 : FreeResource();
199 :
200 0 : if (getContext().aFieldNames.getLength())
201 : {
202 0 : enableFormDatasourceDisplay();
203 : }
204 : else
205 : {
206 0 : adjustControlForNoDSDisplay(&m_aFrame);
207 0 : adjustControlForNoDSDisplay(&m_aRadioNameLabel);
208 0 : adjustControlForNoDSDisplay(&m_aRadioName);
209 0 : adjustControlForNoDSDisplay(&m_aMoveRight);
210 0 : adjustControlForNoDSDisplay(&m_aMoveLeft);
211 0 : adjustControlForNoDSDisplay(&m_aExistingRadiosLabel);
212 0 : adjustControlForNoDSDisplay(&m_aExistingRadios, sal_True);
213 : }
214 :
215 0 : m_aMoveLeft.SetClickHdl(LINK(this, ORadioSelectionPage, OnMoveEntry));
216 0 : m_aMoveRight.SetClickHdl(LINK(this, ORadioSelectionPage, OnMoveEntry));
217 0 : m_aRadioName.SetModifyHdl(LINK(this, ORadioSelectionPage, OnNameModified));
218 0 : m_aExistingRadios.SetSelectHdl(LINK(this, ORadioSelectionPage, OnEntrySelected));
219 :
220 0 : implCheckMoveButtons();
221 0 : m_aExistingRadios.EnableMultiSelection(sal_True);
222 :
223 0 : getDialog()->defaultButton(&m_aMoveRight);
224 :
225 0 : m_aExistingRadios.SetAccessibleRelationMemberOf(&m_aExistingRadios);
226 0 : m_aExistingRadios.SetAccessibleRelationLabeledBy(&m_aExistingRadiosLabel);
227 0 : }
228 :
229 : //---------------------------------------------------------------------
230 0 : void ORadioSelectionPage::ActivatePage()
231 : {
232 0 : OGBWPage::ActivatePage();
233 0 : m_aRadioName.GrabFocus();
234 0 : }
235 :
236 : //---------------------------------------------------------------------
237 0 : void ORadioSelectionPage::initializePage()
238 : {
239 0 : OGBWPage::initializePage();
240 :
241 0 : m_aRadioName.SetText(String());
242 :
243 : // no need to initialize the list of radios here
244 : // (we're the only one affecting this special setting, so it will be in the same state as last time this
245 : // page was commited)
246 :
247 0 : implCheckMoveButtons();
248 0 : }
249 :
250 : //---------------------------------------------------------------------
251 0 : sal_Bool ORadioSelectionPage::commitPage( ::svt::WizardTypes::CommitPageReason _eReason )
252 : {
253 0 : if (!OGBWPage::commitPage(_eReason))
254 0 : return sal_False;
255 :
256 : // copy the names of the radio buttons to be inserted
257 : // and initialize the values
258 0 : OOptionGroupSettings& rSettings = getSettings();
259 0 : rSettings.aLabels.clear();
260 0 : rSettings.aValues.clear();
261 0 : rSettings.aLabels.reserve(m_aExistingRadios.GetEntryCount());
262 0 : rSettings.aValues.reserve(m_aExistingRadios.GetEntryCount());
263 0 : for (::svt::WizardTypes::WizardState i=0; i<m_aExistingRadios.GetEntryCount(); ++i)
264 : {
265 0 : rSettings.aLabels.push_back(m_aExistingRadios.GetEntry(i));
266 0 : rSettings.aValues.push_back(rtl::OUString::valueOf((sal_Int32)(i + 1)));
267 : }
268 :
269 0 : return sal_True;
270 : }
271 :
272 : //---------------------------------------------------------------------
273 0 : IMPL_LINK( ORadioSelectionPage, OnMoveEntry, PushButton*, _pButton )
274 : {
275 0 : sal_Bool bMoveLeft = (&m_aMoveLeft == _pButton);
276 0 : if (bMoveLeft)
277 : {
278 0 : while (m_aExistingRadios.GetSelectEntryCount())
279 0 : m_aExistingRadios.RemoveEntry(m_aExistingRadios.GetSelectEntryPos(0));
280 : }
281 : else
282 : {
283 0 : m_aExistingRadios.InsertEntry(m_aRadioName.GetText());
284 0 : m_aRadioName.SetText(String());
285 : }
286 :
287 0 : implCheckMoveButtons();
288 :
289 : //adjust the focus
290 0 : if (bMoveLeft)
291 0 : m_aExistingRadios.GrabFocus();
292 : else
293 0 : m_aRadioName.GrabFocus();
294 0 : return 0L;
295 : }
296 :
297 : //---------------------------------------------------------------------
298 0 : IMPL_LINK( ORadioSelectionPage, OnEntrySelected, ListBox*, /*_pList*/ )
299 : {
300 0 : implCheckMoveButtons();
301 0 : return 0L;
302 : }
303 :
304 : //---------------------------------------------------------------------
305 0 : IMPL_LINK( ORadioSelectionPage, OnNameModified, Edit*, /*_pList*/ )
306 : {
307 0 : implCheckMoveButtons();
308 0 : return 0L;
309 : }
310 :
311 : //---------------------------------------------------------------------
312 0 : bool ORadioSelectionPage::canAdvance() const
313 : {
314 0 : return 0 != m_aExistingRadios.GetEntryCount();
315 : }
316 :
317 : //---------------------------------------------------------------------
318 0 : void ORadioSelectionPage::implCheckMoveButtons()
319 : {
320 0 : sal_Bool bHaveSome = (0 != m_aExistingRadios.GetEntryCount());
321 0 : sal_Bool bSelectedSome = (0 != m_aExistingRadios.GetSelectEntryCount());
322 0 : sal_Bool bUnfinishedInput = (0 != m_aRadioName.GetText().Len());
323 :
324 0 : m_aMoveLeft.Enable(bSelectedSome);
325 0 : m_aMoveRight.Enable(bUnfinishedInput);
326 :
327 0 : getDialog()->enableButtons(WZB_NEXT, bHaveSome);
328 :
329 0 : if (bUnfinishedInput)
330 : {
331 0 : if (0 == (m_aMoveRight.GetStyle() & WB_DEFBUTTON))
332 0 : getDialog()->defaultButton(&m_aMoveRight);
333 : }
334 : else
335 : {
336 0 : if (WB_DEFBUTTON == (m_aMoveRight.GetStyle() & WB_DEFBUTTON))
337 0 : getDialog()->defaultButton(WZB_NEXT);
338 : }
339 0 : }
340 :
341 : //=====================================================================
342 : //= ODefaultFieldSelectionPage
343 : //=====================================================================
344 : //---------------------------------------------------------------------
345 0 : ODefaultFieldSelectionPage::ODefaultFieldSelectionPage( OControlWizard* _pParent )
346 : :OMaybeListSelectionPage(_pParent, ModuleRes(RID_PAGE_DEFAULTFIELDSELECTION))
347 : ,m_aFrame (this, ModuleRes(FL_DEFAULTSELECTION))
348 : ,m_aDefaultSelectionLabel (this, ModuleRes(FT_DEFAULTSELECTION))
349 : ,m_aDefSelYes (this, ModuleRes(RB_DEFSELECTION_YES))
350 : ,m_aDefSelNo (this, ModuleRes(RB_DEFSELECTION_NO))
351 0 : ,m_aDefSelection (this, ModuleRes(LB_DEFSELECTIONFIELD))
352 : {
353 0 : FreeResource();
354 :
355 0 : announceControls(m_aDefSelYes, m_aDefSelNo, m_aDefSelection);
356 0 : m_aDefSelection.SetDropDownLineCount(10);
357 0 : m_aDefSelection.SetAccessibleRelationLabeledBy( &m_aDefSelYes );
358 0 : m_aDefSelection.SetAccessibleRelationMemberOf(&m_aDefaultSelectionLabel);
359 0 : }
360 :
361 : //---------------------------------------------------------------------
362 0 : void ODefaultFieldSelectionPage::initializePage()
363 : {
364 0 : OMaybeListSelectionPage::initializePage();
365 :
366 0 : const OOptionGroupSettings& rSettings = getSettings();
367 :
368 : // fill the listbox
369 0 : m_aDefSelection.Clear();
370 0 : for ( ConstStringArrayIterator aLoop = rSettings.aLabels.begin();
371 0 : aLoop != rSettings.aLabels.end();
372 : ++aLoop
373 : )
374 0 : m_aDefSelection.InsertEntry(*aLoop);
375 :
376 :
377 0 : implInitialize(rSettings.sDefaultField);
378 0 : }
379 :
380 : //---------------------------------------------------------------------
381 0 : sal_Bool ODefaultFieldSelectionPage::commitPage( ::svt::WizardTypes::CommitPageReason _eReason )
382 : {
383 0 : if (!OMaybeListSelectionPage::commitPage(_eReason))
384 0 : return sal_False;
385 :
386 0 : OOptionGroupSettings& rSettings = getSettings();
387 0 : implCommit(rSettings.sDefaultField);
388 :
389 0 : return sal_True;
390 : }
391 :
392 : //=====================================================================
393 : //= OOptionValuesPage
394 : //=====================================================================
395 : //---------------------------------------------------------------------
396 0 : OOptionValuesPage::OOptionValuesPage( OControlWizard* _pParent )
397 : :OGBWPage(_pParent, ModuleRes(RID_PAGE_OPTIONVALUES))
398 : ,m_aFrame (this, ModuleRes(FL_OPTIONVALUES))
399 : ,m_aDescription (this, ModuleRes(FT_OPTIONVALUES_EXPL))
400 : ,m_aValueLabel (this, ModuleRes(FT_OPTIONVALUES))
401 : ,m_aValue (this, ModuleRes(ET_OPTIONVALUE))
402 : ,m_aOptionsLabel (this, ModuleRes(FT_RADIOBUTTONS))
403 : ,m_aOptions (this, ModuleRes(LB_RADIOBUTTONS))
404 0 : ,m_nLastSelection((::svt::WizardTypes::WizardState)-1)
405 : {
406 0 : FreeResource();
407 :
408 0 : m_aOptions.SetSelectHdl(LINK(this, OOptionValuesPage, OnOptionSelected));
409 :
410 0 : m_aOptions.SetAccessibleRelationMemberOf(&m_aOptions);
411 0 : m_aOptions.SetAccessibleRelationLabeledBy(&m_aOptionsLabel);
412 0 : }
413 :
414 : //---------------------------------------------------------------------
415 0 : IMPL_LINK( OOptionValuesPage, OnOptionSelected, ListBox*, /*NOTINTERESTEDIN*/ )
416 : {
417 0 : implTraveledOptions();
418 0 : return 0L;
419 : }
420 :
421 : //---------------------------------------------------------------------
422 0 : void OOptionValuesPage::ActivatePage()
423 : {
424 0 : OGBWPage::ActivatePage();
425 0 : m_aValue.GrabFocus();
426 0 : }
427 :
428 : //---------------------------------------------------------------------
429 0 : void OOptionValuesPage::implTraveledOptions()
430 : {
431 0 : if ((::svt::WizardTypes::WizardState)-1 != m_nLastSelection)
432 : {
433 : // save the value for the last option
434 : DBG_ASSERT((size_t)m_nLastSelection < m_aUncommittedValues.size(), "OOptionValuesPage::implTraveledOptions: invalid previous selection index!");
435 0 : m_aUncommittedValues[m_nLastSelection] = m_aValue.GetText();
436 : }
437 :
438 0 : m_nLastSelection = m_aOptions.GetSelectEntryPos();
439 : DBG_ASSERT((size_t)m_nLastSelection < m_aUncommittedValues.size(), "OOptionValuesPage::implTraveledOptions: invalid new selection index!");
440 0 : m_aValue.SetText(m_aUncommittedValues[m_nLastSelection]);
441 0 : }
442 :
443 : //---------------------------------------------------------------------
444 0 : void OOptionValuesPage::initializePage()
445 : {
446 0 : OGBWPage::initializePage();
447 :
448 0 : const OOptionGroupSettings& rSettings = getSettings();
449 : DBG_ASSERT(rSettings.aLabels.size(), "OOptionValuesPage::initializePage: no options!!");
450 : DBG_ASSERT(rSettings.aLabels.size() == rSettings.aValues.size(), "OOptionValuesPage::initializePage: inconsistent data!");
451 :
452 : // fill the list with all available options
453 0 : m_aOptions.Clear();
454 0 : m_nLastSelection = -1;
455 0 : for ( ConstStringArrayIterator aLoop = rSettings.aLabels.begin();
456 0 : aLoop != rSettings.aLabels.end();
457 : ++aLoop
458 : )
459 0 : m_aOptions.InsertEntry(*aLoop);
460 :
461 : // remember the values ... can't set them directly in the settings without the explicit commit call
462 : // so we need have a copy of the values
463 0 : m_aUncommittedValues = rSettings.aValues;
464 :
465 : // select the first entry
466 0 : m_aOptions.SelectEntryPos(0);
467 0 : implTraveledOptions();
468 0 : }
469 :
470 : //---------------------------------------------------------------------
471 0 : sal_Bool OOptionValuesPage::commitPage( ::svt::WizardTypes::CommitPageReason _eReason )
472 : {
473 0 : if (!OGBWPage::commitPage(_eReason))
474 0 : return sal_False;
475 :
476 0 : OOptionGroupSettings& rSettings = getSettings();
477 :
478 : // commit the current value
479 0 : implTraveledOptions();
480 : // copy the uncommitted values
481 0 : rSettings.aValues = m_aUncommittedValues;
482 :
483 0 : return sal_True;
484 : }
485 :
486 : //=====================================================================
487 : //= OOptionDBFieldPage
488 : //=====================================================================
489 : //---------------------------------------------------------------------
490 0 : OOptionDBFieldPage::OOptionDBFieldPage( OControlWizard* _pParent )
491 0 : :ODBFieldPage(_pParent)
492 : {
493 0 : setDescriptionText(String(ModuleRes(RID_STR_GROUPWIZ_DBFIELD)));
494 0 : }
495 :
496 : //---------------------------------------------------------------------
497 0 : String& OOptionDBFieldPage::getDBFieldSetting()
498 : {
499 0 : return getSettings().sDBField;
500 : }
501 :
502 : //=====================================================================
503 : //= OFinalizeGBWPage
504 : //=====================================================================
505 : //---------------------------------------------------------------------
506 0 : OFinalizeGBWPage::OFinalizeGBWPage( OControlWizard* _pParent )
507 : :OGBWPage(_pParent, ModuleRes(RID_PAGE_OPTIONS_FINAL))
508 : ,m_aFrame (this, ModuleRes(FL_NAMEIT))
509 : ,m_aNameLabel (this, ModuleRes(FT_NAMEIT))
510 : ,m_aName (this, ModuleRes(ET_NAMEIT))
511 0 : ,m_aThatsAll (this, ModuleRes(FT_THATSALL))
512 : {
513 0 : FreeResource();
514 0 : }
515 :
516 : //---------------------------------------------------------------------
517 0 : void OFinalizeGBWPage::ActivatePage()
518 : {
519 0 : OGBWPage::ActivatePage();
520 0 : m_aName.GrabFocus();
521 0 : }
522 :
523 : //---------------------------------------------------------------------
524 0 : bool OFinalizeGBWPage::canAdvance() const
525 : {
526 0 : return false;
527 : }
528 :
529 : //---------------------------------------------------------------------
530 0 : void OFinalizeGBWPage::initializePage()
531 : {
532 0 : OGBWPage::initializePage();
533 :
534 0 : const OOptionGroupSettings& rSettings = getSettings();
535 0 : m_aName.SetText(rSettings.sControlLabel);
536 0 : }
537 :
538 : //---------------------------------------------------------------------
539 0 : sal_Bool OFinalizeGBWPage::commitPage( ::svt::WizardTypes::CommitPageReason _eReason )
540 : {
541 0 : if (!OGBWPage::commitPage(_eReason))
542 0 : return sal_False;
543 :
544 0 : getSettings().sControlLabel = m_aName.GetText();
545 :
546 0 : return sal_True;
547 : }
548 :
549 : //.........................................................................
550 : } // namespace dbp
551 : //.........................................................................
552 :
553 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|