1 /************************************************************************* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 3 * 4 * Copyright 2000, 2010 Oracle and/or its affiliates. 5 * 6 * OpenOffice.org - a multi-platform office productivity suite 7 * 8 * This file is part of OpenOffice.org. 9 * 10 * OpenOffice.org is free software: you can redistribute it and/or modify 11 * it under the terms of the GNU Lesser General Public License version 3 12 * only, as published by the Free Software Foundation. 13 * 14 * OpenOffice.org is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU Lesser General Public License version 3 for more details 18 * (a copy is included in the LICENSE file that accompanied this code). 19 * 20 * You should have received a copy of the GNU Lesser General Public License 21 * version 3 along with OpenOffice.org. If not, see 22 * <http://www.openoffice.org/license.html> 23 * for a copy of the LGPLv3 License. 24 * 25 ************************************************************************/ 26 27 #include "precompiled_framework.hxx" 28 29 #include "framework/undomanagerhelper.hxx" 30 31 /** === begin UNO includes === **/ 32 #include <com/sun/star/lang/XComponent.hpp> 33 /** === end UNO includes === **/ 34 35 #include <cppuhelper/interfacecontainer.hxx> 36 #include <cppuhelper/exc_hlp.hxx> 37 #include <comphelper/flagguard.hxx> 38 #include <comphelper/asyncnotification.hxx> 39 #include <svl/undo.hxx> 40 #include <tools/diagnose_ex.h> 41 #include <osl/conditn.hxx> 42 43 #include <stack> 44 #include <queue> 45 #include <boost/function.hpp> 46 47 //...................................................................................................................... 48 namespace framework 49 { 50 //...................................................................................................................... 51 52 /** === begin UNO using === **/ 53 using ::com::sun::star::uno::Reference; 54 using ::com::sun::star::uno::XInterface; 55 using ::com::sun::star::uno::UNO_QUERY; 56 using ::com::sun::star::uno::UNO_QUERY_THROW; 57 using ::com::sun::star::uno::UNO_SET_THROW; 58 using ::com::sun::star::uno::Exception; 59 using ::com::sun::star::uno::RuntimeException; 60 using ::com::sun::star::uno::Any; 61 using ::com::sun::star::uno::makeAny; 62 using ::com::sun::star::uno::Sequence; 63 using ::com::sun::star::uno::Type; 64 using ::com::sun::star::document::XUndoManagerListener; 65 using ::com::sun::star::document::UndoManagerEvent; 66 using ::com::sun::star::document::EmptyUndoStackException; 67 using ::com::sun::star::document::UndoContextNotClosedException; 68 using ::com::sun::star::document::UndoFailedException; 69 using ::com::sun::star::util::NotLockedException; 70 using ::com::sun::star::lang::EventObject; 71 using ::com::sun::star::document::XUndoAction; 72 using ::com::sun::star::lang::XComponent; 73 using ::com::sun::star::document::XUndoManager; 74 using ::com::sun::star::util::InvalidStateException; 75 using ::com::sun::star::lang::IllegalArgumentException; 76 using ::com::sun::star::util::XModifyListener; 77 /** === end UNO using === **/ 78 using ::svl::IUndoManager; 79 80 //================================================================================================================== 81 //= UndoActionWrapper 82 //================================================================================================================== 83 class UndoActionWrapper : public SfxUndoAction 84 { 85 public: 86 UndoActionWrapper( 87 Reference< XUndoAction > const& i_undoAction 88 ); 89 virtual ~UndoActionWrapper(); 90 91 virtual String GetComment() const; 92 virtual void Undo(); 93 virtual void Redo(); 94 virtual sal_Bool CanRepeat(SfxRepeatTarget&) const; 95 96 private: 97 const Reference< XUndoAction > m_xUndoAction; 98 }; 99 100 //------------------------------------------------------------------------------------------------------------------ 101 UndoActionWrapper::UndoActionWrapper( Reference< XUndoAction > const& i_undoAction ) 102 :SfxUndoAction() 103 ,m_xUndoAction( i_undoAction ) 104 { 105 ENSURE_OR_THROW( m_xUndoAction.is(), "illegal undo action" ); 106 } 107 108 //------------------------------------------------------------------------------------------------------------------ 109 UndoActionWrapper::~UndoActionWrapper() 110 { 111 try 112 { 113 Reference< XComponent > xComponent( m_xUndoAction, UNO_QUERY ); 114 if ( xComponent.is() ) 115 xComponent->dispose(); 116 } 117 catch( const Exception& ) 118 { 119 DBG_UNHANDLED_EXCEPTION(); 120 } 121 } 122 123 //------------------------------------------------------------------------------------------------------------------ 124 String UndoActionWrapper::GetComment() const 125 { 126 String sComment; 127 try 128 { 129 sComment = m_xUndoAction->getTitle(); 130 } 131 catch( const Exception& ) 132 { 133 DBG_UNHANDLED_EXCEPTION(); 134 } 135 return sComment; 136 } 137 138 //------------------------------------------------------------------------------------------------------------------ 139 void UndoActionWrapper::Undo() 140 { 141 m_xUndoAction->undo(); 142 } 143 144 //------------------------------------------------------------------------------------------------------------------ 145 void UndoActionWrapper::Redo() 146 { 147 m_xUndoAction->redo(); 148 } 149 150 //------------------------------------------------------------------------------------------------------------------ 151 sal_Bool UndoActionWrapper::CanRepeat(SfxRepeatTarget&) const 152 { 153 return sal_False; 154 } 155 156 //================================================================================================================== 157 //= UndoManagerRequest 158 //================================================================================================================== 159 class UndoManagerRequest : public ::comphelper::AnyEvent 160 { 161 public: 162 UndoManagerRequest( ::boost::function0< void > const& i_request ) 163 :m_request( i_request ) 164 ,m_caughtException() 165 ,m_finishCondition() 166 { 167 m_finishCondition.reset(); 168 } 169 170 void execute() 171 { 172 try 173 { 174 m_request(); 175 } 176 catch( const Exception& ) 177 { 178 m_caughtException = ::cppu::getCaughtException(); 179 } 180 m_finishCondition.set(); 181 } 182 183 void wait() 184 { 185 m_finishCondition.wait(); 186 if ( m_caughtException.hasValue() ) 187 ::cppu::throwException( m_caughtException ); 188 } 189 190 void cancel( const Reference< XInterface >& i_context ) 191 { 192 m_caughtException <<= RuntimeException( 193 ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Concurrency error: an ealier operation on the stack failed." ) ), 194 i_context 195 ); 196 m_finishCondition.set(); 197 } 198 199 protected: 200 ~UndoManagerRequest() 201 { 202 } 203 204 private: 205 ::boost::function0< void > m_request; 206 Any m_caughtException; 207 ::osl::Condition m_finishCondition; 208 }; 209 210 //------------------------------------------------------------------------------------------------------------------ 211 212 //================================================================================================================== 213 //= UndoManagerHelper_Impl 214 //================================================================================================================== 215 class UndoManagerHelper_Impl : public SfxUndoListener 216 { 217 private: 218 ::osl::Mutex m_aMutex; 219 ::osl::Mutex m_aQueueMutex; 220 bool m_disposed; 221 bool m_bAPIActionRunning; 222 bool m_bProcessingEvents; 223 sal_Int32 m_nLockCount; 224 ::cppu::OInterfaceContainerHelper m_aUndoListeners; 225 ::cppu::OInterfaceContainerHelper m_aModifyListeners; 226 IUndoManagerImplementation& m_rUndoManagerImplementation; 227 UndoManagerHelper& m_rAntiImpl; 228 ::std::stack< bool > m_aContextVisibilities; 229 #if OSL_DEBUG_LEVEL > 0 230 ::std::stack< bool > m_aContextAPIFlags; 231 #endif 232 ::std::queue< ::rtl::Reference< UndoManagerRequest > > 233 m_aEventQueue; 234 235 public: 236 ::osl::Mutex& getMutex() { return m_aMutex; } 237 238 public: 239 UndoManagerHelper_Impl( UndoManagerHelper& i_antiImpl, IUndoManagerImplementation& i_undoManagerImpl ) 240 :m_aMutex() 241 ,m_aQueueMutex() 242 ,m_disposed( false ) 243 ,m_bAPIActionRunning( false ) 244 ,m_bProcessingEvents( false ) 245 ,m_nLockCount( 0 ) 246 ,m_aUndoListeners( m_aMutex ) 247 ,m_aModifyListeners( m_aMutex ) 248 ,m_rUndoManagerImplementation( i_undoManagerImpl ) 249 ,m_rAntiImpl( i_antiImpl ) 250 { 251 getUndoManager().AddUndoListener( *this ); 252 } 253 254 virtual ~UndoManagerHelper_Impl() 255 { 256 } 257 258 //.............................................................................................................. 259 IUndoManager& getUndoManager() const 260 { 261 return m_rUndoManagerImplementation.getImplUndoManager(); 262 } 263 264 //.............................................................................................................. 265 Reference< XUndoManager > getXUndoManager() const 266 { 267 return m_rUndoManagerImplementation.getThis(); 268 } 269 270 // SfxUndoListener 271 virtual void actionUndone( const String& i_actionComment ); 272 virtual void actionRedone( const String& i_actionComment ); 273 virtual void undoActionAdded( const String& i_actionComment ); 274 virtual void cleared(); 275 virtual void clearedRedo(); 276 virtual void resetAll(); 277 virtual void listActionEntered( const String& i_comment ); 278 virtual void listActionLeft( const String& i_comment ); 279 virtual void listActionLeftAndMerged(); 280 virtual void listActionCancelled(); 281 virtual void undoManagerDying(); 282 283 // public operations 284 void disposing(); 285 286 void enterUndoContext( const ::rtl::OUString& i_title, const bool i_hidden, IMutexGuard& i_instanceLock ); 287 void leaveUndoContext( IMutexGuard& i_instanceLock ); 288 void addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock ); 289 void undo( IMutexGuard& i_instanceLock ); 290 void redo( IMutexGuard& i_instanceLock ); 291 void clear( IMutexGuard& i_instanceLock ); 292 void clearRedo( IMutexGuard& i_instanceLock ); 293 void reset( IMutexGuard& i_instanceLock ); 294 295 void lock(); 296 void unlock(); 297 298 void addUndoManagerListener( const Reference< XUndoManagerListener >& i_listener ) 299 { 300 m_aUndoListeners.addInterface( i_listener ); 301 } 302 303 void removeUndoManagerListener( const Reference< XUndoManagerListener >& i_listener ) 304 { 305 m_aUndoListeners.removeInterface( i_listener ); 306 } 307 308 void addModifyListener( const Reference< XModifyListener >& i_listener ) 309 { 310 m_aModifyListeners.addInterface( i_listener ); 311 } 312 313 void removeModifyListener( const Reference< XModifyListener >& i_listener ) 314 { 315 m_aModifyListeners.removeInterface( i_listener ); 316 } 317 318 UndoManagerEvent 319 buildEvent( ::rtl::OUString const& i_title ) const; 320 321 void impl_notifyModified(); 322 void notify( ::rtl::OUString const& i_title, 323 void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& ) 324 ); 325 void notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& ) ) 326 { 327 notify( ::rtl::OUString(), i_notificationMethod ); 328 } 329 330 void notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const EventObject& ) ); 331 332 private: 333 /// adds a function to be called to the request processor's queue 334 void impl_processRequest( ::boost::function0< void > const& i_request, IMutexGuard& i_instanceLock ); 335 336 /// impl-versions of the XUndoManager API. 337 void impl_enterUndoContext( const ::rtl::OUString& i_title, const bool i_hidden ); 338 void impl_leaveUndoContext(); 339 void impl_addUndoAction( const Reference< XUndoAction >& i_action ); 340 void impl_doUndoRedo( IMutexGuard& i_externalLock, const bool i_undo ); 341 void impl_clear(); 342 void impl_clearRedo(); 343 void impl_reset(); 344 }; 345 346 //------------------------------------------------------------------------------------------------------------------ 347 void UndoManagerHelper_Impl::disposing() 348 { 349 EventObject aEvent; 350 aEvent.Source = getXUndoManager(); 351 m_aUndoListeners.disposeAndClear( aEvent ); 352 m_aModifyListeners.disposeAndClear( aEvent ); 353 354 ::osl::MutexGuard aGuard( m_aMutex ); 355 356 getUndoManager().RemoveUndoListener( *this ); 357 358 m_disposed = true; 359 } 360 361 //------------------------------------------------------------------------------------------------------------------ 362 UndoManagerEvent UndoManagerHelper_Impl::buildEvent( ::rtl::OUString const& i_title ) const 363 { 364 UndoManagerEvent aEvent; 365 aEvent.Source = getXUndoManager(); 366 aEvent.UndoActionTitle = i_title; 367 aEvent.UndoContextDepth = getUndoManager().GetListActionDepth(); 368 return aEvent; 369 } 370 371 //------------------------------------------------------------------------------------------------------------------ 372 void UndoManagerHelper_Impl::impl_notifyModified() 373 { 374 const EventObject aEvent( getXUndoManager() ); 375 m_aModifyListeners.notifyEach( &XModifyListener::modified, aEvent ); 376 } 377 378 //------------------------------------------------------------------------------------------------------------------ 379 void UndoManagerHelper_Impl::notify( ::rtl::OUString const& i_title, 380 void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& ) ) 381 { 382 const UndoManagerEvent aEvent( buildEvent( i_title ) ); 383 384 // TODO: this notification method here is used by UndoManagerHelper_Impl, to multiplex the notifications we 385 // receive from the IUndoManager. Those notitications are sent with a locked SolarMutex, which means 386 // we're doing the multiplexing here with a locked SM, too. Which is Bad (TM). 387 // Fixing this properly would require outsourcing all the notifications into an own thread - which might lead 388 // to problems of its own, since clients might expect synchronous notifications. 389 390 m_aUndoListeners.notifyEach( i_notificationMethod, aEvent ); 391 impl_notifyModified(); 392 } 393 394 //------------------------------------------------------------------------------------------------------------------ 395 void UndoManagerHelper_Impl::notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const EventObject& ) ) 396 { 397 const EventObject aEvent( getXUndoManager() ); 398 399 // TODO: the same comment as in the other notify, regarding SM locking applies here ... 400 401 m_aUndoListeners.notifyEach( i_notificationMethod, aEvent ); 402 impl_notifyModified(); 403 } 404 405 //------------------------------------------------------------------------------------------------------------------ 406 void UndoManagerHelper_Impl::enterUndoContext( const ::rtl::OUString& i_title, const bool i_hidden, IMutexGuard& i_instanceLock ) 407 { 408 impl_processRequest( 409 ::boost::bind( 410 &UndoManagerHelper_Impl::impl_enterUndoContext, 411 this, 412 ::boost::cref( i_title ), 413 i_hidden 414 ), 415 i_instanceLock 416 ); 417 } 418 419 //------------------------------------------------------------------------------------------------------------------ 420 void UndoManagerHelper_Impl::leaveUndoContext( IMutexGuard& i_instanceLock ) 421 { 422 impl_processRequest( 423 ::boost::bind( 424 &UndoManagerHelper_Impl::impl_leaveUndoContext, 425 this 426 ), 427 i_instanceLock 428 ); 429 } 430 431 //------------------------------------------------------------------------------------------------------------------ 432 void UndoManagerHelper_Impl::addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock ) 433 { 434 if ( !i_action.is() ) 435 throw IllegalArgumentException( 436 ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "illegal undo action object" ) ), 437 getXUndoManager(), 438 1 439 ); 440 441 impl_processRequest( 442 ::boost::bind( 443 &UndoManagerHelper_Impl::impl_addUndoAction, 444 this, 445 ::boost::ref( i_action ) 446 ), 447 i_instanceLock 448 ); 449 } 450 451 //------------------------------------------------------------------------------------------------------------------ 452 void UndoManagerHelper_Impl::clear( IMutexGuard& i_instanceLock ) 453 { 454 impl_processRequest( 455 ::boost::bind( 456 &UndoManagerHelper_Impl::impl_clear, 457 this 458 ), 459 i_instanceLock 460 ); 461 } 462 463 //------------------------------------------------------------------------------------------------------------------ 464 void UndoManagerHelper_Impl::clearRedo( IMutexGuard& i_instanceLock ) 465 { 466 impl_processRequest( 467 ::boost::bind( 468 &UndoManagerHelper_Impl::impl_clearRedo, 469 this 470 ), 471 i_instanceLock 472 ); 473 } 474 475 //------------------------------------------------------------------------------------------------------------------ 476 void UndoManagerHelper_Impl::reset( IMutexGuard& i_instanceLock ) 477 { 478 impl_processRequest( 479 ::boost::bind( 480 &UndoManagerHelper_Impl::impl_reset, 481 this 482 ), 483 i_instanceLock 484 ); 485 } 486 487 //------------------------------------------------------------------------------------------------------------------ 488 void UndoManagerHelper_Impl::lock() 489 { 490 // SYNCHRONIZED ---> 491 ::osl::MutexGuard aGuard( getMutex() ); 492 493 if ( ++m_nLockCount == 1 ) 494 { 495 IUndoManager& rUndoManager = getUndoManager(); 496 rUndoManager.EnableUndo( false ); 497 } 498 // <--- SYNCHRONIZED 499 } 500 501 //------------------------------------------------------------------------------------------------------------------ 502 void UndoManagerHelper_Impl::unlock() 503 { 504 // SYNCHRONIZED ---> 505 ::osl::MutexGuard aGuard( getMutex() ); 506 507 if ( m_nLockCount == 0 ) 508 throw NotLockedException( ::rtl::OUString::createFromAscii( "Undo manager is not locked" ), getXUndoManager() ); 509 510 if ( --m_nLockCount == 0 ) 511 { 512 IUndoManager& rUndoManager = getUndoManager(); 513 rUndoManager.EnableUndo( true ); 514 } 515 // <--- SYNCHRONIZED 516 } 517 518 //------------------------------------------------------------------------------------------------------------------ 519 void UndoManagerHelper_Impl::impl_processRequest( ::boost::function0< void > const& i_request, IMutexGuard& i_instanceLock ) 520 { 521 // create the request, and add it to our queue 522 ::rtl::Reference< UndoManagerRequest > pRequest( new UndoManagerRequest( i_request ) ); 523 { 524 ::osl::MutexGuard aQueueGuard( m_aQueueMutex ); 525 m_aEventQueue.push( pRequest ); 526 } 527 528 i_instanceLock.clear(); 529 530 if ( m_bProcessingEvents ) 531 { 532 // another thread is processing the event queue currently => it will also process the event which we just added 533 pRequest->wait(); 534 return; 535 } 536 537 m_bProcessingEvents = true; 538 do 539 { 540 pRequest.clear(); 541 { 542 ::osl::MutexGuard aQueueGuard( m_aQueueMutex ); 543 if ( m_aEventQueue.empty() ) 544 { 545 // reset the flag before releasing the queue mutex, otherwise it's possible that another thread 546 // could add an event after we release the mutex, but before we reset the flag. If then this other 547 // thread checks the flag before be reset it, this thread's event would starve. 548 m_bProcessingEvents = false; 549 return; 550 } 551 pRequest = m_aEventQueue.front(); 552 m_aEventQueue.pop(); 553 } 554 try 555 { 556 pRequest->execute(); 557 pRequest->wait(); 558 } 559 catch( ... ) 560 { 561 { 562 // no chance to process further requests, if the current one failed 563 // => discard them 564 ::osl::MutexGuard aQueueGuard( m_aQueueMutex ); 565 while ( !m_aEventQueue.empty() ) 566 { 567 pRequest = m_aEventQueue.front(); 568 m_aEventQueue.pop(); 569 pRequest->cancel( getXUndoManager() ); 570 } 571 m_bProcessingEvents = false; 572 } 573 // re-throw the error 574 throw; 575 } 576 } 577 while ( true ); 578 } 579 580 //------------------------------------------------------------------------------------------------------------------ 581 void UndoManagerHelper_Impl::impl_enterUndoContext( const ::rtl::OUString& i_title, const bool i_hidden ) 582 { 583 // SYNCHRONIZED ---> 584 ::osl::ClearableMutexGuard aGuard( m_aMutex ); 585 586 IUndoManager& rUndoManager = getUndoManager(); 587 if ( !rUndoManager.IsUndoEnabled() ) 588 // ignore this request if the manager is locked 589 return; 590 591 if ( i_hidden && ( rUndoManager.GetUndoActionCount( IUndoManager::CurrentLevel ) == 0 ) ) 592 throw EmptyUndoStackException( 593 ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "can't enter a hidden context without a previous Undo action" ) ), 594 m_rUndoManagerImplementation.getThis() 595 ); 596 597 { 598 ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); 599 rUndoManager.EnterListAction( i_title, ::rtl::OUString() ); 600 } 601 602 m_aContextVisibilities.push( i_hidden ); 603 604 const UndoManagerEvent aEvent( buildEvent( i_title ) ); 605 aGuard.clear(); 606 // <--- SYNCHRONIZED 607 608 m_aUndoListeners.notifyEach( i_hidden ? &XUndoManagerListener::enteredHiddenContext : &XUndoManagerListener::enteredContext, aEvent ); 609 impl_notifyModified(); 610 } 611 612 //------------------------------------------------------------------------------------------------------------------ 613 void UndoManagerHelper_Impl::impl_leaveUndoContext() 614 { 615 // SYNCHRONIZED ---> 616 ::osl::ClearableMutexGuard aGuard( m_aMutex ); 617 618 IUndoManager& rUndoManager = getUndoManager(); 619 if ( !rUndoManager.IsUndoEnabled() ) 620 // ignore this request if the manager is locked 621 return; 622 623 if ( !rUndoManager.IsInListAction() ) 624 throw InvalidStateException( 625 ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "no active undo context" ) ), 626 getXUndoManager() 627 ); 628 629 size_t nContextElements = 0; 630 631 const bool isHiddenContext = m_aContextVisibilities.top();; 632 m_aContextVisibilities.pop(); 633 634 const bool bHadRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::TopLevel ) > 0 ); 635 { 636 ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); 637 if ( isHiddenContext ) 638 nContextElements = rUndoManager.LeaveAndMergeListAction(); 639 else 640 nContextElements = rUndoManager.LeaveListAction(); 641 } 642 const bool bHasRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::TopLevel ) > 0 ); 643 644 // prepare notification 645 void ( SAL_CALL XUndoManagerListener::*notificationMethod )( const UndoManagerEvent& ) = NULL; 646 647 UndoManagerEvent aContextEvent( buildEvent( ::rtl::OUString() ) ); 648 const EventObject aClearedEvent( getXUndoManager() ); 649 if ( nContextElements == 0 ) 650 { 651 notificationMethod = &XUndoManagerListener::cancelledContext; 652 } 653 else if ( isHiddenContext ) 654 { 655 notificationMethod = &XUndoManagerListener::leftHiddenContext; 656 } 657 else 658 { 659 aContextEvent.UndoActionTitle = rUndoManager.GetUndoActionComment( 0, IUndoManager::CurrentLevel ); 660 notificationMethod = &XUndoManagerListener::leftContext; 661 } 662 663 aGuard.clear(); 664 // <--- SYNCHRONIZED 665 666 if ( bHadRedoActions && !bHasRedoActions ) 667 m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aClearedEvent ); 668 m_aUndoListeners.notifyEach( notificationMethod, aContextEvent ); 669 impl_notifyModified(); 670 } 671 672 //------------------------------------------------------------------------------------------------------------------ 673 void UndoManagerHelper_Impl::impl_doUndoRedo( IMutexGuard& i_externalLock, const bool i_undo ) 674 { 675 ::osl::Guard< ::framework::IMutex > aExternalGuard( i_externalLock.getGuardedMutex() ); 676 // note that this assumes that the mutex has been released in the thread which added the 677 // Undo/Redo request, so we can successfully acquire it 678 679 // SYNCHRONIZED ---> 680 ::osl::ClearableMutexGuard aGuard( m_aMutex ); 681 682 IUndoManager& rUndoManager = getUndoManager(); 683 if ( rUndoManager.IsInListAction() ) 684 throw UndoContextNotClosedException( ::rtl::OUString(), getXUndoManager() ); 685 686 const size_t nElements = i_undo 687 ? rUndoManager.GetUndoActionCount( IUndoManager::TopLevel ) 688 : rUndoManager.GetRedoActionCount( IUndoManager::TopLevel ); 689 if ( nElements == 0 ) 690 throw EmptyUndoStackException( ::rtl::OUString::createFromAscii( "stack is empty" ), getXUndoManager() ); 691 692 aGuard.clear(); 693 // <--- SYNCHRONIZED 694 695 try 696 { 697 if ( i_undo ) 698 rUndoManager.Undo(); 699 else 700 rUndoManager.Redo(); 701 } 702 catch( const RuntimeException& ) { /* allowed to leave here */ throw; } 703 catch( const UndoFailedException& ) { /* allowed to leave here */ throw; } 704 catch( const Exception& ) 705 { 706 // not allowed to leave 707 const Any aError( ::cppu::getCaughtException() ); 708 throw UndoFailedException( ::rtl::OUString(), getXUndoManager(), aError ); 709 } 710 711 // note that in opposite to all of the other methods, we do *not* have our mutex locked when calling 712 // into the IUndoManager implementation. This ensures that an actual XUndoAction::undo/redo is also 713 // called without our mutex being locked. 714 // As a consequence, we do not set m_bAPIActionRunning here. Instead, our actionUndone/actionRedone methods 715 // *always* multiplex the event to our XUndoManagerListeners, not only when m_bAPIActionRunning is FALSE (This 716 // again is different from all other SfxUndoListener methods). 717 // So, we do not need to do this notification here ourself. 718 } 719 720 //------------------------------------------------------------------------------------------------------------------ 721 void UndoManagerHelper_Impl::impl_addUndoAction( const Reference< XUndoAction >& i_action ) 722 { 723 // SYNCHRONIZED ---> 724 ::osl::ClearableMutexGuard aGuard( m_aMutex ); 725 726 IUndoManager& rUndoManager = getUndoManager(); 727 if ( !rUndoManager.IsUndoEnabled() ) 728 // ignore the request if the manager is locked 729 return; 730 731 const UndoManagerEvent aEventAdd( buildEvent( i_action->getTitle() ) ); 732 const EventObject aEventClear( getXUndoManager() ); 733 734 const bool bHadRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::CurrentLevel ) > 0 ); 735 { 736 ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); 737 rUndoManager.AddUndoAction( new UndoActionWrapper( i_action ) ); 738 } 739 const bool bHasRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::CurrentLevel ) > 0 ); 740 741 aGuard.clear(); 742 // <--- SYNCHRONIZED 743 744 m_aUndoListeners.notifyEach( &XUndoManagerListener::undoActionAdded, aEventAdd ); 745 if ( bHadRedoActions && !bHasRedoActions ) 746 m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aEventClear ); 747 impl_notifyModified(); 748 } 749 750 //------------------------------------------------------------------------------------------------------------------ 751 void UndoManagerHelper_Impl::impl_clear() 752 { 753 // SYNCHRONIZED ---> 754 ::osl::ClearableMutexGuard aGuard( m_aMutex ); 755 756 IUndoManager& rUndoManager = getUndoManager(); 757 if ( rUndoManager.IsInListAction() ) 758 throw UndoContextNotClosedException( ::rtl::OUString(), getXUndoManager() ); 759 760 { 761 ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); 762 rUndoManager.Clear(); 763 } 764 765 const EventObject aEvent( getXUndoManager() ); 766 aGuard.clear(); 767 // <--- SYNCHRONIZED 768 769 m_aUndoListeners.notifyEach( &XUndoManagerListener::allActionsCleared, aEvent ); 770 impl_notifyModified(); 771 } 772 773 //------------------------------------------------------------------------------------------------------------------ 774 void UndoManagerHelper_Impl::impl_clearRedo() 775 { 776 // SYNCHRONIZED ---> 777 ::osl::ClearableMutexGuard aGuard( m_aMutex ); 778 779 IUndoManager& rUndoManager = getUndoManager(); 780 if ( rUndoManager.IsInListAction() ) 781 throw UndoContextNotClosedException( ::rtl::OUString(), getXUndoManager() ); 782 783 { 784 ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); 785 rUndoManager.ClearRedo(); 786 } 787 788 const EventObject aEvent( getXUndoManager() ); 789 aGuard.clear(); 790 // <--- SYNCHRONIZED 791 792 m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aEvent ); 793 impl_notifyModified(); 794 } 795 796 //------------------------------------------------------------------------------------------------------------------ 797 void UndoManagerHelper_Impl::impl_reset() 798 { 799 // SYNCHRONIZED ---> 800 ::osl::ClearableMutexGuard aGuard( m_aMutex ); 801 802 IUndoManager& rUndoManager = getUndoManager(); 803 { 804 ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning ); 805 rUndoManager.Reset(); 806 } 807 808 const EventObject aEvent( getXUndoManager() ); 809 aGuard.clear(); 810 // <--- SYNCHRONIZED 811 812 m_aUndoListeners.notifyEach( &XUndoManagerListener::resetAll, aEvent ); 813 impl_notifyModified(); 814 } 815 816 //------------------------------------------------------------------------------------------------------------------ 817 void UndoManagerHelper_Impl::actionUndone( const String& i_actionComment ) 818 { 819 UndoManagerEvent aEvent; 820 aEvent.Source = getXUndoManager(); 821 aEvent.UndoActionTitle = i_actionComment; 822 aEvent.UndoContextDepth = 0; // Undo can happen on level 0 only 823 m_aUndoListeners.notifyEach( &XUndoManagerListener::actionUndone, aEvent ); 824 impl_notifyModified(); 825 } 826 827 //------------------------------------------------------------------------------------------------------------------ 828 void UndoManagerHelper_Impl::actionRedone( const String& i_actionComment ) 829 { 830 UndoManagerEvent aEvent; 831 aEvent.Source = getXUndoManager(); 832 aEvent.UndoActionTitle = i_actionComment; 833 aEvent.UndoContextDepth = 0; // Redo can happen on level 0 only 834 m_aUndoListeners.notifyEach( &XUndoManagerListener::actionRedone, aEvent ); 835 impl_notifyModified(); 836 } 837 838 //------------------------------------------------------------------------------------------------------------------ 839 void UndoManagerHelper_Impl::undoActionAdded( const String& i_actionComment ) 840 { 841 if ( m_bAPIActionRunning ) 842 return; 843 844 notify( i_actionComment, &XUndoManagerListener::undoActionAdded ); 845 } 846 847 //------------------------------------------------------------------------------------------------------------------ 848 void UndoManagerHelper_Impl::cleared() 849 { 850 if ( m_bAPIActionRunning ) 851 return; 852 853 notify( &XUndoManagerListener::allActionsCleared ); 854 } 855 856 //------------------------------------------------------------------------------------------------------------------ 857 void UndoManagerHelper_Impl::clearedRedo() 858 { 859 if ( m_bAPIActionRunning ) 860 return; 861 862 notify( &XUndoManagerListener::redoActionsCleared ); 863 } 864 865 //------------------------------------------------------------------------------------------------------------------ 866 void UndoManagerHelper_Impl::resetAll() 867 { 868 if ( m_bAPIActionRunning ) 869 return; 870 871 notify( &XUndoManagerListener::resetAll ); 872 } 873 874 //------------------------------------------------------------------------------------------------------------------ 875 void UndoManagerHelper_Impl::listActionEntered( const String& i_comment ) 876 { 877 #if OSL_DEBUG_LEVEL > 0 878 m_aContextAPIFlags.push( m_bAPIActionRunning ); 879 #endif 880 881 if ( m_bAPIActionRunning ) 882 return; 883 884 notify( i_comment, &XUndoManagerListener::enteredContext ); 885 } 886 887 //------------------------------------------------------------------------------------------------------------------ 888 void UndoManagerHelper_Impl::listActionLeft( const String& i_comment ) 889 { 890 #if OSL_DEBUG_LEVEL > 0 891 const bool bCurrentContextIsAPIContext = m_aContextAPIFlags.top(); 892 m_aContextAPIFlags.pop(); 893 OSL_ENSURE( bCurrentContextIsAPIContext == m_bAPIActionRunning, "UndoManagerHelper_Impl::listActionLeft: API and non-API contexts interwoven!" ); 894 #endif 895 896 if ( m_bAPIActionRunning ) 897 return; 898 899 notify( i_comment, &XUndoManagerListener::leftContext ); 900 } 901 902 //------------------------------------------------------------------------------------------------------------------ 903 void UndoManagerHelper_Impl::listActionLeftAndMerged() 904 { 905 #if OSL_DEBUG_LEVEL > 0 906 const bool bCurrentContextIsAPIContext = m_aContextAPIFlags.top(); 907 m_aContextAPIFlags.pop(); 908 OSL_ENSURE( bCurrentContextIsAPIContext == m_bAPIActionRunning, "UndoManagerHelper_Impl::listActionLeftAndMerged: API and non-API contexts interwoven!" ); 909 #endif 910 911 if ( m_bAPIActionRunning ) 912 return; 913 914 notify( &XUndoManagerListener::leftHiddenContext ); 915 } 916 917 //------------------------------------------------------------------------------------------------------------------ 918 void UndoManagerHelper_Impl::listActionCancelled() 919 { 920 #if OSL_DEBUG_LEVEL > 0 921 const bool bCurrentContextIsAPIContext = m_aContextAPIFlags.top(); 922 m_aContextAPIFlags.pop(); 923 OSL_ENSURE( bCurrentContextIsAPIContext == m_bAPIActionRunning, "UndoManagerHelper_Impl::listActionCancelled: API and non-API contexts interwoven!" ); 924 #endif 925 926 if ( m_bAPIActionRunning ) 927 return; 928 929 notify( &XUndoManagerListener::cancelledContext ); 930 } 931 932 //------------------------------------------------------------------------------------------------------------------ 933 void UndoManagerHelper_Impl::undoManagerDying() 934 { 935 // TODO: do we need to care? Or is this the responsibility of our owner? 936 } 937 938 //================================================================================================================== 939 //= UndoManagerHelper 940 //================================================================================================================== 941 //------------------------------------------------------------------------------------------------------------------ 942 UndoManagerHelper::UndoManagerHelper( IUndoManagerImplementation& i_undoManagerImpl ) 943 :m_pImpl( new UndoManagerHelper_Impl( *this, i_undoManagerImpl ) ) 944 { 945 } 946 947 //------------------------------------------------------------------------------------------------------------------ 948 UndoManagerHelper::~UndoManagerHelper() 949 { 950 } 951 952 //------------------------------------------------------------------------------------------------------------------ 953 void UndoManagerHelper::disposing() 954 { 955 m_pImpl->disposing(); 956 } 957 958 //------------------------------------------------------------------------------------------------------------------ 959 void UndoManagerHelper::enterUndoContext( const ::rtl::OUString& i_title, IMutexGuard& i_instanceLock ) 960 { 961 m_pImpl->enterUndoContext( i_title, false, i_instanceLock ); 962 } 963 964 //------------------------------------------------------------------------------------------------------------------ 965 void UndoManagerHelper::enterHiddenUndoContext( IMutexGuard& i_instanceLock ) 966 { 967 m_pImpl->enterUndoContext( ::rtl::OUString(), true, i_instanceLock ); 968 } 969 970 //------------------------------------------------------------------------------------------------------------------ 971 void UndoManagerHelper::leaveUndoContext( IMutexGuard& i_instanceLock ) 972 { 973 m_pImpl->leaveUndoContext( i_instanceLock ); 974 } 975 976 //------------------------------------------------------------------------------------------------------------------ 977 void UndoManagerHelper_Impl::undo( IMutexGuard& i_instanceLock ) 978 { 979 impl_processRequest( 980 ::boost::bind( 981 &UndoManagerHelper_Impl::impl_doUndoRedo, 982 this, 983 ::boost::ref( i_instanceLock ), 984 true 985 ), 986 i_instanceLock 987 ); 988 } 989 990 //------------------------------------------------------------------------------------------------------------------ 991 void UndoManagerHelper_Impl::redo( IMutexGuard& i_instanceLock ) 992 { 993 impl_processRequest( 994 ::boost::bind( 995 &UndoManagerHelper_Impl::impl_doUndoRedo, 996 this, 997 ::boost::ref( i_instanceLock ), 998 false 999 ), 1000 i_instanceLock 1001 ); 1002 } 1003 1004 //------------------------------------------------------------------------------------------------------------------ 1005 void UndoManagerHelper::addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock ) 1006 { 1007 m_pImpl->addUndoAction( i_action, i_instanceLock ); 1008 } 1009 1010 //------------------------------------------------------------------------------------------------------------------ 1011 void UndoManagerHelper::undo( IMutexGuard& i_instanceLock ) 1012 { 1013 m_pImpl->undo( i_instanceLock ); 1014 } 1015 1016 //------------------------------------------------------------------------------------------------------------------ 1017 void UndoManagerHelper::redo( IMutexGuard& i_instanceLock ) 1018 { 1019 m_pImpl->redo( i_instanceLock ); 1020 } 1021 1022 //------------------------------------------------------------------------------------------------------------------ 1023 ::sal_Bool UndoManagerHelper::isUndoPossible() const 1024 { 1025 // SYNCHRONIZED ---> 1026 ::osl::MutexGuard aGuard( m_pImpl->getMutex() ); 1027 IUndoManager& rUndoManager = m_pImpl->getUndoManager(); 1028 if ( rUndoManager.IsInListAction() ) 1029 return sal_False; 1030 return rUndoManager.GetUndoActionCount( IUndoManager::TopLevel ) > 0; 1031 // <--- SYNCHRONIZED 1032 } 1033 1034 //------------------------------------------------------------------------------------------------------------------ 1035 ::sal_Bool UndoManagerHelper::isRedoPossible() const 1036 { 1037 // SYNCHRONIZED ---> 1038 ::osl::MutexGuard aGuard( m_pImpl->getMutex() ); 1039 const IUndoManager& rUndoManager = m_pImpl->getUndoManager(); 1040 if ( rUndoManager.IsInListAction() ) 1041 return sal_False; 1042 return rUndoManager.GetRedoActionCount( IUndoManager::TopLevel ) > 0; 1043 // <--- SYNCHRONIZED 1044 } 1045 1046 //------------------------------------------------------------------------------------------------------------------ 1047 namespace 1048 { 1049 //.............................................................................................................. 1050 ::rtl::OUString lcl_getCurrentActionTitle( UndoManagerHelper_Impl& i_impl, const bool i_undo ) 1051 { 1052 // SYNCHRONIZED ---> 1053 ::osl::MutexGuard aGuard( i_impl.getMutex() ); 1054 1055 const IUndoManager& rUndoManager = i_impl.getUndoManager(); 1056 const size_t nActionCount = i_undo 1057 ? rUndoManager.GetUndoActionCount( IUndoManager::TopLevel ) 1058 : rUndoManager.GetRedoActionCount( IUndoManager::TopLevel ); 1059 if ( nActionCount == 0 ) 1060 throw EmptyUndoStackException( 1061 i_undo ? ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "no action on the undo stack" ) ) 1062 : ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "no action on the redo stack" ) ), 1063 i_impl.getXUndoManager() 1064 ); 1065 return i_undo 1066 ? rUndoManager.GetUndoActionComment( 0, IUndoManager::TopLevel ) 1067 : rUndoManager.GetRedoActionComment( 0, IUndoManager::TopLevel ); 1068 // <--- SYNCHRONIZED 1069 } 1070 1071 //.............................................................................................................. 1072 Sequence< ::rtl::OUString > lcl_getAllActionTitles( UndoManagerHelper_Impl& i_impl, const bool i_undo ) 1073 { 1074 // SYNCHRONIZED ---> 1075 ::osl::MutexGuard aGuard( i_impl.getMutex() ); 1076 1077 const IUndoManager& rUndoManager = i_impl.getUndoManager(); 1078 const size_t nCount = i_undo 1079 ? rUndoManager.GetUndoActionCount( IUndoManager::TopLevel ) 1080 : rUndoManager.GetRedoActionCount( IUndoManager::TopLevel ); 1081 1082 Sequence< ::rtl::OUString > aTitles( nCount ); 1083 for ( size_t i=0; i<nCount; ++i ) 1084 { 1085 aTitles[i] = i_undo 1086 ? rUndoManager.GetUndoActionComment( i, IUndoManager::TopLevel ) 1087 : rUndoManager.GetRedoActionComment( i, IUndoManager::TopLevel ); 1088 } 1089 return aTitles; 1090 // <--- SYNCHRONIZED 1091 } 1092 } 1093 1094 //------------------------------------------------------------------------------------------------------------------ 1095 ::rtl::OUString UndoManagerHelper::getCurrentUndoActionTitle() const 1096 { 1097 return lcl_getCurrentActionTitle( *m_pImpl, true ); 1098 } 1099 1100 //------------------------------------------------------------------------------------------------------------------ 1101 ::rtl::OUString UndoManagerHelper::getCurrentRedoActionTitle() const 1102 { 1103 return lcl_getCurrentActionTitle( *m_pImpl, false ); 1104 } 1105 1106 //------------------------------------------------------------------------------------------------------------------ 1107 Sequence< ::rtl::OUString > UndoManagerHelper::getAllUndoActionTitles() const 1108 { 1109 return lcl_getAllActionTitles( *m_pImpl, true ); 1110 } 1111 1112 //------------------------------------------------------------------------------------------------------------------ 1113 Sequence< ::rtl::OUString > UndoManagerHelper::getAllRedoActionTitles() const 1114 { 1115 return lcl_getAllActionTitles( *m_pImpl, false ); 1116 } 1117 1118 //------------------------------------------------------------------------------------------------------------------ 1119 void UndoManagerHelper::clear( IMutexGuard& i_instanceLock ) 1120 { 1121 m_pImpl->clear( i_instanceLock ); 1122 } 1123 1124 //------------------------------------------------------------------------------------------------------------------ 1125 void UndoManagerHelper::clearRedo( IMutexGuard& i_instanceLock ) 1126 { 1127 m_pImpl->clearRedo( i_instanceLock ); 1128 } 1129 1130 //------------------------------------------------------------------------------------------------------------------ 1131 void UndoManagerHelper::reset( IMutexGuard& i_instanceLock ) 1132 { 1133 m_pImpl->reset( i_instanceLock ); 1134 } 1135 1136 //------------------------------------------------------------------------------------------------------------------ 1137 void UndoManagerHelper::lock() 1138 { 1139 m_pImpl->lock(); 1140 } 1141 1142 //------------------------------------------------------------------------------------------------------------------ 1143 void UndoManagerHelper::unlock() 1144 { 1145 m_pImpl->unlock(); 1146 } 1147 1148 //------------------------------------------------------------------------------------------------------------------ 1149 ::sal_Bool UndoManagerHelper::isLocked() 1150 { 1151 // SYNCHRONIZED ---> 1152 ::osl::MutexGuard aGuard( m_pImpl->getMutex() ); 1153 1154 IUndoManager& rUndoManager = m_pImpl->getUndoManager(); 1155 return !rUndoManager.IsUndoEnabled(); 1156 // <--- SYNCHRONIZED 1157 } 1158 1159 //------------------------------------------------------------------------------------------------------------------ 1160 void UndoManagerHelper::addUndoManagerListener( const Reference< XUndoManagerListener >& i_listener ) 1161 { 1162 if ( i_listener.is() ) 1163 m_pImpl->addUndoManagerListener( i_listener ); 1164 } 1165 1166 //------------------------------------------------------------------------------------------------------------------ 1167 void UndoManagerHelper::removeUndoManagerListener( const Reference< XUndoManagerListener >& i_listener ) 1168 { 1169 if ( i_listener.is() ) 1170 m_pImpl->removeUndoManagerListener( i_listener ); 1171 } 1172 1173 //------------------------------------------------------------------------------------------------------------------ 1174 void UndoManagerHelper::addModifyListener( const Reference< XModifyListener >& i_listener ) 1175 { 1176 if ( i_listener.is() ) 1177 m_pImpl->addModifyListener( i_listener ); 1178 } 1179 1180 //------------------------------------------------------------------------------------------------------------------ 1181 void UndoManagerHelper::removeModifyListener( const Reference< XModifyListener >& i_listener ) 1182 { 1183 if ( i_listener.is() ) 1184 m_pImpl->removeModifyListener( i_listener ); 1185 } 1186 1187 //...................................................................................................................... 1188 } // namespace framework 1189 //...................................................................................................................... 1190