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 277 : UndoActionWrapper::UndoActionWrapper( Reference< XUndoAction > const& i_undoAction )
85 : :SfxUndoAction()
86 277 : ,m_xUndoAction( i_undoAction )
87 : {
88 277 : ENSURE_OR_THROW( m_xUndoAction.is(), "illegal undo action" );
89 277 : }
90 :
91 819 : UndoActionWrapper::~UndoActionWrapper()
92 : {
93 : try
94 : {
95 273 : Reference< XComponent > xComponent( m_xUndoAction, UNO_QUERY );
96 273 : if ( xComponent.is() )
97 144 : xComponent->dispose();
98 : }
99 0 : catch( const Exception& )
100 : {
101 : DBG_UNHANDLED_EXCEPTION();
102 : }
103 546 : }
104 :
105 650 : OUString UndoActionWrapper::GetComment() const
106 : {
107 650 : OUString sComment;
108 : try
109 : {
110 650 : sComment = m_xUndoAction->getTitle();
111 : }
112 0 : catch( const Exception& )
113 : {
114 : DBG_UNHANDLED_EXCEPTION();
115 : }
116 650 : return sComment;
117 : }
118 :
119 216 : void UndoActionWrapper::Undo()
120 : {
121 216 : m_xUndoAction->undo();
122 208 : }
123 :
124 11 : void UndoActionWrapper::Redo()
125 : {
126 11 : m_xUndoAction->redo();
127 3 : }
128 :
129 0 : bool UndoActionWrapper::CanRepeat(SfxRepeatTarget&) const
130 : {
131 0 : return false;
132 : }
133 :
134 : //= UndoManagerRequest
135 :
136 : class UndoManagerRequest : public ::comphelper::AnyEvent
137 : {
138 : public:
139 747 : UndoManagerRequest( ::boost::function0< void > const& i_request )
140 : :m_request( i_request )
141 : ,m_caughtException()
142 747 : ,m_finishCondition()
143 : {
144 747 : m_finishCondition.reset();
145 747 : }
146 :
147 747 : void execute()
148 : {
149 : try
150 : {
151 747 : m_request();
152 : }
153 46 : catch( const Exception& )
154 : {
155 46 : m_caughtException = ::cppu::getCaughtException();
156 : }
157 747 : m_finishCondition.set();
158 747 : }
159 :
160 827 : void wait()
161 : {
162 827 : m_finishCondition.wait();
163 827 : if ( m_caughtException.hasValue() )
164 46 : ::cppu::throwException( m_caughtException );
165 781 : }
166 :
167 0 : void cancel( const Reference< XInterface >& i_context )
168 : {
169 0 : m_caughtException <<= RuntimeException(
170 : OUString( "Concurrency error: an earlier operation on the stack failed." ),
171 : i_context
172 0 : );
173 0 : m_finishCondition.set();
174 0 : }
175 :
176 : protected:
177 1494 : virtual ~UndoManagerRequest()
178 747 : {
179 1494 : }
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 965 : ::osl::Mutex& getMutex() { return m_aMutex; }
210 :
211 : public:
212 391 : 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 391 : ,m_rUndoManagerImplementation( i_undoManagerImpl )
222 : {
223 391 : getUndoManager().AddUndoListener( *this );
224 391 : }
225 :
226 74 : virtual ~UndoManagerHelper_Impl()
227 37 : {
228 74 : }
229 :
230 28620 : IUndoManager& getUndoManager() const
231 : {
232 28620 : return m_rUndoManagerImplementation.getImplUndoManager();
233 : }
234 :
235 65191 : Reference< XUndoManager > getXUndoManager() const
236 : {
237 65191 : 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 85 : void addUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
269 : {
270 85 : m_aUndoListeners.addInterface( i_listener );
271 85 : }
272 :
273 79 : void removeUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
274 : {
275 79 : m_aUndoListeners.removeInterface( i_listener );
276 79 : }
277 :
278 17 : void addModifyListener( const Reference< XModifyListener >& i_listener )
279 : {
280 17 : m_aModifyListeners.addInterface( i_listener );
281 17 : }
282 :
283 17 : void removeModifyListener( const Reference< XModifyListener >& i_listener )
284 : {
285 17 : m_aModifyListeners.removeInterface( i_listener );
286 17 : }
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 407 : void notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& ) )
296 : {
297 407 : notify( OUString(), i_notificationMethod );
298 407 : }
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 391 : void UndoManagerHelper_Impl::disposing()
317 : {
318 391 : EventObject aEvent;
319 391 : aEvent.Source = getXUndoManager();
320 391 : m_aUndoListeners.disposeAndClear( aEvent );
321 391 : m_aModifyListeners.disposeAndClear( aEvent );
322 :
323 782 : ::osl::MutexGuard aGuard( m_aMutex );
324 :
325 391 : getUndoManager().RemoveUndoListener( *this );
326 :
327 782 : m_disposed = true;
328 391 : }
329 :
330 26130 : UndoManagerEvent UndoManagerHelper_Impl::buildEvent( OUString const& i_title ) const
331 : {
332 26130 : UndoManagerEvent aEvent;
333 26130 : aEvent.Source = getXUndoManager();
334 26130 : aEvent.UndoActionTitle = i_title;
335 26130 : aEvent.UndoContextDepth = getUndoManager().GetListActionDepth();
336 26130 : return aEvent;
337 : }
338 :
339 32210 : void UndoManagerHelper_Impl::impl_notifyModified()
340 : {
341 32210 : const EventObject aEvent( getXUndoManager() );
342 32210 : m_aModifyListeners.notifyEach( &XModifyListener::modified, aEvent );
343 32210 : }
344 :
345 25722 : void UndoManagerHelper_Impl::notify( OUString const& i_title,
346 : void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& ) )
347 : {
348 25722 : 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 25722 : m_aUndoListeners.notifyEach( i_notificationMethod, aEvent );
357 25722 : impl_notifyModified();
358 25722 : }
359 :
360 5804 : void UndoManagerHelper_Impl::notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const EventObject& ) )
361 : {
362 5804 : const EventObject aEvent( getXUndoManager() );
363 :
364 : // TODO: the same comment as in the other notify, regarding SM locking applies here ...
365 :
366 5804 : m_aUndoListeners.notifyEach( i_notificationMethod, aEvent );
367 5804 : impl_notifyModified();
368 5804 : }
369 :
370 88 : 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 96 : );
381 80 : }
382 :
383 71 : 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 75 : );
392 67 : }
393 :
394 288 : void UndoManagerHelper_Impl::addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock )
395 : {
396 288 : if ( !i_action.is() )
397 : throw IllegalArgumentException(
398 : "illegal undo action object",
399 : getXUndoManager(),
400 : 1
401 4 : );
402 :
403 : impl_processRequest(
404 : ::boost::bind(
405 : &UndoManagerHelper_Impl::impl_addUndoAction,
406 : this,
407 : ::boost::ref( i_action )
408 : ),
409 : i_instanceLock
410 284 : );
411 284 : }
412 :
413 24 : 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 28 : );
422 20 : }
423 :
424 8 : 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 12 : );
433 4 : }
434 :
435 64 : 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 64 : );
444 64 : }
445 :
446 12 : void UndoManagerHelper_Impl::lock()
447 : {
448 : // SYNCHRONIZED --->
449 12 : ::osl::MutexGuard aGuard( getMutex() );
450 :
451 12 : if ( ++m_nLockCount == 1 )
452 : {
453 12 : IUndoManager& rUndoManager = getUndoManager();
454 12 : rUndoManager.EnableUndo( false );
455 12 : }
456 : // <--- SYNCHRONIZED
457 12 : }
458 :
459 16 : void UndoManagerHelper_Impl::unlock()
460 : {
461 : // SYNCHRONIZED --->
462 16 : ::osl::MutexGuard aGuard( getMutex() );
463 :
464 16 : if ( m_nLockCount == 0 )
465 4 : throw NotLockedException( "Undo manager is not locked", getXUndoManager() );
466 :
467 12 : if ( --m_nLockCount == 0 )
468 : {
469 12 : IUndoManager& rUndoManager = getUndoManager();
470 12 : rUndoManager.EnableUndo( true );
471 16 : }
472 : // <--- SYNCHRONIZED
473 12 : }
474 :
475 747 : 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 747 : ::rtl::Reference< UndoManagerRequest > pRequest( new UndoManagerRequest( i_request ) );
479 : {
480 747 : ::osl::MutexGuard aQueueGuard( m_aQueueMutex );
481 747 : m_aEventQueue.push( pRequest );
482 : }
483 :
484 747 : i_instanceLock.clear();
485 :
486 747 : if ( m_bProcessingEvents )
487 : {
488 : // another thread is processing the event queue currently => it will also process the event which we just added
489 80 : pRequest->wait();
490 80 : return;
491 : }
492 :
493 667 : m_bProcessingEvents = true;
494 : do
495 : {
496 1368 : pRequest.clear();
497 : {
498 1368 : ::osl::MutexGuard aQueueGuard( m_aQueueMutex );
499 1368 : 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 621 : m_bProcessingEvents = false;
505 621 : return;
506 : }
507 747 : pRequest = m_aEventQueue.front();
508 747 : m_aEventQueue.pop();
509 : }
510 : try
511 : {
512 747 : pRequest->execute();
513 747 : pRequest->wait();
514 : }
515 46 : catch( ... )
516 : {
517 : {
518 : // no chance to process further requests, if the current one failed
519 : // => discard them
520 46 : ::osl::MutexGuard aQueueGuard( m_aQueueMutex );
521 92 : while ( !m_aEventQueue.empty() )
522 : {
523 0 : pRequest = m_aEventQueue.front();
524 0 : m_aEventQueue.pop();
525 0 : pRequest->cancel( getXUndoManager() );
526 : }
527 46 : m_bProcessingEvents = false;
528 : }
529 : // re-throw the error
530 46 : throw;
531 : }
532 : }
533 747 : while ( true );
534 : }
535 :
536 88 : void UndoManagerHelper_Impl::impl_enterUndoContext( const OUString& i_title, const bool i_hidden )
537 : {
538 : // SYNCHRONIZED --->
539 88 : ::osl::ClearableMutexGuard aGuard( m_aMutex );
540 :
541 88 : IUndoManager& rUndoManager = getUndoManager();
542 88 : if ( !rUndoManager.IsUndoEnabled() )
543 : // ignore this request if the manager is locked
544 88 : return;
545 :
546 80 : if ( i_hidden && ( rUndoManager.GetUndoActionCount( IUndoManager::CurrentLevel ) == 0 ) )
547 : throw EmptyUndoStackException(
548 : "can't enter a hidden context without a previous Undo action",
549 8 : m_rUndoManagerImplementation.getThis()
550 16 : );
551 :
552 : {
553 72 : ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
554 72 : rUndoManager.EnterListAction( i_title, OUString() );
555 : }
556 :
557 72 : m_aContextVisibilities.push( i_hidden );
558 :
559 144 : const UndoManagerEvent aEvent( buildEvent( i_title ) );
560 72 : aGuard.clear();
561 : // <--- SYNCHRONIZED
562 :
563 72 : m_aUndoListeners.notifyEach( i_hidden ? &XUndoManagerListener::enteredHiddenContext : &XUndoManagerListener::enteredContext, aEvent );
564 152 : impl_notifyModified();
565 : }
566 :
567 71 : void UndoManagerHelper_Impl::impl_leaveUndoContext()
568 : {
569 : // SYNCHRONIZED --->
570 71 : ::osl::ClearableMutexGuard aGuard( m_aMutex );
571 :
572 71 : IUndoManager& rUndoManager = getUndoManager();
573 71 : if ( !rUndoManager.IsUndoEnabled() )
574 : // ignore this request if the manager is locked
575 75 : return;
576 :
577 63 : if ( !rUndoManager.IsInListAction() )
578 : throw InvalidStateException(
579 : "no active undo context",
580 : getXUndoManager()
581 4 : );
582 :
583 59 : size_t nContextElements = 0;
584 :
585 59 : const bool isHiddenContext = m_aContextVisibilities.top();;
586 59 : m_aContextVisibilities.pop();
587 :
588 59 : const bool bHadRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::TopLevel ) > 0 );
589 : {
590 59 : ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
591 59 : if ( isHiddenContext )
592 16 : nContextElements = rUndoManager.LeaveAndMergeListAction();
593 : else
594 43 : nContextElements = rUndoManager.LeaveListAction();
595 : }
596 59 : const bool bHasRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::TopLevel ) > 0 );
597 :
598 : // prepare notification
599 59 : void ( SAL_CALL XUndoManagerListener::*notificationMethod )( const UndoManagerEvent& ) = NULL;
600 :
601 118 : UndoManagerEvent aContextEvent( buildEvent( OUString() ) );
602 118 : const EventObject aClearedEvent( getXUndoManager() );
603 59 : if ( nContextElements == 0 )
604 : {
605 19 : notificationMethod = &XUndoManagerListener::cancelledContext;
606 : }
607 40 : else if ( isHiddenContext )
608 : {
609 8 : notificationMethod = &XUndoManagerListener::leftHiddenContext;
610 : }
611 : else
612 : {
613 32 : aContextEvent.UndoActionTitle = rUndoManager.GetUndoActionComment( 0, IUndoManager::CurrentLevel );
614 32 : notificationMethod = &XUndoManagerListener::leftContext;
615 : }
616 :
617 59 : aGuard.clear();
618 : // <--- SYNCHRONIZED
619 :
620 59 : if ( bHadRedoActions && !bHasRedoActions )
621 7 : m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aClearedEvent );
622 59 : m_aUndoListeners.notifyEach( notificationMethod, aContextEvent );
623 122 : impl_notifyModified();
624 : }
625 :
626 208 : void UndoManagerHelper_Impl::impl_doUndoRedo( IMutexGuard& i_externalLock, const bool i_undo )
627 : {
628 208 : ::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 416 : ::osl::ClearableMutexGuard aGuard( m_aMutex );
634 :
635 208 : IUndoManager& rUndoManager = getUndoManager();
636 208 : if ( rUndoManager.IsInListAction() )
637 8 : throw UndoContextNotClosedException( OUString(), getXUndoManager() );
638 :
639 : const size_t nElements = i_undo
640 187 : ? rUndoManager.GetUndoActionCount( IUndoManager::TopLevel )
641 387 : : rUndoManager.GetRedoActionCount( IUndoManager::TopLevel );
642 200 : if ( nElements == 0 )
643 8 : throw EmptyUndoStackException("stack is empty", getXUndoManager() );
644 :
645 192 : aGuard.clear();
646 : // <--- SYNCHRONIZED
647 :
648 : try
649 : {
650 192 : if ( i_undo )
651 183 : rUndoManager.Undo();
652 : else
653 9 : rUndoManager.Redo();
654 : }
655 0 : catch( const RuntimeException& ) { /* allowed to leave here */ throw; }
656 20 : 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 208 : }
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 182 : }
672 :
673 284 : void UndoManagerHelper_Impl::impl_addUndoAction( const Reference< XUndoAction >& i_action )
674 : {
675 : // SYNCHRONIZED --->
676 284 : ::osl::ClearableMutexGuard aGuard( m_aMutex );
677 :
678 284 : IUndoManager& rUndoManager = getUndoManager();
679 284 : if ( !rUndoManager.IsUndoEnabled() )
680 : // ignore the request if the manager is locked
681 291 : return;
682 :
683 554 : const UndoManagerEvent aEventAdd( buildEvent( i_action->getTitle() ) );
684 554 : const EventObject aEventClear( getXUndoManager() );
685 :
686 277 : const bool bHadRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::CurrentLevel ) > 0 );
687 : {
688 277 : ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
689 277 : rUndoManager.AddUndoAction( new UndoActionWrapper( i_action ) );
690 : }
691 277 : const bool bHasRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::CurrentLevel ) > 0 );
692 :
693 277 : aGuard.clear();
694 : // <--- SYNCHRONIZED
695 :
696 277 : m_aUndoListeners.notifyEach( &XUndoManagerListener::undoActionAdded, aEventAdd );
697 277 : if ( bHadRedoActions && !bHasRedoActions )
698 8 : m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aEventClear );
699 554 : impl_notifyModified();
700 : }
701 :
702 24 : void UndoManagerHelper_Impl::impl_clear()
703 : {
704 : // SYNCHRONIZED --->
705 24 : ::osl::ClearableMutexGuard aGuard( m_aMutex );
706 :
707 24 : IUndoManager& rUndoManager = getUndoManager();
708 24 : if ( rUndoManager.IsInListAction() )
709 4 : throw UndoContextNotClosedException( OUString(), getXUndoManager() );
710 :
711 : {
712 20 : ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
713 20 : rUndoManager.Clear();
714 : }
715 :
716 40 : const EventObject aEvent( getXUndoManager() );
717 20 : aGuard.clear();
718 : // <--- SYNCHRONIZED
719 :
720 20 : m_aUndoListeners.notifyEach( &XUndoManagerListener::allActionsCleared, aEvent );
721 44 : impl_notifyModified();
722 20 : }
723 :
724 8 : void UndoManagerHelper_Impl::impl_clearRedo()
725 : {
726 : // SYNCHRONIZED --->
727 8 : ::osl::ClearableMutexGuard aGuard( m_aMutex );
728 :
729 8 : IUndoManager& rUndoManager = getUndoManager();
730 8 : if ( rUndoManager.IsInListAction() )
731 4 : throw UndoContextNotClosedException( OUString(), getXUndoManager() );
732 :
733 : {
734 4 : ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
735 4 : rUndoManager.ClearRedo();
736 : }
737 :
738 8 : const EventObject aEvent( getXUndoManager() );
739 4 : aGuard.clear();
740 : // <--- SYNCHRONIZED
741 :
742 4 : m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aEvent );
743 12 : impl_notifyModified();
744 4 : }
745 :
746 64 : void UndoManagerHelper_Impl::impl_reset()
747 : {
748 : // SYNCHRONIZED --->
749 64 : ::osl::ClearableMutexGuard aGuard( m_aMutex );
750 :
751 64 : IUndoManager& rUndoManager = getUndoManager();
752 : {
753 64 : ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
754 64 : rUndoManager.Reset();
755 : }
756 :
757 128 : const EventObject aEvent( getXUndoManager() );
758 64 : aGuard.clear();
759 : // <--- SYNCHRONIZED
760 :
761 64 : m_aUndoListeners.notifyEach( &XUndoManagerListener::resetAll, aEvent );
762 128 : impl_notifyModified();
763 64 : }
764 :
765 184 : void UndoManagerHelper_Impl::actionUndone( const OUString& i_actionComment )
766 : {
767 184 : UndoManagerEvent aEvent;
768 184 : aEvent.Source = getXUndoManager();
769 184 : aEvent.UndoActionTitle = i_actionComment;
770 184 : aEvent.UndoContextDepth = 0; // Undo can happen on level 0 only
771 184 : m_aUndoListeners.notifyEach( &XUndoManagerListener::actionUndone, aEvent );
772 184 : impl_notifyModified();
773 184 : }
774 :
775 4 : void UndoManagerHelper_Impl::actionRedone( const OUString& i_actionComment )
776 : {
777 4 : UndoManagerEvent aEvent;
778 4 : aEvent.Source = getXUndoManager();
779 4 : aEvent.UndoActionTitle = i_actionComment;
780 4 : aEvent.UndoContextDepth = 0; // Redo can happen on level 0 only
781 4 : m_aUndoListeners.notifyEach( &XUndoManagerListener::actionRedone, aEvent );
782 4 : impl_notifyModified();
783 4 : }
784 :
785 18051 : void UndoManagerHelper_Impl::undoActionAdded( const OUString& i_actionComment )
786 : {
787 18051 : if ( m_bAPIActionRunning )
788 18328 : return;
789 :
790 17774 : notify( i_actionComment, &XUndoManagerListener::undoActionAdded );
791 : }
792 :
793 28 : void UndoManagerHelper_Impl::cleared()
794 : {
795 28 : if ( m_bAPIActionRunning )
796 48 : return;
797 :
798 8 : notify( &XUndoManagerListener::allActionsCleared );
799 : }
800 :
801 5797 : void UndoManagerHelper_Impl::clearedRedo()
802 : {
803 5797 : if ( m_bAPIActionRunning )
804 5798 : return;
805 :
806 5796 : notify( &XUndoManagerListener::redoActionsCleared );
807 : }
808 :
809 64 : void UndoManagerHelper_Impl::resetAll()
810 : {
811 64 : if ( m_bAPIActionRunning )
812 128 : return;
813 :
814 0 : notify( &XUndoManagerListener::resetAll );
815 : }
816 :
817 4046 : 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 4046 : if ( m_bAPIActionRunning )
824 4118 : return;
825 :
826 3974 : notify( i_comment, &XUndoManagerListener::enteredContext );
827 : }
828 :
829 3607 : 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 3607 : if ( m_bAPIActionRunning )
838 3647 : return;
839 :
840 3567 : 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 426 : 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 426 : if ( m_bAPIActionRunning )
866 445 : return;
867 :
868 407 : 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 391 : UndoManagerHelper::UndoManagerHelper( IUndoManagerImplementation& i_undoManagerImpl )
879 391 : :m_xImpl( new UndoManagerHelper_Impl( i_undoManagerImpl ) )
880 : {
881 391 : }
882 :
883 37 : UndoManagerHelper::~UndoManagerHelper()
884 : {
885 37 : }
886 :
887 391 : void UndoManagerHelper::disposing()
888 : {
889 391 : m_xImpl->disposing();
890 391 : }
891 :
892 56 : void UndoManagerHelper::enterUndoContext( const OUString& i_title, IMutexGuard& i_instanceLock )
893 : {
894 56 : m_xImpl->enterUndoContext( i_title, false, i_instanceLock );
895 56 : }
896 :
897 32 : void UndoManagerHelper::enterHiddenUndoContext( IMutexGuard& i_instanceLock )
898 : {
899 40 : m_xImpl->enterUndoContext( OUString(), true, i_instanceLock );
900 24 : }
901 :
902 71 : void UndoManagerHelper::leaveUndoContext( IMutexGuard& i_instanceLock )
903 : {
904 71 : m_xImpl->leaveUndoContext( i_instanceLock );
905 67 : }
906 :
907 191 : 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 204 : );
918 178 : }
919 :
920 17 : 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 30 : );
931 4 : }
932 :
933 288 : void UndoManagerHelper::addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock )
934 : {
935 288 : m_xImpl->addUndoAction( i_action, i_instanceLock );
936 284 : }
937 :
938 191 : void UndoManagerHelper::undo( IMutexGuard& i_instanceLock )
939 : {
940 191 : m_xImpl->undo( i_instanceLock );
941 178 : }
942 :
943 17 : void UndoManagerHelper::redo( IMutexGuard& i_instanceLock )
944 : {
945 17 : m_xImpl->redo( i_instanceLock );
946 4 : }
947 :
948 385 : bool UndoManagerHelper::isUndoPossible() const
949 : {
950 : // SYNCHRONIZED --->
951 385 : ::osl::MutexGuard aGuard( m_xImpl->getMutex() );
952 385 : IUndoManager& rUndoManager = m_xImpl->getUndoManager();
953 385 : if ( rUndoManager.IsInListAction() )
954 72 : return false;
955 313 : return rUndoManager.GetUndoActionCount( IUndoManager::TopLevel ) > 0;
956 : // <--- SYNCHRONIZED
957 : }
958 :
959 377 : bool UndoManagerHelper::isRedoPossible() const
960 : {
961 : // SYNCHRONIZED --->
962 377 : ::osl::MutexGuard aGuard( m_xImpl->getMutex() );
963 377 : const IUndoManager& rUndoManager = m_xImpl->getUndoManager();
964 377 : if ( rUndoManager.IsInListAction() )
965 72 : return false;
966 305 : return rUndoManager.GetRedoActionCount( IUndoManager::TopLevel ) > 0;
967 : // <--- SYNCHRONIZED
968 : }
969 :
970 : namespace
971 : {
972 :
973 91 : OUString lcl_getCurrentActionTitle( UndoManagerHelper_Impl& i_impl, const bool i_undo )
974 : {
975 : // SYNCHRONIZED --->
976 91 : ::osl::MutexGuard aGuard( i_impl.getMutex() );
977 :
978 91 : const IUndoManager& rUndoManager = i_impl.getUndoManager();
979 : const size_t nActionCount = i_undo
980 64 : ? rUndoManager.GetUndoActionCount( IUndoManager::TopLevel )
981 155 : : rUndoManager.GetRedoActionCount( IUndoManager::TopLevel );
982 91 : 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 8 : );
988 : return i_undo
989 60 : ? rUndoManager.GetUndoActionComment( 0, IUndoManager::TopLevel )
990 151 : : rUndoManager.GetRedoActionComment( 0, IUndoManager::TopLevel );
991 : // <--- SYNCHRONIZED
992 : }
993 :
994 64 : Sequence< OUString > lcl_getAllActionTitles( UndoManagerHelper_Impl& i_impl, const bool i_undo )
995 : {
996 : // SYNCHRONIZED --->
997 64 : ::osl::MutexGuard aGuard( i_impl.getMutex() );
998 :
999 64 : const IUndoManager& rUndoManager = i_impl.getUndoManager();
1000 : const size_t nCount = i_undo
1001 28 : ? rUndoManager.GetUndoActionCount( IUndoManager::TopLevel )
1002 92 : : rUndoManager.GetRedoActionCount( IUndoManager::TopLevel );
1003 :
1004 64 : Sequence< OUString > aTitles( nCount );
1005 124 : for ( size_t i=0; i<nCount; ++i )
1006 : {
1007 180 : aTitles[i] = i_undo
1008 32 : ? rUndoManager.GetUndoActionComment( i, IUndoManager::TopLevel )
1009 88 : : rUndoManager.GetRedoActionComment( i, IUndoManager::TopLevel );
1010 : }
1011 64 : return aTitles;
1012 : // <--- SYNCHRONIZED
1013 : }
1014 : }
1015 :
1016 64 : OUString UndoManagerHelper::getCurrentUndoActionTitle() const
1017 : {
1018 64 : return lcl_getCurrentActionTitle( *m_xImpl, true );
1019 : }
1020 :
1021 27 : OUString UndoManagerHelper::getCurrentRedoActionTitle() const
1022 : {
1023 27 : return lcl_getCurrentActionTitle( *m_xImpl, false );
1024 : }
1025 :
1026 28 : Sequence< OUString > UndoManagerHelper::getAllUndoActionTitles() const
1027 : {
1028 28 : return lcl_getAllActionTitles( *m_xImpl, true );
1029 : }
1030 :
1031 36 : Sequence< OUString > UndoManagerHelper::getAllRedoActionTitles() const
1032 : {
1033 36 : return lcl_getAllActionTitles( *m_xImpl, false );
1034 : }
1035 :
1036 24 : void UndoManagerHelper::clear( IMutexGuard& i_instanceLock )
1037 : {
1038 24 : m_xImpl->clear( i_instanceLock );
1039 20 : }
1040 :
1041 8 : void UndoManagerHelper::clearRedo( IMutexGuard& i_instanceLock )
1042 : {
1043 8 : m_xImpl->clearRedo( i_instanceLock );
1044 4 : }
1045 :
1046 64 : void UndoManagerHelper::reset( IMutexGuard& i_instanceLock )
1047 : {
1048 64 : m_xImpl->reset( i_instanceLock );
1049 64 : }
1050 :
1051 12 : void UndoManagerHelper::lock()
1052 : {
1053 12 : m_xImpl->lock();
1054 12 : }
1055 :
1056 16 : void UndoManagerHelper::unlock()
1057 : {
1058 16 : m_xImpl->unlock();
1059 12 : }
1060 :
1061 20 : bool UndoManagerHelper::isLocked()
1062 : {
1063 : // SYNCHRONIZED --->
1064 20 : ::osl::MutexGuard aGuard( m_xImpl->getMutex() );
1065 :
1066 20 : IUndoManager& rUndoManager = m_xImpl->getUndoManager();
1067 20 : return !rUndoManager.IsUndoEnabled();
1068 : // <--- SYNCHRONIZED
1069 : }
1070 :
1071 85 : void UndoManagerHelper::addUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
1072 : {
1073 85 : if ( i_listener.is() )
1074 85 : m_xImpl->addUndoManagerListener( i_listener );
1075 85 : }
1076 :
1077 79 : void UndoManagerHelper::removeUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
1078 : {
1079 79 : if ( i_listener.is() )
1080 79 : m_xImpl->removeUndoManagerListener( i_listener );
1081 79 : }
1082 :
1083 17 : void UndoManagerHelper::addModifyListener( const Reference< XModifyListener >& i_listener )
1084 : {
1085 17 : if ( i_listener.is() )
1086 17 : m_xImpl->addModifyListener( i_listener );
1087 17 : }
1088 :
1089 17 : void UndoManagerHelper::removeModifyListener( const Reference< XModifyListener >& i_listener )
1090 : {
1091 17 : if ( i_listener.is() )
1092 17 : m_xImpl->removeModifyListener( i_listener );
1093 17 : }
1094 :
1095 660 : } // namespace framework
1096 :
1097 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|