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