Get the style color and number just once
[LibreOffice.git] / slideshow / source / engine / shapeattributelayer.cxx
blobc17c858d08550bd7745b4aa0a70f8a0eb79dc9aa
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 .
21 // must be first
22 #include <comphelper/diagnose_ex.hxx>
23 #include <shapeattributelayer.hxx>
25 #include <com/sun/star/awt/FontUnderline.hpp>
26 #include <com/sun/star/awt/FontWeight.hpp>
27 #include <com/sun/star/animations/AnimationAdditiveMode.hpp>
30 using namespace ::com::sun::star;
33 namespace slideshow::internal
35 /** Update state ids
37 This method updates all state IDs from possible
38 children. Whenever a child's state ID changed, we
39 increment ours.
41 void ShapeAttributeLayer::updateStateIds()
43 if( !haveChild() )
44 return;
46 if( mnTransformationState != mpChild->getTransformationState() )
47 ++mnTransformationState;
48 if( mnClipState != mpChild->getClipState() )
49 ++mnClipState;
50 if( mnAlphaState != mpChild->getAlphaState() )
51 ++mnAlphaState;
52 if( mnPositionState != mpChild->getPositionState() )
53 ++mnPositionState;
54 if( mnContentState != mpChild->getContentState() )
55 ++mnContentState;
56 if( mnVisibilityState != mpChild->getVisibilityState() )
57 ++mnVisibilityState;
60 /** Calc attribute value.
62 This method determines the current attribute value,
63 appropriately combining it with children values (by
64 evaluating the mnAdditiveMode member).
66 template< typename T > T ShapeAttributeLayer::calcValue( const T& rCurrValue,
67 bool bThisInstanceValid,
68 bool (ShapeAttributeLayer::*pIsValid)() const,
69 T (ShapeAttributeLayer::*pGetValue)() const ) const
71 // deviated from the (*shared_ptr).*mpFuncPtr notation
72 // here, since gcc does not seem to parse that as a member
73 // function call anymore.
74 const bool bChildInstanceValueValid( haveChild() && (mpChild.get()->*pIsValid)() );
76 if( bThisInstanceValid )
78 if( bChildInstanceValueValid )
80 // merge with child value
81 switch( mnAdditiveMode )
83 default:
84 // FALTHROUGH intended
85 case animations::AnimationAdditiveMode::NONE:
86 // FALTHROUGH intended
87 case animations::AnimationAdditiveMode::BASE:
88 // FALTHROUGH intended
89 case animations::AnimationAdditiveMode::REPLACE:
90 // TODO(F2): reverse-engineer the semantics of these
91 // values
93 // currently, treat them the same and replace
94 // the child value by our own
95 return rCurrValue;
97 case animations::AnimationAdditiveMode::SUM:
98 return rCurrValue + ((*mpChild).*pGetValue)();
100 case animations::AnimationAdditiveMode::MULTIPLY:
101 return rCurrValue * ((*mpChild).*pGetValue)();
104 else
106 // this object is the only one defining
107 // the value, so take it
108 return rCurrValue;
111 else
113 return bChildInstanceValueValid ?
114 ((*mpChild).*pGetValue)() :
115 T(); // pass on child value, regardless
116 // if it's valid or not. If not, it's
117 // a default anyway
121 ShapeAttributeLayer::ShapeAttributeLayer( const ShapeAttributeLayerSharedPtr& rChildLayer ) :
122 mpChild( rChildLayer ),
124 maSize(),
125 maPosition(),
126 maClip(),
128 maFontFamily(),
130 mnRotationAngle(),
131 mnShearXAngle(),
132 mnShearYAngle(),
133 mnAlpha(),
134 mnCharScale(),
135 mnCharWeight(),
137 meFillStyle( drawing::FillStyle_NONE ),
138 meLineStyle( drawing::LineStyle_NONE ),
139 meCharPosture( awt::FontSlant_NONE ),
140 mnUnderlineMode(),
142 maDimColor(),
143 maFillColor(),
144 maLineColor(),
145 maCharColor(),
147 mnTransformationState( rChildLayer ? rChildLayer->getTransformationState() : 0 ),
148 mnClipState( rChildLayer ? rChildLayer->getClipState() : 0),
149 mnAlphaState( rChildLayer ? rChildLayer->getAlphaState() : 0),
150 mnPositionState( rChildLayer ? rChildLayer->getPositionState() : 0 ),
151 mnContentState( rChildLayer ? rChildLayer->getContentState() : 0 ),
152 mnVisibilityState( rChildLayer ? rChildLayer->getVisibilityState() : 0 ),
154 mnAdditiveMode( animations::AnimationAdditiveMode::BASE ),
156 mbVisibility( false ),
158 mbWidthValid( false ),
159 mbHeightValid( false ),
160 mbPosXValid( false ),
161 mbPosYValid( false ),
162 mbClipValid( false ),
164 mbFontFamilyValid( false ),
166 mbRotationAngleValid( false ),
167 mbShearXAngleValid( false ),
168 mbShearYAngleValid( false ),
170 mbAlphaValid( false ),
172 mbCharScaleValid( false ),
174 mbDimColorValid( false ),
175 mbFillColorValid( false ),
176 mbLineColorValid( false ),
177 mbCharColorValid( false ),
179 mbFillStyleValid( false ),
180 mbLineStyleValid( false ),
181 mbCharWeightValid( false ),
182 mbUnderlineModeValid( false ),
183 mbCharPostureValid( false ),
184 mbVisibilityValid( false )
188 bool ShapeAttributeLayer::revokeChildLayer( const ShapeAttributeLayerSharedPtr& rChildLayer )
190 ENSURE_OR_RETURN_FALSE( rChildLayer,
191 "ShapeAttributeLayer::revokeChildLayer(): Will not remove NULL child" );
193 if( !haveChild() )
194 return false; // no children, nothing to revoke.
196 if( mpChild == rChildLayer )
198 // we have it - replace by removed child's sibling.
199 mpChild = rChildLayer->getChildLayer();
201 // if we're now the first one, defensively increment _all_
202 // state ids: possibly all underlying attributes have now
203 // changed to default
204 if( !haveChild() )
206 // TODO(P1): Check whether it pays off to check more
207 // detailed, which attributes really change
208 ++mnTransformationState;
209 ++mnClipState;
210 ++mnAlphaState;
211 ++mnPositionState;
212 ++mnContentState;
213 ++mnVisibilityState;
216 else
218 // we don't have it - pass on the request
219 if( !mpChild->revokeChildLayer( rChildLayer ) )
220 return false; // nobody has it - bail out
223 // something might have changed - update ids.
224 updateStateIds();
226 return true;
229 const ShapeAttributeLayerSharedPtr& ShapeAttributeLayer::getChildLayer() const
231 return mpChild;
234 void ShapeAttributeLayer::setAdditiveMode( sal_Int16 nMode )
236 if( mnAdditiveMode != nMode )
238 // TODO(P1): Check whether it pays off to check more
239 // detailed, which attributes really change
241 // defensively increment all states - possibly each of them
242 // will change with different additive mode
243 ++mnTransformationState;
244 ++mnClipState;
245 ++mnAlphaState;
246 ++mnPositionState;
247 ++mnContentState;
248 ++mnVisibilityState;
251 mnAdditiveMode = nMode;
254 bool ShapeAttributeLayer::isWidthValid() const
256 return mbWidthValid || (haveChild() && mpChild->isWidthValid());
259 double ShapeAttributeLayer::getWidth() const
261 return calcValue< double >(
262 maSize.getWidth(),
263 mbWidthValid,
264 &ShapeAttributeLayer::isWidthValid,
265 &ShapeAttributeLayer::getWidth );
268 void ShapeAttributeLayer::setWidth( const double& rNewWidth )
270 ENSURE_OR_THROW( std::isfinite(rNewWidth),
271 "ShapeAttributeLayer::setWidth(): Invalid width" );
273 maSize.setWidth( rNewWidth );
274 mbWidthValid = true;
275 ++mnTransformationState;
278 bool ShapeAttributeLayer::isHeightValid() const
280 return mbHeightValid || ( haveChild() && mpChild->isHeightValid() );
283 double ShapeAttributeLayer::getHeight() const
285 return calcValue< double >(
286 maSize.getHeight(),
287 mbHeightValid,
288 &ShapeAttributeLayer::isHeightValid,
289 &ShapeAttributeLayer::getHeight );
292 void ShapeAttributeLayer::setHeight( const double& rNewHeight )
294 ENSURE_OR_THROW( std::isfinite(rNewHeight),
295 "ShapeAttributeLayer::setHeight(): Invalid height" );
297 maSize.setHeight( rNewHeight );
298 mbHeightValid = true;
299 ++mnTransformationState;
302 void ShapeAttributeLayer::setSize( const ::basegfx::B2DSize& rNewSize )
304 ENSURE_OR_THROW( std::isfinite(rNewSize.getWidth()) &&
305 std::isfinite(rNewSize.getHeight()),
306 "ShapeAttributeLayer::setSize(): Invalid size" );
308 maSize = rNewSize;
309 mbWidthValid = mbHeightValid = true;
310 ++mnTransformationState;
313 bool ShapeAttributeLayer::isPosXValid() const
315 return mbPosXValid || ( haveChild() && mpChild->isPosXValid() );
318 double ShapeAttributeLayer::getPosX() const
320 return calcValue< double >(
321 maPosition.getX(),
322 mbPosXValid,
323 &ShapeAttributeLayer::isPosXValid,
324 &ShapeAttributeLayer::getPosX );
327 void ShapeAttributeLayer::setPosX( const double& rNewX )
329 ENSURE_OR_THROW( std::isfinite(rNewX),
330 "ShapeAttributeLayer::setPosX(): Invalid position" );
332 maPosition.setX( rNewX );
333 mbPosXValid = true;
334 ++mnPositionState;
337 bool ShapeAttributeLayer::isPosYValid() const
339 return mbPosYValid || ( haveChild() && mpChild->isPosYValid() );
342 double ShapeAttributeLayer::getPosY() const
344 return calcValue< double >(
345 maPosition.getY(),
346 mbPosYValid,
347 &ShapeAttributeLayer::isPosYValid,
348 &ShapeAttributeLayer::getPosY );
351 void ShapeAttributeLayer::setPosY( const double& rNewY )
353 ENSURE_OR_THROW( std::isfinite(rNewY),
354 "ShapeAttributeLayer::setPosY(): Invalid position" );
356 maPosition.setY( rNewY );
357 mbPosYValid = true;
358 ++mnPositionState;
361 void ShapeAttributeLayer::setPosition( const ::basegfx::B2DPoint& rNewPos )
363 maPosition = rNewPos;
364 mbPosXValid = mbPosYValid = true;
365 ++mnPositionState;
368 bool ShapeAttributeLayer::isRotationAngleValid() const
370 return mbRotationAngleValid || ( haveChild() && mpChild->isRotationAngleValid() );
373 double ShapeAttributeLayer::getRotationAngle() const
375 return calcValue< double >(
376 mnRotationAngle,
377 mbRotationAngleValid,
378 &ShapeAttributeLayer::isRotationAngleValid,
379 &ShapeAttributeLayer::getRotationAngle );
382 void ShapeAttributeLayer::setRotationAngle( const double& rNewAngle )
384 ENSURE_OR_THROW( std::isfinite(rNewAngle),
385 "ShapeAttributeLayer::setRotationAngle(): Invalid angle" );
387 mnRotationAngle = rNewAngle;
388 mbRotationAngleValid = true;
389 ++mnTransformationState;
392 bool ShapeAttributeLayer::isShearXAngleValid() const
394 return mbShearXAngleValid || ( haveChild() && mpChild->isShearXAngleValid() );
397 double ShapeAttributeLayer::getShearXAngle() const
399 return calcValue( mnShearXAngle,
400 mbShearXAngleValid,
401 &ShapeAttributeLayer::isShearXAngleValid,
402 &ShapeAttributeLayer::getShearXAngle );
405 void ShapeAttributeLayer::setShearXAngle( const double& rNewAngle )
407 ENSURE_OR_THROW( std::isfinite(rNewAngle),
408 "ShapeAttributeLayer::setShearXAngle(): Invalid angle" );
410 mnShearXAngle = rNewAngle;
411 mbShearXAngleValid = true;
412 ++mnTransformationState;
415 bool ShapeAttributeLayer::isShearYAngleValid() const
417 return mbShearYAngleValid || ( haveChild() && mpChild->isShearYAngleValid() );
420 double ShapeAttributeLayer::getShearYAngle() const
422 return calcValue( mnShearYAngle,
423 mbShearYAngleValid,
424 &ShapeAttributeLayer::isShearYAngleValid,
425 &ShapeAttributeLayer::getShearYAngle );
428 void ShapeAttributeLayer::setShearYAngle( const double& rNewAngle )
430 ENSURE_OR_THROW( std::isfinite(rNewAngle),
431 "ShapeAttributeLayer::setShearYAngle(): Invalid angle" );
433 mnShearYAngle = rNewAngle;
434 mbShearYAngleValid = true;
435 ++mnTransformationState;
438 bool ShapeAttributeLayer::isAlphaValid() const
440 return mbAlphaValid || ( haveChild() && mpChild->isAlphaValid() );
443 double ShapeAttributeLayer::getAlpha() const
445 return calcValue( mnAlpha,
446 mbAlphaValid,
447 &ShapeAttributeLayer::isAlphaValid,
448 &ShapeAttributeLayer::getAlpha );
451 void ShapeAttributeLayer::setAlpha( const double& rNewValue )
453 ENSURE_OR_THROW( std::isfinite(rNewValue),
454 "ShapeAttributeLayer::setAlpha(): Invalid alpha" );
456 mnAlpha = rNewValue;
457 mbAlphaValid = true;
458 ++mnAlphaState;
461 bool ShapeAttributeLayer::isClipValid() const
463 return mbClipValid || ( haveChild() && mpChild->isClipValid() );
466 ::basegfx::B2DPolyPolygon ShapeAttributeLayer::getClip() const
468 // TODO(F1): Implement polygon algebra for additive modes
469 if( mbClipValid )
470 return maClip;
471 else if( haveChild() )
472 return mpChild->getClip();
473 else
474 return ::basegfx::B2DPolyPolygon();
477 void ShapeAttributeLayer::setClip( const ::basegfx::B2DPolyPolygon& rNewClip )
479 maClip = rNewClip;
480 mbClipValid = true;
481 ++mnClipState;
484 bool ShapeAttributeLayer::isDimColorValid() const
486 return mbDimColorValid || ( haveChild() && mpChild->isDimColorValid() );
489 RGBColor ShapeAttributeLayer::getDimColor() const
491 return calcValue( maDimColor,
492 mbDimColorValid,
493 &ShapeAttributeLayer::isDimColorValid,
494 &ShapeAttributeLayer::getDimColor );
497 void ShapeAttributeLayer::setDimColor( const RGBColor& nNewColor )
499 maDimColor = nNewColor;
500 mbDimColorValid = true;
501 ++mnContentState;
504 bool ShapeAttributeLayer::isFillColorValid() const
506 return mbFillColorValid || ( haveChild() && mpChild->isFillColorValid() );
509 RGBColor ShapeAttributeLayer::getFillColor() const
511 return calcValue( maFillColor,
512 mbFillColorValid,
513 &ShapeAttributeLayer::isFillColorValid,
514 &ShapeAttributeLayer::getFillColor );
517 void ShapeAttributeLayer::setFillColor( const RGBColor& nNewColor )
519 maFillColor = nNewColor;
520 mbFillColorValid = true;
521 ++mnContentState;
524 bool ShapeAttributeLayer::isLineColorValid() const
526 return mbLineColorValid || ( haveChild() && mpChild->isLineColorValid() );
529 RGBColor ShapeAttributeLayer::getLineColor() const
531 return calcValue( maLineColor,
532 mbLineColorValid,
533 &ShapeAttributeLayer::isLineColorValid,
534 &ShapeAttributeLayer::getLineColor );
537 void ShapeAttributeLayer::setLineColor( const RGBColor& nNewColor )
539 maLineColor = nNewColor;
540 mbLineColorValid = true;
541 ++mnContentState;
544 bool ShapeAttributeLayer::isFillStyleValid() const
546 return mbFillStyleValid || ( haveChild() && mpChild->isFillStyleValid() );
549 sal_Int16 ShapeAttributeLayer::getFillStyle() const
551 // mnAdditiveMode is ignored, cannot combine strings in
552 // any sensible way
553 if( mbFillStyleValid )
554 return sal::static_int_cast<sal_Int16>(meFillStyle);
555 else if( haveChild() )
556 return sal::static_int_cast<sal_Int16>(mpChild->getFillStyle());
557 else
558 return sal::static_int_cast<sal_Int16>(drawing::FillStyle_SOLID);
561 void ShapeAttributeLayer::setFillStyle( const sal_Int16& rStyle )
563 // TODO(Q1): Check range here.
564 meFillStyle = static_cast<drawing::FillStyle>(rStyle);
565 mbFillStyleValid = true;
566 ++mnContentState;
569 bool ShapeAttributeLayer::isLineStyleValid() const
571 return mbLineStyleValid || ( haveChild() && mpChild->isLineStyleValid() );
574 sal_Int16 ShapeAttributeLayer::getLineStyle() const
576 // mnAdditiveMode is ignored, cannot combine strings in
577 // any sensible way
578 if( mbLineStyleValid )
579 return sal::static_int_cast<sal_Int16>(meLineStyle);
580 else if( haveChild() )
581 return sal::static_int_cast<sal_Int16>(mpChild->getLineStyle());
582 else
583 return sal::static_int_cast<sal_Int16>(drawing::LineStyle_SOLID);
586 void ShapeAttributeLayer::setLineStyle( const sal_Int16& rStyle )
588 // TODO(Q1): Check range here.
589 meLineStyle = static_cast<drawing::LineStyle>(rStyle);
590 mbLineStyleValid = true;
591 ++mnContentState;
594 bool ShapeAttributeLayer::isVisibilityValid() const
596 return mbVisibilityValid || ( haveChild() && mpChild->isVisibilityValid() );
599 bool ShapeAttributeLayer::getVisibility() const
601 // mnAdditiveMode is ignored, SMIL spec requires to not combine
602 // bools in any sensible way
603 if( mbVisibilityValid )
604 return mbVisibility;
605 else if( haveChild() )
606 return mpChild->getVisibility();
607 else
608 return true; // default is always visible
611 void ShapeAttributeLayer::setVisibility( const bool& bVisible )
613 mbVisibility = bVisible;
614 mbVisibilityValid = true;
615 ++mnVisibilityState;
618 bool ShapeAttributeLayer::isCharColorValid() const
620 return mbCharColorValid || ( haveChild() && mpChild->isCharColorValid() );
623 RGBColor ShapeAttributeLayer::getCharColor() const
625 return calcValue( maCharColor,
626 mbCharColorValid,
627 &ShapeAttributeLayer::isCharColorValid,
628 &ShapeAttributeLayer::getCharColor );
631 void ShapeAttributeLayer::setCharColor( const RGBColor& nNewColor )
633 maCharColor = nNewColor;
634 mbCharColorValid = true;
635 ++mnContentState;
638 bool ShapeAttributeLayer::isCharWeightValid() const
640 return mbCharWeightValid || ( haveChild() && mpChild->isCharWeightValid() );
643 double ShapeAttributeLayer::getCharWeight() const
645 // mnAdditiveMode is ignored, cannot combine strings in
646 // any sensible way
647 if( mbCharWeightValid )
648 return mnCharWeight;
649 else if( haveChild() )
650 return mpChild->getCharWeight();
651 else
652 return awt::FontWeight::NORMAL;
655 void ShapeAttributeLayer::setCharWeight( const double& rValue )
657 // TODO(Q1): Check range here.
658 mnCharWeight = rValue;
659 mbCharWeightValid = true;
660 ++mnContentState;
663 bool ShapeAttributeLayer::isUnderlineModeValid() const
665 return mbUnderlineModeValid || ( haveChild() && mpChild->isUnderlineModeValid() );
668 sal_Int16 ShapeAttributeLayer::getUnderlineMode() const
670 // mnAdditiveMode is ignored, SMIL spec requires to not combine
671 // bools in any sensible way
672 if( mbUnderlineModeValid )
673 return mnUnderlineMode;
674 else if( haveChild() )
675 return mpChild->getUnderlineMode();
676 else
677 return awt::FontUnderline::NONE; // default is no underline
680 void ShapeAttributeLayer::setUnderlineMode( const sal_Int16& rUnderlineMode )
682 // TODO(Q1): Check range here.
683 mnUnderlineMode = rUnderlineMode;
684 mbUnderlineModeValid = true;
685 ++mnContentState;
688 bool ShapeAttributeLayer::isFontFamilyValid() const
690 return mbFontFamilyValid || ( haveChild() && mpChild->isFontFamilyValid() );
693 OUString ShapeAttributeLayer::getFontFamily() const
695 // mnAdditiveMode is ignored, cannot combine strings in
696 // any sensible way
697 if( mbFontFamilyValid )
698 return maFontFamily;
699 else if( haveChild() )
700 return mpChild->getFontFamily();
701 else
702 return OUString();
705 void ShapeAttributeLayer::setFontFamily( const OUString& rName )
707 maFontFamily = rName;
708 mbFontFamilyValid = true;
709 ++mnContentState;
712 bool ShapeAttributeLayer::isCharPostureValid() const
714 return mbCharPostureValid || ( haveChild() && mpChild->isCharPostureValid() );
717 sal_Int16 ShapeAttributeLayer::getCharPosture() const
719 // mnAdditiveMode is ignored, cannot combine strings in
720 // any sensible way
721 if( mbCharPostureValid )
722 return sal::static_int_cast<sal_Int16>(meCharPosture);
723 else if( haveChild() )
724 return sal::static_int_cast<sal_Int16>(mpChild->getCharPosture());
725 else
726 return sal::static_int_cast<sal_Int16>(awt::FontSlant_NONE);
729 void ShapeAttributeLayer::setCharPosture( const sal_Int16& rStyle )
731 // TODO(Q1): Check range here.
732 meCharPosture = static_cast<awt::FontSlant>(rStyle);
733 mbCharPostureValid = true;
734 ++mnContentState;
737 bool ShapeAttributeLayer::isCharScaleValid() const
739 return mbCharScaleValid || ( haveChild() && mpChild->isCharScaleValid() );
742 double ShapeAttributeLayer::getCharScale() const
744 return calcValue( mnCharScale,
745 mbCharScaleValid,
746 &ShapeAttributeLayer::isCharScaleValid,
747 &ShapeAttributeLayer::getCharScale );
750 void ShapeAttributeLayer::setCharScale( const double& rNewHeight )
752 ENSURE_OR_THROW( std::isfinite(rNewHeight),
753 "ShapeAttributeLayer::setCharScale(): Invalid height" );
755 mnCharScale = rNewHeight;
756 mbCharScaleValid = true;
757 ++mnContentState;
760 State::StateId ShapeAttributeLayer::getTransformationState() const
762 return haveChild() ?
763 ::std::max( mnTransformationState,
764 mpChild->getTransformationState() ) :
765 mnTransformationState;
768 State::StateId ShapeAttributeLayer::getClipState() const
770 return haveChild() ?
771 ::std::max( mnClipState,
772 mpChild->getClipState() ) :
773 mnClipState;
776 State::StateId ShapeAttributeLayer::getAlphaState() const
778 return haveChild() ?
779 ::std::max( mnAlphaState,
780 mpChild->getAlphaState() ) :
781 mnAlphaState;
784 State::StateId ShapeAttributeLayer::getPositionState() const
786 return haveChild() ?
787 ::std::max( mnPositionState,
788 mpChild->getPositionState() ) :
789 mnPositionState;
792 State::StateId ShapeAttributeLayer::getContentState() const
794 return haveChild() ?
795 ::std::max( mnContentState,
796 mpChild->getContentState() ) :
797 mnContentState;
800 State::StateId ShapeAttributeLayer::getVisibilityState() const
802 return haveChild() ?
803 ::std::max( mnVisibilityState,
804 mpChild->getVisibilityState() ) :
805 mnVisibilityState;
810 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */