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