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