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