diff options
Diffstat (limited to 'sw/source/core/undo/docundo.cxx')
-rw-r--r-- | sw/source/core/undo/docundo.cxx | 581 |
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: */ |