Branch data 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 <canvas/debug.hxx>
21 : : #include <tools/diagnose_ex.h>
22 : : #include <canvas/canvastools.hxx>
23 : :
24 : : #include "eventqueue.hxx"
25 : : #include "eventmultiplexer.hxx"
26 : : #include "slideview.hxx"
27 : : #include "delayevent.hxx"
28 : : #include "unoview.hxx"
29 : :
30 : : #include <rtl/instance.hxx>
31 : : #include <cppuhelper/basemutex.hxx>
32 : : #include <cppuhelper/compbase2.hxx>
33 : : #include <cppuhelper/implementationentry.hxx>
34 : : #include <cppuhelper/interfacecontainer.h>
35 : : #include <comphelper/make_shared_from_uno.hxx>
36 : :
37 : : #include <cppcanvas/spritecanvas.hxx>
38 : : #include <cppcanvas/customsprite.hxx>
39 : : #include <cppcanvas/vclfactory.hxx>
40 : : #include <cppcanvas/basegfxfactory.hxx>
41 : :
42 : : #include <tools/debug.hxx>
43 : :
44 : : #include <basegfx/range/b1drange.hxx>
45 : : #include <basegfx/range/b2drange.hxx>
46 : : #include <basegfx/range/b2irange.hxx>
47 : : #include <basegfx/point/b2dpoint.hxx>
48 : : #include <basegfx/polygon/b2dpolygon.hxx>
49 : : #include <basegfx/matrix/b2dhommatrix.hxx>
50 : : #include <basegfx/polygon/b2dpolygontools.hxx>
51 : : #include <basegfx/polygon/b2dpolypolygontools.hxx>
52 : : #include <basegfx/tools/canvastools.hxx>
53 : : #include <basegfx/polygon/b2dpolygonclipper.hxx>
54 : : #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
55 : :
56 : : #include <com/sun/star/presentation/XSlideShow.hpp>
57 : :
58 : : #include <boost/noncopyable.hpp>
59 : : #include <boost/bind.hpp>
60 : : #include <boost/weak_ptr.hpp>
61 : :
62 : : #include <vector>
63 : : #include <iterator>
64 : : #include <algorithm>
65 : :
66 : : using namespace com::sun::star;
67 : :
68 : : namespace slideshow {
69 : : namespace internal {
70 : :
71 : : namespace {
72 : :
73 : : struct StaticUnitRectPoly : public rtl::StaticWithInit<basegfx::B2DPolygon, StaticUnitRectPoly>
74 : : {
75 : : basegfx::B2DPolygon operator()()
76 : : {
77 : : return basegfx::tools::createUnitPolygon();
78 : : }
79 : : };
80 : :
81 : : /** Sprite entry, to store sprite plus priority
82 : :
83 : : The operator<() defines a strict weak ordering of sprites, sort
84 : : key is the sprite priority.
85 : : */
86 : 0 : struct SpriteEntry
87 : : {
88 : 0 : SpriteEntry( const cppcanvas::CustomSpriteSharedPtr& rSprite,
89 : : double nPrio ) :
90 : : mpSprite( rSprite ),
91 : 0 : mnPriority( nPrio )
92 : : {
93 : 0 : }
94 : :
95 : 0 : bool operator<(const SpriteEntry& rRHS) const
96 : : {
97 : 0 : return mnPriority < rRHS.mnPriority;
98 : : }
99 : :
100 : : boost::weak_ptr< cppcanvas::CustomSprite > mpSprite;
101 : : double mnPriority;
102 : : };
103 : :
104 : : typedef std::vector< SpriteEntry > SpriteVector;
105 : :
106 : :
107 : : /** Create a clip polygon for slide views
108 : :
109 : : @param rClip
110 : : Clip to set (can be empty)
111 : :
112 : : @param rCanvas
113 : : Canvas to create the clip polygon for
114 : :
115 : : @param rUserSize
116 : : The size of the view. Note that the returned clip will
117 : : <em>always</em> clip to at least the rect defined herein.
118 : :
119 : : @return the view clip polygon, in view coordinates, which is
120 : : guaranteed to at least clip to the view size.
121 : : */
122 : 0 : basegfx::B2DPolyPolygon createClipPolygon( const basegfx::B2DPolyPolygon& rClip,
123 : : const cppcanvas::CanvasSharedPtr& /*rCanvas*/,
124 : : const basegfx::B2DSize& rUserSize )
125 : : {
126 : : // setup canvas clipping
127 : : // =====================
128 : :
129 : : // AW: Simplified
130 : 0 : const basegfx::B2DRange aClipRange(0, 0, rUserSize.getX(), rUserSize.getY());
131 : :
132 : 0 : if(rClip.count())
133 : : {
134 : 0 : return basegfx::tools::clipPolyPolygonOnRange(rClip, aClipRange, true, false);
135 : : }
136 : : else
137 : : {
138 : 0 : return basegfx::B2DPolyPolygon(basegfx::tools::createPolygonFromRect(aClipRange));
139 : : }
140 : : }
141 : :
142 : : /** Prepare given clip polygon to be stored as the current clip
143 : :
144 : : Note that this is separate from createClipPolygon(), to allow
145 : : SlideView implementations to store this intermediate result
146 : : (createClipPolygon() has to be called every time the view size
147 : : changes)
148 : : */
149 : 0 : basegfx::B2DPolyPolygon prepareClip( const basegfx::B2DPolyPolygon& rClip )
150 : : {
151 : 0 : basegfx::B2DPolyPolygon aClip( rClip );
152 : :
153 : : // TODO(P2): unnecessary, once XCanvas is correctly handling this
154 : : // AW: Should be no longer necessary; tools are now bezier-safe
155 : 0 : if( aClip.areControlPointsUsed() )
156 : 0 : aClip = basegfx::tools::adaptiveSubdivideByAngle( aClip );
157 : :
158 : : // normalize polygon, preparation for clipping
159 : : // in updateCanvas()
160 : 0 : aClip = basegfx::tools::correctOrientations(aClip);
161 : 0 : aClip = basegfx::tools::solveCrossovers(aClip);
162 : 0 : aClip = basegfx::tools::stripNeutralPolygons(aClip);
163 : 0 : aClip = basegfx::tools::stripDispensablePolygons(aClip, false);
164 : :
165 : 0 : return aClip;
166 : : }
167 : :
168 : :
169 : 0 : void clearRect( ::cppcanvas::CanvasSharedPtr const& pCanvas,
170 : : basegfx::B2IRange const& rArea )
171 : : {
172 : : // convert clip polygon to device coordinate system
173 : 0 : ::basegfx::B2DPolyPolygon const* pClipPoly( pCanvas->getClip() );
174 : 0 : if( pClipPoly )
175 : : {
176 : 0 : ::basegfx::B2DPolyPolygon aClipPoly( *pClipPoly );
177 : 0 : aClipPoly.transform( pCanvas->getTransformation() );
178 : 0 : pCanvas->setClip( aClipPoly );
179 : : }
180 : :
181 : : // set transformation to identitiy (->device pixel)
182 : 0 : pCanvas->setTransformation( ::basegfx::B2DHomMatrix() );
183 : :
184 : : // #i42440# Fill the _full_ background in
185 : : // black. Since we had to extend the bitmap by one
186 : : // pixel, and the bitmap is initialized white,
187 : : // depending on the slide content a one pixel wide
188 : : // line will show to the bottom and the right.
189 : : const ::basegfx::B2DPolygon aPoly(
190 : : ::basegfx::tools::createPolygonFromRect(
191 : 0 : basegfx::B2DRange(rArea)));
192 : :
193 : : ::cppcanvas::PolyPolygonSharedPtr pPolyPoly(
194 : 0 : ::cppcanvas::BaseGfxFactory::getInstance().createPolyPolygon( pCanvas,
195 : 0 : aPoly ) );
196 : :
197 : 0 : if( pPolyPoly )
198 : : {
199 : 0 : pPolyPoly->setCompositeOp( cppcanvas::CanvasGraphic::SOURCE );
200 : 0 : pPolyPoly->setRGBAFillColor( 0xFFFFFF00U );
201 : 0 : pPolyPoly->draw();
202 : 0 : }
203 : :
204 : : #if OSL_DEBUG_LEVEL >= 2 && defined(DBG_UTIL)
205 : : ::cppcanvas::CanvasSharedPtr pCliplessCanvas( pCanvas->clone() );
206 : : pCliplessCanvas->setClip();
207 : :
208 : : if( pCanvas->getClip() )
209 : : {
210 : : ::cppcanvas::PolyPolygonSharedPtr pPolyPoly2(
211 : : ::cppcanvas::BaseGfxFactory::getInstance().createPolyPolygon( pCliplessCanvas,
212 : : aPoly ));
213 : : if( pPolyPoly2 )
214 : : {
215 : : pPolyPoly2->setRGBALineColor( 0x008000FFU );
216 : : pPolyPoly2->draw();
217 : : }
218 : : }
219 : : #endif
220 : 0 : }
221 : :
222 : : /** Get bounds in pixel
223 : :
224 : : @param rLayerBounds
225 : : Bound rect, in user space coordinates
226 : :
227 : : @param rTransformation
228 : : User space to device pixel transformation
229 : :
230 : : @return the layer bounds in pixel, extended by one pixel to the
231 : : right and bottom
232 : : */
233 : 0 : basegfx::B2IRange getLayerBoundsPixel( basegfx::B2DRange const& rLayerBounds,
234 : : basegfx::B2DHomMatrix const& rTransformation )
235 : : {
236 : 0 : ::basegfx::B2DRange aTmpRect;
237 : : ::canvas::tools::calcTransformedRectBounds( aTmpRect,
238 : : rLayerBounds,
239 : 0 : rTransformation );
240 : :
241 : 0 : if( aTmpRect.isEmpty() )
242 : 0 : return ::basegfx::B2IRange();
243 : :
244 : : // #i42440# Returned layer size is one pixel too small, as
245 : : // rendering happens one pixel to the right and below the
246 : : // actual bound rect.
247 : : return ::basegfx::B2IRange( ::basegfx::fround(aTmpRect.getMinX()),
248 : : ::basegfx::fround(aTmpRect.getMinY()),
249 : 0 : ::basegfx::fround(aTmpRect.getMaxX()) + 1,
250 : 0 : ::basegfx::fround(aTmpRect.getMaxY()) + 1 );
251 : : }
252 : :
253 : :
254 : : // ----------------------------------------------------------------
255 : :
256 : : /** Container class for sprites issued by a ViewLayer
257 : :
258 : : This class handles the sprite prioritization issues, that are
259 : : needed for layer sprites (e.g. the need to re-prioritize sprites
260 : : when the layer changes prio).
261 : : */
262 : 0 : class LayerSpriteContainer
263 : : {
264 : : /** Max fill level of maSprites, before we try to prune it from
265 : : deceased sprites
266 : : */
267 : : enum{ SPRITE_ULLAGE=256 };
268 : :
269 : : /** All sprites that have been issued by this container (pruned
270 : : from time to time, for invalid references). This vector is
271 : : kept sorted with increasing sprite priority.
272 : : */
273 : : SpriteVector maSprites;
274 : :
275 : : /// Priority of this layer, relative to other view layers
276 : : basegfx::B1DRange maLayerPrioRange;
277 : :
278 : 0 : double getSpritePriority( std::size_t nSpriteNum ) const
279 : : {
280 : : // divide the available layer range equally between all
281 : : // sprites, assign upper bound of individual sprite range as
282 : : // sprite prio (the layer itself gets assigned the lower bound
283 : : // of sprite 0's individual range):
284 : : //
285 : : // | layer 0 | layer 1 | ...
286 : : // | sprite 0 | sprite 1 | sprite 0 | sprite 1 | ...
287 : 0 : return maLayerPrioRange.getMinimum() + maLayerPrioRange.getRange()*(nSpriteNum+1)/(maSprites.size()+1);
288 : : }
289 : :
290 : : /** Rescan sprite vector, and remove deceased sprites (and reset
291 : : sprite prio)
292 : :
293 : : @param aBegin
294 : : Iterator to the first entry to rescan
295 : : */
296 : 0 : void updateSprites()
297 : : {
298 : 0 : SpriteVector aValidSprites;
299 : :
300 : : // check all sprites for validity and set new priority
301 : 0 : SpriteVector::iterator aCurrSprite( maSprites.begin() );
302 : 0 : const SpriteVector::iterator aEnd( maSprites.end() );
303 : 0 : while( aCurrSprite != aEnd )
304 : : {
305 : 0 : cppcanvas::CustomSpriteSharedPtr pCurrSprite( aCurrSprite->mpSprite.lock() );
306 : :
307 : 0 : if( pCurrSprite )
308 : : {
309 : : // only copy still valid sprites over to the refreshed
310 : : // sprite vector.
311 : 0 : aValidSprites.push_back( *aCurrSprite );
312 : :
313 : 0 : pCurrSprite->setPriority(
314 : 0 : getSpritePriority( aValidSprites.size()-1 ));
315 : : }
316 : :
317 : 0 : ++aCurrSprite;
318 : 0 : }
319 : :
320 : : // replace sprite list with pruned one
321 : 0 : maSprites.swap( aValidSprites );
322 : 0 : }
323 : :
324 : : public:
325 : 0 : LayerSpriteContainer() :
326 : : maSprites(),
327 : 0 : maLayerPrioRange()
328 : : {
329 : 0 : }
330 : :
331 : 0 : basegfx::B1DRange getLayerPriority() const
332 : : {
333 : 0 : return maLayerPrioRange;
334 : : }
335 : :
336 : 0 : void setLayerPriority( const basegfx::B1DRange& rRange )
337 : : {
338 : 0 : if( rRange != maLayerPrioRange )
339 : : {
340 : 0 : maLayerPrioRange = rRange;
341 : :
342 : : // prune and recalc sprite prios
343 : 0 : updateSprites();
344 : : }
345 : 0 : }
346 : :
347 : 0 : void addSprite( const cppcanvas::CustomSpriteSharedPtr& pSprite,
348 : : double nPriority )
349 : : {
350 : 0 : if( !pSprite )
351 : 0 : return;
352 : :
353 : 0 : SpriteEntry aEntry( pSprite,nPriority );
354 : :
355 : : // insert new sprite, such that vector stays sorted
356 : : SpriteVector::iterator aInsertPos(
357 : : maSprites.insert(
358 : : std::lower_bound( maSprites.begin(),
359 : : maSprites.end(),
360 : : aEntry ),
361 : 0 : aEntry ));
362 : :
363 : 0 : const std::size_t nNumSprites( maSprites.size() );
364 : 0 : if( nNumSprites > SPRITE_ULLAGE ||
365 : 0 : maSprites.end() - aInsertPos > 1 )
366 : : {
367 : : // updateSprites() also updates all sprite prios
368 : 0 : updateSprites();
369 : : }
370 : : else
371 : : {
372 : : // added sprite to the end, and not too many sprites in
373 : : // vector - perform optimized update (only need to set
374 : : // prio). This basically caters for the common case of
375 : : // iterated character animations, which generate lots of
376 : : // sprites, all added to the end.
377 : 0 : pSprite->setPriority(
378 : 0 : getSpritePriority( nNumSprites-1 ));
379 : 0 : }
380 : : }
381 : :
382 : 0 : void clear()
383 : : {
384 : 0 : maSprites.clear();
385 : 0 : }
386 : : };
387 : :
388 : :
389 : : // ----------------------------------------------------------------
390 : :
391 : :
392 : : /** This class provides layers for a slide view
393 : :
394 : : Layers are used to render animations with the correct z order -
395 : : because sprites are always in front of the static canvas
396 : : background, shapes that must appear <em<before</em> an animation
397 : : must also be displayed as a sprite.
398 : :
399 : : Each layer has a priority assigned to it (valid range [0,1]), which
400 : : also affects all sprites created for this specific layer - i.e. if
401 : : the layer priority changes, the sprites change z order together
402 : : with their parent.
403 : : */
404 : 0 : class SlideViewLayer : public ViewLayer,
405 : : private boost::noncopyable
406 : : {
407 : : /// Smart container for all sprites issued by this layer
408 : : mutable LayerSpriteContainer maSpriteContainer;
409 : :
410 : : /// Bounds of this layer in user space coordinates
411 : : basegfx::B2DRange maLayerBounds;
412 : :
413 : : /// Bounds of this layer in device pixel
414 : : mutable basegfx::B2IRange maLayerBoundsPixel;
415 : :
416 : : /// Current clip polygon in user coordinates
417 : : basegfx::B2DPolyPolygon maClip;
418 : :
419 : : /// Current size of the view in user coordinates
420 : : basegfx::B2DSize maUserSize;
421 : :
422 : : /// Current overall view transformation
423 : : basegfx::B2DHomMatrix maTransformation;
424 : :
425 : : /// 'parent' canvas, this viewlayer is associated with
426 : : const cppcanvas::SpriteCanvasSharedPtr mpSpriteCanvas;
427 : :
428 : : /** output surface (necessarily a sprite, won't otherwise be able
429 : : to display anything <em>before</em> other sprites)
430 : : */
431 : : mutable cppcanvas::CustomSpriteSharedPtr mpSprite;
432 : :
433 : : /// actual output canvas retrieved from a sprite
434 : : mutable cppcanvas::CanvasSharedPtr mpOutputCanvas;
435 : :
436 : : /// ptr back to owning view. needed for isOnView() method
437 : : View const* const mpParentView;
438 : :
439 : : public:
440 : : /** Create a new layer
441 : :
442 : : @param pCanvas
443 : : Sprite canvas to create the layer on
444 : :
445 : : @param rTransform
446 : : Initial overall canvas transformation
447 : :
448 : : @param rLayerBounds
449 : : Initial layer bounds, in view coordinate system
450 : : */
451 : 0 : SlideViewLayer( const cppcanvas::SpriteCanvasSharedPtr& pCanvas,
452 : : const basegfx::B2DHomMatrix& rTransform,
453 : : const basegfx::B2DRange& rLayerBounds,
454 : : const basegfx::B2DSize& rUserSize,
455 : : View const* const pParentView) :
456 : : maSpriteContainer(),
457 : : maLayerBounds(rLayerBounds),
458 : : maLayerBoundsPixel(),
459 : : maClip(),
460 : : maUserSize(rUserSize),
461 : : maTransformation(rTransform),
462 : : mpSpriteCanvas(pCanvas),
463 : : mpSprite(),
464 : : mpOutputCanvas(),
465 : 0 : mpParentView(pParentView)
466 : : {
467 : 0 : }
468 : :
469 : 0 : void updateView( const basegfx::B2DHomMatrix& rMatrix,
470 : : const basegfx::B2DSize& rUserSize )
471 : : {
472 : 0 : maTransformation = rMatrix;
473 : 0 : maUserSize = rUserSize;
474 : :
475 : : // limit layer bounds to visible screen
476 : : maLayerBounds.intersect( basegfx::B2DRange(0.0,
477 : : 0.0,
478 : : maUserSize.getX(),
479 : 0 : maUserSize.getY()) );
480 : :
481 : : basegfx::B2IRange const& rNewLayerPixel(
482 : : getLayerBoundsPixel(maLayerBounds,
483 : 0 : maTransformation) );
484 : 0 : if( rNewLayerPixel != maLayerBoundsPixel )
485 : : {
486 : : // re-gen sprite with new size
487 : 0 : mpOutputCanvas.reset();
488 : 0 : mpSprite.reset();
489 : : }
490 : 0 : }
491 : :
492 : : private:
493 : : // ViewLayer interface
494 : : // ----------------------------------------------
495 : :
496 : 0 : virtual cppcanvas::CustomSpriteSharedPtr createSprite(
497 : : const ::basegfx::B2DSize& rSpriteSizePixel,
498 : : double nPriority ) const
499 : : {
500 : : cppcanvas::CustomSpriteSharedPtr pSprite(
501 : 0 : mpSpriteCanvas->createCustomSprite( rSpriteSizePixel ) );
502 : :
503 : : maSpriteContainer.addSprite( pSprite,
504 : 0 : nPriority );
505 : :
506 : 0 : return pSprite;
507 : : }
508 : :
509 : 0 : virtual void setPriority( const basegfx::B1DRange& rRange )
510 : : {
511 : : OSL_ENSURE( !rRange.isEmpty() &&
512 : : rRange.getMinimum() >= 1.0,
513 : : "SlideViewLayer::setPriority(): prio MUST be larger than 1.0 (because "
514 : : "the background layer already lies there)" );
515 : :
516 : 0 : maSpriteContainer.setLayerPriority( rRange );
517 : :
518 : 0 : if( mpSprite )
519 : 0 : mpSprite->setPriority( rRange.getMinimum() );
520 : 0 : }
521 : :
522 : 0 : virtual basegfx::B2DHomMatrix getTransformation() const
523 : : {
524 : : // Offset given transformation by left, top border of given
525 : : // range (after transformation through given transformation)
526 : 0 : basegfx::B2DRectangle aTmpRect;
527 : : canvas::tools::calcTransformedRectBounds( aTmpRect,
528 : : maLayerBounds,
529 : 0 : maTransformation );
530 : :
531 : 0 : basegfx::B2DHomMatrix aMatrix( maTransformation );
532 : :
533 : : // Add translation according to the origin of aTmpRect. Ignore the
534 : : // translation when aTmpRect was not properly initialized.
535 : 0 : if ( ! aTmpRect.isEmpty())
536 : : {
537 : 0 : aMatrix.translate( -basegfx::fround(aTmpRect.getMinX()),
538 : 0 : -basegfx::fround(aTmpRect.getMinY()) );
539 : : }
540 : :
541 : 0 : return aMatrix;
542 : : }
543 : :
544 : 0 : virtual basegfx::B2DHomMatrix getSpriteTransformation() const
545 : : {
546 : 0 : return maTransformation;
547 : : }
548 : :
549 : 0 : virtual void clear() const
550 : : {
551 : : // grab canvas - that also lazy-initializes maLayerBoundsPixel
552 : 0 : cppcanvas::CanvasSharedPtr pCanvas=getCanvas()->clone();
553 : :
554 : : // clear whole canvas
555 : 0 : const basegfx::B2I64Tuple& rSpriteSize(maLayerBoundsPixel.getRange());
556 : : clearRect(pCanvas,
557 : 0 : basegfx::B2IRange(0,0,rSpriteSize.getX(),rSpriteSize.getY()));
558 : 0 : }
559 : :
560 : 0 : virtual void clearAll() const
561 : : {
562 : : // grab canvas - that also lazy-initializes maLayerBoundsPixel
563 : 0 : ::cppcanvas::CanvasSharedPtr pCanvas( getCanvas()->clone() );
564 : :
565 : : // clear layer clip, to clear whole area
566 : 0 : pCanvas->setClip();
567 : :
568 : : // clear whole canvas
569 : 0 : const basegfx::B2I64Tuple& rSpriteSize(maLayerBoundsPixel.getRange());
570 : : clearRect(pCanvas,
571 : 0 : basegfx::B2IRange(0,0,rSpriteSize.getX(),rSpriteSize.getY()));
572 : 0 : }
573 : :
574 : 0 : virtual bool isOnView(boost::shared_ptr<View> const& rView) const
575 : : {
576 : 0 : return rView.get() == mpParentView;
577 : : }
578 : :
579 : 0 : virtual cppcanvas::CanvasSharedPtr getCanvas() const
580 : : {
581 : 0 : if( !mpOutputCanvas )
582 : : {
583 : 0 : if( !mpSprite )
584 : : {
585 : : maLayerBoundsPixel = getLayerBoundsPixel(maLayerBounds,
586 : 0 : maTransformation);
587 : :
588 : : // HACK: ensure at least 1x1 pixel size. clients might
589 : : // need an actual canvas (e.g. for bound rect
590 : : // calculations) without rendering anything. Better
591 : : // solution: introduce something like a reference
592 : : // canvas for ViewLayers, which is always available.
593 : 0 : if( maLayerBoundsPixel.isEmpty() )
594 : 0 : maLayerBoundsPixel = basegfx::B2IRange(0,0,1,1);
595 : :
596 : 0 : const basegfx::B2I64Tuple& rSpriteSize(maLayerBoundsPixel.getRange());
597 : 0 : mpSprite = mpSpriteCanvas->createCustomSprite(
598 : 0 : basegfx::B2DVector(sal::static_int_cast<sal_Int32>(rSpriteSize.getX()),
599 : 0 : sal::static_int_cast<sal_Int32>(rSpriteSize.getY())) );
600 : :
601 : 0 : mpSprite->setPriority(
602 : 0 : maSpriteContainer.getLayerPriority().getMinimum() );
603 : :
604 : : #if OSL_DEBUG_LEVEL >= 2 && defined(DBG_UTIL)
605 : : mpSprite->movePixel(
606 : : basegfx::B2DPoint(maLayerBoundsPixel.getMinimum()) +
607 : : basegfx::B2DPoint(10,10) );
608 : :
609 : : mpSprite->setAlpha(0.5);
610 : : #else
611 : 0 : mpSprite->movePixel(
612 : 0 : basegfx::B2DPoint(maLayerBoundsPixel.getMinimum()) );
613 : :
614 : 0 : mpSprite->setAlpha(1.0);
615 : : #endif
616 : 0 : mpSprite->show();
617 : : }
618 : :
619 : 0 : ENSURE_OR_THROW( mpSprite,
620 : : "SlideViewLayer::getCanvas(): no layer sprite" );
621 : :
622 : 0 : mpOutputCanvas = mpSprite->getContentCanvas();
623 : :
624 : 0 : ENSURE_OR_THROW( mpOutputCanvas,
625 : : "SlideViewLayer::getCanvas(): sprite doesn't yield a canvas" );
626 : :
627 : : // new canvas retrieved - setup transformation and clip
628 : 0 : mpOutputCanvas->setTransformation( getTransformation() );
629 : 0 : mpOutputCanvas->setClip(
630 : : createClipPolygon( maClip,
631 : : mpOutputCanvas,
632 : 0 : maUserSize ));
633 : : }
634 : :
635 : 0 : return mpOutputCanvas;
636 : : }
637 : :
638 : 0 : virtual void setClip( const basegfx::B2DPolyPolygon& rClip )
639 : : {
640 : 0 : basegfx::B2DPolyPolygon aNewClip = prepareClip( rClip );
641 : :
642 : 0 : if( aNewClip != maClip )
643 : : {
644 : 0 : maClip = aNewClip;
645 : :
646 : 0 : if(mpOutputCanvas )
647 : 0 : mpOutputCanvas->setClip(
648 : : createClipPolygon( maClip,
649 : : mpOutputCanvas,
650 : 0 : maUserSize ));
651 : 0 : }
652 : 0 : }
653 : :
654 : 0 : virtual bool resize( const ::basegfx::B2DRange& rArea )
655 : : {
656 : 0 : const bool bRet( maLayerBounds != rArea );
657 : 0 : maLayerBounds = rArea;
658 : : updateView( maTransformation,
659 : 0 : maUserSize );
660 : :
661 : 0 : return bRet;
662 : : }
663 : : };
664 : :
665 : :
666 : : // ---------------------------------------------------------
667 : :
668 : : typedef cppu::WeakComponentImplHelper2<
669 : : ::com::sun::star::util::XModifyListener,
670 : : ::com::sun::star::awt::XPaintListener> SlideViewBase;
671 : :
672 : : /** SlideView class
673 : :
674 : : This class implements the View interface, encapsulating
675 : : <em>one</em> view a slideshow is displayed on.
676 : : */
677 : 0 : class SlideView : private cppu::BaseMutex,
678 : : public SlideViewBase,
679 : : public UnoView
680 : : {
681 : : public:
682 : : SlideView( const uno::Reference<presentation::XSlideShowView>& xView,
683 : : EventQueue& rEventQueue,
684 : : EventMultiplexer& rEventMultiplexer );
685 : : void updateCanvas();
686 : :
687 : : private:
688 : : // View:
689 : : virtual ViewLayerSharedPtr createViewLayer( const basegfx::B2DRange& rLayerBounds ) const;
690 : : virtual bool updateScreen() const;
691 : : virtual bool paintScreen() const;
692 : : virtual void setViewSize( const ::basegfx::B2DSize& );
693 : : virtual void setCursorShape( sal_Int16 nPointerShape );
694 : :
695 : : // ViewLayer interface
696 : : virtual bool isOnView(boost::shared_ptr<View> const& rView) const;
697 : : virtual void clear() const;
698 : : virtual void clearAll() const;
699 : : virtual cppcanvas::CanvasSharedPtr getCanvas() const;
700 : : virtual cppcanvas::CustomSpriteSharedPtr createSprite( const ::basegfx::B2DSize& rSpriteSizePixel,
701 : : double nPriority ) const;
702 : : virtual void setPriority( const basegfx::B1DRange& rRange );
703 : : virtual ::basegfx::B2DHomMatrix getTransformation() const;
704 : : virtual basegfx::B2DHomMatrix getSpriteTransformation() const;
705 : : virtual void setClip( const ::basegfx::B2DPolyPolygon& rClip );
706 : : virtual bool resize( const ::basegfx::B2DRange& rArea );
707 : :
708 : : // UnoView:
709 : : virtual void _dispose();
710 : : virtual uno::Reference<presentation::XSlideShowView> getUnoView()const;
711 : : virtual void setIsSoundEnabled (const bool bValue);
712 : : virtual bool isSoundEnabled (void) const;
713 : :
714 : : // XEventListener:
715 : : virtual void SAL_CALL disposing( lang::EventObject const& evt )
716 : : throw (uno::RuntimeException);
717 : : // XModifyListener:
718 : : virtual void SAL_CALL modified( const lang::EventObject& aEvent )
719 : : throw (uno::RuntimeException);
720 : : // XPaintListener:
721 : : virtual void SAL_CALL windowPaint( const awt::PaintEvent& e )
722 : : throw (uno::RuntimeException);
723 : :
724 : : // WeakComponentImplHelperBase:
725 : : virtual void SAL_CALL disposing();
726 : :
727 : : void updateClip();
728 : :
729 : : private:
730 : : typedef std::vector< boost::weak_ptr<SlideViewLayer> > ViewLayerVector;
731 : :
732 : : /// Prune viewlayers from deceased ones, optionally update them
733 : : void pruneLayers( bool bWithViewLayerUpdate=false ) const;
734 : :
735 : : /** Max fill level of maViewLayers, before we try to prune it from
736 : : deceased layers
737 : : */
738 : : enum{ LAYER_ULLAGE=8 };
739 : :
740 : : uno::Reference<presentation::XSlideShowView> mxView;
741 : : cppcanvas::SpriteCanvasSharedPtr mpCanvas;
742 : :
743 : : EventMultiplexer& mrEventMultiplexer;
744 : : EventQueue& mrEventQueue;
745 : :
746 : : mutable LayerSpriteContainer maSprites;
747 : : mutable ViewLayerVector maViewLayers;
748 : :
749 : : basegfx::B2DPolyPolygon maClip;
750 : :
751 : : basegfx::B2DHomMatrix maViewTransform;
752 : : basegfx::B2DSize maUserSize;
753 : : bool mbIsSoundEnabled;
754 : : };
755 : :
756 : :
757 : 0 : SlideView::SlideView( const uno::Reference<presentation::XSlideShowView>& xView,
758 : : EventQueue& rEventQueue,
759 : : EventMultiplexer& rEventMultiplexer ) :
760 : : SlideViewBase( m_aMutex ),
761 : : mxView( xView ),
762 : : mpCanvas(),
763 : : mrEventMultiplexer( rEventMultiplexer ),
764 : : mrEventQueue( rEventQueue ),
765 : : maSprites(),
766 : : maViewLayers(),
767 : : maClip(),
768 : : maViewTransform(),
769 : : maUserSize( 1.0, 1.0 ), // default size: one-by-one rectangle
770 : 0 : mbIsSoundEnabled(true)
771 : : {
772 : : // take care not constructing any UNO references to this _inside_
773 : : // ctor, shift that code to createSlideView()!
774 : 0 : ENSURE_OR_THROW( mxView.is(),
775 : : "SlideView::SlideView(): Invalid view" );
776 : :
777 : 0 : mpCanvas = cppcanvas::VCLFactory::getInstance().createSpriteCanvas(
778 : 0 : xView->getCanvas() );
779 : 0 : ENSURE_OR_THROW( mpCanvas,
780 : : "Could not create cppcanvas" );
781 : :
782 : : geometry::AffineMatrix2D aViewTransform(
783 : 0 : xView->getTransformation() );
784 : :
785 : 0 : if( basegfx::fTools::equalZero(
786 : : basegfx::B2DVector(aViewTransform.m00,
787 : 0 : aViewTransform.m10).getLength()) ||
788 : : basegfx::fTools::equalZero(
789 : : basegfx::B2DVector(aViewTransform.m01,
790 : 0 : aViewTransform.m11).getLength()) )
791 : : {
792 : : OSL_FAIL( "SlideView::SlideView(): Singular matrix!" );
793 : :
794 : 0 : canvas::tools::setIdentityAffineMatrix2D(aViewTransform);
795 : : }
796 : :
797 : : basegfx::unotools::homMatrixFromAffineMatrix(
798 : 0 : maViewTransform, aViewTransform );
799 : :
800 : : // once and forever: set fixed prio to this 'layer' (we're always
801 : : // the background layer)
802 : 0 : maSprites.setLayerPriority( basegfx::B1DRange(0.0,1.0) );
803 : 0 : }
804 : :
805 : 0 : void SlideView::disposing()
806 : : {
807 : 0 : osl::MutexGuard aGuard( m_aMutex );
808 : :
809 : 0 : maViewLayers.clear();
810 : 0 : maSprites.clear();
811 : 0 : mpCanvas.reset();
812 : :
813 : : // additionally, also de-register from XSlideShowView
814 : 0 : if (mxView.is())
815 : : {
816 : 0 : mxView->removeTransformationChangedListener( this );
817 : 0 : mxView->removePaintListener( this );
818 : 0 : mxView.clear();
819 : 0 : }
820 : 0 : }
821 : :
822 : 0 : ViewLayerSharedPtr SlideView::createViewLayer( const basegfx::B2DRange& rLayerBounds ) const
823 : : {
824 : 0 : osl::MutexGuard aGuard( m_aMutex );
825 : :
826 : 0 : ENSURE_OR_THROW( mpCanvas,
827 : : "SlideView::createViewLayer(): Disposed" );
828 : :
829 : 0 : const std::size_t nNumLayers( maViewLayers.size() );
830 : :
831 : : // avoid filling up layer vector with lots of deceased layer weak
832 : : // ptrs
833 : 0 : if( nNumLayers > LAYER_ULLAGE )
834 : 0 : pruneLayers();
835 : :
836 : : boost::shared_ptr<SlideViewLayer> pViewLayer( new SlideViewLayer(mpCanvas,
837 : 0 : getTransformation(),
838 : : rLayerBounds,
839 : : maUserSize,
840 : 0 : this) );
841 : 0 : maViewLayers.push_back( pViewLayer );
842 : :
843 : 0 : return pViewLayer;
844 : : }
845 : :
846 : 0 : bool SlideView::updateScreen() const
847 : : {
848 : 0 : osl::MutexGuard aGuard( m_aMutex );
849 : :
850 : 0 : ENSURE_OR_RETURN_FALSE( mpCanvas.get(),
851 : : "SlideView::updateScreen(): Disposed" );
852 : :
853 : 0 : return mpCanvas->updateScreen( false );
854 : : }
855 : :
856 : 0 : bool SlideView::paintScreen() const
857 : : {
858 : 0 : osl::MutexGuard aGuard( m_aMutex );
859 : :
860 : 0 : ENSURE_OR_RETURN_FALSE( mpCanvas.get(),
861 : : "SlideView::paintScreen(): Disposed" );
862 : :
863 : 0 : return mpCanvas->updateScreen( true );
864 : : }
865 : :
866 : 0 : void SlideView::clear() const
867 : : {
868 : 0 : osl::MutexGuard aGuard( m_aMutex );
869 : :
870 : : OSL_ENSURE( mxView.is() && mpCanvas,
871 : : "SlideView::clear(): Disposed" );
872 : 0 : if( !mxView.is() || !mpCanvas )
873 : 0 : return;
874 : :
875 : : // keep layer clip
876 : 0 : clearRect(getCanvas()->clone(),
877 : : getLayerBoundsPixel(
878 : : basegfx::B2DRange(0,0,
879 : : maUserSize.getX(),
880 : : maUserSize.getY()),
881 : 0 : getTransformation()));
882 : : }
883 : :
884 : 0 : void SlideView::clearAll() const
885 : : {
886 : 0 : osl::MutexGuard aGuard( m_aMutex );
887 : :
888 : : OSL_ENSURE( mxView.is() && mpCanvas,
889 : : "SlideView::clear(): Disposed" );
890 : 0 : if( !mxView.is() || !mpCanvas )
891 : 0 : return;
892 : :
893 : : // clear whole view
894 : 0 : mxView->clear();
895 : : }
896 : :
897 : 0 : void SlideView::setViewSize( const basegfx::B2DSize& rSize )
898 : : {
899 : 0 : osl::MutexGuard aGuard( m_aMutex );
900 : :
901 : 0 : maUserSize = rSize;
902 : 0 : updateCanvas();
903 : 0 : }
904 : :
905 : 0 : void SlideView::setCursorShape( sal_Int16 nPointerShape )
906 : : {
907 : 0 : osl::MutexGuard const guard( m_aMutex );
908 : :
909 : 0 : if (mxView.is())
910 : 0 : mxView->setMouseCursor( nPointerShape );
911 : 0 : }
912 : :
913 : 0 : bool SlideView::isOnView(boost::shared_ptr<View> const& rView) const
914 : : {
915 : 0 : return rView.get() == this;
916 : : }
917 : :
918 : 0 : cppcanvas::CanvasSharedPtr SlideView::getCanvas() const
919 : : {
920 : 0 : osl::MutexGuard aGuard( m_aMutex );
921 : :
922 : 0 : ENSURE_OR_THROW( mpCanvas,
923 : : "SlideView::getCanvas(): Disposed" );
924 : :
925 : 0 : return mpCanvas;
926 : : }
927 : :
928 : 0 : cppcanvas::CustomSpriteSharedPtr SlideView::createSprite(
929 : : const basegfx::B2DSize& rSpriteSizePixel,
930 : : double nPriority ) const
931 : : {
932 : 0 : osl::MutexGuard aGuard( m_aMutex );
933 : :
934 : 0 : ENSURE_OR_THROW( mpCanvas, "SlideView::createSprite(): Disposed" );
935 : :
936 : : cppcanvas::CustomSpriteSharedPtr pSprite(
937 : 0 : mpCanvas->createCustomSprite( rSpriteSizePixel ) );
938 : :
939 : : maSprites.addSprite( pSprite,
940 : 0 : nPriority );
941 : :
942 : 0 : return pSprite;
943 : : }
944 : :
945 : 0 : void SlideView::setPriority( const basegfx::B1DRange& /*rRange*/ )
946 : : {
947 : 0 : osl::MutexGuard aGuard( m_aMutex );
948 : :
949 : : OSL_FAIL( "SlideView::setPriority() is a NOOP for slide view - "
950 : 0 : "content will always be shown in the background" );
951 : 0 : }
952 : :
953 : 0 : basegfx::B2DHomMatrix SlideView::getTransformation() const
954 : : {
955 : 0 : osl::MutexGuard aGuard( m_aMutex );
956 : :
957 : 0 : basegfx::B2DHomMatrix aMatrix;
958 : 0 : aMatrix.scale( 1.0/maUserSize.getX(), 1.0/maUserSize.getY() );
959 : :
960 : 0 : return maViewTransform * aMatrix;
961 : : }
962 : :
963 : 0 : basegfx::B2DHomMatrix SlideView::getSpriteTransformation() const
964 : : {
965 : 0 : return getTransformation();
966 : : }
967 : :
968 : 0 : void SlideView::setClip( const basegfx::B2DPolyPolygon& rClip )
969 : : {
970 : 0 : osl::MutexGuard aGuard( m_aMutex );
971 : :
972 : 0 : basegfx::B2DPolyPolygon aNewClip = prepareClip( rClip );
973 : :
974 : 0 : if( aNewClip != maClip )
975 : : {
976 : 0 : maClip = aNewClip;
977 : :
978 : 0 : updateClip();
979 : 0 : }
980 : 0 : }
981 : :
982 : 0 : bool SlideView::resize( const ::basegfx::B2DRange& /*rArea*/ )
983 : : {
984 : 0 : osl::MutexGuard aGuard( m_aMutex );
985 : :
986 : : OSL_FAIL( "SlideView::resize(): ignored for the View, can't change size "
987 : : "effectively, anyway" );
988 : :
989 : 0 : return false;
990 : : }
991 : :
992 : 0 : uno::Reference<presentation::XSlideShowView> SlideView::getUnoView() const
993 : : {
994 : 0 : osl::MutexGuard aGuard( m_aMutex );
995 : 0 : return mxView;
996 : : }
997 : :
998 : 0 : void SlideView::setIsSoundEnabled (const bool bValue)
999 : : {
1000 : 0 : mbIsSoundEnabled = bValue;
1001 : 0 : }
1002 : :
1003 : 0 : bool SlideView::isSoundEnabled (void) const
1004 : : {
1005 : 0 : return mbIsSoundEnabled;
1006 : : }
1007 : :
1008 : 0 : void SlideView::_dispose()
1009 : : {
1010 : 0 : dispose();
1011 : 0 : }
1012 : :
1013 : : // XEventListener
1014 : 0 : void SlideView::disposing( lang::EventObject const& evt )
1015 : : throw (uno::RuntimeException)
1016 : : {
1017 : : (void)evt;
1018 : :
1019 : : // no deregistration necessary anymore, XView has left:
1020 : 0 : osl::MutexGuard const guard( m_aMutex );
1021 : :
1022 : 0 : if (mxView.is())
1023 : : {
1024 : : OSL_ASSERT( evt.Source == mxView );
1025 : 0 : mxView.clear();
1026 : : }
1027 : :
1028 : 0 : dispose();
1029 : 0 : }
1030 : :
1031 : : // XModifyListener
1032 : 0 : void SlideView::modified( const lang::EventObject& /*aEvent*/ )
1033 : : throw (uno::RuntimeException)
1034 : : {
1035 : 0 : osl::MutexGuard const guard( m_aMutex );
1036 : :
1037 : : OSL_ENSURE( mxView.is(), "SlideView::modified(): "
1038 : : "Disposed, but event received from XSlideShowView?!");
1039 : :
1040 : 0 : if( !mxView.is() )
1041 : : return;
1042 : :
1043 : : geometry::AffineMatrix2D aViewTransform(
1044 : 0 : mxView->getTransformation() );
1045 : :
1046 : 0 : if( basegfx::fTools::equalZero(
1047 : : basegfx::B2DVector(aViewTransform.m00,
1048 : 0 : aViewTransform.m10).getLength()) ||
1049 : : basegfx::fTools::equalZero(
1050 : : basegfx::B2DVector(aViewTransform.m01,
1051 : 0 : aViewTransform.m11).getLength()) )
1052 : : {
1053 : : OSL_FAIL( "SlideView::modified(): Singular matrix!" );
1054 : :
1055 : 0 : canvas::tools::setIdentityAffineMatrix2D(aViewTransform);
1056 : : }
1057 : :
1058 : : // view transformation really changed?
1059 : 0 : basegfx::B2DHomMatrix aNewTransform;
1060 : : basegfx::unotools::homMatrixFromAffineMatrix(
1061 : : aNewTransform,
1062 : 0 : aViewTransform );
1063 : :
1064 : 0 : if( aNewTransform == maViewTransform )
1065 : : return; // No change, nothing to do
1066 : :
1067 : 0 : maViewTransform = aNewTransform;
1068 : :
1069 : 0 : updateCanvas();
1070 : :
1071 : : // notify view change. Don't call EventMultiplexer directly, this
1072 : : // might not be the main thread!
1073 : : mrEventQueue.addEvent(
1074 : : makeEvent( boost::bind( (bool (EventMultiplexer::*)(
1075 : : const uno::Reference<presentation::XSlideShowView>&))
1076 : : &EventMultiplexer::notifyViewChanged,
1077 : : boost::ref(mrEventMultiplexer), mxView ),
1078 : 0 : "EventMultiplexer::notifyViewChanged"));
1079 : : }
1080 : :
1081 : : // XPaintListener
1082 : 0 : void SlideView::windowPaint( const awt::PaintEvent& /*e*/ )
1083 : : throw (uno::RuntimeException)
1084 : : {
1085 : 0 : osl::MutexGuard aGuard( m_aMutex );
1086 : :
1087 : : OSL_ENSURE( mxView.is() && mpCanvas, "Disposed, but event received?!" );
1088 : :
1089 : : // notify view clobbering. Don't call EventMultiplexer directly,
1090 : : // this might not be the main thread!
1091 : : mrEventQueue.addEvent(
1092 : : makeEvent( boost::bind( &EventMultiplexer::notifyViewClobbered,
1093 : : boost::ref(mrEventMultiplexer), mxView ),
1094 : 0 : "EventMultiplexer::notifyViewClobbered") );
1095 : 0 : }
1096 : :
1097 : 0 : void SlideView::updateCanvas()
1098 : : {
1099 : : OSL_ENSURE( mpCanvas,
1100 : : "SlideView::updateCanvasTransform(): Disposed" );
1101 : :
1102 : 0 : if( !mpCanvas || !mxView.is())
1103 : 0 : return;
1104 : :
1105 : 0 : mpCanvas->clear(); // this is unnecessary, strictly speaking. but
1106 : : // it makes the SlideView behave exactly like a
1107 : : // sprite-based SlideViewLayer, because those
1108 : : // are created from scratch after a resize
1109 : 0 : clearAll();
1110 : 0 : mpCanvas->setTransformation( getTransformation() );
1111 : 0 : mpCanvas->setClip(
1112 : : createClipPolygon( maClip,
1113 : : mpCanvas,
1114 : 0 : maUserSize ));
1115 : :
1116 : : // forward update to viewlayers
1117 : 0 : pruneLayers( true );
1118 : : }
1119 : :
1120 : 0 : void SlideView::updateClip()
1121 : : {
1122 : : OSL_ENSURE( mpCanvas,
1123 : : "SlideView::updateClip(): Disposed" );
1124 : :
1125 : 0 : if( !mpCanvas )
1126 : 0 : return;
1127 : :
1128 : 0 : mpCanvas->setClip(
1129 : : createClipPolygon( maClip,
1130 : : mpCanvas,
1131 : 0 : maUserSize ));
1132 : :
1133 : 0 : pruneLayers( false );
1134 : : }
1135 : :
1136 : 0 : void SlideView::pruneLayers( bool bWithViewLayerUpdate ) const
1137 : : {
1138 : 0 : ViewLayerVector aValidLayers;
1139 : :
1140 : : const basegfx::B2DHomMatrix& rCurrTransform(
1141 : 0 : getTransformation() );
1142 : :
1143 : : // check all layers for validity, and retain only the live ones
1144 : 0 : ViewLayerVector::const_iterator aCurr( maViewLayers.begin() );
1145 : 0 : const ViewLayerVector::const_iterator aEnd( maViewLayers.end() );
1146 : 0 : while( aCurr != aEnd )
1147 : : {
1148 : 0 : boost::shared_ptr< SlideViewLayer > pCurrLayer( aCurr->lock() );
1149 : :
1150 : 0 : if( pCurrLayer )
1151 : : {
1152 : 0 : aValidLayers.push_back( pCurrLayer );
1153 : :
1154 : 0 : if( bWithViewLayerUpdate )
1155 : : pCurrLayer->updateView( rCurrTransform,
1156 : 0 : maUserSize );
1157 : : }
1158 : :
1159 : 0 : ++aCurr;
1160 : 0 : }
1161 : :
1162 : : // replace layer list with pruned one
1163 : 0 : maViewLayers.swap( aValidLayers );
1164 : 0 : }
1165 : :
1166 : : } // anonymous namespace
1167 : :
1168 : 0 : UnoViewSharedPtr createSlideView( uno::Reference< presentation::XSlideShowView> const& xView,
1169 : : EventQueue& rEventQueue,
1170 : : EventMultiplexer& rEventMultiplexer )
1171 : : {
1172 : : boost::shared_ptr<SlideView> const that(
1173 : : comphelper::make_shared_from_UNO(
1174 : : new SlideView(xView,
1175 : : rEventQueue,
1176 : 0 : rEventMultiplexer)));
1177 : :
1178 : : // register listeners with XSlideShowView
1179 : 0 : xView->addTransformationChangedListener( that.get() );
1180 : 0 : xView->addPaintListener( that.get() );
1181 : :
1182 : : // set new transformation
1183 : 0 : that->updateCanvas();
1184 : :
1185 : 0 : return that;
1186 : : }
1187 : :
1188 : : } // namespace internal
1189 : 0 : } // namespace slideshow
1190 : :
1191 : : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|