summaryrefslogtreecommitdiff
path: root/sw/source/core/undo/docundo.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sw/source/core/undo/docundo.cxx')
-rw-r--r--sw/source/core/undo/docundo.cxx581
1 files changed, 581 insertions, 0 deletions
diff --git a/sw/source/core/undo/docundo.cxx b/sw/source/core/undo/docundo.cxx
new file mode 100644
index 000000000000..441bf84096a4
--- /dev/null
+++ b/sw/source/core/undo/docundo.cxx
@@ -0,0 +1,581 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*************************************************************************
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * Copyright 2000, 2010 Oracle and/or its affiliates.
+ *
+ * OpenOffice.org - a multi-platform office productivity suite
+ *
+ * This file is part of OpenOffice.org.
+ *
+ * OpenOffice.org is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 3
+ * only, as published by the Free Software Foundation.
+ *
+ * OpenOffice.org is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License version 3 for more details
+ * (a copy is included in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * version 3 along with OpenOffice.org. If not, see
+ * <http://www.openoffice.org/license.html>
+ * for a copy of the LGPLv3 License.
+ *
+ ************************************************************************/
+
+// MARKER(update_precomp.py): autogen include statement, do not remove
+#include "precompiled_sw.hxx"
+
+#include <UndoManager.hxx>
+
+#include <unotools/undoopt.hxx>
+
+#include <vcl/wrkwin.hxx>
+
+#include <svx/svdmodel.hxx>
+
+#include <swmodule.hxx>
+#include <doc.hxx>
+#include <ndarr.hxx>
+#include <pam.hxx>
+#include <ndtxt.hxx>
+#include <swundo.hxx>
+#include <UndoCore.hxx>
+#include <rolbck.hxx>
+#include <undo.hrc>
+#include <editsh.hxx>
+#include <unobaseclass.hxx>
+
+#include <limits>
+
+using namespace ::com::sun::star;
+
+
+// the undo array should never grow beyond this limit:
+#define UNDO_ACTION_LIMIT (USHRT_MAX - 1000)
+
+
+// UndoManager ///////////////////////////////////////////////////////////
+
+namespace sw {
+
+UndoManager::UndoManager(::std::auto_ptr<SwNodes> pUndoNodes,
+ IDocumentDrawModelAccess & rDrawModelAccess,
+ IDocumentRedlineAccess & rRedlineAccess,
+ IDocumentState & rState)
+ : m_rDrawModelAccess(rDrawModelAccess)
+ , m_rRedlineAccess(rRedlineAccess)
+ , m_rState(rState)
+ , m_pUndoNodes(pUndoNodes)
+ , m_bGroupUndo(true)
+ , m_bDrawUndo(true)
+ , m_bLockUndoNoModifiedPosition(false)
+ , m_UndoSaveMark(MARK_INVALID)
+{
+ OSL_ASSERT(m_pUndoNodes.get());
+ // writer expects it to be disabled initially
+ // Undo is enabled by SwEditShell constructor
+ SfxUndoManager::EnableUndo(false);
+}
+
+SwNodes const& UndoManager::GetUndoNodes() const
+{
+ return *m_pUndoNodes;
+}
+
+SwNodes & UndoManager::GetUndoNodes()
+{
+ return *m_pUndoNodes;
+}
+
+bool UndoManager::IsUndoNodes(SwNodes const& rNodes) const
+{
+ return & rNodes == m_pUndoNodes.get();
+}
+
+void UndoManager::DoUndo(bool const bDoUndo)
+{
+ EnableUndo(bDoUndo);
+
+ SdrModel *const pSdrModel = m_rDrawModelAccess.GetDrawModel();
+ if( pSdrModel )
+ {
+ pSdrModel->EnableUndo(bDoUndo);
+ }
+}
+
+bool UndoManager::DoesUndo() const
+{
+ return IsUndoEnabled();
+}
+
+void UndoManager::DoGroupUndo(bool const bDoUndo)
+{
+ m_bGroupUndo = bDoUndo;
+}
+
+bool UndoManager::DoesGroupUndo() const
+{
+ return m_bGroupUndo;
+}
+
+void UndoManager::DoDrawUndo(bool const bDoUndo)
+{
+ m_bDrawUndo = bDoUndo;
+}
+
+bool UndoManager::DoesDrawUndo() const
+{
+ return m_bDrawUndo;
+}
+
+
+bool UndoManager::IsUndoNoResetModified() const
+{
+ return MARK_INVALID == m_UndoSaveMark;
+}
+
+void UndoManager::SetUndoNoResetModified()
+{
+ if (MARK_INVALID != m_UndoSaveMark)
+ {
+ RemoveMark(m_UndoSaveMark);
+ m_UndoSaveMark = MARK_INVALID;
+ }
+}
+
+void UndoManager::SetUndoNoModifiedPosition()
+{
+ if (!m_bLockUndoNoModifiedPosition)
+ {
+ m_UndoSaveMark = MarkTopUndoAction();
+ }
+}
+
+void UndoManager::LockUndoNoModifiedPosition()
+{
+ m_bLockUndoNoModifiedPosition = true;
+}
+
+void UndoManager::UnLockUndoNoModifiedPosition()
+{
+ m_bLockUndoNoModifiedPosition = false;
+}
+
+
+SwUndo* UndoManager::GetLastUndo()
+{
+ if (!SfxUndoManager::GetUndoActionCount(CurrentLevel))
+ {
+ return 0;
+ }
+ SfxUndoAction *const pAction( SfxUndoManager::GetUndoAction(0) );
+ return dynamic_cast<SwUndo*>(pAction);
+}
+
+void UndoManager::AppendUndo(SwUndo *const pUndo)
+{
+ AddUndoAction(pUndo);
+}
+
+void UndoManager::ClearRedo()
+{
+ return SfxUndoManager::ImplClearRedo_NoLock(TopLevel);
+}
+
+void UndoManager::DelAllUndoObj()
+{
+ ::sw::UndoGuard const undoGuard(*this);
+
+ SfxUndoManager::ClearAllLevels();
+
+ m_UndoSaveMark = MARK_INVALID;
+}
+
+
+/**************** UNDO ******************/
+
+SwUndoId
+UndoManager::StartUndo(SwUndoId const i_eUndoId,
+ SwRewriter const*const pRewriter)
+{
+ if (!IsUndoEnabled())
+ {
+ return UNDO_EMPTY;
+ }
+
+ SwUndoId const eUndoId( (0 == i_eUndoId) ? UNDO_START : i_eUndoId );
+
+ OSL_ASSERT(UNDO_END != eUndoId);
+ String comment( (UNDO_START == eUndoId)
+ ? String("??", RTL_TEXTENCODING_ASCII_US)
+ : String(SW_RES(UNDO_BASE + eUndoId)) );
+ if (pRewriter)
+ {
+ OSL_ASSERT(UNDO_START != eUndoId);
+ comment = pRewriter->Apply(comment);
+ }
+
+ SfxUndoManager::EnterListAction(comment, comment, eUndoId);
+
+ return eUndoId;
+}
+
+
+SwUndoId
+UndoManager::EndUndo(SwUndoId const i_eUndoId, SwRewriter const*const pRewriter)
+{
+ if (!IsUndoEnabled())
+ {
+ return UNDO_EMPTY;
+ }
+
+ SwUndoId const eUndoId( ((0 == i_eUndoId) || (UNDO_START == i_eUndoId))
+ ? UNDO_END : i_eUndoId );
+ OSL_ENSURE(!((UNDO_END == eUndoId) && pRewriter),
+ "EndUndo(): no Undo ID, but rewriter given?");
+
+ SfxUndoAction *const pLastUndo(
+ (0 == SfxUndoManager::GetUndoActionCount(CurrentLevel))
+ ? 0 : SfxUndoManager::GetUndoAction(0) );
+
+ int const nCount = LeaveListAction();
+
+ if (nCount) // otherwise: empty list action not inserted!
+ {
+ OSL_ASSERT(pLastUndo);
+ OSL_ASSERT(UNDO_START != eUndoId);
+ SfxUndoAction *const pUndoAction(SfxUndoManager::GetUndoAction(0));
+ SfxListUndoAction *const pListAction(
+ dynamic_cast<SfxListUndoAction*>(pUndoAction));
+ OSL_ASSERT(pListAction);
+ if (pListAction)
+ {
+ if (UNDO_END != eUndoId)
+ {
+ OSL_ENSURE(pListAction->GetId() == eUndoId,
+ "EndUndo(): given ID different from StartUndo()");
+ // comment set by caller of EndUndo
+ String comment = String(SW_RES(UNDO_BASE + eUndoId));
+ if (pRewriter)
+ {
+ comment = pRewriter->Apply(comment);
+ }
+ pListAction->SetComment(comment);
+ }
+ else if ((UNDO_START != pListAction->GetId()))
+ {
+ // comment set by caller of StartUndo: nothing to do here
+ }
+ else if (pLastUndo)
+ {
+ // comment was not set at StartUndo or EndUndo:
+ // take comment of last contained action
+ // (note that this works recursively, i.e. the last contained
+ // action may be a list action created by StartUndo/EndUndo)
+ String const comment(pLastUndo->GetComment());
+ pListAction->SetComment(comment);
+ }
+ else
+ {
+ OSL_ENSURE(false, "EndUndo(): no comment?");
+ }
+ }
+ }
+
+ return eUndoId;
+}
+
+bool
+UndoManager::GetLastUndoInfo(
+ ::rtl::OUString *const o_pStr, SwUndoId *const o_pId) const
+{
+ // this is actually expected to work on the current level,
+ // but that was really not obvious from the previous implementation...
+ if (!SfxUndoManager::GetUndoActionCount(CurrentLevel))
+ {
+ return false;
+ }
+
+ SfxUndoAction *const pAction( SfxUndoManager::GetUndoAction(0) );
+ if (o_pStr)
+ {
+ *o_pStr = pAction->GetComment();
+ }
+ if (o_pId)
+ {
+ sal_uInt16 const nId(pAction->GetId());
+ *o_pId = static_cast<SwUndoId>(nId);
+ }
+
+ return true;
+}
+
+SwUndoComments_t UndoManager::GetUndoComments() const
+{
+ OSL_ENSURE(!SfxUndoManager::IsInListAction(),
+ "GetUndoComments() called while in list action?");
+
+ SwUndoComments_t ret;
+ sal_uInt16 const nUndoCount(SfxUndoManager::GetUndoActionCount(TopLevel));
+ for (sal_uInt16 n = 0; n < nUndoCount; ++n)
+ {
+ ::rtl::OUString const comment(
+ SfxUndoManager::GetUndoActionComment(n, TopLevel));
+ ret.push_back(comment);
+ }
+
+ return ret;
+}
+
+
+/**************** REDO ******************/
+bool UndoManager::GetFirstRedoInfo(::rtl::OUString *const o_pStr) const
+{
+ if (!SfxUndoManager::GetRedoActionCount(CurrentLevel))
+ {
+ return false;
+ }
+
+ if (o_pStr)
+ {
+ *o_pStr = SfxUndoManager::GetRedoActionComment(0, CurrentLevel);
+ }
+
+ return true;
+}
+
+
+SwUndoComments_t UndoManager::GetRedoComments() const
+{
+ OSL_ENSURE(!SfxUndoManager::IsInListAction(),
+ "GetRedoComments() called while in list action?");
+
+ SwUndoComments_t ret;
+ sal_uInt16 const nRedoCount(SfxUndoManager::GetRedoActionCount(TopLevel));
+ for (sal_uInt16 n = 0; n < nRedoCount; ++n)
+ {
+ ::rtl::OUString const comment(
+ SfxUndoManager::GetRedoActionComment(n, TopLevel));
+ ret.push_back(comment);
+ }
+
+ return ret;
+}
+
+/**************** REPEAT ******************/
+
+SwUndoId UndoManager::GetRepeatInfo(::rtl::OUString *const o_pStr) const
+{
+ SwUndoId nRepeatId(UNDO_EMPTY);
+ GetLastUndoInfo(o_pStr, & nRepeatId);
+ if( REPEAT_START <= nRepeatId && REPEAT_END > nRepeatId )
+ {
+ return nRepeatId;
+ }
+ if (o_pStr) // not repeatable -> clear comment
+ {
+ *o_pStr = String();
+ }
+ return UNDO_EMPTY;
+}
+
+SwUndo * UndoManager::RemoveLastUndo()
+{
+ if (SfxUndoManager::GetRedoActionCount(CurrentLevel) ||
+ SfxUndoManager::GetRedoActionCount(TopLevel))
+ {
+ OSL_ENSURE(false, "RemoveLastUndoAction(): there are Redo actions?");
+ return 0;
+ }
+ if (!SfxUndoManager::GetUndoActionCount(CurrentLevel))
+ {
+ OSL_ENSURE(false, "RemoveLastUndoAction(): no Undo actions");
+ return 0;
+ }
+ SfxUndoAction *const pLastUndo(GetUndoAction(0));
+ SfxUndoManager::RemoveLastUndoAction();
+ return dynamic_cast<SwUndo *>(pLastUndo);
+}
+
+// svl::IUndoManager /////////////////////////////////////////////////////
+
+void UndoManager::EnableUndo(bool bEnable)
+{
+ // UGLY: SfxUndoManager has a counter to match enable/disable calls
+ // but the writer code expects that a single call switches
+ while (IsUndoEnabled() != bEnable)
+ {
+ SfxUndoManager::EnableUndo(bEnable);
+ }
+}
+
+void UndoManager::AddUndoAction(SfxUndoAction *pAction, sal_Bool bTryMerge)
+{
+ SwUndo *const pUndo( dynamic_cast<SwUndo *>(pAction) );
+ if (pUndo)
+ {
+ if (nsRedlineMode_t::REDLINE_NONE == pUndo->GetRedlineMode())
+ {
+ pUndo->SetRedlineMode( m_rRedlineAccess.GetRedlineMode() );
+ }
+ }
+ SfxUndoManager::AddUndoAction(pAction, bTryMerge);
+ // if the undo nodes array is too large, delete some actions
+ while (UNDO_ACTION_LIMIT < GetUndoNodes().Count())
+ {
+ RemoveOldestUndoActions(1);
+ }
+}
+
+class CursorGuard
+{
+public:
+ CursorGuard(SwEditShell & rShell, bool const bSave)
+ : m_rShell(rShell)
+ , m_bSaveCursor(bSave)
+ {
+ if (m_bSaveCursor)
+ {
+ m_rShell.Push(); // prevent modification of current cursor
+ }
+ }
+ ~CursorGuard()
+ {
+ if (m_bSaveCursor)
+ {
+ m_rShell.Pop();
+ }
+ }
+private:
+ SwEditShell & m_rShell;
+ bool const m_bSaveCursor;
+};
+
+bool UndoManager::impl_DoUndoRedo(UndoOrRedo_t const undoOrRedo)
+{
+ SwDoc & rDoc(*GetUndoNodes().GetDoc());
+
+ UnoActionContext c(& rDoc); // exception-safe StartAllAction/EndAllAction
+
+ SwEditShell *const pEditShell( rDoc.GetEditShell() );
+
+ OSL_ENSURE(pEditShell, "sw::UndoManager needs a SwEditShell!");
+ if (!pEditShell)
+ {
+ throw uno::RuntimeException();
+ }
+
+ // in case the model has controllers locked, the Undo should not
+ // change the view cursors!
+ bool const bSaveCursors(pEditShell->CursorsLocked());
+ CursorGuard(*pEditShell, bSaveCursors);
+ if (!bSaveCursors)
+ {
+ // (in case Undo was called via API) clear the cursors:
+ pEditShell->KillPams();
+ pEditShell->SetMark();
+ pEditShell->ClearMark();
+ }
+
+ bool bRet(false);
+
+ ::sw::UndoRedoContext context(rDoc, *pEditShell);
+
+ // N.B. these may throw!
+ if (UNDO == undoOrRedo)
+ {
+ bRet = SfxUndoManager::UndoWithContext(context);
+ }
+ else
+ {
+ bRet = SfxUndoManager::RedoWithContext(context);
+ }
+
+ if (bRet)
+ {
+ // if we are at the "last save" position, the document is not modified
+ if (SfxUndoManager::HasTopUndoActionMark(m_UndoSaveMark))
+ {
+ m_rState.ResetModified();
+ }
+ else
+ {
+ m_rState.SetModified();
+ }
+ }
+
+ pEditShell->HandleUndoRedoContext(context);
+
+ return bRet;
+}
+
+sal_Bool UndoManager::Undo()
+{
+ bool const bRet = impl_DoUndoRedo(UNDO);
+ return bRet;
+}
+
+sal_Bool UndoManager::Redo()
+{
+ bool const bRet = impl_DoUndoRedo(REDO);
+ return bRet;
+}
+
+/** N.B.: this does _not_ call SfxUndoManager::Repeat because it is not
+ possible to wrap a list action around it:
+ calling EnterListAction here will cause SfxUndoManager::Repeat
+ to repeat the list action!
+ */
+bool
+UndoManager::Repeat(::sw::RepeatContext & rContext,
+ sal_uInt16 const nRepeatCount)
+{
+ if (SfxUndoManager::IsInListAction())
+ {
+ OSL_ENSURE(false, "repeat in open list action???");
+ return false;
+ }
+ if (!SfxUndoManager::GetUndoActionCount(TopLevel))
+ {
+ return false;
+ }
+ SfxUndoAction *const pRepeatAction(GetUndoAction(0));
+ OSL_ASSERT(pRepeatAction);
+ if (!pRepeatAction || !pRepeatAction->CanRepeat(rContext))
+ {
+ return false;
+ }
+
+ ::rtl::OUString const comment(pRepeatAction->GetComment());
+ ::rtl::OUString const rcomment(pRepeatAction->GetRepeatComment(rContext));
+ sal_uInt16 const nId(pRepeatAction->GetId());
+ if (DoesUndo())
+ {
+ EnterListAction(comment, rcomment, nId);
+ }
+
+ SwPaM *const pFirstCursor(& rContext.GetRepeatPaM());
+ do { // iterate over ring
+ for (sal_uInt16 nRptCnt = nRepeatCount; nRptCnt > 0; --nRptCnt)
+ {
+ pRepeatAction->Repeat(rContext);
+ }
+ rContext.m_bDeleteRepeated = false; // reset for next PaM
+ rContext.m_pCurrentPaM =
+ static_cast<SwPaM*>(rContext.m_pCurrentPaM->GetNext());
+ } while (pFirstCursor != & rContext.GetRepeatPaM());
+
+ if (DoesUndo())
+ {
+ LeaveListAction();
+ }
+ return true;
+}
+
+} // namespace sw
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */