/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; // the undo array should never grow beyond this limit: #define UNDO_ACTION_LIMIT (USHRT_MAX - 1000) // UndoManager /////////////////////////////////////////////////////////// namespace sw { SAL_WNODEPRECATED_DECLARATIONS_PUSH UndoManager::UndoManager(::std::auto_ptr 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 SdrUndoManager::EnableUndo(false); } SAL_WNODEPRECATED_DECLARATIONS_POP 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) { if(!isTextEditActive()) { EnableUndo(bDoUndo); SdrModel *const pSdrModel = m_rDrawModelAccess.GetDrawModel(); if( pSdrModel ) { pSdrModel->EnableUndo(bDoUndo); } } } bool UndoManager::DoesUndo() const { if(isTextEditActive()) { return false; } else { 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 (!SdrUndoManager::GetUndoActionCount(CurrentLevel)) { return 0; } SfxUndoAction *const pAction( SdrUndoManager::GetUndoAction(0) ); return dynamic_cast(pAction); } void UndoManager::AppendUndo(SwUndo *const pUndo) { AddUndoAction(pUndo); } void UndoManager::ClearRedo() { return SdrUndoManager::ImplClearRedo_NoLock(TopLevel); } void UndoManager::DelAllUndoObj() { ::sw::UndoGuard const undoGuard(*this); SdrUndoManager::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); } SdrUndoManager::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 == SdrUndoManager::GetUndoActionCount(CurrentLevel)) ? 0 : SdrUndoManager::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(SdrUndoManager::GetUndoAction(0)); SfxListUndoAction *const pListAction( dynamic_cast(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( 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 (!SdrUndoManager::GetUndoActionCount(CurrentLevel)) { return false; } SfxUndoAction *const pAction( SdrUndoManager::GetUndoAction(0) ); if (o_pStr) { *o_pStr = pAction->GetComment(); } if (o_pId) { sal_uInt16 const nId(pAction->GetId()); *o_pId = static_cast(nId); } return true; } SwUndoComments_t UndoManager::GetUndoComments() const { OSL_ENSURE(!SdrUndoManager::IsInListAction(), "GetUndoComments() called while in list action?"); SwUndoComments_t ret; sal_uInt16 const nUndoCount(SdrUndoManager::GetUndoActionCount(TopLevel)); for (sal_uInt16 n = 0; n < nUndoCount; ++n) { OUString const comment( SdrUndoManager::GetUndoActionComment(n, TopLevel)); ret.push_back(comment); } return ret; } /**************** REDO ******************/ bool UndoManager::GetFirstRedoInfo(OUString *const o_pStr) const { if (!SdrUndoManager::GetRedoActionCount(CurrentLevel)) { return false; } if (o_pStr) { *o_pStr = SdrUndoManager::GetRedoActionComment(0, CurrentLevel); } return true; } SwUndoComments_t UndoManager::GetRedoComments() const { OSL_ENSURE(!SdrUndoManager::IsInListAction(), "GetRedoComments() called while in list action?"); SwUndoComments_t ret; sal_uInt16 const nRedoCount(SdrUndoManager::GetRedoActionCount(TopLevel)); for (sal_uInt16 n = 0; n < nRedoCount; ++n) { OUString const comment( SdrUndoManager::GetRedoActionComment(n, TopLevel)); ret.push_back(comment); } return ret; } /**************** REPEAT ******************/ SwUndoId UndoManager::GetRepeatInfo(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 (SdrUndoManager::GetRedoActionCount(CurrentLevel) || SdrUndoManager::GetRedoActionCount(TopLevel)) { OSL_ENSURE(false, "RemoveLastUndoAction(): there are Redo actions?"); return 0; } if (!SdrUndoManager::GetUndoActionCount(CurrentLevel)) { OSL_ENSURE(false, "RemoveLastUndoAction(): no Undo actions"); return 0; } SfxUndoAction *const pLastUndo(GetUndoAction(0)); SdrUndoManager::RemoveLastUndoAction(); return dynamic_cast(pLastUndo); } // svl::IUndoManager ///////////////////////////////////////////////////// void UndoManager::EnableUndo(bool bEnable) { // SdrUndoManager does not have a counter anymore, but reverted to the old behavior of // having a simple boolean flag for locking. So, simply forward. SdrUndoManager::EnableUndo(bEnable); } void UndoManager::AddUndoAction(SfxUndoAction *pAction, sal_Bool bTryMerge) { SwUndo *const pUndo( dynamic_cast(pAction) ); if (pUndo) { if (nsRedlineMode_t::REDLINE_NONE == pUndo->GetRedlineMode()) { pUndo->SetRedlineMode( m_rRedlineAccess.GetRedlineMode() ); } } SdrUndoManager::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 = SdrUndoManager::UndoWithContext(context); } else { bRet = SdrUndoManager::RedoWithContext(context); } if (bRet) { // if we are at the "last save" position, the document is not modified if (SdrUndoManager::HasTopUndoActionMark(m_UndoSaveMark)) { m_rState.ResetModified(); } else { m_rState.SetModified(); } } pEditShell->HandleUndoRedoContext(context); return bRet; } sal_Bool UndoManager::Undo() { if(isTextEditActive()) { return SdrUndoManager::Undo(); } else { return impl_DoUndoRedo(UNDO); } } sal_Bool UndoManager::Redo() { if(isTextEditActive()) { return SdrUndoManager::Redo(); } else { return impl_DoUndoRedo(REDO); } } /** N.B.: this does _not_ call SdrUndoManager::Repeat because it is not possible to wrap a list action around it: calling EnterListAction here will cause SdrUndoManager::Repeat to repeat the list action! */ bool UndoManager::Repeat(::sw::RepeatContext & rContext, sal_uInt16 const nRepeatCount) { if (SdrUndoManager::IsInListAction()) { OSL_ENSURE(false, "repeat in open list action???"); return false; } if (!SdrUndoManager::GetUndoActionCount(TopLevel)) { return false; } SfxUndoAction *const pRepeatAction(GetUndoAction(0)); OSL_ASSERT(pRepeatAction); if (!pRepeatAction || !pRepeatAction->CanRepeat(rContext)) { return false; } OUString const comment(pRepeatAction->GetComment()); 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(rContext.m_pCurrentPaM->GetNext()); } while (pFirstCursor != & rContext.GetRepeatPaM()); if (DoesUndo()) { LeaveListAction(); } return true; } } // namespace sw /* vim:set shiftwidth=4 softtabstop=4 expandtab: */