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 .
21 #include <tools/diagnose_ex.h>
22 #include <basegfx/range/b1drange.hxx>
23 #include <basegfx/matrix/b2dhommatrix.hxx>
25 #include <comphelper/anytostring.hxx>
26 #include <cppuhelper/exc_hlp.hxx>
31 #include "layermanager.hxx"
33 using namespace ::com::sun::star
;
37 // add operator!= for weak_ptr
38 inline bool operator!=( slideshow::internal::LayerWeakPtr
const& rLHS
,
39 slideshow::internal::LayerWeakPtr
const& rRHS
)
41 return rLHS
.lock().get() != rRHS
.lock().get();
49 template<typename LayerFunc
,
50 typename ShapeFunc
> void LayerManager::manageViews(
54 LayerSharedPtr pCurrLayer
;
55 ViewLayerSharedPtr pCurrViewLayer
;
56 for( const auto& rShape
: maAllShapes
)
58 LayerSharedPtr pLayer
= rShape
.second
.lock();
59 if( pLayer
&& pLayer
!= pCurrLayer
)
62 pCurrViewLayer
= layerFunc(pCurrLayer
);
66 shapeFunc(rShape
.first
,pCurrViewLayer
);
70 LayerManager::LayerManager( const UnoViewContainer
& rViews
,
71 bool bDisableAnimationZOrder
) :
78 mbLayerAssociationDirty(false),
80 mbDisableAnimationZOrder(bDisableAnimationZOrder
)
82 // prevent frequent resizes (won't have more than 4 layers
83 // for 99.9% of the cases)
86 // create initial background layer
87 maLayers
.push_back( Layer::createBackgroundLayer() );
90 for( const auto& rView
: mrViews
)
94 void LayerManager::activate()
97 maUpdateShapes
.clear(); // update gets forced via area, or
98 // has happened outside already
100 // clear all possibly pending update areas - content
102 for( const auto& pLayer
: maLayers
)
103 pLayer
->clearUpdateRanges();
105 updateShapeLayers( true/*bSlideBackgoundPainted*/ );
108 void LayerManager::deactivate()
110 // TODO(F3): This is mostly a hack. Problem is, there's
111 // currently no smart way of telling shapes "remove your
112 // sprites". Others, like MediaShapes, listen to
113 // start/stop animation events, which is too much overhead
114 // for all shapes, though.
116 const bool bMoreThanOneLayer(maLayers
.size() > 1);
117 if (mnActiveSprites
|| bMoreThanOneLayer
)
119 // clear all viewlayers, dump everything but the
120 // background layer - this will also remove all shape
122 for (auto& rShape
: maAllShapes
)
123 rShape
.first
->clearAllViewLayers();
125 for (auto& rShape
: maAllShapes
)
126 rShape
.second
.reset();
128 if (bMoreThanOneLayer
)
129 maLayers
.erase(maLayers
.begin() + 1, maLayers
.end());
131 mbLayerAssociationDirty
= true;
136 // only background layer left
137 OSL_ASSERT( maLayers
.size() == 1 && maLayers
.front()->isBackgroundLayer() );
140 void LayerManager::viewAdded( const UnoViewSharedPtr
& rView
)
142 // view must be member of mrViews container
143 OSL_ASSERT( std::find(mrViews
.begin(),
145 rView
) != mrViews
.end() );
151 // add View to all registered shapes
153 [&rView
]( const LayerSharedPtr
& pLayer
)
154 { return pLayer
->addView( rView
); },
155 []( const ShapeSharedPtr
& pShape
, const ViewLayerSharedPtr
& pLayer
)
156 { return pShape
->addViewLayer( pLayer
, true ); } );
158 // in case we haven't reached all layers from the
159 // maAllShapes, issue addView again for good measure
160 for( const auto& pLayer
: maLayers
)
161 pLayer
->addView( rView
);
164 void LayerManager::viewRemoved( const UnoViewSharedPtr
& rView
)
166 // view must not be member of mrViews container anymore
167 OSL_ASSERT( std::find(mrViews
.begin(),
169 rView
) == mrViews
.end() );
171 // remove View from all registered shapes
173 [&rView
]( const LayerSharedPtr
& pLayer
)
174 { return pLayer
->removeView( rView
); },
175 []( const ShapeSharedPtr
& pShape
, const ViewLayerSharedPtr
& pLayer
)
176 { return pShape
->removeViewLayer( pLayer
); } );
178 // in case we haven't reached all layers from the
179 // maAllShapes, issue removeView again for good measure
180 for( const auto& pLayer
: maLayers
)
181 pLayer
->removeView( rView
);
184 void LayerManager::viewChanged( const UnoViewSharedPtr
& rView
)
186 // view must be member of mrViews container
187 OSL_ASSERT( std::find(mrViews
.begin(),
189 rView
) != mrViews
.end() );
191 // TODO(P2): selectively update only changed view
195 void LayerManager::viewsChanged()
201 for( const auto& pView
: mrViews
)
204 // TODO(F3): resize and repaint all layers
207 for( const auto& rShape
: maAllShapes
)
208 rShape
.first
->render();
211 void LayerManager::addShape( const ShapeSharedPtr
& rShape
)
213 OSL_ASSERT( !maLayers
.empty() ); // always at least background layer
214 ENSURE_OR_THROW( rShape
, "LayerManager::addShape(): invalid Shape" );
216 // add shape to XShape hash map
217 if( !maXShapeHash
.emplace(rShape
->getXShape(),
220 // entry already present, nothing to do
224 // add shape to appropriate layer
225 implAddShape( rShape
);
228 void LayerManager::putShape2BackgroundLayer( LayerShapeMap::value_type
& rShapeEntry
)
230 LayerSharedPtr
& rBgLayer( maLayers
.front() );
231 rBgLayer
->setShapeViews(rShapeEntry
.first
);
232 rShapeEntry
.second
= rBgLayer
;
235 void LayerManager::implAddShape( const ShapeSharedPtr
& rShape
)
237 OSL_ASSERT( !maLayers
.empty() ); // always at least background layer
238 ENSURE_OR_THROW( rShape
, "LayerManager::implAddShape(): invalid Shape" );
240 LayerShapeMap::value_type
aValue (rShape
, LayerWeakPtr());
242 OSL_ASSERT( maAllShapes
.find(rShape
) == maAllShapes
.end() ); // shape must not be added already
243 mbLayerAssociationDirty
= true;
245 if( mbDisableAnimationZOrder
)
246 putShape2BackgroundLayer(
247 *maAllShapes
.insert(aValue
).first
);
249 maAllShapes
.insert(aValue
);
251 // update shape, it's just added and not yet painted
252 if( rShape
->isVisible() )
253 notifyShapeUpdate( rShape
);
256 void LayerManager::implRemoveShape( const ShapeSharedPtr
& rShape
)
258 OSL_ASSERT( !maLayers
.empty() ); // always at least background layer
259 ENSURE_OR_THROW( rShape
, "LayerManager::implRemoveShape(): invalid Shape" );
261 const LayerShapeMap::iterator
aShapeEntry( maAllShapes
.find(rShape
) );
263 if( aShapeEntry
== maAllShapes
.end() )
266 const bool bShapeUpdateNotified
= maUpdateShapes
.erase( rShape
) != 0;
268 // Enter shape area to the update area, but only if shape
269 // is visible and not in sprite mode (otherwise, updating
270 // the area doesn't do actual harm, but costs time)
271 // Actually, also add it if it was listed in
272 // maUpdateShapes (might have just gone invisible).
273 if( bShapeUpdateNotified
||
274 (rShape
->isVisible() &&
275 !rShape
->isBackgroundDetached()) )
277 LayerSharedPtr pLayer
= aShapeEntry
->second
.lock();
280 // store area early, once the shape is removed from
281 // the layers, it no longer has any view references
282 pLayer
->addUpdateRange( rShape
->getUpdateArea() );
286 rShape
->clearAllViewLayers();
287 maAllShapes
.erase( aShapeEntry
);
289 mbLayerAssociationDirty
= true;
292 ShapeSharedPtr
LayerManager::lookupShape( const uno::Reference
< drawing::XShape
>& xShape
) const
294 ENSURE_OR_THROW( xShape
.is(), "LayerManager::lookupShape(): invalid Shape" );
296 const XShapeHash::const_iterator
aIter( maXShapeHash
.find( xShape
));
297 if( aIter
== maXShapeHash
.end() )
298 return ShapeSharedPtr(); // not found
300 // found, return data part of entry pair.
301 return aIter
->second
;
304 AttributableShapeSharedPtr
LayerManager::getSubsetShape( const AttributableShapeSharedPtr
& rOrigShape
,
305 const DocTreeNode
& rTreeNode
)
307 OSL_ASSERT( !maLayers
.empty() ); // always at least background layer
309 AttributableShapeSharedPtr pSubset
;
311 // shape already added?
312 if( rOrigShape
->createSubset( pSubset
,
315 OSL_ENSURE( pSubset
, "LayerManager::getSubsetShape(): failed to create subset" );
317 // don't add to shape hash, we're dupes to the
318 // original XShape anyway - all subset shapes return
319 // the same XShape as the original one.
321 // add shape to corresponding layer
322 implAddShape( pSubset
);
324 // update original shape, it now shows less content
325 // (the subset is removed from its displayed
326 // output). Subset shape is updated within
328 if( rOrigShape
->isVisible() )
329 notifyShapeUpdate( rOrigShape
);
335 void LayerManager::revokeSubset( const AttributableShapeSharedPtr
& rOrigShape
,
336 const AttributableShapeSharedPtr
& rSubsetShape
)
338 OSL_ASSERT( !maLayers
.empty() ); // always at least background layer
340 if( rOrigShape
->revokeSubset( rSubsetShape
) )
342 OSL_ASSERT( maAllShapes
.find(rSubsetShape
) != maAllShapes
.end() );
344 implRemoveShape( rSubsetShape
);
346 // update original shape, it now shows more content
347 // (the subset is added back to its displayed output)
348 if( rOrigShape
->isVisible() )
349 notifyShapeUpdate( rOrigShape
);
353 void LayerManager::enterAnimationMode( const AnimatableShapeSharedPtr
& rShape
)
355 OSL_ASSERT( !maLayers
.empty() ); // always at least background layer
356 ENSURE_OR_THROW( rShape
, "LayerManager::enterAnimationMode(): invalid Shape" );
358 const bool bPrevAnimState( rShape
->isBackgroundDetached() );
360 rShape
->enterAnimationMode();
362 // if this call _really_ enabled the animation mode at
363 // rShape, insert it to our enter animation queue, to
364 // perform the necessary layer reorg lazily on
365 // LayerManager::update()/render().
366 if( bPrevAnimState
!= rShape
->isBackgroundDetached() )
369 mbLayerAssociationDirty
= true;
371 // area needs update (shape is removed from normal
372 // slide, and now rendered as an autonomous
373 // sprite). store in update set
374 if( rShape
->isVisible() )
375 addUpdateArea( rShape
);
378 // TODO(P1): this can lead to potential wasted effort, if
379 // a shape gets toggled animated/unanimated a few times
380 // between two frames, returning to the original state.
383 void LayerManager::leaveAnimationMode( const AnimatableShapeSharedPtr
& rShape
)
385 ENSURE_OR_THROW( !maLayers
.empty(), "LayerManager::leaveAnimationMode(): no layers" );
386 ENSURE_OR_THROW( rShape
, "LayerManager::leaveAnimationMode(): invalid Shape" );
388 const bool bPrevAnimState( rShape
->isBackgroundDetached() );
390 rShape
->leaveAnimationMode();
392 // if this call _really_ ended the animation mode at
393 // rShape, insert it to our leave animation queue, to
394 // perform the necessary layer reorg lazily on
395 // LayerManager::update()/render().
396 if( bPrevAnimState
!= rShape
->isBackgroundDetached() )
399 mbLayerAssociationDirty
= true;
401 // shape needs update, no previous rendering, fast
403 if( rShape
->isVisible() )
404 notifyShapeUpdate( rShape
);
407 // TODO(P1): this can lead to potential wasted effort, if
408 // a shape gets toggled animated/unanimated a few times
409 // between two frames, returning to the original state.
412 void LayerManager::notifyShapeUpdate( const ShapeSharedPtr
& rShape
)
414 if( !mbActive
|| mrViews
.empty() )
417 // hidden sprite-shape needs render() call still, to hide sprite
418 if( rShape
->isVisible() || rShape
->isBackgroundDetached() )
419 maUpdateShapes
.insert( rShape
);
421 addUpdateArea( rShape
);
424 bool LayerManager::isUpdatePending() const
429 if( mbLayerAssociationDirty
|| !maUpdateShapes
.empty() )
432 return std::any_of( maLayers
.begin(),
434 std::mem_fn(&Layer::isUpdatePending
) );
437 bool LayerManager::updateSprites()
441 // send update() calls to every shape in the
442 // maUpdateShapes set, which is _animated_ (i.e. a
444 for( const auto& pShape
: maUpdateShapes
)
446 if( pShape
->isBackgroundDetached() )
448 // can update shape directly, without
449 // affecting layer content (shape is
450 // currently displayed in a sprite)
451 if( !pShape
->update() )
452 bRet
= false; // delay error exit
456 // TODO(P2): addUpdateArea() involves log(n)
457 // search for shape layer. Have a frequent
458 // shape/layer association cache, or ptr back to
459 // layer at the shape?
461 // cannot update shape directly, it's not
462 // animated and update() calls will prolly
463 // overwrite other page content.
464 addUpdateArea( pShape
);
468 maUpdateShapes
.clear();
473 bool LayerManager::update()
480 // going to render - better flush any pending layer reorg
482 updateShapeLayers(false);
485 bRet
= updateSprites();
487 // any non-sprite update areas left?
488 if( std::none_of( maLayers
.begin(),
490 std::mem_fn( &Layer::isUpdatePending
) ) )
491 return bRet
; // nope, done.
493 // update each shape on each layer, that has
495 bool bIsCurrLayerUpdating(false);
496 Layer::EndUpdater aEndUpdater
;
497 LayerSharedPtr pCurrLayer
;
498 for( const auto& rShape
: maAllShapes
)
500 LayerSharedPtr pLayer
= rShape
.second
.lock();
501 if( pLayer
!= pCurrLayer
)
504 bIsCurrLayerUpdating
= pCurrLayer
->isUpdatePending();
506 if( bIsCurrLayerUpdating
)
507 aEndUpdater
= pCurrLayer
->beginUpdate();
510 if( bIsCurrLayerUpdating
&&
511 !rShape
.first
->isBackgroundDetached() &&
512 pCurrLayer
->isInsideUpdateArea(rShape
.first
) )
514 if( !rShape
.first
->render() )
524 /** Little wrapper around a Canvas, to render one-shot
527 class DummyLayer
: public ViewLayer
530 explicit DummyLayer( const ::cppcanvas::CanvasSharedPtr
& rCanvas
) :
535 virtual bool isOnView(std::shared_ptr
<View
> const& /*rView*/) const override
537 return true; // visible on all views
540 virtual ::cppcanvas::CanvasSharedPtr
getCanvas() const override
545 virtual void clear() const override
550 virtual void clearAll() const override
555 virtual ::cppcanvas::CustomSpriteSharedPtr
createSprite( const ::basegfx::B2DSize
& /*rSpriteSizePixel*/,
556 double /*nSpritePrio*/ ) const override
558 ENSURE_OR_THROW( false,
559 "DummyLayer::createSprite(): This method is not supposed to be called!" );
560 return ::cppcanvas::CustomSpriteSharedPtr();
563 virtual void setPriority( const basegfx::B1DRange
& /*rRange*/ ) override
565 OSL_FAIL( "BitmapView::setPriority(): This method is not supposed to be called!" );
568 virtual css::geometry::IntegerSize2D
getTranslationOffset() const override
570 return geometry::IntegerSize2D(0,0);
573 virtual ::basegfx::B2DHomMatrix
getTransformation() const override
575 return mpCanvas
->getTransformation();
578 virtual ::basegfx::B2DHomMatrix
getSpriteTransformation() const override
580 OSL_FAIL( "BitmapView::getSpriteTransformation(): This method is not supposed to be called!" );
581 return ::basegfx::B2DHomMatrix();
584 virtual void setClip( const ::basegfx::B2DPolyPolygon
& /*rClip*/ ) override
586 OSL_FAIL( "BitmapView::setClip(): This method is not supposed to be called!" );
589 virtual bool resize( const ::basegfx::B2DRange
& /*rArea*/ ) override
591 OSL_FAIL( "BitmapView::resize(): This method is not supposed to be called!" );
596 ::cppcanvas::CanvasSharedPtr mpCanvas
;
600 bool LayerManager::renderTo( const ::cppcanvas::CanvasSharedPtr
& rTargetCanvas
) const
603 ViewLayerSharedPtr
pTmpLayer( new DummyLayer( rTargetCanvas
) );
605 for( const auto& rShape
: maAllShapes
)
609 // forward to all shape's addViewLayer method (which
610 // we request to render the Shape on the new
611 // ViewLayer. Since we add the shapes in the
612 // maShapeSet order (which is also the render order),
613 // this is equivalent to a subsequent render() call)
614 rShape
.first
->addViewLayer( pTmpLayer
,
617 // and remove again, this is only temporary
618 rShape
.first
->removeViewLayer( pTmpLayer
);
620 catch( uno::Exception
& )
622 // TODO(E1): Might be superfluous. Nowadays,
623 // addViewLayer swallows all errors, anyway.
624 SAL_WARN( "slideshow", comphelper::anyToString( cppu::getCaughtException() ) );
625 // at least one shape could not be rendered
633 void LayerManager::addUpdateArea( ShapeSharedPtr
const& rShape
)
635 OSL_ASSERT( !maLayers
.empty() ); // always at least background layer
636 ENSURE_OR_THROW( rShape
, "LayerManager::addUpdateArea(): invalid Shape" );
638 const LayerShapeMap::const_iterator
aShapeEntry( maAllShapes
.find(rShape
) );
640 if( aShapeEntry
== maAllShapes
.end() )
643 LayerSharedPtr pLayer
= aShapeEntry
->second
.lock();
645 pLayer
->addUpdateRange( rShape
->getUpdateArea() );
648 void LayerManager::commitLayerChanges( std::size_t nCurrLayerIndex
,
649 LayerShapeMap::const_iterator aFirstLayerShape
,
650 const LayerShapeMap::const_iterator
& aEndLayerShapes
)
652 const bool bLayerExists( maLayers
.size() > nCurrLayerIndex
);
655 const LayerSharedPtr
& rLayer( maLayers
.at(nCurrLayerIndex
) );
656 const bool bLayerResized( rLayer
->commitBounds() );
657 rLayer
->setPriority( basegfx::B1DRange(nCurrLayerIndex
,
658 nCurrLayerIndex
+1) );
662 // need to re-render whole layer - start from
664 rLayer
->clearContent();
666 // render and remove from update set
667 while( aFirstLayerShape
!= aEndLayerShapes
)
669 maUpdateShapes
.erase(aFirstLayerShape
->first
);
670 aFirstLayerShape
->first
->render();
677 LayerSharedPtr
LayerManager::createForegroundLayer() const
679 OSL_ASSERT( mbActive
);
681 LayerSharedPtr
pLayer( Layer::createLayer() );
683 // create ViewLayers for all registered views, and add to
684 // newly created layer.
685 for( const auto& rView
: mrViews
)
686 pLayer
->addView( rView
);
691 void LayerManager::updateShapeLayers( bool bBackgroundLayerPainted
)
693 OSL_ASSERT( !maLayers
.empty() ); // always at least background layer
694 OSL_ASSERT( mbActive
);
696 // do we need to process shapes?
697 if( !mbLayerAssociationDirty
)
700 if( mbDisableAnimationZOrder
)
702 // layer setup happened elsewhere, is only bg layer
704 mbLayerAssociationDirty
= false;
708 // scan through maAllShapes, and determine shape animation
709 // discontinuities: when a shape that has
710 // isBackgroundDetached() return false follows a shape
711 // with isBackgroundDetached() true, the former and all
712 // following ones must be moved into an own layer.
714 // to avoid tons of temporaries, create weak_ptr to Layers
716 std::vector
< LayerWeakPtr
> aWeakLayers(maLayers
.size());
717 std::copy(maLayers
.begin(),maLayers
.end(),aWeakLayers
.begin());
719 std::size_t nCurrLayerIndex(0);
720 bool bIsBackgroundLayer(true);
721 bool bLastWasBackgroundDetached(false); // last shape sprite state
722 LayerShapeMap::iterator
aCurrShapeEntry( maAllShapes
.begin() );
723 LayerShapeMap::iterator
aCurrLayerFirstShapeEntry( maAllShapes
.begin() );
724 const LayerShapeMap::iterator
aEndShapeEntry ( maAllShapes
.end() );
725 while( aCurrShapeEntry
!= aEndShapeEntry
)
727 const ShapeSharedPtr
pCurrShape( aCurrShapeEntry
->first
);
728 const bool bThisIsBackgroundDetached(
729 pCurrShape
->isBackgroundDetached() );
731 if( bLastWasBackgroundDetached
&&
732 !bThisIsBackgroundDetached
)
734 // discontinuity found - current shape needs to
735 // get into a new layer
738 // commit changes to previous layer
739 commitLayerChanges(nCurrLayerIndex
,
740 aCurrLayerFirstShapeEntry
,
742 aCurrLayerFirstShapeEntry
=aCurrShapeEntry
;
744 bIsBackgroundLayer
= false;
746 if( aWeakLayers
.size() <= nCurrLayerIndex
||
747 aWeakLayers
.at(nCurrLayerIndex
) != aCurrShapeEntry
->second
)
749 // no more layers left, or shape was not
750 // member of this layer - create a new one
751 maLayers
.insert( maLayers
.begin()+nCurrLayerIndex
,
752 createForegroundLayer() );
753 aWeakLayers
.insert( aWeakLayers
.begin()+nCurrLayerIndex
,
754 maLayers
[nCurrLayerIndex
] );
758 OSL_ASSERT( maLayers
.size() == aWeakLayers
.size() );
760 // note: using indices here, since vector::insert
761 // above invalidates iterators
762 LayerSharedPtr
& rCurrLayer( maLayers
.at(nCurrLayerIndex
) );
763 LayerWeakPtr
& rCurrWeakLayer( aWeakLayers
.at(nCurrLayerIndex
) );
764 if( rCurrWeakLayer
!= aCurrShapeEntry
->second
)
766 // mismatch: shape is not contained in current
767 // layer - move shape to that layer, then.
768 maLayers
.at(nCurrLayerIndex
)->setShapeViews(
771 // layer got new shape(s), need full repaint, if
773 if( !bThisIsBackgroundDetached
&& pCurrShape
->isVisible() )
775 LayerSharedPtr
pOldLayer( aCurrShapeEntry
->second
.lock() );
778 // old layer still valid? then we need to
779 // repaint former shape area
780 pOldLayer
->addUpdateRange(
781 pCurrShape
->getUpdateArea() );
784 // render on new layer (only if not
785 // explicitly disabled)
786 if( !(bBackgroundLayerPainted
&& bIsBackgroundLayer
) )
787 maUpdateShapes
.insert( pCurrShape
);
790 aCurrShapeEntry
->second
= rCurrWeakLayer
;
793 // update layerbounds regardless of the fact that the
794 // shape might be contained in said layer
795 // already. updateBounds() is dumb and needs to
796 // collect all shape bounds.
797 // of course, no need to expand layer bounds for
798 // shapes that reside in sprites themselves.
799 if( !bThisIsBackgroundDetached
&& !bIsBackgroundLayer
)
800 rCurrLayer
->updateBounds( pCurrShape
);
802 bLastWasBackgroundDetached
= bThisIsBackgroundDetached
;
806 // commit very last layer data
807 commitLayerChanges(nCurrLayerIndex
,
808 aCurrLayerFirstShapeEntry
,
811 // any layers left? Bin them!
812 if( maLayers
.size() > nCurrLayerIndex
+1 )
813 maLayers
.erase(maLayers
.begin()+nCurrLayerIndex
+1,
816 mbLayerAssociationDirty
= false;
821 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */