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