1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <UndoManager.hxx>
22 #include <libxml/xmlwriter.h>
28 #include <drawdoc.hxx>
32 #include <UndoCore.hxx>
34 #include <unobaseclass.hxx>
35 #include <IDocumentDrawModelAccess.hxx>
36 #include <IDocumentRedlineAccess.hxx>
37 #include <IDocumentState.hxx>
38 #include <comphelper/lok.hxx>
41 #include <sfx2/viewfrm.hxx>
42 #include <sfx2/bindings.hxx>
43 #include <osl/diagnose.h>
44 #include <o3tl/temporary.hxx>
46 #include <UndoInsert.hxx>
48 using namespace ::com::sun::star
;
50 // the undo array should never grow beyond this limit:
51 #define UNDO_ACTION_LIMIT (USHRT_MAX - 1000)
55 UndoManager::UndoManager(std::shared_ptr
<SwNodes
> xUndoNodes
,
56 IDocumentDrawModelAccess
& rDrawModelAccess
,
57 IDocumentRedlineAccess
& rRedlineAccess
,
58 IDocumentState
& rState
)
59 : m_rDrawModelAccess(rDrawModelAccess
)
60 , m_rRedlineAccess(rRedlineAccess
)
62 , m_xUndoNodes(std::move(xUndoNodes
))
66 , m_bLockUndoNoModifiedPosition(false)
67 , m_isAddWithIgnoreRepeat(false)
68 , m_UndoSaveMark(MARK_INVALID
)
69 , m_pDocShell(nullptr)
72 assert(bool(m_xUndoNodes
));
73 // writer expects it to be disabled initially
74 // Undo is enabled by SwEditShell constructor
75 SdrUndoManager::EnableUndo(false);
78 SwNodes
const& UndoManager::GetUndoNodes() const
83 SwNodes
& UndoManager::GetUndoNodes()
88 bool UndoManager::IsUndoNodes(SwNodes
const& rNodes
) const
90 return & rNodes
== m_xUndoNodes
.get();
93 void UndoManager::SetDocShell(SwDocShell
* pDocShell
)
95 m_pDocShell
= pDocShell
;
98 void UndoManager::SetView(SwView
* pView
)
103 size_t UndoManager::GetUndoActionCount(const bool bCurrentLevel
) const
105 size_t nRet
= SdrUndoManager::GetUndoActionCount(bCurrentLevel
);
106 if (!comphelper::LibreOfficeKit::isActive() || !m_pView
)
109 if (!nRet
|| !SdrUndoManager::GetUndoActionCount())
112 const SfxUndoAction
* pAction
= SdrUndoManager::GetUndoAction();
118 // If another view created the last undo action, prevent undoing it from this view.
119 ViewShellId nViewShellId
= m_pView
->GetViewShellId();
120 if (pAction
->GetViewShellId() != nViewShellId
)
127 size_t UndoManager::GetRedoActionCount(const bool bCurrentLevel
) const
129 size_t nRet
= SdrUndoManager::GetRedoActionCount(bCurrentLevel
);
130 if (!comphelper::LibreOfficeKit::isActive() || !m_pView
)
133 if (!nRet
|| !SdrUndoManager::GetRedoActionCount())
136 const SfxUndoAction
* pAction
= SdrUndoManager::GetRedoAction();
142 // If another view created the first redo action, prevent redoing it from this view.
143 ViewShellId nViewShellId
= m_pView
->GetViewShellId();
144 if (pAction
->GetViewShellId() != nViewShellId
)
151 void UndoManager::DoUndo(bool const bDoUndo
)
153 if(!isTextEditActive())
157 SwDrawModel
*const pSdrModel
= m_rDrawModelAccess
.GetDrawModel();
160 pSdrModel
->EnableUndo(bDoUndo
);
165 bool UndoManager::DoesUndo() const
167 if(isTextEditActive())
173 return IsUndoEnabled();
177 void UndoManager::DoGroupUndo(bool const bDoUndo
)
179 m_bGroupUndo
= bDoUndo
;
182 bool UndoManager::DoesGroupUndo() const
187 void UndoManager::DoDrawUndo(bool const bDoUndo
)
189 m_bDrawUndo
= bDoUndo
;
192 bool UndoManager::DoesDrawUndo() const
197 void UndoManager::DoRepair(bool bRepair
)
202 bool UndoManager::DoesRepair() const
207 bool UndoManager::IsUndoNoResetModified() const
209 return MARK_INVALID
== m_UndoSaveMark
;
212 void UndoManager::SetUndoNoResetModified()
214 if (MARK_INVALID
!= m_UndoSaveMark
)
216 RemoveMark(m_UndoSaveMark
);
217 m_UndoSaveMark
= MARK_INVALID
;
221 void UndoManager::SetUndoNoModifiedPosition()
223 if (!m_bLockUndoNoModifiedPosition
)
225 m_UndoSaveMark
= MarkTopUndoAction();
229 void UndoManager::LockUndoNoModifiedPosition()
231 m_bLockUndoNoModifiedPosition
= true;
234 void UndoManager::UnLockUndoNoModifiedPosition()
236 m_bLockUndoNoModifiedPosition
= false;
239 SwUndo
* UndoManager::GetLastUndo()
241 if (!SdrUndoManager::GetUndoActionCount())
245 SfxUndoAction
*const pAction( SdrUndoManager::GetUndoAction() );
246 return dynamic_cast<SwUndo
*>(pAction
);
249 void UndoManager::AppendUndo(std::unique_ptr
<SwUndo
> pUndo
)
251 AddUndoAction(std::move(pUndo
));
254 void UndoManager::ClearRedo()
256 return SdrUndoManager::ImplClearRedo_NoLock(TopLevel
);
259 void UndoManager::DelAllUndoObj()
261 ::sw::UndoGuard
const undoGuard(*this);
263 SdrUndoManager::ClearAllLevels();
265 m_UndoSaveMark
= MARK_INVALID
;
269 UndoManager::StartUndo(SwUndoId
const i_eUndoId
,
270 SwRewriter
const*const pRewriter
)
272 if (!IsUndoEnabled())
274 return SwUndoId::EMPTY
;
277 SwUndoId
const eUndoId( (i_eUndoId
== SwUndoId::EMPTY
) ? SwUndoId::START
: i_eUndoId
);
279 assert(SwUndoId::END
!= eUndoId
);
280 OUString
comment( (SwUndoId::START
== eUndoId
)
282 : GetUndoComment(eUndoId
) );
285 assert(SwUndoId::START
!= eUndoId
);
286 comment
= pRewriter
->Apply(comment
);
289 ViewShellId
nViewShellId(-1);
292 if (const SwView
* pView
= m_pDocShell
->GetView())
293 nViewShellId
= pView
->GetViewShellId();
295 SdrUndoManager::EnterListAction(comment
, comment
, static_cast<sal_uInt16
>(eUndoId
), nViewShellId
);
301 UndoManager::EndUndo(SwUndoId eUndoId
, SwRewriter
const*const pRewriter
)
303 if (!IsUndoEnabled())
305 return SwUndoId::EMPTY
;
308 if ((eUndoId
== SwUndoId::EMPTY
) || (SwUndoId::START
== eUndoId
))
309 eUndoId
= SwUndoId::END
;
310 OSL_ENSURE(!((SwUndoId::END
== eUndoId
) && pRewriter
),
311 "EndUndo(): no Undo ID, but rewriter given?");
313 SfxUndoAction
*const pLastUndo(
314 (0 == SdrUndoManager::GetUndoActionCount())
315 ? nullptr : SdrUndoManager::GetUndoAction() );
317 int const nCount
= LeaveListAction();
319 if (nCount
) // otherwise: empty list action not inserted!
322 assert(SwUndoId::START
!= eUndoId
);
323 auto pListAction
= dynamic_cast<SfxListUndoAction
*>(SdrUndoManager::GetUndoAction());
325 if (SwUndoId::END
!= eUndoId
)
327 OSL_ENSURE(static_cast<SwUndoId
>(pListAction
->GetId()) == eUndoId
,
328 "EndUndo(): given ID different from StartUndo()");
329 // comment set by caller of EndUndo
330 OUString comment
= GetUndoComment(eUndoId
);
333 comment
= pRewriter
->Apply(comment
);
335 pListAction
->SetComment(comment
);
337 else if (SwUndoId::START
!= static_cast<SwUndoId
>(pListAction
->GetId()))
339 // comment set by caller of StartUndo: nothing to do here
343 // comment was not set at StartUndo or EndUndo:
344 // take comment of last contained action
345 // (note that this works recursively, i.e. the last contained
346 // action may be a list action created by StartUndo/EndUndo)
347 OUString
const comment(pLastUndo
->GetComment());
348 pListAction
->SetComment(comment
);
352 OSL_ENSURE(false, "EndUndo(): no comment?");
360 * Checks if the topmost undo action owned by pView is independent from the topmost action undo
363 bool UndoManager::IsViewUndoActionIndependent(const SwView
* pView
, sal_uInt16
& rOffset
) const
365 if (GetUndoActionCount() <= 1)
367 // Single or less undo, owned by another view.
376 // Last undo action that doesn't belong to the view.
377 const SfxUndoAction
* pTopAction
= GetUndoAction();
379 ViewShellId nViewId
= pView
->GetViewShellId();
381 // Earlier undo action that belongs to the view, but is not the top one.
382 const SfxUndoAction
* pViewAction
= nullptr;
384 for (size_t i
= 0; i
< GetUndoActionCount(); ++i
)
386 const SfxUndoAction
* pAction
= GetUndoAction(i
);
387 if (pAction
->GetViewShellId() == nViewId
)
389 pViewAction
= pAction
;
397 // Found no earlier undo action that belongs to the view.
401 auto pTopSwAction
= dynamic_cast<const SwUndo
*>(pTopAction
);
402 if (!pTopSwAction
|| pTopSwAction
->GetId() != SwUndoId::TYPING
)
407 auto pViewSwAction
= dynamic_cast<const SwUndo
*>(pViewAction
);
408 if (!pViewSwAction
|| pViewSwAction
->GetId() != SwUndoId::TYPING
)
413 const auto& rTopInsert
= *static_cast<const SwUndoInsert
*>(pTopSwAction
);
414 const auto& rViewInsert
= *static_cast<const SwUndoInsert
*>(pViewSwAction
);
416 for (size_t i
= 0; i
< GetRedoActionCount(); ++i
)
418 auto pRedoAction
= dynamic_cast<const SwUndo
*>(GetRedoAction(i
));
419 if (!pRedoAction
|| pRedoAction
->GetId() != SwUndoId::TYPING
)
424 const auto& rRedoInsert
= *static_cast<const SwUndoInsert
*>(pRedoAction
);
425 if (!rViewInsert
.IsIndependent(rRedoInsert
) && rRedoInsert
.GetViewShellId() != nViewId
)
427 // Dependent redo action and owned by another view.
432 if (!rViewInsert
.IsIndependent(rTopInsert
))
442 UndoManager::GetLastUndoInfo(
443 OUString
*const o_pStr
, SwUndoId
*const o_pId
, const SwView
* pView
) const
445 // this is actually expected to work on the current level,
446 // but that was really not obvious from the previous implementation...
447 if (!SdrUndoManager::GetUndoActionCount())
452 SfxUndoAction
*const pAction( SdrUndoManager::GetUndoAction() );
454 if (comphelper::LibreOfficeKit::isActive() && !m_bRepair
)
456 // If another view created the undo action, prevent undoing it from this view.
457 ViewShellId nViewShellId
= pView
? pView
->GetViewShellId() : m_pDocShell
->GetView()->GetViewShellId();
458 // Unless we know that the other view's undo action is independent from us.
459 if (pAction
->GetViewShellId() != nViewShellId
460 && !IsViewUndoActionIndependent(pView
, o3tl::temporary(sal_uInt16())))
464 *o_pId
= SwUndoId::CONFLICT
;
472 *o_pStr
= pAction
->GetComment();
476 if (auto pListAction
= dynamic_cast<const SfxListUndoAction
*>(pAction
))
477 *o_pId
= static_cast<SwUndoId
>(pListAction
->GetId());
478 else if (auto pSwAction
= dynamic_cast<const SwUndo
*>(pAction
))
479 *o_pId
= pSwAction
->GetId();
481 *o_pId
= SwUndoId::EMPTY
;
487 SwUndoComments_t
UndoManager::GetUndoComments() const
489 OSL_ENSURE(!SdrUndoManager::IsInListAction(),
490 "GetUndoComments() called while in list action?");
492 SwUndoComments_t ret
;
493 const size_t nUndoCount(SdrUndoManager::GetUndoActionCount(TopLevel
));
494 for (size_t n
= 0; n
< nUndoCount
; ++n
)
496 OUString
const comment(
497 SdrUndoManager::GetUndoActionComment(n
, TopLevel
));
498 ret
.push_back(comment
);
504 bool UndoManager::GetFirstRedoInfo(OUString
*const o_pStr
,
505 SwUndoId
*const o_pId
,
506 const SwView
* pView
) const
508 if (!SdrUndoManager::GetRedoActionCount())
513 SfxUndoAction
*const pAction( SdrUndoManager::GetRedoAction() );
514 if ( pAction
== nullptr )
519 if (comphelper::LibreOfficeKit::isActive() && !m_bRepair
)
521 // If another view created the undo action, prevent redoing it from this view.
522 ViewShellId nViewShellId
= pView
? pView
->GetViewShellId() : m_pDocShell
->GetView()->GetViewShellId();
523 if (pAction
->GetViewShellId() != nViewShellId
)
527 *o_pId
= SwUndoId::CONFLICT
;
535 *o_pStr
= pAction
->GetComment();
539 if (auto pListAction
= dynamic_cast<const SfxListUndoAction
*>(pAction
))
540 *o_pId
= static_cast<SwUndoId
>(pListAction
->GetId());
541 else if (auto pSwAction
= dynamic_cast<const SwUndo
*>(pAction
))
542 *o_pId
= pSwAction
->GetId();
544 *o_pId
= SwUndoId::EMPTY
;
550 SwUndoComments_t
UndoManager::GetRedoComments() const
552 OSL_ENSURE(!SdrUndoManager::IsInListAction(),
553 "GetRedoComments() called while in list action?");
555 SwUndoComments_t ret
;
556 const size_t nRedoCount(SdrUndoManager::GetRedoActionCount(TopLevel
));
557 for (size_t n
= 0; n
< nRedoCount
; ++n
)
559 OUString
const comment(
560 SdrUndoManager::GetRedoActionComment(n
, TopLevel
));
561 ret
.push_back(comment
);
567 SwUndoId
UndoManager::GetRepeatInfo(OUString
*const o_pStr
) const
569 SwUndoId
nRepeatId(SwUndoId::EMPTY
);
570 GetLastUndoInfo(o_pStr
, & nRepeatId
);
571 if( SwUndoId::REPEAT_START
<= nRepeatId
&& SwUndoId::REPEAT_END
> nRepeatId
)
575 if (o_pStr
) // not repeatable -> clear comment
579 return SwUndoId::EMPTY
;
582 SwUndo
* UndoManager::RemoveLastUndo()
584 if (SdrUndoManager::GetRedoActionCount() ||
585 SdrUndoManager::GetRedoActionCount(TopLevel
))
587 OSL_ENSURE(false, "RemoveLastUndoAction(): there are Redo actions?");
590 if (!SdrUndoManager::GetUndoActionCount())
592 OSL_ENSURE(false, "RemoveLastUndoAction(): no Undo actions");
595 SfxUndoAction
*const pLastUndo(GetUndoAction());
596 SdrUndoManager::RemoveLastUndoAction();
597 return dynamic_cast<SwUndo
*>(pLastUndo
);
602 void UndoManager::AddUndoAction(std::unique_ptr
<SfxUndoAction
> pAction
, bool bTryMerge
)
604 SwUndo
*const pUndo( dynamic_cast<SwUndo
*>(pAction
.get()) );
607 if (RedlineFlags::NONE
== pUndo
->GetRedlineFlags())
609 pUndo
->SetRedlineFlags( m_rRedlineAccess
.GetRedlineFlags() );
611 if (m_isAddWithIgnoreRepeat
)
613 pUndo
->IgnoreRepeat();
616 SdrUndoManager::AddUndoAction(std::move(pAction
), bTryMerge
);
619 SfxViewFrame
* pViewFrame
= SfxViewFrame::GetFirst( m_pDocShell
);
622 pViewFrame
->GetBindings().Invalidate( SID_UNDO
);
623 pViewFrame
->GetBindings().Invalidate( SID_REDO
);
624 pViewFrame
= SfxViewFrame::GetNext( *pViewFrame
, m_pDocShell
);
628 // if the undo nodes array is too large, delete some actions
629 while (UNDO_ACTION_LIMIT
< sal_Int32(GetUndoNodes().Count()))
631 RemoveOldestUndoAction();
640 CursorGuard(SwEditShell
& rShell
, bool const bSave
)
642 , m_bSaveCursor(bSave
)
646 m_rShell
.Push(); // prevent modification of current cursor
649 ~CursorGuard() COVERITY_NOEXCEPT_FALSE
653 m_rShell
.Pop(SwCursorShell::PopMode::DeleteCurrent
);
657 SwEditShell
& m_rShell
;
658 bool const m_bSaveCursor
;
663 bool UndoManager::impl_DoUndoRedo(UndoOrRedoType undoOrRedo
, size_t nUndoOffset
)
665 SwDoc
& rDoc(GetUndoNodes().GetDoc());
667 UnoActionContext
c(& rDoc
); // exception-safe StartAllAction/EndAllAction
669 SwEditShell
*const pEditShell(rDoc
.GetEditShell());
670 OSL_ENSURE(pEditShell
, "sw::UndoManager needs a SwEditShell!");
673 throw uno::RuntimeException();
676 // in case the model has controllers locked, the Undo should not
677 // change the view cursors!
678 bool const bSaveCursors(pEditShell
->CursorsLocked());
679 CursorGuard
aCursorGuard(*pEditShell
, bSaveCursors
);
682 // (in case Undo was called via API) clear the cursors:
683 pEditShell
->KillPams();
684 pEditShell
->SetMark();
685 pEditShell
->ClearMark();
690 ::sw::UndoRedoContext
context(rDoc
, *pEditShell
);
691 context
.SetUndoOffset(nUndoOffset
);
693 // N.B. these may throw!
694 if (UndoOrRedoType::Undo
== undoOrRedo
)
696 bRet
= SdrUndoManager::UndoWithContext(context
);
700 bRet
= SdrUndoManager::RedoWithContext(context
);
705 // if we are at the "last save" position, the document is not modified
706 if (SdrUndoManager::HasTopUndoActionMark(m_UndoSaveMark
))
708 m_rState
.ResetModified();
712 m_rState
.SetModified();
716 pEditShell
->HandleUndoRedoContext(context
);
721 bool UndoManager::Undo() { return UndoWithOffset(0); }
723 bool UndoManager::UndoWithOffset(size_t nUndoOffset
)
725 if(isTextEditActive())
727 return SdrUndoManager::Undo();
731 return impl_DoUndoRedo(UndoOrRedoType::Undo
, nUndoOffset
);
735 bool UndoManager::Redo()
737 if(isTextEditActive())
739 return SdrUndoManager::Redo();
743 return impl_DoUndoRedo(UndoOrRedoType::Redo
, /*nUndoOffset=*/0);
747 void UndoManager::dumpAsXml(xmlTextWriterPtr pWriter
) const
749 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("swUndoManager"));
750 SdrUndoManager::dumpAsXml(pWriter
);
752 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("m_xUndoNodes"));
753 m_xUndoNodes
->dumpAsXml(pWriter
);
754 (void)xmlTextWriterEndElement(pWriter
);
756 (void)xmlTextWriterEndElement(pWriter
);
759 void UndoManager::EmptyActionsChanged()
763 m_pDocShell
->Broadcast(SfxHint(SfxHintId::DocumentRepair
));
767 /** N.B.: this does _not_ call SdrUndoManager::Repeat because it is not
768 possible to wrap a list action around it:
769 calling EnterListAction here will cause SdrUndoManager::Repeat
770 to repeat the list action!
772 bool UndoManager::Repeat(::sw::RepeatContext
& rContext
,
773 sal_uInt16
const nRepeatCount
)
775 if (SdrUndoManager::IsInListAction())
777 OSL_ENSURE(false, "repeat in open list action???");
780 if (!SdrUndoManager::GetUndoActionCount(TopLevel
))
784 SfxUndoAction
*const pRepeatAction(GetUndoAction());
785 assert(pRepeatAction
);
786 if (!pRepeatAction
->CanRepeat(rContext
))
791 OUString
const comment(pRepeatAction
->GetComment());
792 OUString
const rcomment(pRepeatAction
->GetRepeatComment(rContext
));
794 if (auto const* const pSwAction
= dynamic_cast<SwUndo
*>(pRepeatAction
))
795 nId
= pSwAction
->GetId();
796 else if (auto const* const pListAction
= dynamic_cast<SfxListUndoAction
*>(pRepeatAction
))
797 nId
= static_cast<SwUndoId
>(pListAction
->GetId());
802 ViewShellId
nViewShellId(-1);
805 if (const SwView
* pView
= m_pDocShell
->GetView())
806 nViewShellId
= pView
->GetViewShellId();
808 EnterListAction(comment
, rcomment
, static_cast<sal_uInt16
>(nId
), nViewShellId
);
811 SwPaM
* pTmp
= rContext
.m_pCurrentPaM
;
812 for(SwPaM
& rPaM
: rContext
.GetRepeatPaM().GetRingContainer())
813 { // iterate over ring
814 rContext
.m_pCurrentPaM
= &rPaM
;
815 if (DoesUndo() && & rPaM
!= pTmp
)
817 m_isAddWithIgnoreRepeat
= true;
819 for (sal_uInt16 nRptCnt
= nRepeatCount
; nRptCnt
> 0; --nRptCnt
)
821 pRepeatAction
->Repeat(rContext
);
823 if (DoesUndo() && & rPaM
!= pTmp
)
825 m_isAddWithIgnoreRepeat
= false;
827 rContext
.m_bDeleteRepeated
= false; // reset for next PaM
829 rContext
.m_pCurrentPaM
= pTmp
;
840 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */