xref: /trunk/main/sw/source/core/undo/docundo.cxx (revision cdf0e10c)
1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_sw.hxx"
30 
31 #include <UndoManager.hxx>
32 
33 #include <unotools/undoopt.hxx>
34 
35 #include <vcl/wrkwin.hxx>
36 
37 #include <svx/svdmodel.hxx>
38 
39 #include <swmodule.hxx>
40 #include <doc.hxx>
41 #include <ndarr.hxx>
42 #include <pam.hxx>
43 #include <ndtxt.hxx>
44 #include <swundo.hxx>
45 #include <UndoCore.hxx>
46 #include <rolbck.hxx>
47 #include <undo.hrc>
48 #include <editsh.hxx>
49 #include <unobaseclass.hxx>
50 #include <limits>
51 
52 #include <limits>
53 
54 using namespace ::com::sun::star;
55 
56 
57 // the undo array should never grow beyond this limit:
58 #define UNDO_ACTION_LIMIT (USHRT_MAX - 1000)
59 
60 
61 // UndoManager ///////////////////////////////////////////////////////////
62 
63 namespace sw {
64 
65 UndoManager::UndoManager(::std::auto_ptr<SwNodes> pUndoNodes,
66             IDocumentDrawModelAccess & rDrawModelAccess,
67             IDocumentRedlineAccess & rRedlineAccess,
68             IDocumentState & rState)
69     :   m_rDrawModelAccess(rDrawModelAccess)
70     ,   m_rRedlineAccess(rRedlineAccess)
71     ,   m_rState(rState)
72     ,   m_pUndoNodes(pUndoNodes)
73     ,   m_bGroupUndo(true)
74     ,   m_bDrawUndo(true)
75     ,   m_bLockUndoNoModifiedPosition(false)
76     ,   m_UndoSaveMark(MARK_INVALID)
77 {
78     OSL_ASSERT(m_pUndoNodes.get());
79     // writer expects it to be disabled initially
80     // Undo is enabled by SwEditShell constructor
81     SfxUndoManager::EnableUndo(false);
82 }
83 
84 SwNodes const& UndoManager::GetUndoNodes() const
85 {
86     return *m_pUndoNodes;
87 }
88 
89 SwNodes      & UndoManager::GetUndoNodes()
90 {
91     return *m_pUndoNodes;
92 }
93 
94 bool UndoManager::IsUndoNodes(SwNodes const& rNodes) const
95 {
96     return & rNodes == m_pUndoNodes.get();
97 }
98 
99 void UndoManager::DoUndo(bool const bDoUndo)
100 {
101     EnableUndo(bDoUndo);
102 
103     SdrModel *const pSdrModel = m_rDrawModelAccess.GetDrawModel();
104 	if( pSdrModel )
105     {
106         pSdrModel->EnableUndo(bDoUndo);
107     }
108 }
109 
110 bool UndoManager::DoesUndo() const
111 {
112     return IsUndoEnabled();
113 }
114 
115 void UndoManager::DoGroupUndo(bool const bDoUndo)
116 {
117     m_bGroupUndo = bDoUndo;
118 }
119 
120 bool UndoManager::DoesGroupUndo() const
121 {
122     return m_bGroupUndo;
123 }
124 
125 void UndoManager::DoDrawUndo(bool const bDoUndo)
126 {
127     m_bDrawUndo = bDoUndo;
128 }
129 
130 bool UndoManager::DoesDrawUndo() const
131 {
132     return m_bDrawUndo;
133 }
134 
135 
136 bool UndoManager::IsUndoNoResetModified() const
137 {
138     return MARK_INVALID == m_UndoSaveMark;
139 }
140 
141 void UndoManager::SetUndoNoResetModified()
142 {
143     if (MARK_INVALID != m_UndoSaveMark)
144     {
145         RemoveMark(m_UndoSaveMark);
146         m_UndoSaveMark = MARK_INVALID;
147     }
148 }
149 
150 void UndoManager::SetUndoNoModifiedPosition()
151 {
152     if (!m_bLockUndoNoModifiedPosition)
153     {
154         m_UndoSaveMark = MarkTopUndoAction();
155     }
156 }
157 
158 void UndoManager::LockUndoNoModifiedPosition()
159 {
160     m_bLockUndoNoModifiedPosition = true;
161 }
162 
163 void UndoManager::UnLockUndoNoModifiedPosition()
164 {
165     m_bLockUndoNoModifiedPosition = false;
166 }
167 
168 
169 SwUndo* UndoManager::GetLastUndo()
170 {
171     if (!SfxUndoManager::GetUndoActionCount(CurrentLevel))
172     {
173         return 0;
174     }
175     SfxUndoAction *const pAction( SfxUndoManager::GetUndoAction(0) );
176     return dynamic_cast<SwUndo*>(pAction);
177 }
178 
179 void UndoManager::AppendUndo(SwUndo *const pUndo)
180 {
181     AddUndoAction(pUndo);
182 }
183 
184 void UndoManager::ClearRedo()
185 {
186     return SfxUndoManager::ImplClearRedo_NoLock(TopLevel);
187 }
188 
189 void UndoManager::DelAllUndoObj()
190 {
191     ::sw::UndoGuard const undoGuard(*this);
192 
193     SfxUndoManager::ClearAllLevels();
194 
195     m_UndoSaveMark = MARK_INVALID;
196 }
197 
198 
199 /**************** UNDO ******************/
200 
201 SwUndoId
202 UndoManager::StartUndo(SwUndoId const i_eUndoId,
203         SwRewriter const*const pRewriter)
204 {
205     if (!IsUndoEnabled())
206     {
207         return UNDO_EMPTY;
208     }
209 
210     SwUndoId const eUndoId( (0 == i_eUndoId) ? UNDO_START : i_eUndoId );
211 
212     OSL_ASSERT(UNDO_END != eUndoId);
213     String comment( (UNDO_START == eUndoId)
214         ?   String("??", RTL_TEXTENCODING_ASCII_US)
215         :   String(SW_RES(UNDO_BASE + eUndoId)) );
216     if (pRewriter)
217     {
218         OSL_ASSERT(UNDO_START != eUndoId);
219         comment = pRewriter->Apply(comment);
220     }
221 
222     SfxUndoManager::EnterListAction(comment, comment, eUndoId);
223 
224     return eUndoId;
225 }
226 
227 
228 SwUndoId
229 UndoManager::EndUndo(SwUndoId const i_eUndoId, SwRewriter const*const pRewriter)
230 {
231     if (!IsUndoEnabled())
232     {
233         return UNDO_EMPTY;
234     }
235 
236     SwUndoId const eUndoId( ((0 == i_eUndoId) || (UNDO_START == i_eUndoId))
237             ? UNDO_END : i_eUndoId );
238     OSL_ENSURE(!((UNDO_END == eUndoId) && pRewriter),
239                 "EndUndo(): no Undo ID, but rewriter given?");
240 
241     SfxUndoAction *const pLastUndo(
242         (0 == SfxUndoManager::GetUndoActionCount(CurrentLevel))
243             ? 0 : SfxUndoManager::GetUndoAction(0) );
244 
245     int const nCount = LeaveListAction();
246 
247     if (nCount) // otherwise: empty list action not inserted!
248     {
249         OSL_ASSERT(pLastUndo);
250         OSL_ASSERT(UNDO_START != eUndoId);
251         SfxUndoAction *const pUndoAction(SfxUndoManager::GetUndoAction(0));
252         SfxListUndoAction *const pListAction(
253             dynamic_cast<SfxListUndoAction*>(pUndoAction));
254         OSL_ASSERT(pListAction);
255         if (pListAction)
256         {
257             if (UNDO_END != eUndoId)
258             {
259                 OSL_ENSURE(pListAction->GetId() == eUndoId,
260                         "EndUndo(): given ID different from StartUndo()");
261                 // comment set by caller of EndUndo
262                 String comment = String(SW_RES(UNDO_BASE + eUndoId));
263                 if (pRewriter)
264                 {
265                     comment = pRewriter->Apply(comment);
266                 }
267                 pListAction->SetComment(comment);
268             }
269             else if ((UNDO_START != pListAction->GetId()))
270             {
271                 // comment set by caller of StartUndo: nothing to do here
272             }
273             else if (pLastUndo)
274             {
275                 // comment was not set at StartUndo or EndUndo:
276                 // take comment of last contained action
277                 // (note that this works recursively, i.e. the last contained
278                 // action may be a list action created by StartUndo/EndUndo)
279                 String const comment(pLastUndo->GetComment());
280                 pListAction->SetComment(comment);
281             }
282             else
283             {
284                 OSL_ENSURE(false, "EndUndo(): no comment?");
285             }
286         }
287     }
288 
289     return eUndoId;
290 }
291 
292 bool
293 UndoManager::GetLastUndoInfo(
294         ::rtl::OUString *const o_pStr, SwUndoId *const o_pId) const
295 {
296     // this is actually expected to work on the current level,
297     // but that was really not obvious from the previous implementation...
298     if (!SfxUndoManager::GetUndoActionCount(CurrentLevel))
299     {
300         return false;
301     }
302 
303     SfxUndoAction *const pAction( SfxUndoManager::GetUndoAction(0) );
304 
305     if (o_pStr)
306     {
307         *o_pStr = pAction->GetComment();
308     }
309     if (o_pId)
310     {
311         sal_uInt16 const nId(pAction->GetId());
312         *o_pId = static_cast<SwUndoId>(nId);
313     }
314 
315     return true;
316 }
317 
318 SwUndoComments_t UndoManager::GetUndoComments() const
319 {
320     OSL_ENSURE(!SfxUndoManager::IsInListAction(),
321             "GetUndoComments() called while in list action?");
322 
323     SwUndoComments_t ret;
324     sal_uInt16 const nUndoCount(SfxUndoManager::GetUndoActionCount(TopLevel));
325     for (sal_uInt16 n = 0; n < nUndoCount; ++n)
326     {
327         ::rtl::OUString const comment(
328                 SfxUndoManager::GetUndoActionComment(n, TopLevel));
329         ret.push_back(comment);
330     }
331 
332     return ret;
333 }
334 
335 
336 /**************** REDO ******************/
337 
338 bool UndoManager::GetFirstRedoInfo(::rtl::OUString *const o_pStr) const
339 {
340     if (!SfxUndoManager::GetRedoActionCount(CurrentLevel))
341     {
342         return false;
343     }
344 
345     if (o_pStr)
346     {
347         *o_pStr = SfxUndoManager::GetRedoActionComment(0, CurrentLevel);
348     }
349 
350     return true;
351 }
352 
353 
354 SwUndoComments_t UndoManager::GetRedoComments() const
355 {
356     OSL_ENSURE(!SfxUndoManager::IsInListAction(),
357             "GetRedoComments() called while in list action?");
358 
359     SwUndoComments_t ret;
360     sal_uInt16 const nRedoCount(SfxUndoManager::GetRedoActionCount(TopLevel));
361     for (sal_uInt16 n = 0; n < nRedoCount; ++n)
362     {
363         ::rtl::OUString const comment(
364                 SfxUndoManager::GetRedoActionComment(n, TopLevel));
365         ret.push_back(comment);
366     }
367 
368     return ret;
369 }
370 
371 /**************** REPEAT ******************/
372 
373 SwUndoId UndoManager::GetRepeatInfo(::rtl::OUString *const o_pStr) const
374 {
375     SwUndoId nRepeatId(UNDO_EMPTY);
376     GetLastUndoInfo(o_pStr, & nRepeatId);
377 	if( REPEAT_START <= nRepeatId && REPEAT_END > nRepeatId )
378     {
379 		return nRepeatId;
380     }
381     if (o_pStr) // not repeatable -> clear comment
382     {
383         *o_pStr = String();
384     }
385     return UNDO_EMPTY;
386 }
387 
388 SwUndo * UndoManager::RemoveLastUndo()
389 {
390     if (SfxUndoManager::GetRedoActionCount(CurrentLevel) ||
391         SfxUndoManager::GetRedoActionCount(TopLevel))
392     {
393         OSL_ENSURE(false, "RemoveLastUndoAction(): there are Redo actions?");
394         return 0;
395     }
396     if (!SfxUndoManager::GetUndoActionCount(CurrentLevel))
397     {
398         OSL_ENSURE(false, "RemoveLastUndoAction(): no Undo actions");
399         return 0;
400     }
401     SfxUndoAction *const pLastUndo(GetUndoAction(0));
402     SfxUndoManager::RemoveLastUndoAction();
403     return dynamic_cast<SwUndo *>(pLastUndo);
404 }
405 
406 // svl::IUndoManager /////////////////////////////////////////////////////
407 
408 void UndoManager::EnableUndo(bool bEnable)
409 {
410     // SfxUndoManager does not have a counter anymore, but reverted to the old behavior of
411     // having a simple boolean flag for locking. So, simply forward.
412     SfxUndoManager::EnableUndo(bEnable);
413 }
414 
415 void UndoManager::AddUndoAction(SfxUndoAction *pAction, sal_Bool bTryMerge)
416 {
417     SwUndo *const pUndo( dynamic_cast<SwUndo *>(pAction) );
418     if (pUndo)
419     {
420         if (nsRedlineMode_t::REDLINE_NONE == pUndo->GetRedlineMode())
421         {
422             pUndo->SetRedlineMode( m_rRedlineAccess.GetRedlineMode() );
423         }
424     }
425     SfxUndoManager::AddUndoAction(pAction, bTryMerge);
426     // if the undo nodes array is too large, delete some actions
427     while (UNDO_ACTION_LIMIT < GetUndoNodes().Count())
428     {
429         RemoveOldestUndoActions(1);
430     }
431 }
432 
433 class CursorGuard
434 {
435 public:
436     CursorGuard(SwEditShell & rShell, bool const bSave)
437         : m_rShell(rShell)
438         , m_bSaveCursor(bSave)
439     {
440         if (m_bSaveCursor)
441         {
442             m_rShell.Push(); // prevent modification of current cursor
443         }
444     }
445     ~CursorGuard()
446     {
447         if (m_bSaveCursor)
448         {
449             m_rShell.Pop();
450         }
451     }
452 private:
453     SwEditShell & m_rShell;
454     bool const m_bSaveCursor;
455 };
456 
457 bool UndoManager::impl_DoUndoRedo(UndoOrRedo_t const undoOrRedo)
458 {
459     SwDoc & rDoc(*GetUndoNodes().GetDoc());
460 
461     UnoActionContext c(& rDoc); // exception-safe StartAllAction/EndAllAction
462 
463     SwEditShell *const pEditShell( rDoc.GetEditShell() );
464 
465     OSL_ENSURE(pEditShell, "sw::UndoManager needs a SwEditShell!");
466     if (!pEditShell)
467     {
468         throw uno::RuntimeException();
469     }
470 
471     // in case the model has controllers locked, the Undo should not
472     // change the view cursors!
473     bool const bSaveCursors(pEditShell->CursorsLocked());
474     CursorGuard(*pEditShell, bSaveCursors);
475     if (!bSaveCursors)
476     {
477         // (in case Undo was called via API) clear the cursors:
478         pEditShell->KillPams();
479         pEditShell->SetMark();
480         pEditShell->ClearMark();
481     }
482 
483     bool bRet(false);
484 
485     ::sw::UndoRedoContext context(rDoc, *pEditShell);
486 
487     // N.B. these may throw!
488     if (UNDO == undoOrRedo)
489     {
490         bRet = SfxUndoManager::UndoWithContext(context);
491     }
492     else
493     {
494         bRet = SfxUndoManager::RedoWithContext(context);
495     }
496 
497     if (bRet)
498     {
499         // if we are at the "last save" position, the document is not modified
500         if (SfxUndoManager::HasTopUndoActionMark(m_UndoSaveMark))
501         {
502             m_rState.ResetModified();
503         }
504         else
505         {
506             m_rState.SetModified();
507         }
508     }
509 
510     pEditShell->HandleUndoRedoContext(context);
511 
512     return bRet;
513 }
514 
515 sal_Bool UndoManager::Undo()
516 {
517     bool const bRet = impl_DoUndoRedo(UNDO);
518     return bRet;
519 }
520 
521 sal_Bool UndoManager::Redo()
522 {
523     bool const bRet = impl_DoUndoRedo(REDO);
524     return bRet;
525 }
526 
527 /** N.B.: this does _not_ call SfxUndoManager::Repeat because it is not
528           possible to wrap a list action around it:
529           calling EnterListAction here will cause SfxUndoManager::Repeat
530           to repeat the list action!
531  */
532 bool
533 UndoManager::Repeat(::sw::RepeatContext & rContext,
534         sal_uInt16 const nRepeatCount)
535 {
536     if (SfxUndoManager::IsInListAction())
537     {
538         OSL_ENSURE(false, "repeat in open list action???");
539         return false;
540     }
541     if (!SfxUndoManager::GetUndoActionCount(TopLevel))
542     {
543         return false;
544     }
545     SfxUndoAction *const pRepeatAction(GetUndoAction(0));
546     OSL_ASSERT(pRepeatAction);
547     if (!pRepeatAction || !pRepeatAction->CanRepeat(rContext))
548     {
549         return false;
550     }
551 
552     ::rtl::OUString const comment(pRepeatAction->GetComment());
553     ::rtl::OUString const rcomment(pRepeatAction->GetRepeatComment(rContext));
554     sal_uInt16 const nId(pRepeatAction->GetId());
555     if (DoesUndo())
556     {
557         EnterListAction(comment, rcomment, nId);
558     }
559 
560     SwPaM *const pFirstCursor(& rContext.GetRepeatPaM());
561     do {    // iterate over ring
562         for (sal_uInt16 nRptCnt = nRepeatCount; nRptCnt > 0; --nRptCnt)
563         {
564             pRepeatAction->Repeat(rContext);
565         }
566         rContext.m_bDeleteRepeated = false; // reset for next PaM
567         rContext.m_pCurrentPaM =
568             static_cast<SwPaM*>(rContext.m_pCurrentPaM->GetNext());
569     } while (pFirstCursor != & rContext.GetRepeatPaM());
570 
571     if (DoesUndo())
572     {
573         LeaveListAction();
574     }
575     return true;
576 }
577 
578 } // namespace sw
579 
580