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 : #include "tbzoomsliderctrl.hxx"
20 : #include <vcl/image.hxx>
21 : #include <vcl/toolbox.hxx>
22 : #include <vcl/virdev.hxx>
23 : #include <vcl/svapp.hxx>
24 : #include <vcl/gradient.hxx>
25 : #include <vcl/settings.hxx>
26 : #include <svl/itemset.hxx>
27 : #include <sfx2/viewfrm.hxx>
28 : #include <sfx2/objsh.hxx>
29 : #include <svx/zoomslideritem.hxx>
30 : #include <svx/dialmgr.hxx>
31 : #include <svx/dialogs.hrc>
32 : #include <set>
33 : #include "docsh.hxx"
34 : #include "stlpool.hxx"
35 : #include "scitems.hxx"
36 : #include "printfun.hxx"
37 :
38 : // class ScZoomSliderControl ---------------------------------------
39 :
40 92 : SFX_IMPL_TOOLBOX_CONTROL( ScZoomSliderControl, SvxZoomSliderItem );
41 :
42 16 : ScZoomSliderControl::ScZoomSliderControl(
43 : sal_uInt16 nSlotId,
44 : sal_uInt16 nId,
45 : ToolBox& rTbx )
46 16 : :SfxToolBoxControl( nSlotId, nId, rTbx )
47 : {
48 16 : rTbx.Invalidate();
49 16 : }
50 :
51 32 : ScZoomSliderControl::~ScZoomSliderControl()
52 : {
53 :
54 32 : }
55 :
56 26 : void ScZoomSliderControl::StateChanged( sal_uInt16 /*nSID*/, SfxItemState eState,
57 : const SfxPoolItem* pState )
58 : {
59 26 : sal_uInt16 nId = GetId();
60 26 : ToolBox& rTbx = GetToolBox();
61 26 : ScZoomSliderWnd* pBox = static_cast<ScZoomSliderWnd*>(rTbx.GetItemWindow( nId ));
62 : OSL_ENSURE( pBox ,"Control not found!" );
63 :
64 26 : if ( SfxItemState::DEFAULT != eState || pState->ISA( SfxVoidItem ) )
65 : {
66 0 : SvxZoomSliderItem aZoomSliderItem( 100 );
67 0 : pBox->Disable();
68 0 : pBox->UpdateFromItem( &aZoomSliderItem );
69 : }
70 : else
71 : {
72 26 : pBox->Enable();
73 : OSL_ENSURE( pState->ISA( SvxZoomSliderItem ), "invalid item type" );
74 26 : const SvxZoomSliderItem* pZoomSliderItem = dynamic_cast< const SvxZoomSliderItem* >( pState );
75 :
76 : OSL_ENSURE( pZoomSliderItem, "Sc::ScZoomSliderControl::StateChanged(), wrong item type!" );
77 26 : if( pZoomSliderItem )
78 26 : pBox->UpdateFromItem( pZoomSliderItem );
79 : }
80 26 : }
81 :
82 16 : vcl::Window* ScZoomSliderControl::CreateItemWindow( vcl::Window *pParent )
83 : {
84 : // #i98000# Don't try to get a value via SfxViewFrame::Current here.
85 : // The view's value is always notified via StateChanged later.
86 : ScZoomSliderWnd* pSlider = new ScZoomSliderWnd( pParent,
87 16 : ::com::sun::star::uno::Reference< ::com::sun::star::frame::XDispatchProvider >( m_xFrame->getController(),
88 16 : ::com::sun::star::uno::UNO_QUERY ), m_xFrame, 100 );
89 16 : return pSlider;
90 : }
91 :
92 16 : struct ScZoomSliderWnd::ScZoomSliderWnd_Impl
93 : {
94 : sal_uInt16 mnCurrentZoom;
95 : sal_uInt16 mnMinZoom;
96 : sal_uInt16 mnMaxZoom;
97 : sal_uInt16 mnSliderCenter;
98 : std::vector< long > maSnappingPointOffsets;
99 : std::vector< sal_uInt16 > maSnappingPointZooms;
100 : Image maSliderButton;
101 : Image maIncreaseButton;
102 : Image maDecreaseButton;
103 : bool mbValuesSet;
104 : bool mbOmitPaint;
105 :
106 16 : ScZoomSliderWnd_Impl( sal_uInt16 nCurrentZoom ) :
107 : mnCurrentZoom( nCurrentZoom ),
108 : mnMinZoom( 10 ),
109 : mnMaxZoom( 400 ),
110 : mnSliderCenter( 100 ),
111 : maSnappingPointOffsets(),
112 : maSnappingPointZooms(),
113 : maSliderButton(),
114 : maIncreaseButton(),
115 : maDecreaseButton(),
116 : mbValuesSet( true ),
117 16 : mbOmitPaint( false )
118 : {
119 :
120 16 : }
121 : };
122 :
123 : const long nButtonWidth = 10;
124 : const long nButtonHeight = 10;
125 : const long nIncDecWidth = 11;
126 : const long nIncDecHeight = 11;
127 : const long nSliderHeight = 2;
128 : const long nSliderWidth = 4;
129 : const long nSnappingHeight = 4;
130 : const long nSliderXOffset = 20;
131 : const long nSnappingEpsilon = 5; // snapping epsilon in pixels
132 : const long nSnappingPointsMinDist = nSnappingEpsilon; // minimum distance of two adjacent snapping points
133 :
134 0 : sal_uInt16 ScZoomSliderWnd::Offset2Zoom( long nOffset ) const
135 : {
136 0 : Size aSliderWindowSize = GetOutputSizePixel();
137 0 : const long nControlWidth = aSliderWindowSize.Width();
138 0 : sal_uInt16 nRet = 0;
139 :
140 0 : if( nOffset < nSliderXOffset )
141 0 : return mpImpl->mnMinZoom;
142 0 : if( nOffset > nControlWidth - nSliderXOffset )
143 0 : return mpImpl->mnMaxZoom;
144 :
145 : // check for snapping points:
146 0 : sal_uInt16 nCount = 0;
147 0 : std::vector< long >::iterator aSnappingPointIter;
148 0 : for ( aSnappingPointIter = mpImpl->maSnappingPointOffsets.begin();
149 0 : aSnappingPointIter != mpImpl->maSnappingPointOffsets.end();
150 : ++aSnappingPointIter )
151 : {
152 0 : const long nCurrent = *aSnappingPointIter;
153 0 : if ( std::abs(nCurrent - nOffset) < nSnappingEpsilon )
154 : {
155 0 : nOffset = nCurrent;
156 0 : nRet = mpImpl->maSnappingPointZooms[ nCount ];
157 0 : break;
158 : }
159 0 : ++nCount;
160 : }
161 :
162 0 : if( 0 == nRet )
163 : {
164 0 : if( nOffset < nControlWidth / 2 )
165 : {
166 : // first half of slider
167 0 : const long nFirstHalfRange = mpImpl->mnSliderCenter - mpImpl->mnMinZoom;
168 0 : const long nHalfSliderWidth = nControlWidth/2 - nSliderXOffset;
169 0 : const long nZoomPerSliderPixel = (1000 * nFirstHalfRange) / nHalfSliderWidth;
170 0 : const long nOffsetToSliderLeft = nOffset - nSliderXOffset;
171 0 : nRet = mpImpl->mnMinZoom + sal_uInt16( nOffsetToSliderLeft * nZoomPerSliderPixel / 1000 );
172 : }
173 : else
174 : {
175 : // second half of slider
176 0 : const long nSecondHalfRange = mpImpl->mnMaxZoom - mpImpl->mnSliderCenter;
177 0 : const long nHalfSliderWidth = nControlWidth/2 - nSliderXOffset;
178 0 : const long nZoomPerSliderPixel = 1000 * nSecondHalfRange / nHalfSliderWidth;
179 0 : const long nOffsetToSliderCenter = nOffset - nControlWidth/2;
180 0 : nRet = mpImpl->mnSliderCenter + sal_uInt16( nOffsetToSliderCenter * nZoomPerSliderPixel / 1000 );
181 : }
182 : }
183 :
184 0 : if( nRet < mpImpl->mnMinZoom )
185 0 : return mpImpl->mnMinZoom;
186 :
187 0 : else if( nRet > mpImpl->mnMaxZoom )
188 0 : return mpImpl->mnMaxZoom;
189 :
190 0 : return nRet;
191 : }
192 :
193 68 : long ScZoomSliderWnd::Zoom2Offset( sal_uInt16 nCurrentZoom ) const
194 : {
195 68 : Size aSliderWindowSize = GetOutputSizePixel();
196 68 : const long nControlWidth = aSliderWindowSize.Width();
197 68 : long nRect = nSliderXOffset;
198 :
199 68 : const long nHalfSliderWidth = nControlWidth/2 - nSliderXOffset;
200 68 : if( nCurrentZoom <= mpImpl->mnSliderCenter )
201 : {
202 68 : nCurrentZoom = nCurrentZoom - mpImpl->mnMinZoom;
203 68 : const long nFirstHalfRange = mpImpl->mnSliderCenter - mpImpl->mnMinZoom;
204 68 : const long nSliderPixelPerZoomPercent = 1000 * nHalfSliderWidth / nFirstHalfRange;
205 68 : const long nOffset = (nSliderPixelPerZoomPercent * nCurrentZoom) / 1000;
206 68 : nRect += nOffset;
207 : }
208 : else
209 : {
210 0 : nCurrentZoom = nCurrentZoom - mpImpl->mnSliderCenter;
211 0 : const long nSecondHalfRange = mpImpl->mnMaxZoom - mpImpl->mnSliderCenter;
212 0 : const long nSliderPixelPerZoomPercent = 1000 * nHalfSliderWidth / nSecondHalfRange;
213 0 : const long nOffset = (nSliderPixelPerZoomPercent * nCurrentZoom) / 1000;
214 0 : nRect += nHalfSliderWidth + nOffset;
215 : }
216 68 : return nRect;
217 : }
218 :
219 16 : ScZoomSliderWnd::ScZoomSliderWnd( vcl::Window* pParent, const ::com::sun::star::uno::Reference< ::com::sun::star::frame::XDispatchProvider >& rDispatchProvider,
220 : const ::com::sun::star::uno::Reference< ::com::sun::star::frame::XFrame >& _xFrame , sal_uInt16 nCurrentZoom ):
221 : Window( pParent ),
222 16 : mpImpl( new ScZoomSliderWnd_Impl( nCurrentZoom ) ),
223 : aLogicalSize( 115, 40 ),
224 : m_xDispatchProvider( rDispatchProvider ),
225 32 : m_xFrame( _xFrame )
226 : {
227 16 : mpImpl->maSliderButton = Image( SVX_RES( RID_SVXBMP_SLIDERBUTTON ) );
228 16 : mpImpl->maIncreaseButton = Image( SVX_RES( RID_SVXBMP_SLIDERINCREASE ) );
229 16 : mpImpl->maDecreaseButton = Image( SVX_RES( RID_SVXBMP_SLIDERDECREASE ) );
230 16 : Size aSliderSize = LogicToPixel( Size( aLogicalSize), MapMode( MAP_10TH_MM ) );
231 16 : SetSizePixel( Size( aSliderSize.Width() * nSliderWidth-1, aSliderSize.Height() + nSliderHeight ) );
232 16 : }
233 :
234 48 : ScZoomSliderWnd::~ScZoomSliderWnd()
235 : {
236 16 : delete mpImpl;
237 32 : }
238 :
239 0 : void ScZoomSliderWnd::MouseButtonDown( const MouseEvent& rMEvt )
240 : {
241 0 : if ( !mpImpl->mbValuesSet )
242 0 : return ;
243 0 : Size aSliderWindowSize = GetOutputSizePixel();
244 :
245 0 : const Point aPoint = rMEvt.GetPosPixel();
246 :
247 0 : const long nButtonLeftOffset = ( nSliderXOffset - nIncDecWidth )/2;
248 0 : const long nButtonRightOffset = ( nSliderXOffset + nIncDecWidth )/2;
249 :
250 0 : const long nOldZoom = mpImpl->mnCurrentZoom;
251 :
252 : // click to - button
253 0 : if ( aPoint.X() >= nButtonLeftOffset && aPoint.X() <= nButtonRightOffset )
254 : {
255 0 : mpImpl->mnCurrentZoom = mpImpl->mnCurrentZoom - 5;
256 : }
257 : // click to + button
258 0 : else if ( aPoint.X() >= aSliderWindowSize.Width() - nSliderXOffset + nButtonLeftOffset &&
259 0 : aPoint.X() <= aSliderWindowSize.Width() - nSliderXOffset + nButtonRightOffset )
260 : {
261 0 : mpImpl->mnCurrentZoom = mpImpl->mnCurrentZoom + 5;
262 : }
263 0 : else if( aPoint.X() >= nSliderXOffset && aPoint.X() <= aSliderWindowSize.Width() - nSliderXOffset )
264 : {
265 0 : mpImpl->mnCurrentZoom = Offset2Zoom( aPoint.X() );
266 : }
267 :
268 0 : if( mpImpl->mnCurrentZoom < mpImpl->mnMinZoom )
269 0 : mpImpl->mnCurrentZoom = mpImpl->mnMinZoom;
270 0 : else if( mpImpl->mnCurrentZoom > mpImpl->mnMaxZoom )
271 0 : mpImpl->mnCurrentZoom = mpImpl->mnMaxZoom;
272 :
273 0 : if( nOldZoom == mpImpl->mnCurrentZoom )
274 0 : return ;
275 :
276 0 : Rectangle aRect( Point( 0, 0 ), aSliderWindowSize );
277 :
278 0 : Paint( aRect );
279 0 : mpImpl->mbOmitPaint = true;
280 :
281 0 : SvxZoomSliderItem aZoomSliderItem( mpImpl->mnCurrentZoom );
282 :
283 0 : ::com::sun::star::uno::Any a;
284 0 : aZoomSliderItem.QueryValue( a );
285 :
286 0 : ::com::sun::star::uno::Sequence< ::com::sun::star::beans::PropertyValue > aArgs( 1 );
287 0 : aArgs[0].Name = "ScalingFactor";
288 0 : aArgs[0].Value = a;
289 :
290 0 : SfxToolBoxControl::Dispatch( m_xDispatchProvider, OUString(".uno:ScalingFactor"), aArgs );
291 :
292 0 : mpImpl->mbOmitPaint = false;
293 : }
294 :
295 0 : void ScZoomSliderWnd::MouseMove( const MouseEvent& rMEvt )
296 : {
297 0 : if ( !mpImpl->mbValuesSet )
298 0 : return ;
299 :
300 0 : Size aSliderWindowSize = GetOutputSizePixel();
301 0 : const long nControlWidth = aSliderWindowSize.Width();
302 0 : const short nButtons = rMEvt.GetButtons();
303 :
304 : // check mouse move with button pressed
305 0 : if ( 1 == nButtons )
306 : {
307 0 : const Point aPoint = rMEvt.GetPosPixel();
308 :
309 0 : if ( aPoint.X() >= nSliderXOffset && aPoint.X() <= nControlWidth - nSliderXOffset )
310 : {
311 0 : mpImpl->mnCurrentZoom = Offset2Zoom( aPoint.X() );
312 :
313 0 : Rectangle aRect( Point( 0, 0 ), aSliderWindowSize );
314 0 : Paint( aRect );
315 :
316 0 : mpImpl->mbOmitPaint = true; // optimization: paint before executing command,
317 :
318 : // commit state change
319 0 : SvxZoomSliderItem aZoomSliderItem( mpImpl->mnCurrentZoom );
320 :
321 0 : ::com::sun::star::uno::Any a;
322 0 : aZoomSliderItem.QueryValue( a );
323 :
324 0 : ::com::sun::star::uno::Sequence< ::com::sun::star::beans::PropertyValue > aArgs( 1 );
325 0 : aArgs[0].Name = "ScalingFactor";
326 0 : aArgs[0].Value = a;
327 :
328 0 : SfxToolBoxControl::Dispatch( m_xDispatchProvider, OUString(".uno:ScalingFactor"), aArgs );
329 :
330 0 : mpImpl->mbOmitPaint = false;
331 : }
332 : }
333 : }
334 :
335 26 : void ScZoomSliderWnd::UpdateFromItem( const SvxZoomSliderItem* pZoomSliderItem )
336 : {
337 26 : if( pZoomSliderItem )
338 : {
339 26 : mpImpl->mnCurrentZoom = pZoomSliderItem->GetValue();
340 26 : mpImpl->mnMinZoom = pZoomSliderItem->GetMinZoom();
341 26 : mpImpl->mnMaxZoom = pZoomSliderItem->GetMaxZoom();
342 :
343 : OSL_ENSURE( mpImpl->mnMinZoom <= mpImpl->mnCurrentZoom &&
344 : mpImpl->mnMinZoom < mpImpl->mnSliderCenter &&
345 : mpImpl->mnMaxZoom >= mpImpl->mnCurrentZoom &&
346 : mpImpl->mnMaxZoom > mpImpl->mnSliderCenter,
347 : "Looks like the zoom slider item is corrupted" );
348 26 : const com::sun::star::uno::Sequence < sal_Int32 > rSnappingPoints = pZoomSliderItem->GetSnappingPoints();
349 26 : mpImpl->maSnappingPointOffsets.clear();
350 26 : mpImpl->maSnappingPointZooms.clear();
351 :
352 : // get all snapping points:
353 52 : std::set< sal_uInt16 > aTmpSnappingPoints;
354 52 : for ( sal_uInt16 j = 0; j < rSnappingPoints.getLength(); ++j )
355 : {
356 26 : const sal_Int32 nSnappingPoint = rSnappingPoints[j];
357 26 : aTmpSnappingPoints.insert( (sal_uInt16)nSnappingPoint );
358 : }
359 :
360 : // remove snapping points that are to close to each other:
361 26 : std::set< sal_uInt16 >::iterator aSnappingPointIter;
362 26 : long nLastOffset = 0;
363 :
364 52 : for ( aSnappingPointIter = aTmpSnappingPoints.begin(); aSnappingPointIter != aTmpSnappingPoints.end(); ++aSnappingPointIter )
365 : {
366 26 : const sal_uInt16 nCurrent = *aSnappingPointIter;
367 26 : const long nCurrentOffset = Zoom2Offset( nCurrent );
368 :
369 26 : if ( nCurrentOffset - nLastOffset >= nSnappingPointsMinDist )
370 : {
371 26 : mpImpl->maSnappingPointOffsets.push_back( nCurrentOffset );
372 26 : mpImpl->maSnappingPointZooms.push_back( nCurrent );
373 26 : nLastOffset = nCurrentOffset;
374 : }
375 26 : }
376 : }
377 :
378 26 : Size aSliderWindowSize = GetOutputSizePixel();
379 26 : Rectangle aRect( Point( 0, 0 ), aSliderWindowSize );
380 :
381 26 : if ( !mpImpl->mbOmitPaint )
382 26 : Paint(aRect);
383 26 : }
384 :
385 42 : void ScZoomSliderWnd::Paint( const Rectangle& rRect )
386 : {
387 42 : DoPaint( rRect );
388 42 : }
389 :
390 42 : void ScZoomSliderWnd::DoPaint( const Rectangle& /*rRect*/ )
391 : {
392 42 : if( mpImpl->mbOmitPaint )
393 42 : return;
394 :
395 42 : Size aSliderWindowSize = GetOutputSizePixel();
396 42 : Rectangle aRect( Point( 0, 0 ), aSliderWindowSize );
397 :
398 42 : VirtualDevice* pVDev = new VirtualDevice( *this );
399 42 : pVDev->SetOutputSizePixel( aSliderWindowSize );
400 :
401 42 : Rectangle aSlider = aRect;
402 :
403 42 : aSlider.Top() += ( aSliderWindowSize.Height() - nSliderHeight )/2 - 1;
404 42 : aSlider.Bottom() = aSlider.Top() + nSliderHeight;
405 42 : aSlider.Left() += nSliderXOffset;
406 42 : aSlider.Right() -= nSliderXOffset;
407 :
408 42 : Rectangle aFirstLine( aSlider );
409 42 : aFirstLine.Bottom() = aFirstLine.Top();
410 :
411 42 : Rectangle aSecondLine( aSlider );
412 42 : aSecondLine.Top() = aSecondLine.Bottom();
413 :
414 42 : Rectangle aLeft( aSlider );
415 42 : aLeft.Right() = aLeft.Left();
416 :
417 42 : Rectangle aRight( aSlider );
418 42 : aRight.Left() = aRight.Right();
419 :
420 : // draw VirtualDevice's background color
421 42 : Color aStartColor,aEndColor;
422 42 : aStartColor = GetSettings().GetStyleSettings().GetFaceColor();
423 42 : aEndColor = GetSettings().GetStyleSettings().GetFaceColor();
424 42 : if( aEndColor.IsDark() )
425 0 : aStartColor = aEndColor;
426 :
427 42 : Gradient g;
428 42 : g.SetAngle( 0 );
429 42 : g.SetStyle( GradientStyle_LINEAR );
430 :
431 42 : g.SetStartColor( aStartColor );
432 42 : g.SetEndColor( aEndColor );
433 42 : pVDev->DrawGradient( aRect, g );
434 :
435 : // draw slider
436 42 : pVDev->SetLineColor( Color ( COL_WHITE ) );
437 42 : pVDev->DrawRect( aSecondLine );
438 42 : pVDev->DrawRect( aRight );
439 :
440 42 : pVDev->SetLineColor( Color( COL_GRAY ) );
441 42 : pVDev->DrawRect( aFirstLine );
442 42 : pVDev->DrawRect( aLeft );
443 :
444 : // draw snapping points:
445 42 : std::vector< long >::iterator aSnappingPointIter;
446 204 : for ( aSnappingPointIter = mpImpl->maSnappingPointOffsets.begin();
447 136 : aSnappingPointIter != mpImpl->maSnappingPointOffsets.end();
448 : ++aSnappingPointIter )
449 : {
450 26 : pVDev->SetLineColor( Color( COL_GRAY ) );
451 26 : Rectangle aSnapping( aRect );
452 26 : aSnapping.Bottom() = aSlider.Top();
453 26 : aSnapping.Top() = aSnapping.Bottom() - nSnappingHeight;
454 26 : aSnapping.Left() += *aSnappingPointIter;
455 26 : aSnapping.Right() = aSnapping.Left();
456 26 : pVDev->DrawRect( aSnapping );
457 :
458 26 : aSnapping.Top() += nSnappingHeight + nSliderHeight;
459 26 : aSnapping.Bottom() += nSnappingHeight + nSliderHeight;
460 26 : pVDev->DrawRect( aSnapping );
461 : }
462 :
463 : // draw slider button
464 42 : Point aImagePoint = aRect.TopLeft();
465 42 : aImagePoint.X() += Zoom2Offset( mpImpl->mnCurrentZoom );
466 42 : aImagePoint.X() -= nButtonWidth/2;
467 42 : aImagePoint.Y() += ( aSliderWindowSize.Height() - nButtonHeight)/2;
468 42 : pVDev->DrawImage( aImagePoint, mpImpl->maSliderButton );
469 :
470 : // draw decrease button
471 42 : aImagePoint = aRect.TopLeft();
472 42 : aImagePoint.X() += (nSliderXOffset - nIncDecWidth)/2;
473 42 : aImagePoint.Y() += ( aSliderWindowSize.Height() - nIncDecHeight)/2;
474 42 : pVDev->DrawImage( aImagePoint, mpImpl->maDecreaseButton );
475 :
476 : // draw increase button
477 42 : aImagePoint.X() = aRect.TopLeft().X() + aSliderWindowSize.Width() - nIncDecWidth - (nSliderXOffset - nIncDecWidth)/2;
478 42 : pVDev->DrawImage( aImagePoint, mpImpl->maIncreaseButton );
479 :
480 42 : DrawOutDev( Point(0, 0), aSliderWindowSize, Point(0, 0), aSliderWindowSize, *pVDev );
481 :
482 42 : delete pVDev;
483 :
484 228 : }
485 :
486 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|