1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_canvas.hxx"
30 
31 #include <canvas/debug.hxx>
32 #include <tools/diagnose_ex.h>
33 #include <canvas/spriteredrawmanager.hxx>
34 
35 #include <basegfx/range/b2drectangle.hxx>
36 #include <basegfx/tools/canvastools.hxx>
37 #include <basegfx/vector/b2dsize.hxx>
38 #include <basegfx/range/rangeexpander.hxx>
39 
40 #include <algorithm>
41 #include <functional>
42 #include <boost/bind.hpp>
43 
44 
45 namespace canvas
46 {
47     namespace
48     {
49         /** Helper class to condense sprite updates into a single action
50 
51         	This class tracks the sprite changes over the recorded
52         	change list, and generates a single update action from
53         	that (note that per screen update, several moves,
54         	visibility changes and content updates might happen)
55          */
56         class SpriteTracer
57         {
58         public:
59             SpriteTracer( const Sprite::Reference& rAffectedSprite ) :
60                 mpAffectedSprite(rAffectedSprite),
61                 maMoveStartArea(),
62                 maMoveEndArea(),
63                 mbIsMove( false ),
64                 mbIsGenericUpdate( false )
65             {
66             }
67 
68             void operator()( const SpriteRedrawManager::SpriteChangeRecord& rSpriteRecord )
69             {
70                 // only deal with change events from the currently
71                 // affected sprite
72                 if( rSpriteRecord.mpAffectedSprite == mpAffectedSprite )
73                 {
74                     switch( rSpriteRecord.meChangeType )
75                     {
76                         case SpriteRedrawManager::SpriteChangeRecord::move:
77                             if( !mbIsMove )
78                             {
79                                 // no move yet - this must be the first one
80                                 maMoveStartArea = ::basegfx::B2DRectangle(
81                                     rSpriteRecord.maOldPos,
82                                     rSpriteRecord.maOldPos + rSpriteRecord.maUpdateArea.getRange() );
83                                 mbIsMove	 	= true;
84                             }
85 
86                             maMoveEndArea	= rSpriteRecord.maUpdateArea;
87                             break;
88 
89                         case SpriteRedrawManager::SpriteChangeRecord::update:
90                             // update end update area of the
91                             // sprite. Thus, every update() action
92                             // _after_ the last move will correctly
93                             // update the final repaint area. And this
94                             // does not interfere with subsequent
95                             // moves, because moves always perform a
96                             // hard set of maMoveEndArea to their
97                             // stored value
98                             maMoveEndArea.expand( rSpriteRecord.maUpdateArea );
99                             mbIsGenericUpdate = true;
100                             break;
101 
102                         default:
103                             ENSURE_OR_THROW( false,
104                                               "Unexpected case in SpriteUpdater::operator()" );
105                             break;
106                     }
107                 }
108             }
109 
110             void commit( SpriteRedrawManager::SpriteConnectedRanges& rUpdateCollector ) const
111             {
112                 if( mbIsMove )
113                 {
114                     if( !maMoveStartArea.isEmpty() ||
115                         !maMoveEndArea.isEmpty() )
116                     {
117                         // if mbIsGenericUpdate is false, this is a
118                         // pure move (i.e. no other update
119                         // operations). Pass that information on to
120                         // the SpriteInfo
121                         const bool bIsPureMove( !mbIsGenericUpdate );
122 
123                         // ignore the case that start and end update
124                         // area overlap - the b2dconnectedranges
125                         // handle that, anyway. doing it this way
126                         // ensures that we have both old and new area
127                         // stored
128 
129                         // round all given range up to enclosing
130                         // integer rectangle - since the whole thing
131                         // here is about
132 
133                         // first, draw the new sprite position
134                         rUpdateCollector.addRange(
135                             ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ),
136                             SpriteRedrawManager::SpriteInfo(
137                                 mpAffectedSprite,
138                                 maMoveEndArea,
139                                 true,
140                                 bIsPureMove ) );
141 
142                         // then, clear the old place (looks smoother
143                         // this way)
144                         rUpdateCollector.addRange(
145                             ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveStartArea ),
146                             SpriteRedrawManager::SpriteInfo(
147                                 Sprite::Reference(),
148                                 maMoveStartArea,
149                                 true,
150                                 bIsPureMove ) );
151                     }
152                 }
153                 else if( mbIsGenericUpdate &&
154                          !maMoveEndArea.isEmpty() )
155                 {
156                     rUpdateCollector.addRange(
157                         ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ),
158                         SpriteRedrawManager::SpriteInfo(
159                             mpAffectedSprite,
160                             maMoveEndArea,
161                             true ) );
162                 }
163             }
164 
165         private:
166             Sprite::Reference		mpAffectedSprite;
167             ::basegfx::B2DRectangle	maMoveStartArea;
168             ::basegfx::B2DRectangle	maMoveEndArea;
169 
170             /// True, if at least one move was encountered
171             bool					mbIsMove;
172 
173             /// True, if at least one generic update was encountered
174             bool					mbIsGenericUpdate;
175         };
176 
177 
178         /** SpriteChecker functor, which for every sprite checks the
179             given update vector for necessary screen updates
180          */
181         class SpriteUpdater
182         {
183         public:
184             /** Generate update area list
185 
186             	@param rUpdater
187                 Reference to an updater object, which will receive the
188                 update areas.
189 
190                 @param rChangeContainer
191                 Container with all sprite change requests
192 
193              */
194             SpriteUpdater( SpriteRedrawManager::SpriteConnectedRanges&			rUpdater,
195                            const SpriteRedrawManager::VectorOfChangeRecords&	rChangeContainer ) :
196                 mrUpdater( rUpdater ),
197                 mrChangeContainer( rChangeContainer )
198             {
199             }
200 
201             /** Call this method for every sprite on your screen
202 
203             	This method scans the change container, collecting all
204             	update info for the given sprite into one or two
205             	update operations, which in turn are inserted into the
206             	connected ranges processor.
207 
208                 @param rSprite
209                 Current sprite to collect update info for.
210              */
211             void operator()( const Sprite::Reference& rSprite )
212             {
213                 const SpriteTracer aSpriteTracer(
214                     ::std::for_each( mrChangeContainer.begin(),
215                                      mrChangeContainer.end(),
216                                      SpriteTracer( rSprite ) ) );
217 
218                 aSpriteTracer.commit( mrUpdater );
219             }
220 
221         private:
222             SpriteRedrawManager::SpriteConnectedRanges&			mrUpdater;
223             const SpriteRedrawManager::VectorOfChangeRecords&	mrChangeContainer;
224         };
225     }
226 
227     void SpriteRedrawManager::setupUpdateAreas( SpriteConnectedRanges& rUpdateAreas ) const
228     {
229         // TODO(T3): This is NOT thread safe at all. This only works
230         // under the assumption that NOBODY changes ANYTHING
231         // concurrently, while this method is on the stack. We should
232         // really rework the canvas::Sprite interface, in such a way
233         // that it dumps ALL its state with a single, atomic
234         // call. Then, we store that state locally. This prolly goes
235         // in line with the problem of having sprite state available
236         // for the frame before the last frame; plus, it avoids
237         // frequent locks of the object mutices
238         SpriteComparator aSpriteComparator;
239 
240         // put all sprites that have changed content into update areas
241         ListOfSprites::const_iterator       aCurrSprite( maSprites.begin() );
242         const ListOfSprites::const_iterator aEndSprite ( maSprites.end() );
243         while( aCurrSprite != aEndSprite )
244         {
245             if( (*aCurrSprite)->isContentChanged() )
246                 const_cast<SpriteRedrawManager*>(this)->updateSprite( *aCurrSprite,
247                                                                       (*aCurrSprite)->getPosPixel(),
248                                                                       (*aCurrSprite)->getUpdateArea() );
249             ++aCurrSprite;
250         }
251 
252         // sort sprites after prio
253         VectorOfSprites aSortedSpriteVector;
254         ::std::copy( maSprites.begin(),
255                      maSprites.end(),
256                      ::std::back_insert_iterator< VectorOfSprites >(aSortedSpriteVector) );
257         ::std::sort( aSortedSpriteVector.begin(),
258                      aSortedSpriteVector.end(),
259                      aSpriteComparator );
260 
261         // extract all referenced sprites from the maChangeRecords
262         // (copy sprites, make the list unique, regarding the
263         // sprite pointer). This assumes that, until this scope
264         // ends, nobody changes the maChangeRecords vector!
265         VectorOfSprites aUpdatableSprites;
266         VectorOfChangeRecords::const_iterator       aCurrRecord( maChangeRecords.begin() );
267         const VectorOfChangeRecords::const_iterator aEndRecords( maChangeRecords.end() );
268         while( aCurrRecord != aEndRecords )
269         {
270             const Sprite::Reference& rSprite( aCurrRecord->getSprite() );
271             if( rSprite.is() )
272                 aUpdatableSprites.push_back( rSprite );
273             ++aCurrRecord;
274         }
275 
276         VectorOfSprites::iterator aBegin( aUpdatableSprites.begin() );
277         VectorOfSprites::iterator aEnd  ( aUpdatableSprites.end() );
278         ::std::sort( aBegin,
279                      aEnd,
280                      aSpriteComparator );
281 
282         aEnd = ::std::unique( aBegin, aEnd );
283 
284         // for each unique sprite, check the change event vector,
285         // calculate the update operation from that, and add the
286         // result to the aUpdateArea.
287         ::std::for_each( aBegin,
288                          aEnd,
289                          SpriteUpdater( rUpdateAreas,
290                                         maChangeRecords) );
291 
292         // TODO(P2): Implement your own output iterator adapter, to
293         // avoid that totally superfluous temp aUnchangedSprites
294         // vector.
295 
296         // add all sprites to rUpdateAreas, that are _not_ already
297         // contained in the uniquified vector of changed ones
298         // (i.e. the difference between aSortedSpriteVector and
299         // aUpdatableSprites).
300         VectorOfSprites aUnchangedSprites;
301         ::std::set_difference( aSortedSpriteVector.begin(),
302                                aSortedSpriteVector.end(),
303                                aBegin, aEnd,
304                                ::std::back_insert_iterator< VectorOfSprites >(aUnchangedSprites) );
305 
306         // add each remaining unchanged sprite to connected ranges,
307         // marked as "don't need update"
308         VectorOfSprites::const_iterator 		aCurr( aUnchangedSprites.begin() );
309         const VectorOfSprites::const_iterator	aEnd2( aUnchangedSprites.end() );
310         while( aCurr != aEnd2 )
311         {
312             const ::basegfx::B2DRange& rUpdateArea( (*aCurr)->getUpdateArea() );
313             rUpdateAreas.addRange(
314                 ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( rUpdateArea ),
315                 SpriteInfo(*aCurr,
316                            rUpdateArea,
317                            false) );
318             ++aCurr;
319         }
320     }
321 
322 #if OSL_DEBUG_LEVEL > 0
323     bool impIsEqualB2DRange(const basegfx::B2DRange& rRangeA, const basegfx::B2DRange& rRangeB, double fSmallValue)
324     {
325         return fabs(rRangeB.getMinX() - rRangeA.getMinX()) <= fSmallValue
326             && fabs(rRangeB.getMinY() - rRangeA.getMinY()) <= fSmallValue
327             && fabs(rRangeB.getMaxX() - rRangeA.getMaxX()) <= fSmallValue
328             && fabs(rRangeB.getMaxY() - rRangeA.getMaxY()) <= fSmallValue;
329     }
330 
331     bool impIsEqualB2DVector(const basegfx::B2DVector& rVecA, const basegfx::B2DVector& rVecB, double fSmallValue)
332     {
333         return fabs(rVecB.getX() - rVecA.getX()) <= fSmallValue
334             && fabs(rVecB.getY() - rVecA.getY()) <= fSmallValue;
335     }
336 #endif
337 
338     bool SpriteRedrawManager::isAreaUpdateScroll( ::basegfx::B2DRectangle& 	o_rMoveStart,
339                                                   ::basegfx::B2DRectangle& 	o_rMoveEnd,
340                                                   const UpdateArea& 		rUpdateArea,
341                                                   ::std::size_t				nNumSprites ) const
342     {
343         // check for a solitary move, which consists of exactly two
344         // pure-move entries, the first with valid, the second with
345         // invalid sprite (see SpriteTracer::commit()).  Note that we
346         // cannot simply store some flag in SpriteTracer::commit()
347         // above and just check that here, since during the connected
348         // range calculations, other sprites might get merged into the
349         // same region (thus spoiling the scrolling move
350         // optimization).
351         if( nNumSprites != 2 )
352             return false;
353 
354         const SpriteConnectedRanges::ComponentListType::const_iterator aFirst(
355             rUpdateArea.maComponentList.begin() );
356         SpriteConnectedRanges::ComponentListType::const_iterator aSecond(
357             aFirst ); ++aSecond;
358 
359         if( !aFirst->second.isPureMove() ||
360             !aSecond->second.isPureMove() ||
361             !aFirst->second.getSprite().is() ||
362             // use _true_ update area, not the rounded version
363             !aFirst->second.getSprite()->isAreaUpdateOpaque( aFirst->second.getUpdateArea() ) ||
364             aSecond->second.getSprite().is() )
365         {
366             // either no move update, or incorrect sprite, or sprite
367             // content not fully opaque over update region.
368             return false;
369         }
370 
371         o_rMoveStart 	  = aSecond->second.getUpdateArea();
372         o_rMoveEnd   	  = aFirst->second.getUpdateArea();
373 
374 #if OSL_DEBUG_LEVEL > 0
375         ::basegfx::B2DRectangle aTotalBounds( o_rMoveStart );
376         aTotalBounds.expand( o_rMoveEnd );
377 
378         OSL_POSTCOND(impIsEqualB2DRange(rUpdateArea.maTotalBounds, basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange(aTotalBounds), 0.5),
379             "SpriteRedrawManager::isAreaUpdateScroll(): sprite area and total area mismatch");
380         OSL_POSTCOND(impIsEqualB2DVector(o_rMoveStart.getRange(), o_rMoveEnd.getRange(), 0.5),
381             "SpriteRedrawManager::isAreaUpdateScroll(): scroll start and end area have mismatching size");
382 #endif
383 
384         return true;
385     }
386 
387     bool SpriteRedrawManager::isAreaUpdateNotOpaque( const ::basegfx::B2DRectangle& rUpdateRect,
388                                                      const AreaComponent&			rComponent ) const
389     {
390         const Sprite::Reference& pAffectedSprite( rComponent.second.getSprite() );
391 
392         if( !pAffectedSprite.is() )
393             return true; // no sprite, no opaque update!
394 
395         return !pAffectedSprite->isAreaUpdateOpaque( rUpdateRect );
396     }
397 
398     bool SpriteRedrawManager::isAreaUpdateOpaque( const UpdateArea&	rUpdateArea,
399                                                   ::std::size_t		nNumSprites ) const
400     {
401         // check whether the sprites in the update area's list will
402         // fully cover the given area _and_ do that in an opaque way
403         // (i.e. no alpha, no non-rectangular sprite content).
404 
405         // TODO(P1): Come up with a smarter early-exit criterion here
406         // (though, I think, the case that _lots_ of sprites _fully_
407         // cover a rectangular area _without_ any holes is extremely
408         // improbable)
409 
410         // avoid checking large number of sprites (and probably fail,
411         // anyway). Note: the case nNumSprites < 1 should normally not
412         // happen, as handleArea() calls backgroundPaint() then.
413         if( nNumSprites > 3 || nNumSprites < 1 )
414             return false;
415 
416         const SpriteConnectedRanges::ComponentListType::const_iterator aBegin(
417             rUpdateArea.maComponentList.begin() );
418         const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
419             rUpdateArea.maComponentList.end() );
420 
421         // now, calc the _true_ update area, by merging all sprite's
422         // true update areas into one rectangle
423         ::basegfx::B2DRange aTrueArea( aBegin->second.getUpdateArea() );
424         ::std::for_each( aBegin,
425                          aEnd,
426                          ::boost::bind( ::basegfx::B2DRangeExpander(aTrueArea),
427                                         ::boost::bind( &SpriteInfo::getUpdateArea,
428                                                        ::boost::bind( ::std::select2nd<AreaComponent>(),
429                                                                       _1 ) ) ) );
430 
431         // and check whether _any_ of the sprites tells that its area
432         // update will not be opaque.
433         return (::std::find_if( aBegin,
434                                 aEnd,
435                                 ::boost::bind( &SpriteRedrawManager::isAreaUpdateNotOpaque,
436                                                this,
437                                                ::boost::cref(aTrueArea),
438                                                _1 ) ) == aEnd );
439     }
440 
441     bool SpriteRedrawManager::areSpritesChanged( const UpdateArea& rUpdateArea ) const
442     {
443         // check whether SpriteInfo::needsUpdate returns false for
444         // all elements of this area's contained sprites
445         //
446         // if not a single changed sprite found - just ignore this
447         // component (return false)
448         const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
449             rUpdateArea.maComponentList.end() );
450         return (::std::find_if( rUpdateArea.maComponentList.begin(),
451                                 aEnd,
452                                 ::boost::bind( &SpriteInfo::needsUpdate,
453                                                ::boost::bind(
454                                                    ::std::select2nd<SpriteConnectedRanges::ComponentType>(),
455                                                    _1 ) ) ) != aEnd );
456     }
457 
458     SpriteRedrawManager::SpriteRedrawManager() :
459         maSprites(),
460         maChangeRecords()
461     {
462     }
463 
464     void SpriteRedrawManager::disposing()
465     {
466         // drop all references
467         maChangeRecords.clear();
468 
469         // dispose all sprites - the spritecanvas, and by delegation,
470         // this object, is the owner of the sprites. After all, a
471         // sprite without a canvas to render into makes not terribly
472         // much sense.
473 
474         // TODO(Q3): Once boost 1.33 is in, change back to for_each
475         // with ::boost::mem_fn. For the time being, explicit loop due
476         // to cdecl declaration of all UNO methods.
477         ListOfSprites::reverse_iterator aCurr( maSprites.rbegin() );
478         ListOfSprites::reverse_iterator aEnd( maSprites.rend() );
479         while( aCurr != aEnd )
480             (*aCurr++)->dispose();
481 
482         maSprites.clear();
483     }
484 
485     void SpriteRedrawManager::clearChangeRecords()
486     {
487         maChangeRecords.clear();
488     }
489 
490     void SpriteRedrawManager::showSprite( const Sprite::Reference& rSprite )
491     {
492         maSprites.push_back( rSprite );
493     }
494 
495     void SpriteRedrawManager::hideSprite( const Sprite::Reference& rSprite )
496     {
497         maSprites.remove( rSprite );
498     }
499 
500     void SpriteRedrawManager::moveSprite( const Sprite::Reference&		rSprite,
501                                           const ::basegfx::B2DPoint& 	rOldPos,
502                                           const ::basegfx::B2DPoint&	rNewPos,
503                                           const ::basegfx::B2DVector& 	rSpriteSize )
504     {
505         maChangeRecords.push_back( SpriteChangeRecord( rSprite,
506                                                        rOldPos,
507                                                        rNewPos,
508                                                        rSpriteSize ) );
509     }
510 
511     void SpriteRedrawManager::updateSprite( const Sprite::Reference& 	rSprite,
512                                             const ::basegfx::B2DPoint& 	rPos,
513                                             const ::basegfx::B2DRange&	rUpdateArea )
514     {
515         maChangeRecords.push_back( SpriteChangeRecord( rSprite,
516                                                        rPos,
517                                                        rUpdateArea ) );
518     }
519 
520 }
521