From 267c6f2ac71f92999e969232431ba04678e7437e Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 07:54:39 +0200 Subject: Adding upstream version 4:24.2.0. Signed-off-by: Daniel Baumann --- sw/source/core/undo/SwRewriter.cxx | 67 + sw/source/core/undo/SwUndoField.cxx | 149 ++ sw/source/core/undo/SwUndoFmt.cxx | 467 +++++ sw/source/core/undo/SwUndoPageDesc.cxx | 352 ++++ sw/source/core/undo/SwUndoTOXChange.cxx | 85 + sw/source/core/undo/UndoThemeChange.cxx | 48 + sw/source/core/undo/docundo.cxx | 844 ++++++++ sw/source/core/undo/rolbck.cxx | 1525 +++++++++++++++ sw/source/core/undo/unattr.cxx | 1155 +++++++++++ sw/source/core/undo/unbkmk.cxx | 223 +++ sw/source/core/undo/undel.cxx | 1361 +++++++++++++ sw/source/core/undo/undobj.cxx | 1768 +++++++++++++++++ sw/source/core/undo/undobj1.cxx | 720 +++++++ sw/source/core/undo/undoflystrattr.cxx | 122 ++ sw/source/core/undo/undraw.cxx | 679 +++++++ sw/source/core/undo/unfmco.cxx | 87 + sw/source/core/undo/unins.cxx | 1035 ++++++++++ sw/source/core/undo/unmove.cxx | 305 +++ sw/source/core/undo/unnum.cxx | 397 ++++ sw/source/core/undo/unoutl.cxx | 73 + sw/source/core/undo/unovwr.cxx | 475 +++++ sw/source/core/undo/unredln.cxx | 602 ++++++ sw/source/core/undo/unsect.cxx | 613 ++++++ sw/source/core/undo/unsort.cxx | 236 +++ sw/source/core/undo/unspnd.cxx | 192 ++ sw/source/core/undo/untbl.cxx | 3179 +++++++++++++++++++++++++++++++ sw/source/core/undo/untblk.cxx | 484 +++++ 27 files changed, 17243 insertions(+) create mode 100644 sw/source/core/undo/SwRewriter.cxx create mode 100644 sw/source/core/undo/SwUndoField.cxx create mode 100644 sw/source/core/undo/SwUndoFmt.cxx create mode 100644 sw/source/core/undo/SwUndoPageDesc.cxx create mode 100644 sw/source/core/undo/SwUndoTOXChange.cxx create mode 100644 sw/source/core/undo/UndoThemeChange.cxx create mode 100644 sw/source/core/undo/docundo.cxx create mode 100644 sw/source/core/undo/rolbck.cxx create mode 100644 sw/source/core/undo/unattr.cxx create mode 100644 sw/source/core/undo/unbkmk.cxx create mode 100644 sw/source/core/undo/undel.cxx create mode 100644 sw/source/core/undo/undobj.cxx create mode 100644 sw/source/core/undo/undobj1.cxx create mode 100644 sw/source/core/undo/undoflystrattr.cxx create mode 100644 sw/source/core/undo/undraw.cxx create mode 100644 sw/source/core/undo/unfmco.cxx create mode 100644 sw/source/core/undo/unins.cxx create mode 100644 sw/source/core/undo/unmove.cxx create mode 100644 sw/source/core/undo/unnum.cxx create mode 100644 sw/source/core/undo/unoutl.cxx create mode 100644 sw/source/core/undo/unovwr.cxx create mode 100644 sw/source/core/undo/unredln.cxx create mode 100644 sw/source/core/undo/unsect.cxx create mode 100644 sw/source/core/undo/unsort.cxx create mode 100644 sw/source/core/undo/unspnd.cxx create mode 100644 sw/source/core/undo/untbl.cxx create mode 100644 sw/source/core/undo/untblk.cxx (limited to 'sw/source/core/undo') diff --git a/sw/source/core/undo/SwRewriter.cxx b/sw/source/core/undo/SwRewriter.cxx new file mode 100644 index 0000000000..58e6d06e9e --- /dev/null +++ b/sw/source/core/undo/SwRewriter.cxx @@ -0,0 +1,67 @@ +/* -*- 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 + +SwRewriter::SwRewriter() {} + +void SwRewriter::AddRule(SwUndoArg eWhat, const OUString& rWith) +{ + SwRewriteRule aRule(eWhat, rWith); + + std::vector::iterator aIt + = find_if(mRules.begin(), mRules.end(), + [&aRule](SwRewriteRule const& a) { return a.first == aRule.first; }); + + if (aIt != mRules.end()) + *aIt = aRule; + else + mRules.push_back(aRule); +} + +OUString SwRewriter::Apply(const OUString& rStr) const +{ + OUString aResult = rStr; + + for (const auto& rRule : mRules) + { + aResult = aResult.replaceAll(GetPlaceHolder(rRule.first), rRule.second); + } + + return aResult; +} + +OUString SwRewriter::GetPlaceHolder(SwUndoArg eId) +{ + switch (eId) + { + case UndoArg1: + return "$1"; + case UndoArg2: + return "$2"; + case UndoArg3: + return "$3"; + default: + break; + } + return "$1"; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/SwUndoField.cxx b/sw/source/core/undo/SwUndoField.cxx new file mode 100644 index 0000000000..57d615e04f --- /dev/null +++ b/sw/source/core/undo/SwUndoField.cxx @@ -0,0 +1,149 @@ +/* -*- 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 + +using namespace ::com::sun::star::uno; + +SwUndoField::SwUndoField(const SwPosition & rPos ) + : SwUndo(SwUndoId::FIELD, &rPos.GetDoc()) +{ + m_nNodeIndex = rPos.GetNodeIndex(); + m_nOffset = rPos.GetContentIndex(); + m_pDoc = &rPos.GetDoc(); +} + +SwUndoField::~SwUndoField() +{ +} + +SwPosition SwUndoField::GetPosition() +{ + SwNode * pNode = m_pDoc->GetNodes()[m_nNodeIndex]; + SwContentIndex aIndex(pNode->GetContentNode(), m_nOffset); + SwPosition aResult(*pNode, aIndex); + + return aResult; +} + +SwUndoFieldFromDoc::SwUndoFieldFromDoc(const SwPosition & rPos, + const SwField & rOldField, + const SwField & rNewField, + bool _bUpdate) + : SwUndoField(rPos) + , m_pOldField(rOldField.CopyField()) + , m_pNewField(rNewField.CopyField()) + , m_bUpdate(_bUpdate) +{ + OSL_ENSURE(m_pOldField, "No old field!"); + OSL_ENSURE(m_pNewField, "No new field!"); + OSL_ENSURE(m_pDoc, "No document!"); +} + +SwUndoFieldFromDoc::~SwUndoFieldFromDoc() +{ +} + +void SwUndoFieldFromDoc::UndoImpl(::sw::UndoRedoContext &) +{ + SwTextField * pTextField = sw::DocumentFieldsManager::GetTextFieldAtPos(GetPosition()); + const SwField * pField = pTextField ? pTextField->GetFormatField().GetField() : nullptr; + + if (pField) + { + m_pDoc->getIDocumentFieldsAccess().UpdateField(pTextField, *m_pOldField, m_bUpdate); + } +} + +void SwUndoFieldFromDoc::DoImpl() +{ + SwTextField * pTextField = sw::DocumentFieldsManager::GetTextFieldAtPos(GetPosition()); + const SwField * pField = pTextField ? pTextField->GetFormatField().GetField() : nullptr; + + if (pField) + { + m_pDoc->getIDocumentFieldsAccess().UpdateField(pTextField, *m_pNewField, m_bUpdate); + SwFormatField* pDstFormatField = const_cast(&pTextField->GetFormatField()); + + if (m_pDoc->getIDocumentFieldsAccess().GetFieldType(SwFieldIds::Postit, OUString(), false) == pDstFormatField->GetField()->GetTyp()) + m_pDoc->GetDocShell()->Broadcast( SwFormatFieldHint( pDstFormatField, SwFormatFieldHintWhich::INSERTED ) ); + } +} + +void SwUndoFieldFromDoc::RedoImpl(::sw::UndoRedoContext &) +{ + DoImpl(); +} + +void SwUndoFieldFromDoc::RepeatImpl(::sw::RepeatContext &) +{ + ::sw::UndoGuard const undoGuard(m_pDoc->GetIDocumentUndoRedo()); + DoImpl(); +} + +SwUndoFieldFromAPI::SwUndoFieldFromAPI(const SwPosition & rPos, + Any aOldVal, Any aNewVal, + sal_uInt16 _nWhich) + : SwUndoField(rPos), m_aOldVal(std::move(aOldVal)), m_aNewVal(std::move(aNewVal)), m_nWhich(_nWhich) +{ +} + +SwUndoFieldFromAPI::~SwUndoFieldFromAPI() +{ +} + +void SwUndoFieldFromAPI::UndoImpl(::sw::UndoRedoContext &) +{ + SwField * pField = sw::DocumentFieldsManager::GetFieldAtPos(GetPosition()); + + if (pField) + pField->PutValue(m_aOldVal, m_nWhich); +} + +void SwUndoFieldFromAPI::DoImpl() +{ + SwField * pField = sw::DocumentFieldsManager::GetFieldAtPos(GetPosition()); + + if (pField) + pField->PutValue(m_aNewVal, m_nWhich); +} + +void SwUndoFieldFromAPI::RedoImpl(::sw::UndoRedoContext &) +{ + DoImpl(); +} + +void SwUndoFieldFromAPI::RepeatImpl(::sw::RepeatContext &) +{ + DoImpl(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/SwUndoFmt.cxx b/sw/source/core/undo/SwUndoFmt.cxx new file mode 100644 index 0000000000..3493ad56f3 --- /dev/null +++ b/sw/source/core/undo/SwUndoFmt.cxx @@ -0,0 +1,467 @@ +/* -*- 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 + +SwUndoFormatCreate::SwUndoFormatCreate +(SwUndoId nUndoId, SwFormat * _pNew, SwFormat const * _pDerivedFrom, SwDoc& rDoc) + : SwUndo(nUndoId, &rDoc), m_pNew(_pNew), + m_rDoc(rDoc), m_nId(0), m_bAuto(false) +{ + if (_pDerivedFrom) + m_sDerivedFrom = _pDerivedFrom->GetName(); +} + +SwUndoFormatCreate::~SwUndoFormatCreate() +{ +} + +void SwUndoFormatCreate::UndoImpl(::sw::UndoRedoContext &) +{ + if (!m_pNew) + return; + + if (m_sNewName.isEmpty()) + m_sNewName = m_pNew->GetName(); + + if (!m_sNewName.isEmpty()) + m_pNew = Find(m_sNewName); + + if (m_pNew) + { + m_pNewSet.reset(new SfxItemSet(m_pNew->GetAttrSet())); + m_nId = m_pNew->GetPoolFormatId() & COLL_GET_RANGE_BITS; + m_bAuto = m_pNew->IsAuto(); + + Delete(); + } +} + +void SwUndoFormatCreate::RedoImpl(::sw::UndoRedoContext &) +{ + SwFormat * pDerivedFrom = Find(m_sDerivedFrom); + SwFormat * pFormat = Create(pDerivedFrom); + + if (pFormat && m_pNewSet) + { + pFormat->SetAuto(m_bAuto); + m_rDoc.ChgFormat(*pFormat, *m_pNewSet); + pFormat->SetPoolFormatId((pFormat->GetPoolFormatId() + & ~COLL_GET_RANGE_BITS) + | m_nId); + + m_pNew = pFormat; + } + else + m_pNew = nullptr; +} + +SwRewriter SwUndoFormatCreate::GetRewriter() const +{ + if (m_sNewName.isEmpty() && m_pNew) + m_sNewName = m_pNew->GetName(); + + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, m_sNewName); + + return aRewriter; +} + +SwUndoFormatDelete::SwUndoFormatDelete +(SwUndoId nUndoId, SwFormat const * _pOld, SwDoc& rDoc) + : SwUndo(nUndoId, &rDoc), + m_rDoc(rDoc), m_sOldName(_pOld->GetName()), + m_aOldSet(_pOld->GetAttrSet()) +{ + m_sDerivedFrom = _pOld->DerivedFrom()->GetName(); + m_nId = _pOld->GetPoolFormatId() & COLL_GET_RANGE_BITS; + m_bAuto = _pOld->IsAuto(); +} + +SwUndoFormatDelete::~SwUndoFormatDelete() +{ +} + +void SwUndoFormatDelete::UndoImpl(::sw::UndoRedoContext &) +{ + SwFormat * pDerivedFrom = Find(m_sDerivedFrom); + + SwFormat * pFormat = Create(pDerivedFrom); + + if (pFormat) + { + m_rDoc.ChgFormat(*pFormat, m_aOldSet); + pFormat->SetAuto(m_bAuto); + pFormat->SetPoolFormatId((pFormat->GetPoolFormatId() & + ~COLL_GET_RANGE_BITS) + | m_nId); + } +} + +void SwUndoFormatDelete::RedoImpl(::sw::UndoRedoContext &) +{ + SwFormat * pOld = Find(m_sOldName); + + if (pOld) + { + Delete(pOld); + } +} + +SwRewriter SwUndoFormatDelete::GetRewriter() const +{ + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, m_sOldName); + + return aRewriter; +} + +SwUndoRenameFormat::SwUndoRenameFormat(SwUndoId nUndoId, + OUString _sOldName, + OUString _sNewName, + SwDoc& rDoc) + : SwUndo(nUndoId, &rDoc), m_sOldName(std::move(_sOldName)), + m_sNewName(std::move(_sNewName)), m_rDoc(rDoc) +{ +} + +SwUndoRenameFormat::~SwUndoRenameFormat() +{ +} + +void SwUndoRenameFormat::UndoImpl(::sw::UndoRedoContext &) +{ + SwFormat * pFormat = Find(m_sNewName); + + if (pFormat) + { + m_rDoc.RenameFormat(*pFormat, m_sOldName, true); + } +} + +void SwUndoRenameFormat::RedoImpl(::sw::UndoRedoContext &) +{ + SwFormat * pFormat = Find(m_sOldName); + + if (pFormat) + { + m_rDoc.RenameFormat(*pFormat, m_sNewName, true); + } +} + +SwRewriter SwUndoRenameFormat::GetRewriter() const +{ + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, m_sOldName); + aRewriter.AddRule(UndoArg2, SwResId(STR_YIELDS)); + aRewriter.AddRule(UndoArg3, m_sNewName); + + return aRewriter; +} + +SwUndoTextFormatCollCreate::SwUndoTextFormatCollCreate +(SwTextFormatColl * _pNew, SwTextFormatColl const * _pDerivedFrom, SwDoc& rDoc) + : SwUndoFormatCreate(SwUndoId::TXTFMTCOL_CREATE, _pNew, _pDerivedFrom, rDoc) +{ +} + +SwFormat * SwUndoTextFormatCollCreate::Create(SwFormat * pDerivedFrom) +{ + return m_rDoc.MakeTextFormatColl(m_sNewName, static_cast(pDerivedFrom), true); +} + +void SwUndoTextFormatCollCreate::Delete() +{ + m_rDoc.DelTextFormatColl(static_cast(m_pNew), true); +} + +SwFormat * SwUndoTextFormatCollCreate::Find(const OUString & rName) const +{ + return m_rDoc.FindTextFormatCollByName(rName); +} + +SwUndoTextFormatCollDelete::SwUndoTextFormatCollDelete(SwTextFormatColl const * _pOld, + SwDoc& rDoc) + : SwUndoFormatDelete(SwUndoId::TXTFMTCOL_DELETE, _pOld, rDoc) +{ +} + +SwFormat * SwUndoTextFormatCollDelete::Create(SwFormat * pDerivedFrom) +{ + return m_rDoc.MakeTextFormatColl(m_sOldName, static_cast(pDerivedFrom), true); +} + +void SwUndoTextFormatCollDelete::Delete(SwFormat * pOld) +{ + m_rDoc.DelTextFormatColl(static_cast(pOld), true); +} + +SwFormat * SwUndoTextFormatCollDelete::Find(const OUString & rName) const +{ + return m_rDoc.FindTextFormatCollByName(rName); +} + +SwUndoCondTextFormatCollCreate::SwUndoCondTextFormatCollCreate(SwConditionTextFormatColl *_pNew, + SwTextFormatColl const *_pDerivedFrom, SwDoc& rDoc) + : SwUndoTextFormatCollCreate(_pNew, _pDerivedFrom, rDoc) +{ +} + +SwFormat * SwUndoCondTextFormatCollCreate::Create(SwFormat * pDerivedFrom) +{ + return m_rDoc.MakeCondTextFormatColl(m_sNewName, static_cast(pDerivedFrom), true); +} + +SwUndoCondTextFormatCollDelete::SwUndoCondTextFormatCollDelete(SwTextFormatColl const * _pOld, + SwDoc& rDoc) + : SwUndoTextFormatCollDelete(_pOld, rDoc) +{ +} + +SwFormat * SwUndoCondTextFormatCollDelete::Create(SwFormat * pDerivedFrom) +{ + return m_rDoc.MakeCondTextFormatColl(m_sOldName, static_cast(pDerivedFrom), true); +} + +SwUndoRenameFormatColl::SwUndoRenameFormatColl(const OUString & sInitOldName, + const OUString & sInitNewName, + SwDoc& rDoc) + : SwUndoRenameFormat(SwUndoId::TXTFMTCOL_RENAME, sInitOldName, sInitNewName, rDoc) +{ +} + +SwFormat * SwUndoRenameFormatColl::Find(const OUString & rName) const +{ + return m_rDoc.FindTextFormatCollByName(rName); +} + +SwUndoCharFormatCreate::SwUndoCharFormatCreate(SwCharFormat * pNewFormat, + SwCharFormat const * pDerivedFrom, + SwDoc& rDocument) + : SwUndoFormatCreate(SwUndoId::CHARFMT_CREATE, pNewFormat, pDerivedFrom, rDocument) +{ +} + +SwFormat * SwUndoCharFormatCreate::Create(SwFormat * pDerivedFrom) +{ + return m_rDoc.MakeCharFormat(m_sNewName, static_cast(pDerivedFrom), true); +} + +void SwUndoCharFormatCreate::Delete() +{ + m_rDoc.DelCharFormat(static_cast(m_pNew), true); +} + +SwFormat * SwUndoCharFormatCreate::Find(const OUString & rName) const +{ + return m_rDoc.FindCharFormatByName(rName); +} + +SwUndoCharFormatDelete::SwUndoCharFormatDelete(SwCharFormat const * pOld, SwDoc& rDocument) + : SwUndoFormatDelete(SwUndoId::CHARFMT_DELETE, pOld, rDocument) +{ +} + +SwFormat * SwUndoCharFormatDelete::Create(SwFormat * pDerivedFrom) +{ + return m_rDoc.MakeCharFormat(m_sOldName, static_cast(pDerivedFrom), true); +} + +void SwUndoCharFormatDelete::Delete(SwFormat * pFormat) +{ + m_rDoc.DelCharFormat(static_cast(pFormat), true); +} + +SwFormat * SwUndoCharFormatDelete::Find(const OUString & rName) const +{ + return m_rDoc.FindCharFormatByName(rName); +} + +SwUndoRenameCharFormat::SwUndoRenameCharFormat(const OUString & sInitOldName, + const OUString & sInitNewName, + SwDoc& rDocument) + : SwUndoRenameFormat(SwUndoId::CHARFMT_RENAME, sInitOldName, sInitNewName, rDocument) +{ +} + +SwFormat * SwUndoRenameCharFormat::Find(const OUString & rName) const +{ + return m_rDoc.FindCharFormatByName(rName); +} + +SwUndoFrameFormatCreate::SwUndoFrameFormatCreate(SwFrameFormat * pNewFormat, + SwFrameFormat const * pDerivedFrom, + SwDoc& rDocument) + : SwUndoFormatCreate(SwUndoId::FRMFMT_CREATE, pNewFormat, pDerivedFrom, rDocument) +{ +} + +SwFormat * SwUndoFrameFormatCreate::Create(SwFormat * pDerivedFrom) +{ + return m_rDoc.MakeFrameFormat(m_sNewName, static_cast(pDerivedFrom), true, m_pNew->IsAuto()); +} + +void SwUndoFrameFormatCreate::Delete() +{ + m_rDoc.DelFrameFormat(static_cast(m_pNew), true); +} + +SwFormat * SwUndoFrameFormatCreate::Find(const OUString & rName) const +{ + return m_rDoc.FindFrameFormatByName(rName); +} + +SwUndoFrameFormatDelete::SwUndoFrameFormatDelete(SwFrameFormat const * pOld, SwDoc& rDocument) + : SwUndoFormatDelete(SwUndoId::FRMFMT_DELETE, pOld, rDocument) +{ +} + +SwFormat * SwUndoFrameFormatDelete::Create(SwFormat * pDerivedFrom) +{ + return m_rDoc.MakeFrameFormat(m_sOldName, static_cast(pDerivedFrom), true); +} + +void SwUndoFrameFormatDelete::Delete(SwFormat * pFormat) +{ + m_rDoc.DelFrameFormat(static_cast(pFormat), true); +} + +SwFormat * SwUndoFrameFormatDelete::Find(const OUString & rName) const +{ + return m_rDoc.FindFrameFormatByName(rName); +} + +SwUndoRenameFrameFormat::SwUndoRenameFrameFormat(const OUString & sInitOldName, + const OUString & sInitNewName, + SwDoc& rDocument) + : SwUndoRenameFormat(SwUndoId::FRMFMT_RENAME, sInitOldName, sInitNewName, rDocument) +{ +} + +SwFormat * SwUndoRenameFrameFormat::Find(const OUString & rName) const +{ + return m_rDoc.FindFrameFormatByName(rName); +} + +SwUndoNumruleCreate::SwUndoNumruleCreate(const SwNumRule * _pNew, + SwDoc& rDoc) + : SwUndo(SwUndoId::NUMRULE_CREATE, &rDoc), m_pNew(_pNew), m_aNew(*_pNew), m_rDoc(rDoc), + m_bInitialized(false) +{ +} + +void SwUndoNumruleCreate::UndoImpl(::sw::UndoRedoContext &) +{ + if (! m_bInitialized) + { + m_aNew = *m_pNew; + m_bInitialized = true; + } + + m_rDoc.DelNumRule(m_aNew.GetName(), true); +} + +void SwUndoNumruleCreate::RedoImpl(::sw::UndoRedoContext &) +{ + m_rDoc.MakeNumRule(m_aNew.GetName(), &m_aNew, true); +} + +SwRewriter SwUndoNumruleCreate::GetRewriter() const +{ + SwRewriter aResult; + + if (! m_bInitialized) + { + m_aNew = *m_pNew; + m_bInitialized = true; + } + + aResult.AddRule(UndoArg1, m_aNew.GetName()); + + return aResult; +} + +SwUndoNumruleDelete::SwUndoNumruleDelete(const SwNumRule & rRule, + SwDoc& rDoc) + : SwUndo(SwUndoId::NUMRULE_DELETE, &rDoc), m_aOld(rRule), m_rDoc(rDoc) +{ +} + +void SwUndoNumruleDelete::UndoImpl(::sw::UndoRedoContext &) +{ + m_rDoc.MakeNumRule(m_aOld.GetName(), &m_aOld, true); +} + +void SwUndoNumruleDelete::RedoImpl(::sw::UndoRedoContext &) +{ + m_rDoc.DelNumRule(m_aOld.GetName(), true); +} + +SwRewriter SwUndoNumruleDelete::GetRewriter() const +{ + SwRewriter aResult; + + aResult.AddRule(UndoArg1, m_aOld.GetName()); + + return aResult; +} + +SwUndoNumruleRename::SwUndoNumruleRename(OUString _aOldName, + OUString _aNewName, + SwDoc& rDoc) + : SwUndo(SwUndoId::NUMRULE_RENAME, &rDoc), m_aOldName(std::move(_aOldName)), m_aNewName(std::move(_aNewName)), + m_rDoc(rDoc) +{ +} + +void SwUndoNumruleRename::UndoImpl(::sw::UndoRedoContext &) +{ + m_rDoc.RenameNumRule(m_aNewName, m_aOldName, true); +} + +void SwUndoNumruleRename::RedoImpl(::sw::UndoRedoContext &) +{ + m_rDoc.RenameNumRule(m_aOldName, m_aNewName, true); +} + +SwRewriter SwUndoNumruleRename::GetRewriter() const +{ + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, m_aOldName); + aRewriter.AddRule(UndoArg2, SwResId(STR_YIELDS)); + aRewriter.AddRule(UndoArg3, m_aNewName); + + return aRewriter; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/SwUndoPageDesc.cxx b/sw/source/core/undo/SwUndoPageDesc.cxx new file mode 100644 index 0000000000..eec1300d5e --- /dev/null +++ b/sw/source/core/undo/SwUndoPageDesc.cxx @@ -0,0 +1,352 @@ +/* -*- 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 + +SwUndoPageDesc::SwUndoPageDesc(const SwPageDesc & _aOld, + const SwPageDesc & _aNew, + SwDoc * _pDoc) + : SwUndo( _aOld.GetName() != _aNew.GetName() ? + SwUndoId::RENAME_PAGEDESC : + SwUndoId::CHANGE_PAGEDESC, + _pDoc ), + m_aOld(_aOld, _pDoc), m_aNew(_aNew, _pDoc), m_pDoc(_pDoc), m_bExchange( false ) +{ + OSL_ENSURE(nullptr != m_pDoc, "no document?"); + + /* + The page description changes. + If there are no header/footer content changes like header on/off or change from shared content + to unshared etc., there is no reason to duplicate the content nodes (Crash i55547) + But this happens, this Undo Ctor will destroy the unnecessary duplicate and manipulate the + content pointer of the both page descriptions. + */ + SwPageDesc &rOldDesc = m_aOld.m_PageDesc; + SwPageDesc &rNewDesc = m_aNew.m_PageDesc; + const SwFormatHeader& rOldHead = rOldDesc.GetMaster().GetHeader(); + const SwFormatHeader& rNewHead = rNewDesc.GetMaster().GetHeader(); + const SwFormatFooter& rOldFoot = rOldDesc.GetMaster().GetFooter(); + const SwFormatFooter& rNewFoot = rNewDesc.GetMaster().GetFooter(); + /* bExchange must not be set, if the old page descriptor will stay active. + Two known situations: + #i67735#: renaming a page descriptor + #i67334#: changing the follow style + If header/footer will be activated or deactivated, this undo will not work. + */ + m_bExchange = ( m_aOld.GetName() == m_aNew.GetName() ) && + ( _aOld.GetFollow() == _aNew.GetFollow() ) && + ( rOldHead.IsActive() == rNewHead.IsActive() ) && + ( rOldFoot.IsActive() == rNewFoot.IsActive() ); + if( rOldHead.IsActive() && ( rOldDesc.IsHeaderShared() != rNewDesc.IsHeaderShared() ) ) + m_bExchange = false; + if( rOldFoot.IsActive() && ( rOldDesc.IsFooterShared() != rNewDesc.IsFooterShared() ) ) + m_bExchange = false; + if( ( rOldHead.IsActive() || rOldFoot.IsActive() ) && ( rOldDesc.IsFirstShared() != rNewDesc.IsFirstShared() ) ) + m_bExchange = false; + if( !m_bExchange ) + return; + + if( rNewHead.IsActive() ) + { + SwFrameFormat* pFormat = new SwFrameFormat( *rNewHead.GetHeaderFormat() ); + // The Ctor of this object will remove the duplicate! + SwFormatHeader aFormatHeader(pFormat); + (void)aFormatHeader; + if (!rNewDesc.IsHeaderShared()) + { + pFormat = new SwFrameFormat( *rNewDesc.GetLeft().GetHeader().GetHeaderFormat() ); + // The Ctor of this object will remove the duplicate! + SwFormatHeader aLeftHeader(pFormat); + (void)aLeftHeader; + } + if (!rNewDesc.IsFirstShared()) + { + pFormat = new SwFrameFormat( *rNewDesc.GetFirstMaster().GetHeader().GetHeaderFormat() ); + // The Ctor of this object will remove the duplicate! + SwFormatHeader aFirstHeader(pFormat); + (void)aFirstHeader; + } + } + // Same procedure for footers... + if( rNewFoot.IsActive() ) + { + SwFrameFormat* pFormat = new SwFrameFormat( *rNewFoot.GetFooterFormat() ); + // The Ctor of this object will remove the duplicate! + SwFormatFooter aFormatFooter(pFormat); + (void)aFormatFooter; + if (!rNewDesc.IsFooterShared()) + { + pFormat = new SwFrameFormat( *rNewDesc.GetLeft().GetFooter().GetFooterFormat() ); + // The Ctor of this object will remove the duplicate! + SwFormatFooter aLeftFooter(pFormat); + (void)aLeftFooter; + } + if (!rNewDesc.IsFirstShared()) + { + pFormat = new SwFrameFormat( *rNewDesc.GetFirstMaster().GetFooter().GetFooterFormat() ); + // The Ctor of this object will remove the duplicate! + SwFormatFooter aFirstFooter(pFormat); + (void)aFirstFooter; + } + } + + // After this exchange method the old page description will point to zero, + // the new one will point to the node position of the original content nodes. + ExchangeContentNodes( m_aOld.m_PageDesc, m_aNew.m_PageDesc ); +} + +SwUndoPageDesc::~SwUndoPageDesc() +{ +} + +void SwUndoPageDesc::ExchangeContentNodes( SwPageDesc& rSource, SwPageDesc &rDest ) +{ + OSL_ENSURE( m_bExchange, "You shouldn't do that." ); + const SwFormatHeader& rDestHead = rDest.GetMaster().GetHeader(); + const SwFormatHeader& rSourceHead = rSource.GetMaster().GetHeader(); + if( rDestHead.IsActive() ) + { + // Let the destination page description point to the source node position, + // from now on this descriptor is responsible for the content nodes! + const SwFormatHeader* pItem = rDest.GetMaster().GetAttrSet().GetItemIfSet( RES_HEADER, false ); + std::unique_ptr pNewItem(pItem->Clone()); + SwFrameFormat* pNewFormat = pNewItem->GetHeaderFormat(); + pNewFormat->SetFormatAttr( rSourceHead.GetHeaderFormat()->GetContent() ); + + // Let the source page description point to zero node position, + // it loses the responsible and can be destroyed without removing the content nodes. + pItem = rSource.GetMaster().GetAttrSet().GetItemIfSet( RES_HEADER, false ); + pNewItem.reset(pItem->Clone()); + pNewFormat = pNewItem->GetHeaderFormat(); + pNewFormat->SetFormatAttr( SwFormatContent() ); + + if( !rDest.IsHeaderShared() ) + { + // Same procedure for unshared header... + const SwFormatHeader& rSourceLeftHead = rSource.GetLeft().GetHeader(); + pItem = rDest.GetLeft().GetAttrSet().GetItemIfSet( RES_HEADER, false ); + pNewItem.reset(pItem->Clone()); + pNewFormat = pNewItem->GetHeaderFormat(); + pNewFormat->SetFormatAttr( rSourceLeftHead.GetHeaderFormat()->GetContent() ); + pItem = rSource.GetLeft().GetAttrSet().GetItemIfSet( RES_HEADER, false ); + pNewItem.reset(pItem->Clone()); + pNewFormat = pNewItem->GetHeaderFormat(); + pNewFormat->SetFormatAttr( SwFormatContent() ); + } + if (!rDest.IsFirstShared()) + { + // Same procedure for unshared header... + const SwFormatHeader& rSourceFirstMasterHead = rSource.GetFirstMaster().GetHeader(); + pItem = rDest.GetFirstMaster().GetAttrSet().GetItemIfSet( RES_HEADER, false ); + pNewItem.reset(pItem->Clone()); + pNewFormat = pNewItem->GetHeaderFormat(); + pNewFormat->SetFormatAttr( rSourceFirstMasterHead.GetHeaderFormat()->GetContent() ); + pItem = rSource.GetFirstMaster().GetAttrSet().GetItemIfSet( RES_HEADER, false ); + pNewItem.reset(pItem->Clone()); + pNewFormat = pNewItem->GetHeaderFormat(); + pNewFormat->SetFormatAttr( SwFormatContent() ); + } + } + // Same procedure for footers... + const SwFormatFooter& rDestFoot = rDest.GetMaster().GetFooter(); + const SwFormatFooter& rSourceFoot = rSource.GetMaster().GetFooter(); + if( !rDestFoot.IsActive() ) + return; + + const SwFormatFooter* pItem; + pItem = rDest.GetMaster().GetAttrSet().GetItemIfSet( RES_FOOTER, false ); + std::unique_ptr pNewItem(pItem->Clone()); + SwFrameFormat *pNewFormat = pNewItem->GetFooterFormat(); + pNewFormat->SetFormatAttr( rSourceFoot.GetFooterFormat()->GetContent() ); + + pItem = rSource.GetMaster().GetAttrSet().GetItemIfSet( RES_FOOTER, false ); + pNewItem.reset(pItem->Clone()); + pNewFormat = pNewItem->GetFooterFormat(); + pNewFormat->SetFormatAttr( SwFormatContent() ); + + if( !rDest.IsFooterShared() ) + { + const SwFormatFooter& rSourceLeftFoot = rSource.GetLeft().GetFooter(); + pItem = rDest.GetLeft().GetAttrSet().GetItemIfSet( RES_FOOTER, false ); + pNewItem.reset(pItem->Clone()); + pNewFormat = pNewItem->GetFooterFormat(); + pNewFormat->SetFormatAttr( rSourceLeftFoot.GetFooterFormat()->GetContent() ); + pItem = rSource.GetLeft().GetAttrSet().GetItemIfSet( RES_FOOTER, false ); + pNewItem.reset(pItem->Clone()); + pNewFormat = pNewItem->GetFooterFormat(); + pNewFormat->SetFormatAttr( SwFormatContent() ); + } + if (rDest.IsFirstShared()) + return; + + const SwFormatFooter& rSourceFirstMasterFoot = rSource.GetFirstMaster().GetFooter(); + pItem = rDest.GetFirstMaster().GetAttrSet().GetItemIfSet( RES_FOOTER, false ); + pNewItem.reset(pItem->Clone()); + pNewFormat = pNewItem->GetFooterFormat(); + pNewFormat->SetFormatAttr( rSourceFirstMasterFoot.GetFooterFormat()->GetContent() ); + pItem = rSource.GetFirstMaster().GetAttrSet().GetItemIfSet( RES_FOOTER, false ); + pNewItem.reset(pItem->Clone()); + pNewFormat = pNewItem->GetFooterFormat(); + pNewFormat->SetFormatAttr( SwFormatContent() ); +} + +void SwUndoPageDesc::ExitHeaderFooterEdit() +{ + SwEditShell* pESh = m_pDoc->GetEditShell(); + if (!pESh) + return; + if (pESh->IsHeaderFooterEdit()) + pESh->ToggleHeaderFooterEdit(); +} + +void SwUndoPageDesc::UndoImpl(::sw::UndoRedoContext &) +{ + // Move (header/footer)content node responsibility from new page descriptor to old one again. + if( m_bExchange ) + ExchangeContentNodes( m_aNew.m_PageDesc, m_aOld.m_PageDesc ); + m_pDoc->ChgPageDesc(m_aOld.GetName(), m_aOld); + ExitHeaderFooterEdit(); +} + +void SwUndoPageDesc::RedoImpl(::sw::UndoRedoContext &) +{ + // Move (header/footer)content node responsibility from old page descriptor to new one again. + if( m_bExchange ) + ExchangeContentNodes( m_aOld.m_PageDesc, m_aNew.m_PageDesc ); + m_pDoc->ChgPageDesc(m_aNew.GetName(), m_aNew); + ExitHeaderFooterEdit(); +} + +SwRewriter SwUndoPageDesc::GetRewriter() const +{ + SwRewriter aResult; + + aResult.AddRule(UndoArg1, m_aOld.GetName()); + aResult.AddRule(UndoArg2, SwResId(STR_YIELDS)); + aResult.AddRule(UndoArg3, m_aNew.GetName()); + + return aResult; +} + +SwUndoPageDescCreate::SwUndoPageDescCreate(const SwPageDesc * pNew, + SwDoc * _pDoc) + : SwUndo(SwUndoId::CREATE_PAGEDESC, _pDoc), m_pDesc(pNew), m_aNew(*pNew, _pDoc), + m_pDoc(_pDoc) +{ + OSL_ENSURE(nullptr != m_pDoc, "no document?"); +} + +SwUndoPageDescCreate::~SwUndoPageDescCreate() +{ +} + +void SwUndoPageDescCreate::UndoImpl(::sw::UndoRedoContext &) +{ + if (m_pDesc) + { + m_aNew = *m_pDesc; + m_pDesc = nullptr; + } + + m_pDoc->DelPageDesc(m_aNew.GetName(), true); +} + +void SwUndoPageDescCreate::DoImpl() +{ + SwPageDesc aPageDesc = m_aNew; + m_pDoc->MakePageDesc(m_aNew.GetName(), &aPageDesc, false, true); +} + +void SwUndoPageDescCreate::RedoImpl(::sw::UndoRedoContext &) +{ + DoImpl(); +} + +void SwUndoPageDescCreate::RepeatImpl(::sw::RepeatContext &) +{ + ::sw::UndoGuard const undoGuard(m_pDoc->GetIDocumentUndoRedo()); + DoImpl(); +} + +SwRewriter SwUndoPageDescCreate::GetRewriter() const +{ + SwRewriter aResult; + + if (m_pDesc) + aResult.AddRule(UndoArg1, m_pDesc->GetName()); + else + aResult.AddRule(UndoArg1, m_aNew.GetName()); + + return aResult; +} + +SwUndoPageDescDelete::SwUndoPageDescDelete(const SwPageDesc & _aOld, + SwDoc * _pDoc) + : SwUndo(SwUndoId::DELETE_PAGEDESC, _pDoc), m_aOld(_aOld, _pDoc), m_pDoc(_pDoc) +{ + OSL_ENSURE(nullptr != m_pDoc, "no document?"); +} + +SwUndoPageDescDelete::~SwUndoPageDescDelete() +{ +} + +void SwUndoPageDescDelete::UndoImpl(::sw::UndoRedoContext &) +{ + SwPageDesc aPageDesc = m_aOld; + m_pDoc->MakePageDesc(m_aOld.GetName(), &aPageDesc, false, true); +} + +void SwUndoPageDescDelete::DoImpl() +{ + m_pDoc->DelPageDesc(m_aOld.GetName(), true); +} + +void SwUndoPageDescDelete::RedoImpl(::sw::UndoRedoContext &) +{ + DoImpl(); +} + +void SwUndoPageDescDelete::RepeatImpl(::sw::RepeatContext &) +{ + ::sw::UndoGuard const undoGuard(m_pDoc->GetIDocumentUndoRedo()); + DoImpl(); +} + +SwRewriter SwUndoPageDescDelete::GetRewriter() const +{ + SwRewriter aResult; + + aResult.AddRule(UndoArg1, m_aOld.GetName()); + + return aResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/SwUndoTOXChange.cxx b/sw/source/core/undo/SwUndoTOXChange.cxx new file mode 100644 index 0000000000..5223e9993e --- /dev/null +++ b/sw/source/core/undo/SwUndoTOXChange.cxx @@ -0,0 +1,85 @@ +/* -*- 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 + +namespace +{ + SwNodeOffset GetSectionNodeIndex(SwTOXBaseSection const& rTOX) + { + const SwSectionNode* pSectNd = rTOX.GetFormat()->GetSectionNode(); + assert(pSectNd); + return pSectNd->GetIndex(); + } +} + +SwUndoTOXChange::SwUndoTOXChange(const SwDoc& rDoc, + SwTOXBaseSection const& rTOX, SwTOXBase const& rNew) + : SwUndo(SwUndoId::TOXCHANGE, &rDoc) + , m_Old(rTOX) + , m_New(rNew) + , m_nNodeIndex(GetSectionNodeIndex(rTOX)) +{ +} + +SwUndoTOXChange::~SwUndoTOXChange() +{ +} + +// get the current ToXBase, which is not necessarily the same instance that existed there before +static SwTOXBase & GetTOX(SwDoc & rDoc, SwNodeOffset const nNodeIndex) +{ + SwSectionNode *const pNode(rDoc.GetNodes()[nNodeIndex]->GetSectionNode()); + assert(pNode); + assert(dynamic_cast(&pNode->GetSection())); + auto & rTOX(static_cast(pNode->GetSection())); + return rTOX; +} + +void SwUndoTOXChange::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc(rContext.GetDoc()); + SwTOXBase & rTOX(GetTOX(rDoc, m_nNodeIndex)); + rTOX = m_Old; +} + +void SwUndoTOXChange::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc(rContext.GetDoc()); + SwTOXBase & rTOX(GetTOX(rDoc, m_nNodeIndex)); + rTOX = m_New; +} + +void SwUndoTOXChange::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc(rContext.GetDoc()); + SwTOXBase *const pTOX(SwDoc::GetCurTOX(*rContext.GetRepeatPaM().GetPoint())); + if (pTOX) + { + rDoc.ChangeTOX(*pTOX, m_New); + // intentionally limited to not Update because we'd need layout + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/UndoThemeChange.cxx b/sw/source/core/undo/UndoThemeChange.cxx new file mode 100644 index 0000000000..8e26106873 --- /dev/null +++ b/sw/source/core/undo/UndoThemeChange.cxx @@ -0,0 +1,48 @@ +/* -*- 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/. + */ + +#include +#include +#include +#include +#include +#include + +#include + +namespace sw +{ +UndoThemeChange::UndoThemeChange(SwDoc& rDocument, + std::shared_ptr const& pOldColorSet, + std::shared_ptr const& pNewColorSet) + : SwUndo(SwUndoId::CHANGE_THEME, &rDocument) + , mrDocument(rDocument) + , mpOldColorSet(pOldColorSet) + , mpNewColorSet(pNewColorSet) +{ +} + +UndoThemeChange::~UndoThemeChange() {} + +void UndoThemeChange::UndoImpl(UndoRedoContext& /*rUndoRedoContext*/) +{ + SdrModel* pModel = mrDocument.getIDocumentDrawModelAccess().GetDrawModel(); + auto pTheme = pModel->getTheme(); + pTheme->setColorSet(mpOldColorSet); +} + +void UndoThemeChange::RedoImpl(UndoRedoContext& /*rUndoRedoContext*/) +{ + SdrModel* pModel = mrDocument.getIDocumentDrawModelAccess().GetDrawModel(); + auto pTheme = pModel->getTheme(); + pTheme->setColorSet(mpNewColorSet); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/docundo.cxx b/sw/source/core/undo/docundo.cxx new file mode 100644 index 0000000000..57202fe363 --- /dev/null +++ b/sw/source/core/undo/docundo.cxx @@ -0,0 +1,844 @@ +/* -*- 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 +#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) + +namespace sw { + +UndoManager::UndoManager(std::shared_ptr xUndoNodes, + IDocumentDrawModelAccess & rDrawModelAccess, + IDocumentRedlineAccess & rRedlineAccess, + IDocumentState & rState) + : m_rDrawModelAccess(rDrawModelAccess) + , m_rRedlineAccess(rRedlineAccess) + , m_rState(rState) + , m_xUndoNodes(std::move(xUndoNodes)) + , m_bGroupUndo(true) + , m_bDrawUndo(true) + , m_bRepair(false) + , m_bLockUndoNoModifiedPosition(false) + , m_isAddWithIgnoreRepeat(false) + , m_UndoSaveMark(MARK_INVALID) + , m_pDocShell(nullptr) + , m_pView(nullptr) +{ + assert(bool(m_xUndoNodes)); + // writer expects it to be disabled initially + // Undo is enabled by SwEditShell constructor + SdrUndoManager::EnableUndo(false); +} + +SwNodes const& UndoManager::GetUndoNodes() const +{ + return *m_xUndoNodes; +} + +SwNodes & UndoManager::GetUndoNodes() +{ + return *m_xUndoNodes; +} + +bool UndoManager::IsUndoNodes(SwNodes const& rNodes) const +{ + return & rNodes == m_xUndoNodes.get(); +} + +void UndoManager::SetDocShell(SwDocShell* pDocShell) +{ + m_pDocShell = pDocShell; +} + +void UndoManager::SetView(SwView* pView) +{ + m_pView = pView; +} + +size_t UndoManager::GetUndoActionCount(const bool bCurrentLevel) const +{ + size_t nRet = SdrUndoManager::GetUndoActionCount(bCurrentLevel); + if (!comphelper::LibreOfficeKit::isActive() || !m_pView) + return nRet; + + if (!nRet || !SdrUndoManager::GetUndoActionCount()) + return nRet; + + const SfxUndoAction* pAction = SdrUndoManager::GetUndoAction(); + if (!pAction) + return nRet; + + if (!m_bRepair) + { + // If another view created the last undo action, prevent undoing it from this view. + ViewShellId nViewShellId = m_pView->GetViewShellId(); + if (pAction->GetViewShellId() != nViewShellId) + nRet = 0; + } + + return nRet; +} + +size_t UndoManager::GetRedoActionCount(const bool bCurrentLevel) const +{ + size_t nRet = SdrUndoManager::GetRedoActionCount(bCurrentLevel); + if (!comphelper::LibreOfficeKit::isActive() || !m_pView) + return nRet; + + if (!nRet || !SdrUndoManager::GetRedoActionCount()) + return nRet; + + const SfxUndoAction* pAction = SdrUndoManager::GetRedoAction(); + if (!pAction) + return nRet; + + if (!m_bRepair) + { + // If another view created the first redo action, prevent redoing it from this view. + ViewShellId nViewShellId = m_pView->GetViewShellId(); + if (pAction->GetViewShellId() != nViewShellId) + nRet = 0; + } + + return nRet; +} + +void UndoManager::DoUndo(bool const bDoUndo) +{ + if(!isTextEditActive()) + { + EnableUndo(bDoUndo); + + SwDrawModel*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; +} + +void UndoManager::DoRepair(bool bRepair) +{ + m_bRepair = bRepair; +} + +bool UndoManager::DoesRepair() const +{ + return m_bRepair; +} + +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()) + { + return nullptr; + } + SfxUndoAction *const pAction( SdrUndoManager::GetUndoAction() ); + return dynamic_cast(pAction); +} + +void UndoManager::AppendUndo(std::unique_ptr pUndo) +{ + AddUndoAction(std::move(pUndo)); +} + +void UndoManager::ClearRedo() +{ + return SdrUndoManager::ImplClearRedo_NoLock(TopLevel); +} + +void UndoManager::DelAllUndoObj() +{ + ::sw::UndoGuard const undoGuard(*this); + + SdrUndoManager::ClearAllLevels(); + + m_UndoSaveMark = MARK_INVALID; +} + +SwUndoId +UndoManager::StartUndo(SwUndoId const i_eUndoId, + SwRewriter const*const pRewriter) +{ + if (!IsUndoEnabled()) + { + return SwUndoId::EMPTY; + } + + SwUndoId const eUndoId( (i_eUndoId == SwUndoId::EMPTY) ? SwUndoId::START : i_eUndoId ); + + assert(SwUndoId::END != eUndoId); + OUString comment( (SwUndoId::START == eUndoId) + ? OUString("??") + : GetUndoComment(eUndoId) ); + if (pRewriter) + { + assert(SwUndoId::START != eUndoId); + comment = pRewriter->Apply(comment); + } + + ViewShellId nViewShellId(-1); + if (m_pDocShell) + { + if (const SwView* pView = m_pDocShell->GetView()) + nViewShellId = pView->GetViewShellId(); + } + SdrUndoManager::EnterListAction(comment, comment, static_cast(eUndoId), nViewShellId); + + return eUndoId; +} + +SwUndoId +UndoManager::EndUndo(SwUndoId eUndoId, SwRewriter const*const pRewriter) +{ + if (!IsUndoEnabled()) + { + return SwUndoId::EMPTY; + } + + if ((eUndoId == SwUndoId::EMPTY) || (SwUndoId::START == eUndoId)) + eUndoId = SwUndoId::END; + OSL_ENSURE(!((SwUndoId::END == eUndoId) && pRewriter), + "EndUndo(): no Undo ID, but rewriter given?"); + + SfxUndoAction *const pLastUndo( + (0 == SdrUndoManager::GetUndoActionCount()) + ? nullptr : SdrUndoManager::GetUndoAction() ); + + int const nCount = LeaveListAction(); + + if (nCount) // otherwise: empty list action not inserted! + { + assert(pLastUndo); + assert(SwUndoId::START != eUndoId); + auto pListAction = dynamic_cast(SdrUndoManager::GetUndoAction()); + assert(pListAction); + if (SwUndoId::END != eUndoId) + { + OSL_ENSURE(static_cast(pListAction->GetId()) == eUndoId, + "EndUndo(): given ID different from StartUndo()"); + // comment set by caller of EndUndo + OUString comment = GetUndoComment(eUndoId); + if (pRewriter) + { + comment = pRewriter->Apply(comment); + } + pListAction->SetComment(comment); + } + else if (SwUndoId::START != static_cast(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) + OUString const comment(pLastUndo->GetComment()); + pListAction->SetComment(comment); + } + else + { + OSL_ENSURE(false, "EndUndo(): no comment?"); + } + } + + return eUndoId; +} + +/** + * Checks if the topmost undo action owned by pView is independent from the topmost action undo + * action. + */ +bool UndoManager::IsViewUndoActionIndependent(const SwView* pView, sal_uInt16& rOffset) const +{ + if (GetUndoActionCount() <= 1) + { + // Single or less undo, owned by another view. + return false; + } + + if (!pView) + { + return false; + } + + // Last undo action that doesn't belong to the view. + const SfxUndoAction* pTopAction = GetUndoAction(); + + ViewShellId nViewId = pView->GetViewShellId(); + + // Earlier undo action that belongs to the view, but is not the top one. + const SfxUndoAction* pViewAction = nullptr; + size_t nOffset = 0; + for (size_t i = 0; i < GetUndoActionCount(); ++i) + { + const SfxUndoAction* pAction = GetUndoAction(i); + if (pAction->GetViewShellId() == nViewId) + { + pViewAction = pAction; + nOffset = i; + break; + } + } + + if (!pViewAction) + { + // Found no earlier undo action that belongs to the view. + return false; + } + + auto pTopSwAction = dynamic_cast(pTopAction); + if (!pTopSwAction || pTopSwAction->GetId() != SwUndoId::TYPING) + { + return false; + } + + auto pViewSwAction = dynamic_cast(pViewAction); + if (!pViewSwAction || pViewSwAction->GetId() != SwUndoId::TYPING) + { + return false; + } + + const auto& rTopInsert = *static_cast(pTopSwAction); + const auto& rViewInsert = *static_cast(pViewSwAction); + + for (size_t i = 0; i < GetRedoActionCount(); ++i) + { + auto pRedoAction = dynamic_cast(GetRedoAction(i)); + if (!pRedoAction || pRedoAction->GetId() != SwUndoId::TYPING) + { + return false; + } + + const auto& rRedoInsert = *static_cast(pRedoAction); + if (!rViewInsert.IsIndependent(rRedoInsert) && rRedoInsert.GetViewShellId() != nViewId) + { + // Dependent redo action and owned by another view. + return false; + } + } + + if (!rViewInsert.IsIndependent(rTopInsert)) + { + return false; + } + + rOffset = nOffset; + return true; +} + +bool +UndoManager::GetLastUndoInfo( + OUString *const o_pStr, SwUndoId *const o_pId, const SwView* pView) const +{ + // this is actually expected to work on the current level, + // but that was really not obvious from the previous implementation... + if (!SdrUndoManager::GetUndoActionCount()) + { + return false; + } + + SfxUndoAction *const pAction( SdrUndoManager::GetUndoAction() ); + + if (comphelper::LibreOfficeKit::isActive() && !m_bRepair) + { + // If another view created the undo action, prevent undoing it from this view. + ViewShellId nViewShellId = pView ? pView->GetViewShellId() : m_pDocShell->GetView()->GetViewShellId(); + // Unless we know that the other view's undo action is independent from us. + if (pAction->GetViewShellId() != nViewShellId + && !IsViewUndoActionIndependent(pView, o3tl::temporary(sal_uInt16()))) + { + if (o_pId) + { + *o_pId = SwUndoId::CONFLICT; + } + return false; + } + } + + if (o_pStr) + { + *o_pStr = pAction->GetComment(); + } + if (o_pId) + { + if (auto pListAction = dynamic_cast(pAction)) + *o_pId = static_cast(pListAction->GetId()); + else if (auto pSwAction = dynamic_cast(pAction)) + *o_pId = pSwAction->GetId(); + else + *o_pId = SwUndoId::EMPTY; + } + + return true; +} + +SwUndoComments_t UndoManager::GetUndoComments() const +{ + OSL_ENSURE(!SdrUndoManager::IsInListAction(), + "GetUndoComments() called while in list action?"); + + SwUndoComments_t ret; + const size_t nUndoCount(SdrUndoManager::GetUndoActionCount(TopLevel)); + for (size_t n = 0; n < nUndoCount; ++n) + { + OUString const comment( + SdrUndoManager::GetUndoActionComment(n, TopLevel)); + ret.push_back(comment); + } + + return ret; +} + +bool UndoManager::GetFirstRedoInfo(OUString *const o_pStr, + SwUndoId *const o_pId, + const SwView* pView) const +{ + if (!SdrUndoManager::GetRedoActionCount()) + { + return false; + } + + SfxUndoAction *const pAction( SdrUndoManager::GetRedoAction() ); + if ( pAction == nullptr ) + { + return false; + } + + if (comphelper::LibreOfficeKit::isActive() && !m_bRepair) + { + // If another view created the undo action, prevent redoing it from this view. + ViewShellId nViewShellId = pView ? pView->GetViewShellId() : m_pDocShell->GetView()->GetViewShellId(); + if (pAction->GetViewShellId() != nViewShellId) + { + if (o_pId) + { + *o_pId = SwUndoId::CONFLICT; + } + return false; + } + } + + if (o_pStr) + { + *o_pStr = pAction->GetComment(); + } + if (o_pId) + { + if (auto pListAction = dynamic_cast(pAction)) + *o_pId = static_cast(pListAction->GetId()); + else if (auto pSwAction = dynamic_cast(pAction)) + *o_pId = pSwAction->GetId(); + else + *o_pId = SwUndoId::EMPTY; + } + + return true; +} + +SwUndoComments_t UndoManager::GetRedoComments() const +{ + OSL_ENSURE(!SdrUndoManager::IsInListAction(), + "GetRedoComments() called while in list action?"); + + SwUndoComments_t ret; + const size_t nRedoCount(SdrUndoManager::GetRedoActionCount(TopLevel)); + for (size_t n = 0; n < nRedoCount; ++n) + { + OUString const comment( + SdrUndoManager::GetRedoActionComment(n, TopLevel)); + ret.push_back(comment); + } + + return ret; +} + +SwUndoId UndoManager::GetRepeatInfo(OUString *const o_pStr) const +{ + SwUndoId nRepeatId(SwUndoId::EMPTY); + GetLastUndoInfo(o_pStr, & nRepeatId); + if( SwUndoId::REPEAT_START <= nRepeatId && SwUndoId::REPEAT_END > nRepeatId ) + { + return nRepeatId; + } + if (o_pStr) // not repeatable -> clear comment + { + o_pStr->clear(); + } + return SwUndoId::EMPTY; +} + +SwUndo * UndoManager::RemoveLastUndo() +{ + if (SdrUndoManager::GetRedoActionCount() || + SdrUndoManager::GetRedoActionCount(TopLevel)) + { + OSL_ENSURE(false, "RemoveLastUndoAction(): there are Redo actions?"); + return nullptr; + } + if (!SdrUndoManager::GetUndoActionCount()) + { + OSL_ENSURE(false, "RemoveLastUndoAction(): no Undo actions"); + return nullptr; + } + SfxUndoAction *const pLastUndo(GetUndoAction()); + SdrUndoManager::RemoveLastUndoAction(); + return dynamic_cast(pLastUndo); +} + +// SfxUndoManager + +void UndoManager::AddUndoAction(std::unique_ptr pAction, bool bTryMerge) +{ + SwUndo *const pUndo( dynamic_cast(pAction.get()) ); + if (pUndo) + { + if (RedlineFlags::NONE == pUndo->GetRedlineFlags()) + { + pUndo->SetRedlineFlags( m_rRedlineAccess.GetRedlineFlags() ); + } + if (m_isAddWithIgnoreRepeat) + { + pUndo->IgnoreRepeat(); + } + } + SdrUndoManager::AddUndoAction(std::move(pAction), bTryMerge); + if (m_pDocShell) + { + SfxViewFrame* pViewFrame = SfxViewFrame::GetFirst( m_pDocShell ); + while ( pViewFrame ) + { + pViewFrame->GetBindings().Invalidate( SID_UNDO ); + pViewFrame->GetBindings().Invalidate( SID_REDO ); + pViewFrame = SfxViewFrame::GetNext( *pViewFrame, m_pDocShell ); + } + } + + // if the undo nodes array is too large, delete some actions + while (UNDO_ACTION_LIMIT < sal_Int32(GetUndoNodes().Count())) + { + RemoveOldestUndoAction(); + } +} + +namespace { + +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() COVERITY_NOEXCEPT_FALSE + { + if (m_bSaveCursor) + { + m_rShell.Pop(SwCursorShell::PopMode::DeleteCurrent); + } + } +private: + SwEditShell & m_rShell; + bool const m_bSaveCursor; +}; + +} + +bool UndoManager::impl_DoUndoRedo(UndoOrRedoType undoOrRedo, size_t nUndoOffset) +{ + SwDoc & rDoc(GetUndoNodes().GetDoc()); + + UnoActionContext c(& rDoc); // exception-safe StartAllAction/EndAllAction + + SwView* pViewShell = dynamic_cast(SfxViewShell::Current()); + SwEditShell *const pEditShell( + comphelper::LibreOfficeKit::isActive() && pViewShell ? pViewShell->GetWrtShellPtr() + : 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 aCursorGuard(*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); + context.SetUndoOffset(nUndoOffset); + + // N.B. these may throw! + if (UndoOrRedoType::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; +} + +bool UndoManager::Undo() { return UndoWithOffset(0); } + +bool UndoManager::UndoWithOffset(size_t nUndoOffset) +{ + if(isTextEditActive()) + { + return SdrUndoManager::Undo(); + } + else + { + return impl_DoUndoRedo(UndoOrRedoType::Undo, nUndoOffset); + } +} + +bool UndoManager::Redo() +{ + if(isTextEditActive()) + { + return SdrUndoManager::Redo(); + } + else + { + return impl_DoUndoRedo(UndoOrRedoType::Redo, /*nUndoOffset=*/0); + } +} + +void UndoManager::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("swUndoManager")); + SdrUndoManager::dumpAsXml(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_xUndoNodes")); + m_xUndoNodes->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +void UndoManager::EmptyActionsChanged() +{ + if (m_pDocShell) + { + m_pDocShell->Broadcast(SfxHint(SfxHintId::DocumentRepair)); + } +} + +/** 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()); + assert(pRepeatAction); + if (!pRepeatAction->CanRepeat(rContext)) + { + return false; + } + + OUString const comment(pRepeatAction->GetComment()); + OUString const rcomment(pRepeatAction->GetRepeatComment(rContext)); + SwUndoId nId; + if (auto const* const pSwAction = dynamic_cast(pRepeatAction)) + nId = pSwAction->GetId(); + else if (auto const* const pListAction = dynamic_cast(pRepeatAction)) + nId = static_cast(pListAction->GetId()); + else + return false; + if (DoesUndo()) + { + ViewShellId nViewShellId(-1); + if (m_pDocShell) + { + if (const SwView* pView = m_pDocShell->GetView()) + nViewShellId = pView->GetViewShellId(); + } + EnterListAction(comment, rcomment, static_cast(nId), nViewShellId); + } + + SwPaM* pTmp = rContext.m_pCurrentPaM; + for(SwPaM& rPaM : rContext.GetRepeatPaM().GetRingContainer()) + { // iterate over ring + rContext.m_pCurrentPaM = &rPaM; + if (DoesUndo() && & rPaM != pTmp) + { + m_isAddWithIgnoreRepeat = true; + } + for (sal_uInt16 nRptCnt = nRepeatCount; nRptCnt > 0; --nRptCnt) + { + pRepeatAction->Repeat(rContext); + } + if (DoesUndo() && & rPaM != pTmp) + { + m_isAddWithIgnoreRepeat = false; + } + rContext.m_bDeleteRepeated = false; // reset for next PaM + } + rContext.m_pCurrentPaM = pTmp; + + if (DoesUndo()) + { + LeaveListAction(); + } + return true; +} + +} // namespace sw + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/rolbck.cxx b/sw/source/core/undo/rolbck.cxx new file mode 100644 index 0000000000..426be22dcc --- /dev/null +++ b/sw/source/core/undo/rolbck.cxx @@ -0,0 +1,1525 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +OUString SwHistoryHint::GetDescription() const +{ + return OUString(); +} + +void SwHistoryHint::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwHistoryHint")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("symbol"), BAD_CAST(typeid(*this).name())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_eWhichId"), + BAD_CAST(OString::number(m_eWhichId).getStr())); + (void)xmlTextWriterEndElement(pWriter); +} + +SwHistorySetFormat::SwHistorySetFormat( const SfxPoolItem* pFormatHt, SwNodeOffset nNd ) + : SwHistoryHint( HSTRY_SETFMTHNT ) + , m_pAttr( pFormatHt->Clone() ) + , m_nNodeIndex( nNd ) +{ + switch ( m_pAttr->Which() ) + { + case RES_PAGEDESC: + static_cast(*m_pAttr).ChgDefinedIn( nullptr ); + break; + case RES_PARATR_DROP: + static_cast(*m_pAttr).ChgDefinedIn(nullptr); + break; + case RES_BOXATR_FORMULA: + { + // save formulas always in plain text + SwTableBoxFormula& rNew = static_cast(*m_pAttr); + if ( rNew.IsIntrnlName() ) + { + const SwTableBoxFormula& rOld = + *static_cast(pFormatHt); + const SwNode* pNd = rOld.GetNodeOfFormula(); + if ( pNd ) + { + const SwTableNode* pTableNode = pNd->FindTableNode(); + if (pTableNode) + { + auto pCpyTable = const_cast(&pTableNode->GetTable()); + pCpyTable->SwitchFormulasToExternalRepresentation(); + rNew.ChgDefinedIn(rOld.GetDefinedIn()); + rNew.ToRelBoxNm(pCpyTable); + } + } + } + rNew.ChgDefinedIn( nullptr ); + } + break; + } +} + +OUString SwHistorySetFormat::GetDescription() const +{ + OUString aResult; + + switch (m_pAttr->Which()) + { + case RES_BREAK: + switch (static_cast(*m_pAttr).GetBreak()) + { + case SvxBreak::PageBefore: + case SvxBreak::PageAfter: + case SvxBreak::PageBoth: + aResult = SwResId(STR_UNDO_PAGEBREAKS); + + break; + case SvxBreak::ColumnBefore: + case SvxBreak::ColumnAfter: + case SvxBreak::ColumnBoth: + aResult = SwResId(STR_UNDO_COLBRKS); + + break; + default: + break; + } + break; + default: + break; + } + + return aResult; +} + +void SwHistorySetFormat::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwHistorySetFormat")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nNodeIndex"), + BAD_CAST(OString::number(sal_Int32(m_nNodeIndex)).getStr())); + SwHistoryHint::dumpAsXml(pWriter); + + if (m_pAttr) + { + m_pAttr->dumpAsXml(pWriter); + } + + (void)xmlTextWriterEndElement(pWriter); +} + +void SwHistorySetFormat::SetInDoc( SwDoc* pDoc, bool bTmpSet ) +{ + SwNode * pNode = pDoc->GetNodes()[ m_nNodeIndex ]; + if ( pNode->IsContentNode() ) + { + static_cast(pNode)->SetAttr( *m_pAttr ); + } + else if ( pNode->IsTableNode() ) + { + static_cast(pNode)->GetTable().GetFrameFormat()->SetFormatAttr( + *m_pAttr ); + } + else if ( pNode->IsStartNode() && (SwTableBoxStartNode == + static_cast(pNode)->GetStartNodeType()) ) + { + SwTableNode* pTNd = pNode->FindTableNode(); + if ( pTNd ) + { + SwTableBox* pBox = pTNd->GetTable().GetTableBox( m_nNodeIndex ); + if (pBox) + { + pBox->ClaimFrameFormat()->SetFormatAttr( *m_pAttr ); + } + } + } + + if ( !bTmpSet ) + { + m_pAttr.reset(); + } +} + +SwHistorySetFormat::~SwHistorySetFormat() +{ +} + +SwHistoryResetFormat::SwHistoryResetFormat(const SfxPoolItem* pFormatHt, SwNodeOffset nNodeIdx) + : SwHistoryHint( HSTRY_RESETFMTHNT ) + , m_nNodeIndex( nNodeIdx ) + , m_nWhich( pFormatHt->Which() ) +{ +} + +void SwHistoryResetFormat::SetInDoc( SwDoc* pDoc, bool ) +{ + SwNode * pNode = pDoc->GetNodes()[ m_nNodeIndex ]; + if ( pNode->IsContentNode() ) + { + static_cast(pNode)->ResetAttr( m_nWhich ); + } + else if ( pNode->IsTableNode() ) + { + static_cast(pNode)->GetTable().GetFrameFormat()-> + ResetFormatAttr( m_nWhich ); + } +} + +SwHistorySetText::SwHistorySetText( SwTextAttr* pTextHt, SwNodeOffset nNodePos ) + : SwHistoryHint( HSTRY_SETTXTHNT ) + , m_nNodeIndex( nNodePos ) + , m_nStart( pTextHt->GetStart() ) + , m_nEnd( pTextHt->GetAnyEnd() ) + , m_bFormatIgnoreStart(pTextHt->IsFormatIgnoreStart()) + , m_bFormatIgnoreEnd (pTextHt->IsFormatIgnoreEnd ()) +{ + // Caution: the following attributes generate no format attributes: + // - NoLineBreak, NoHyphen, Inserted, Deleted + // These cases must be handled separately !!! + + // a little bit complicated but works: first assign a copy of the + // default value and afterwards the values from text attribute + if ( RES_TXTATR_CHARFMT == pTextHt->Which() ) + { + m_pAttr.reset( new SwFormatCharFormat( pTextHt->GetCharFormat().GetCharFormat() ) ); + } + else + { + m_pAttr.reset( pTextHt->GetAttr().Clone() ); + } +} + +SwHistorySetText::~SwHistorySetText() +{ +} + +void SwHistorySetText::SetInDoc( SwDoc* pDoc, bool ) +{ + if (!m_pAttr) + return; + + if ( RES_TXTATR_CHARFMT == m_pAttr->Which() ) + { + // ask the Doc if the CharFormat still exists + if (!pDoc->GetCharFormats()->ContainsFormat(static_cast(*m_pAttr).GetCharFormat())) + return; // do not set, format does not exist + } + + SwTextNode * pTextNd = pDoc->GetNodes()[ m_nNodeIndex ]->GetTextNode(); + OSL_ENSURE( pTextNd, "SwHistorySetText::SetInDoc: not a TextNode" ); + + if ( !pTextNd ) + return; + + SwTextAttr *const pAttr = pTextNd->InsertItem(*m_pAttr, m_nStart, m_nEnd, + SetAttrMode::NOTXTATRCHR | + SetAttrMode::NOHINTADJUST ); + // shouldn't be possible to hit any error/merging path from here + assert(pAttr); + if (m_bFormatIgnoreStart) + { + pAttr->SetFormatIgnoreStart(true); + } + if (m_bFormatIgnoreEnd) + { + pAttr->SetFormatIgnoreEnd(true); + } +} + +SwHistorySetTextField::SwHistorySetTextField( const SwTextField* pTextField, SwNodeOffset nNodePos ) + : SwHistoryHint( HSTRY_SETTXTFLDHNT ) + , m_pField( new SwFormatField( *pTextField->GetFormatField().GetField() ) ) +{ + // only copy if not Sys-FieldType + SwDoc& rDoc = pTextField->GetTextNode().GetDoc(); + + m_nFieldWhich = m_pField->GetField()->GetTyp()->Which(); + if (m_nFieldWhich == SwFieldIds::Database || + m_nFieldWhich == SwFieldIds::User || + m_nFieldWhich == SwFieldIds::SetExp || + m_nFieldWhich == SwFieldIds::Dde || + !rDoc.getIDocumentFieldsAccess().GetSysFieldType( m_nFieldWhich )) + { + m_pFieldType = m_pField->GetField()->GetTyp()->Copy(); + m_pField->GetField()->ChgTyp( m_pFieldType.get() ); // change field type + } + m_nNodeIndex = nNodePos; + m_nPos = pTextField->GetStart(); +} + +OUString SwHistorySetTextField::GetDescription() const +{ + return m_pField->GetField()->GetDescription(); +} + +SwHistorySetTextField::~SwHistorySetTextField() +{ +} + +void SwHistorySetTextField::SetInDoc( SwDoc* pDoc, bool ) +{ + if (!m_pField) + return; + + SwFieldType* pNewFieldType = m_pFieldType.get(); + if ( !pNewFieldType ) + { + pNewFieldType = pDoc->getIDocumentFieldsAccess().GetSysFieldType( m_nFieldWhich ); + } + else + { + // register type with the document + pNewFieldType = pDoc->getIDocumentFieldsAccess().InsertFieldType( *m_pFieldType ); + } + + m_pField->GetField()->ChgTyp( pNewFieldType ); // change field type + + SwTextNode * pTextNd = pDoc->GetNodes()[ m_nNodeIndex ]->GetTextNode(); + OSL_ENSURE( pTextNd, "SwHistorySetTextField: no TextNode" ); + + if ( pTextNd ) + { + pTextNd->InsertItem( *m_pField, m_nPos, m_nPos, + SetAttrMode::NOTXTATRCHR ); + } +} + +SwHistorySetRefMark::SwHistorySetRefMark( const SwTextRefMark* pTextHt, SwNodeOffset nNodePos ) + : SwHistoryHint( HSTRY_SETREFMARKHNT ) + , m_RefName( pTextHt->GetRefMark().GetRefName() ) + , m_nNodeIndex( nNodePos ) + , m_nStart( pTextHt->GetStart() ) + , m_nEnd( pTextHt->GetAnyEnd() ) +{ +} + +void SwHistorySetRefMark::SetInDoc( SwDoc* pDoc, bool ) +{ + SwTextNode * pTextNd = pDoc->GetNodes()[ m_nNodeIndex ]->GetTextNode(); + OSL_ENSURE( pTextNd, "SwHistorySetRefMark: no TextNode" ); + if ( !pTextNd ) + return; + + SwFormatRefMark aRefMark( m_RefName ); + + // if a reference mark without an end already exists here: must not insert! + if ( m_nStart != m_nEnd || + !pTextNd->GetTextAttrForCharAt( m_nStart, RES_TXTATR_REFMARK ) ) + { + pTextNd->InsertItem( aRefMark, m_nStart, m_nEnd, + SetAttrMode::NOTXTATRCHR ); + } +} + +SwHistorySetTOXMark::SwHistorySetTOXMark( const SwTextTOXMark* pTextHt, SwNodeOffset nNodePos ) + : SwHistoryHint( HSTRY_SETTOXMARKHNT ) + , m_TOXMark( pTextHt->GetTOXMark() ) + , m_TOXName( m_TOXMark.GetTOXType()->GetTypeName() ) + , m_eTOXTypes( m_TOXMark.GetTOXType()->GetType() ) + , m_nNodeIndex( nNodePos ) + , m_nStart( pTextHt->GetStart() ) + , m_nEnd( pTextHt->GetAnyEnd() ) +{ + static_cast(&m_TOXMark)->EndListeningAll(); +} + +SwTOXType* SwHistorySetTOXMark::GetSwTOXType(SwDoc& rDoc, TOXTypes eTOXTypes, const OUString& rTOXName) +{ + // search for respective TOX type + const sal_uInt16 nCnt = rDoc.GetTOXTypeCount(eTOXTypes); + SwTOXType* pToxType = nullptr; + for ( sal_uInt16 n = 0; n < nCnt; ++n ) + { + pToxType = const_cast(rDoc.GetTOXType(eTOXTypes, n)); + if (pToxType->GetTypeName() == rTOXName) + break; + pToxType = nullptr; + } + + if ( !pToxType ) // TOX type not found, create new + { + pToxType = const_cast( + rDoc.InsertTOXType(SwTOXType(rDoc, eTOXTypes, rTOXName))); + } + + return pToxType; +} + +void SwHistorySetTOXMark::SetInDoc( SwDoc* pDoc, bool ) +{ + SwTextNode * pTextNd = pDoc->GetNodes()[ m_nNodeIndex ]->GetTextNode(); + OSL_ENSURE( pTextNd, "SwHistorySetTOXMark: no TextNode" ); + if ( !pTextNd ) + return; + + SwTOXType* pToxType = GetSwTOXType(*pDoc, m_eTOXTypes, m_TOXName); + + SwTOXMark aNew( m_TOXMark ); + aNew.RegisterToTOXType( *pToxType ); + + pTextNd->InsertItem( aNew, m_nStart, m_nEnd, + SetAttrMode::NOTXTATRCHR ); +} + +bool SwHistorySetTOXMark::IsEqual( const SwTOXMark& rCmp ) const +{ + return m_TOXName == rCmp.GetTOXType()->GetTypeName() && + m_eTOXTypes == rCmp.GetTOXType()->GetType() && + m_TOXMark.GetAlternativeText() == rCmp.GetAlternativeText() && + ( (TOX_INDEX == m_eTOXTypes) + ? ( m_TOXMark.GetPrimaryKey() == rCmp.GetPrimaryKey() && + m_TOXMark.GetSecondaryKey() == rCmp.GetSecondaryKey() ) + : m_TOXMark.GetLevel() == rCmp.GetLevel() + ); +} + +SwHistoryResetText::SwHistoryResetText( sal_uInt16 nWhich, + sal_Int32 nAttrStart, sal_Int32 nAttrEnd, SwNodeOffset nNodePos ) + : SwHistoryHint( HSTRY_RESETTXTHNT ) + , m_nNodeIndex( nNodePos ), m_nStart( nAttrStart ), m_nEnd( nAttrEnd ) + , m_nAttr( nWhich ) +{ +} + +void SwHistoryResetText::SetInDoc( SwDoc* pDoc, bool ) +{ + SwTextNode * pTextNd = pDoc->GetNodes()[ m_nNodeIndex ]->GetTextNode(); + OSL_ENSURE( pTextNd, "SwHistoryResetText: no TextNode" ); + if ( pTextNd ) + { + pTextNd->DeleteAttributes( m_nAttr, m_nStart, m_nEnd ); + } +} + +SwHistorySetFootnote::SwHistorySetFootnote( SwTextFootnote* pTextFootnote, SwNodeOffset nNodePos ) + : SwHistoryHint( HSTRY_SETFTNHNT ) + , m_pUndo( new SwUndoSaveSection ) + , m_FootnoteNumber( pTextFootnote->GetFootnote().GetNumStr() ) + , m_nNodeIndex( nNodePos ) + , m_nStart( pTextFootnote->GetStart() ) + , m_bEndNote( pTextFootnote->GetFootnote().IsEndNote() ) +{ + OSL_ENSURE( pTextFootnote->GetStartNode(), + "SwHistorySetFootnote: Footnote without Section" ); + + // keep the old NodePos (because who knows what later will be saved/deleted + // in SaveSection) + SwDoc& rDoc = const_cast(pTextFootnote->GetTextNode().GetDoc()); + SwNode* pSaveNd = rDoc.GetNodes()[ m_nNodeIndex ]; + + // keep pointer to StartNode of FootnoteSection and reset its attribute for now + // (as a result, its/all Frames will be deleted automatically) + SwNodeIndex aSttIdx( *pTextFootnote->GetStartNode() ); + pTextFootnote->SetStartNode( nullptr, false ); + + m_pUndo->SaveSection( aSttIdx ); + m_nNodeIndex = pSaveNd->GetIndex(); +} + +SwHistorySetFootnote::SwHistorySetFootnote( const SwTextFootnote &rTextFootnote ) + : SwHistoryHint( HSTRY_SETFTNHNT ) + , m_FootnoteNumber( rTextFootnote.GetFootnote().GetNumStr() ) + , m_nNodeIndex( SwTextFootnote_GetIndex( (&rTextFootnote) ) ) + , m_nStart( rTextFootnote.GetStart() ) + , m_bEndNote( rTextFootnote.GetFootnote().IsEndNote() ) +{ + OSL_ENSURE( rTextFootnote.GetStartNode(), + "SwHistorySetFootnote: Footnote without Section" ); +} + +OUString SwHistorySetFootnote::GetDescription() const +{ + return SwResId(STR_FOOTNOTE); +} + +SwHistorySetFootnote::~SwHistorySetFootnote() +{ +} + +void SwHistorySetFootnote::SetInDoc( SwDoc* pDoc, bool ) +{ + SwTextNode * pTextNd = pDoc->GetNodes()[ m_nNodeIndex ]->GetTextNode(); + OSL_ENSURE( pTextNd, "SwHistorySetFootnote: no TextNode" ); + if ( !pTextNd ) + return; + + if (m_pUndo) + { + // set the footnote in the TextNode + SwFormatFootnote aTemp( m_bEndNote ); + SwFormatFootnote& rNew = const_cast( + pDoc->GetAttrPool().DirectPutItemInPool(aTemp) ); + if ( !m_FootnoteNumber.isEmpty() ) + { + rNew.SetNumStr( m_FootnoteNumber ); + } + SwTextFootnote* pTextFootnote = new SwTextFootnote( rNew, m_nStart ); + + // create the section of the Footnote + SwNodeIndex aIdx( *pTextNd ); + m_pUndo->RestoreSection( pDoc, &aIdx, SwFootnoteStartNode ); + pTextFootnote->SetStartNode( &aIdx ); + if ( m_pUndo->GetHistory() ) + { + // create frames only now + m_pUndo->GetHistory()->Rollback( pDoc ); + } + + pTextNd->InsertHint( pTextFootnote ); + } + else + { + SwTextFootnote * const pFootnote = + static_cast( + pTextNd->GetTextAttrForCharAt( m_nStart )); + assert(pFootnote); + SwFormatFootnote &rFootnote = const_cast(pFootnote->GetFootnote()); + rFootnote.SetNumStr( m_FootnoteNumber ); + if ( rFootnote.IsEndNote() != m_bEndNote ) + { + rFootnote.SetEndNote( m_bEndNote ); + pFootnote->CheckCondColl(); + } + } +} + +SwHistoryChangeFormatColl::SwHistoryChangeFormatColl( SwFormatColl* pFormatColl, SwNodeOffset nNd, + SwNodeType nNodeWhich ) + : SwHistoryHint( HSTRY_CHGFMTCOLL ) + , m_pColl( pFormatColl ) + , m_nNodeIndex( nNd ) + , m_nNodeType( nNodeWhich ) +{ +} + +void SwHistoryChangeFormatColl::SetInDoc( SwDoc* pDoc, bool ) +{ + SwContentNode * pContentNd = pDoc->GetNodes()[ m_nNodeIndex ]->GetContentNode(); + OSL_ENSURE( pContentNd, "SwHistoryChangeFormatColl: no ContentNode" ); + + // before setting the format, check if it is still available in the + // document. if it has been deleted, there is no undo! + if ( !(pContentNd && m_nNodeType == pContentNd->GetNodeType()) ) + return; + + if ( SwNodeType::Text == m_nNodeType ) + { + if (pDoc->GetTextFormatColls()->IsAlive(static_cast(m_pColl))) + { + pContentNd->ChgFormatColl( m_pColl ); + } + } + else if (pDoc->GetGrfFormatColls()->IsAlive(static_cast(m_pColl))) + { + pContentNd->ChgFormatColl( m_pColl ); + } +} + +SwHistoryTextFlyCnt::SwHistoryTextFlyCnt( SwFrameFormat* const pFlyFormat ) + : SwHistoryHint( HSTRY_FLYCNT ) + , m_pUndo( new SwUndoDelLayFormat( pFlyFormat ) ) +{ + OSL_ENSURE( pFlyFormat, "SwHistoryTextFlyCnt: no Format" ); + m_pUndo->ChgShowSel( false ); +} + +SwHistoryTextFlyCnt::~SwHistoryTextFlyCnt() +{ +} + +void SwHistoryTextFlyCnt::SetInDoc( SwDoc* pDoc, bool ) +{ + ::sw::IShellCursorSupplier *const pISCS(pDoc->GetIShellCursorSupplier()); + assert(pISCS); + ::sw::UndoRedoContext context(*pDoc, *pISCS); + m_pUndo->UndoImpl(context); +} + +void SwHistoryTextFlyCnt::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwHistoryTextFlyCnt")); + SwHistoryHint::dumpAsXml(pWriter); + + if (m_pUndo) + { + m_pUndo->dumpAsXml(pWriter); + } + + (void)xmlTextWriterEndElement(pWriter); +} + +SwHistoryBookmark::SwHistoryBookmark( + const ::sw::mark::IMark& rBkmk, + bool bSavePos, + bool bSaveOtherPos) + : SwHistoryHint(HSTRY_BOOKMARK) + , m_aName(rBkmk.GetName()) + , m_bHidden(false) + , m_nNode(bSavePos ? + rBkmk.GetMarkPos().GetNodeIndex() : SwNodeOffset(0)) + , m_nOtherNode(bSaveOtherPos ? + rBkmk.GetOtherMarkPos().GetNodeIndex() : SwNodeOffset(0)) + , m_nContent(bSavePos ? + rBkmk.GetMarkPos().GetContentIndex() : 0) + , m_nOtherContent(bSaveOtherPos ? + rBkmk.GetOtherMarkPos().GetContentIndex() :0) + , m_bSavePos(bSavePos) + , m_bSaveOtherPos(bSaveOtherPos) + , m_bHadOtherPos(rBkmk.IsExpanded()) + , m_eBkmkType(IDocumentMarkAccess::GetType(rBkmk)) +{ + const ::sw::mark::IBookmark* const pBookmark = dynamic_cast< const ::sw::mark::IBookmark* >(&rBkmk); + if(!pBookmark) + return; + + m_aKeycode = pBookmark->GetKeyCode(); + m_aShortName = pBookmark->GetShortName(); + m_bHidden = pBookmark->IsHidden(); + m_aHideCondition = pBookmark->GetHideCondition(); + + ::sfx2::Metadatable const*const pMetadatable( + dynamic_cast< ::sfx2::Metadatable const* >(pBookmark)); + if (pMetadatable) + { + m_pMetadataUndo = pMetadatable->CreateUndo(); + } +} + +void SwHistoryBookmark::SetInDoc( SwDoc* pDoc, bool ) +{ + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + SwNodes& rNds = pDoc->GetNodes(); + IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess(); + std::optional oPam; + ::sw::mark::IMark* pMark = nullptr; + + // now the situation is that m_bSavePos and m_bSaveOtherPos don't determine + // whether the mark was deleted + if (auto const iter = pMarkAccess->findMark(m_aName); iter != pMarkAccess->getAllMarksEnd()) + { + pMark = *iter; + } + if(m_bSavePos) + { + SwContentNode* const pContentNd = rNds[m_nNode]->GetContentNode(); + assert(pContentNd); + oPam.emplace(*pContentNd, m_nContent); + } + else + { + assert(pMark); + oPam.emplace(pMark->GetMarkPos()); + } + assert(oPam); + + if(m_bSaveOtherPos) + { + SwContentNode* const pContentNd = rNds[m_nOtherNode]->GetContentNode(); + assert(pContentNd); + oPam->SetMark(); + oPam->GetMark()->Assign(*pContentNd, m_nOtherContent); + } + else if(m_bHadOtherPos) + { + assert(pMark); + assert(pMark->IsExpanded()); + oPam->SetMark(); + *oPam->GetMark() = pMark->GetOtherMarkPos(); + } + + if ( pMark != nullptr ) + { + pMarkAccess->deleteMark( pMark ); + } + ::sw::mark::IBookmark* const pBookmark = + dynamic_cast<::sw::mark::IBookmark*>( + pMarkAccess->makeMark(*oPam, m_aName, m_eBkmkType, sw::mark::InsertMode::New)); + if ( pBookmark == nullptr ) + return; + + pBookmark->SetKeyCode(m_aKeycode); + pBookmark->SetShortName(m_aShortName); + pBookmark->Hide(m_bHidden); + pBookmark->SetHideCondition(m_aHideCondition); + + if (m_pMetadataUndo) + { + ::sfx2::Metadatable * const pMeta( + dynamic_cast< ::sfx2::Metadatable* >(pBookmark)); + OSL_ENSURE(pMeta, "metadata undo, but not metadatable?"); + if (pMeta) + { + pMeta->RestoreMetadata(m_pMetadataUndo); + } + } +} + +bool SwHistoryBookmark::IsEqualBookmark(const ::sw::mark::IMark& rBkmk) +{ + return m_nNode == rBkmk.GetMarkPos().GetNodeIndex() + && m_nContent == rBkmk.GetMarkPos().GetContentIndex() + && m_aName == rBkmk.GetName(); +} + +SwHistoryNoTextFieldmark::SwHistoryNoTextFieldmark(const ::sw::mark::IFieldmark& rFieldMark) + : SwHistoryHint(HSTRY_NOTEXTFIELDMARK) + , m_sType(rFieldMark.GetFieldname()) + , m_nNode(rFieldMark.GetMarkStart().GetNodeIndex()) + , m_nContent(rFieldMark.GetMarkStart().GetContentIndex()) +{ +} + +void SwHistoryNoTextFieldmark::SetInDoc(SwDoc* pDoc, bool) +{ + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + SwNodes& rNds = pDoc->GetNodes(); + std::optional pPam; + + const SwContentNode* pContentNd = rNds[m_nNode]->GetContentNode(); + if(pContentNd) + pPam.emplace(*pContentNd, m_nContent); + + if (pPam) + { + IDocumentMarkAccess* pMarkAccess = pDoc->getIDocumentMarkAccess(); + pMarkAccess->makeNoTextFieldBookmark(*pPam, OUString(), m_sType); + } +} + +void SwHistoryNoTextFieldmark::ResetInDoc(SwDoc& rDoc) +{ + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + + SwNodes& rNds = rDoc.GetNodes(); + std::optional pPam; + + const SwContentNode* pContentNd = rNds[m_nNode]->GetContentNode(); + assert(pContentNd); + pPam.emplace(*pContentNd, m_nContent); + + if (pPam) + { + IDocumentMarkAccess* pMarkAccess = rDoc.getIDocumentMarkAccess(); + pMarkAccess->deleteFieldmarkAt(*pPam->GetPoint()); + } +} + +SwHistoryTextFieldmark::SwHistoryTextFieldmark(const ::sw::mark::IFieldmark& rFieldMark) + : SwHistoryHint(HSTRY_TEXTFIELDMARK) + , m_sName(rFieldMark.GetName()) + , m_sType(rFieldMark.GetFieldname()) + , m_nStartNode(rFieldMark.GetMarkStart().GetNodeIndex()) + , m_nStartContent(rFieldMark.GetMarkStart().GetContentIndex()) + , m_nEndNode(rFieldMark.GetMarkEnd().GetNodeIndex()) + , m_nEndContent(rFieldMark.GetMarkEnd().GetContentIndex()) +{ + SwPosition const sepPos(sw::mark::FindFieldSep(rFieldMark)); + m_nSepNode = sepPos.GetNodeIndex(); + m_nSepContent = sepPos.GetContentIndex(); +} + +void SwHistoryTextFieldmark::SetInDoc(SwDoc* pDoc, bool) +{ + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + SwNodes& rNds = pDoc->GetNodes(); + + assert(rNds[m_nStartNode]->IsContentNode()); + assert(rNds[m_nEndNode]->IsContentNode()); + assert(rNds[m_nSepNode]->IsContentNode()); + + SwPaM const pam(*rNds[m_nStartNode]->GetContentNode(), m_nStartContent, + *rNds[m_nEndNode]->GetContentNode(), + // subtract 1 for the CH_TXT_ATR_FIELDEND itself, + // plus more if same node as other CH_TXT_ATR + m_nStartNode == m_nEndNode + ? (m_nEndContent - 3) + : m_nSepNode == m_nEndNode + ? (m_nEndContent - 2) + : (m_nEndContent - 1)); + SwPosition const sepPos(*rNds[m_nSepNode]->GetContentNode(), + m_nStartNode == m_nSepNode ? (m_nSepContent - 1) : m_nSepContent); + + IDocumentMarkAccess & rMarksAccess(*pDoc->getIDocumentMarkAccess()); + rMarksAccess.makeFieldBookmark(pam, m_sName, m_sType, &sepPos); +} + +void SwHistoryTextFieldmark::ResetInDoc(SwDoc& rDoc) +{ + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + + SwNodes& rNds = rDoc.GetNodes(); + + assert(rNds[m_nStartNode]->IsContentNode()); + assert(rNds[m_nEndNode]->IsContentNode()); + assert(rNds[m_nSepNode]->IsContentNode()); + + SwPosition const pos(*rNds[m_nStartNode]->GetContentNode(), m_nStartContent); + + IDocumentMarkAccess & rMarksAccess(*rDoc.getIDocumentMarkAccess()); + rMarksAccess.deleteFieldmarkAt(pos); +} + +SwHistorySetAttrSet::SwHistorySetAttrSet( const SfxItemSet& rSet, + SwNodeOffset nNodePos, const o3tl::sorted_vector &rSetArr ) + : SwHistoryHint( HSTRY_SETATTRSET ) + , m_OldSet( rSet ) + , m_ResetArray( 0, 4 ) + , m_nNodeIndex( nNodePos ) +{ + SfxItemIter aIter( m_OldSet ), aOrigIter( rSet ); + const SfxPoolItem* pItem = aIter.GetCurItem(), + * pOrigItem = aOrigIter.GetCurItem(); + while (pItem && pOrigItem) + { + if( !rSetArr.count( pOrigItem->Which() )) + { + m_ResetArray.push_back( pOrigItem->Which() ); + m_OldSet.ClearItem( pOrigItem->Which() ); + } + else + { + switch ( pItem->Which() ) + { + case RES_PAGEDESC: + static_cast( + const_cast(pItem))->ChgDefinedIn( nullptr ); + break; + + case RES_PARATR_DROP: + static_cast( + const_cast(pItem))->ChgDefinedIn(nullptr); + break; + + case RES_BOXATR_FORMULA: + { + // When a formula is set, never save the value. It + // possibly must be recalculated! + // Save formulas always in plain text + m_OldSet.ClearItem( RES_BOXATR_VALUE ); + + SwTableBoxFormula& rNew = + *static_cast( + const_cast(pItem)); + if ( rNew.IsIntrnlName() ) + { + const SwTableBoxFormula& rOld = + rSet.Get( RES_BOXATR_FORMULA ); + const SwNode* pNd = rOld.GetNodeOfFormula(); + if ( pNd ) + { + const SwTableNode* pTableNode + = pNd->FindTableNode(); + if(pTableNode) + { + auto pCpyTable = const_cast(&pTableNode->GetTable()); + pCpyTable->SwitchFormulasToExternalRepresentation(); + rNew.ChgDefinedIn(rOld.GetDefinedIn()); + rNew.PtrToBoxNm(pCpyTable); + } + } + } + rNew.ChgDefinedIn( nullptr ); + } + break; + } + } + + pItem = aIter.NextItem(); + pOrigItem = aOrigIter.NextItem(); + } +} + +void SwHistorySetAttrSet::SetInDoc( SwDoc* pDoc, bool ) +{ + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + SwNode * pNode = pDoc->GetNodes()[ m_nNodeIndex ]; + if ( pNode->IsContentNode() ) + { + static_cast(pNode)->SetAttr( m_OldSet ); + if ( !m_ResetArray.empty() ) + { + static_cast(pNode)->ResetAttr( m_ResetArray ); + } + } + else if ( pNode->IsTableNode() ) + { + SwFormat& rFormat = + *static_cast(pNode)->GetTable().GetFrameFormat(); + rFormat.SetFormatAttr( m_OldSet ); + if ( !m_ResetArray.empty() ) + { + rFormat.ResetFormatAttr( m_ResetArray.front() ); + } + } +} + +SwHistoryChangeFlyAnchor::SwHistoryChangeFlyAnchor(sw::SpzFrameFormat& rFormat) + : SwHistoryHint( HSTRY_CHGFLYANCHOR ) + , m_rFormat(rFormat) + , m_nOldNodeIndex( rFormat.GetAnchor().GetAnchorNode()->GetIndex() ) + , m_nOldContentIndex( (RndStdIds::FLY_AT_CHAR == rFormat.GetAnchor().GetAnchorId()) + ? rFormat.GetAnchor().GetAnchorContentOffset() + : COMPLETE_STRING ) +{ +} + +void SwHistoryChangeFlyAnchor::SetInDoc( SwDoc* pDoc, bool ) +{ + ::sw::UndoGuard const undoGuard(pDoc->GetIDocumentUndoRedo()); + + // One would expect m_rFormat to only contain FlyFormats, given the name of + // this class, but apparently it is also used for DrawFormats. + if (!pDoc->GetSpzFrameFormats()->IsAlive(&m_rFormat)) // Format does still exist + return; + + SwFormatAnchor aTmp( m_rFormat.GetAnchor() ); + + SwNode* pNd = pDoc->GetNodes()[ m_nOldNodeIndex ]; + SwContentNode* pCNd = pNd->GetContentNode(); + SwPosition aPos( *pNd ); + if ( COMPLETE_STRING != m_nOldContentIndex ) + aPos.SetContent( m_nOldContentIndex ); + aTmp.SetAnchor( &aPos ); + + // so the Layout does not get confused + if (!pCNd->getLayoutFrame(pDoc->getIDocumentLayoutAccess().GetCurrentLayout(), nullptr, nullptr)) + { + m_rFormat.DelFrames(); + } + + m_rFormat.SetFormatAttr( aTmp ); +} + +SwHistoryChangeFlyChain::SwHistoryChangeFlyChain( SwFlyFrameFormat& rFormat, + const SwFormatChain& rAttr ) + : SwHistoryHint( HSTRY_CHGFLYCHAIN ) + , m_pPrevFormat( rAttr.GetPrev() ) + , m_pNextFormat( rAttr.GetNext() ) + , m_pFlyFormat( &rFormat ) +{ +} + +void SwHistoryChangeFlyChain::SetInDoc( SwDoc* pDoc, bool ) +{ + if (!pDoc->GetSpzFrameFormats()->IsAlive(m_pFlyFormat)) + return; + + SwFormatChain aChain; + + if (m_pPrevFormat && + pDoc->GetSpzFrameFormats()->IsAlive(m_pPrevFormat)) + { + aChain.SetPrev( m_pPrevFormat ); + SwFormatChain aTmp( m_pPrevFormat->GetChain() ); + aTmp.SetNext( m_pFlyFormat ); + m_pPrevFormat->SetFormatAttr( aTmp ); + } + + if (m_pNextFormat && + pDoc->GetSpzFrameFormats()->IsAlive(m_pNextFormat)) + { + aChain.SetNext( m_pNextFormat ); + SwFormatChain aTmp( m_pNextFormat->GetChain() ); + aTmp.SetPrev( m_pFlyFormat ); + m_pNextFormat->SetFormatAttr( aTmp ); + } + + if ( aChain.GetNext() || aChain.GetPrev() ) + { + m_pFlyFormat->SetFormatAttr( aChain ); + } +} + +// -> #i27615# +SwHistoryChangeCharFormat::SwHistoryChangeCharFormat(SfxItemSet aSet, + OUString sFormat) + : SwHistoryHint(HSTRY_CHGCHARFMT) + , m_OldSet(std::move(aSet)), m_Format(std::move(sFormat)) +{ +} + +void SwHistoryChangeCharFormat::SetInDoc(SwDoc * pDoc, bool ) +{ + SwCharFormat * pCharFormat = pDoc->FindCharFormatByName(m_Format); + + if (pCharFormat) + { + pCharFormat->SetFormatAttr(m_OldSet); + } +} +// <- #i27615# + +SwHistory::SwHistory() + : m_nEndDiff( 0 ) +{ +} + +SwHistory::~SwHistory() +{ +} + +void SwHistory::AddPoolItem( + const SfxPoolItem* pOldValue, + const SfxPoolItem* pNewValue, + SwNodeOffset nNodeIdx) +{ + OSL_ENSURE( !m_nEndDiff, "History was not deleted after REDO" ); + const sal_uInt16 nWhich(pNewValue->Which()); + + // excluded values + if(nWhich == RES_TXTATR_FIELD || nWhich == RES_TXTATR_ANNOTATION) + { + return; + } + + // no default Attribute? + std::unique_ptr pHt; + + // To be able to include the DrawingLayer FillItems something more + // general has to be done to check if an Item is default than to check + // if its pointer equals that in Writer's global PoolDefaults (held in + // aAttrTab and used to fill the pool defaults in Writer - looks as if + // Writer is *older* than the SfxItemPool ?). I checked the possibility to + // get the SfxItemPool here (works), but decided to use the SfxPoolItem's + // global tooling aka IsDefaultItem(const SfxPoolItem*) for now + if(pOldValue && !IsDefaultItem(pOldValue)) + { + pHt.reset( new SwHistorySetFormat( pOldValue, nNodeIdx ) ); + } + else + { + pHt.reset( new SwHistoryResetFormat( pNewValue, nNodeIdx ) ); + } + + m_SwpHstry.push_back( std::move(pHt) ); +} + +// FIXME: refactor the following "Add" methods (DRY)? +void SwHistory::AddTextAttr(SwTextAttr *const pHint, + SwNodeOffset const nNodeIdx, bool const bNewAttr) +{ + OSL_ENSURE( !m_nEndDiff, "History was not deleted after REDO" ); + + std::unique_ptr pHt; + if( !bNewAttr ) + { + switch ( pHint->Which() ) + { + case RES_TXTATR_FTN: + pHt.reset( new SwHistorySetFootnote( + static_cast(pHint), nNodeIdx ) ); + break; + case RES_TXTATR_FLYCNT: + pHt.reset( new SwHistoryTextFlyCnt( static_cast(pHint) + ->GetFlyCnt().GetFrameFormat() ) ); + break; + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + pHt.reset( new SwHistorySetTextField( + static_txtattr_cast(pHint), nNodeIdx) ); + break; + case RES_TXTATR_TOXMARK: + pHt.reset( new SwHistorySetTOXMark( + static_txtattr_cast(pHint), nNodeIdx) ); + break; + case RES_TXTATR_REFMARK: + pHt.reset( new SwHistorySetRefMark( + static_txtattr_cast(pHint), nNodeIdx) ); + break; + default: + pHt.reset( new SwHistorySetText( pHint, nNodeIdx ) ); + } + } + else + { + pHt.reset( new SwHistoryResetText( pHint->Which(), pHint->GetStart(), + pHint->GetAnyEnd(), nNodeIdx ) ); + } + m_SwpHstry.push_back( std::move(pHt) ); +} + +void SwHistory::AddColl(SwFormatColl *const pColl, SwNodeOffset const nNodeIdx, + SwNodeType const nWhichNd) +{ + OSL_ENSURE( !m_nEndDiff, "History was not deleted after REDO" ); + + std::unique_ptr pHt( + new SwHistoryChangeFormatColl( pColl, nNodeIdx, nWhichNd )); + m_SwpHstry.push_back( std::move(pHt) ); +} + +void SwHistory::AddIMark(const ::sw::mark::IMark& rBkmk, + bool const bSavePos, bool const bSaveOtherPos) +{ + OSL_ENSURE( !m_nEndDiff, "History was not deleted after REDO" ); + + std::unique_ptr pHt; + + switch (IDocumentMarkAccess::GetType(rBkmk)) + { + case IDocumentMarkAccess::MarkType::TEXT_FIELDMARK: + case IDocumentMarkAccess::MarkType::DATE_FIELDMARK: + assert(bSavePos && bSaveOtherPos); // must be deleted completely! + pHt.reset(new SwHistoryTextFieldmark(dynamic_cast(rBkmk))); + break; + case IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK: + case IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK: + assert(bSavePos && bSaveOtherPos); // must be deleted completely! + pHt.reset(new SwHistoryNoTextFieldmark(dynamic_cast(rBkmk))); + break; + default: + pHt.reset(new SwHistoryBookmark(rBkmk, bSavePos, bSaveOtherPos)); + break; + } + + assert(pHt); + m_SwpHstry.push_back( std::move(pHt) ); +} + +void SwHistory::AddChangeFlyAnchor(sw::SpzFrameFormat& rFormat) +{ + std::unique_ptr pHt(new SwHistoryChangeFlyAnchor(rFormat)); + m_SwpHstry.push_back( std::move(pHt) ); +} + +void SwHistory::AddDeleteFly(SwFrameFormat& rFormat, sal_uInt16& rSetPos) +{ + OSL_ENSURE( !m_nEndDiff, "History was not deleted after REDO" ); + + const sal_uInt16 nWh = rFormat.Which(); + (void) nWh; + // only Flys! + assert((RES_FLYFRMFMT == nWh && dynamic_cast(&rFormat)) + || (RES_DRAWFRMFMT == nWh && dynamic_cast(&rFormat))); + { + std::unique_ptr pHint(new SwHistoryTextFlyCnt( &rFormat )); + m_SwpHstry.push_back( std::move(pHint) ); + + if( const SwFormatChain* pChainItem = rFormat.GetItemIfSet( RES_CHAIN, false ) ) + { + assert(RES_FLYFRMFMT == nWh); // not supported on SdrObjects + if( pChainItem->GetNext() || pChainItem->GetPrev() ) + { + std::unique_ptr pHt( + new SwHistoryChangeFlyChain(static_cast(rFormat), *pChainItem)); + m_SwpHstry.insert( m_SwpHstry.begin() + rSetPos++, std::move(pHt) ); + if ( pChainItem->GetNext() ) + { + SwFormatChain aTmp( pChainItem->GetNext()->GetChain() ); + aTmp.SetPrev( nullptr ); + pChainItem->GetNext()->SetFormatAttr( aTmp ); + } + if ( pChainItem->GetPrev() ) + { + SwFormatChain aTmp( pChainItem->GetPrev()->GetChain() ); + aTmp.SetNext( nullptr ); + pChainItem->GetPrev()->SetFormatAttr( aTmp ); + } + } + rFormat.ResetFormatAttr( RES_CHAIN ); + } + } +} + +void SwHistory::AddFootnote(const SwTextFootnote& rFootnote) +{ + std::unique_ptr pHt(new SwHistorySetFootnote( rFootnote )); + m_SwpHstry.push_back( std::move(pHt) ); +} + +// #i27615# +void SwHistory::AddCharFormat(const SfxItemSet & rSet, const SwCharFormat & rFormat) +{ + std::unique_ptr pHt(new SwHistoryChangeCharFormat(rSet, rFormat.GetName())); + m_SwpHstry.push_back( std::move(pHt) ); +} + +bool SwHistory::Rollback( SwDoc* pDoc, sal_uInt16 nStart ) +{ + if ( !Count() ) + return false; + + for ( sal_uInt16 i = Count(); i > nStart ; ) + { + SwHistoryHint * pHHt = m_SwpHstry[ --i ].get(); + pHHt->SetInDoc( pDoc, false ); + } + m_SwpHstry.erase( m_SwpHstry.begin() + nStart, m_SwpHstry.end() ); + m_nEndDiff = 0; + return true; +} + +bool SwHistory::TmpRollback( SwDoc* pDoc, sal_uInt16 nStart, bool bToFirst ) +{ + sal_uInt16 nEnd = Count() - m_nEndDiff; + if ( !Count() || !nEnd || nStart >= nEnd ) + return false; + + if ( bToFirst ) + { + for ( ; nEnd > nStart; ++m_nEndDiff ) + { + SwHistoryHint* pHHt = m_SwpHstry[ --nEnd ].get(); + pHHt->SetInDoc( pDoc, true ); + } + } + else + { + for ( ; nStart < nEnd; ++m_nEndDiff, ++nStart ) + { + SwHistoryHint* pHHt = m_SwpHstry[ nStart ].get(); + pHHt->SetInDoc( pDoc, true ); + } + } + return true; +} + +sal_uInt16 SwHistory::SetTmpEnd( sal_uInt16 nNewTmpEnd ) +{ + OSL_ENSURE( nNewTmpEnd <= Count(), "SwHistory::SetTmpEnd: out of bounds" ); + + const sal_uInt16 nOld = Count() - m_nEndDiff; + m_nEndDiff = Count() - nNewTmpEnd; + + // for every SwHistoryFlyCnt, call the Redo of its UndoObject. + // this saves the formats of the flys! + for ( sal_uInt16 n = nOld; n < nNewTmpEnd; n++ ) + { + if ( HSTRY_FLYCNT == (*this)[ n ]->Which() ) + { + static_cast((*this)[ n ]) + ->GetUDelLFormat()->RedoForRollback(); + } + } + + return nOld; +} + +void SwHistory::CopyFormatAttr( + const SfxItemSet& rSet, + SwNodeOffset nNodeIdx) +{ + if(!rSet.Count()) + return; + + SfxItemIter aIter(rSet); + const SfxPoolItem* pItem = aIter.GetCurItem(); + do + { + if(!IsInvalidItem(pItem)) + { + AddPoolItem(pItem, pItem, nNodeIdx); + } + + pItem = aIter.NextItem(); + + } while(pItem); +} + +void SwHistory::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwHistory")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_SwpHstry")); + for (const auto& pHistory : m_SwpHstry) + { + pHistory->dumpAsXml(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +void SwHistory::CopyAttr( + SwpHints const * pHts, + const SwNodeOffset nNodeIdx, + const sal_Int32 nStart, + const sal_Int32 nEnd, + const bool bCopyFields ) +{ + if( !pHts ) + return; + + // copy all attributes of the TextNode in the area from nStart to nEnd + SwTextAttr* pHt; + for( size_t n = 0; n < pHts->Count(); ++n ) + { + // nAttrStt must even be set when !pEndIdx + pHt = pHts->Get(n); + const sal_Int32 nAttrStt = pHt->GetStart(); + const sal_Int32 * pEndIdx = pHt->GetEnd(); + if( nullptr != pEndIdx && nAttrStt > nEnd ) + break; + + // never copy Flys and Footnote !! + bool bNextAttr = false; + switch( pHt->Which() ) + { + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + case RES_TXTATR_INPUTFIELD: + if( !bCopyFields ) + bNextAttr = true; + break; + case RES_TXTATR_FLYCNT: + case RES_TXTATR_FTN: + bNextAttr = true; + break; + } + + if( bNextAttr ) + continue; + + // save all attributes that are somehow in this area + if ( nStart <= nAttrStt ) + { + if ( nEnd > nAttrStt ) + { + AddTextAttr(pHt, nNodeIdx, false); + } + } + else if ( pEndIdx && nStart < *pEndIdx ) + { + AddTextAttr(pHt, nNodeIdx, false); + } + } +} + +// Class to register the history at a Node, Format, HintsArray, ... +SwRegHistory::SwRegHistory( SwHistory* pHst ) + : SwClient( nullptr ) + , m_pHistory( pHst ) + , m_nNodeIndex( NODE_OFFSET_MAX ) +{ + MakeSetWhichIds(); +} + +SwRegHistory::SwRegHistory( sw::BroadcastingModify* pRegIn, const SwNode& rNd, + SwHistory* pHst ) + : SwClient( pRegIn ) + , m_pHistory( pHst ) + , m_nNodeIndex( rNd.GetIndex() ) +{ + MakeSetWhichIds(); +} + +SwRegHistory::SwRegHistory( const SwNode& rNd, SwHistory* pHst ) + : SwClient( nullptr ) + , m_pHistory( pHst ) + , m_nNodeIndex( rNd.GetIndex() ) +{ + MakeSetWhichIds(); +} + +void SwRegHistory::SwClientNotify(const SwModify&, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacyHint = static_cast(&rHint); + if ( !(m_pHistory && pLegacyHint->m_pNew && !areSfxPoolItemPtrsEqual(pLegacyHint->m_pOld, pLegacyHint->m_pNew) ) ) + return; + + if ( pLegacyHint->m_pNew->Which() < POOLATTR_END ) + { + if(RES_UPDATE_ATTR == pLegacyHint->m_pNew->Which()) + { + m_pHistory->AddPoolItem(pLegacyHint->m_pOld, pLegacyHint->m_pNew, m_nNodeIndex); + } + else + { + OSL_ENSURE(false, "Unexpected update attribute (!)"); + } + } + else if (pLegacyHint->m_pOld && RES_ATTRSET_CHG == pLegacyHint->m_pNew->Which()) + { + std::unique_ptr pNewHstr; + const SfxItemSet& rSet = *static_cast< const SwAttrSetChg* >(pLegacyHint->m_pOld)->GetChgSet(); + + if ( 1 < rSet.Count() ) + { + pNewHstr.reset( new SwHistorySetAttrSet( rSet, m_nNodeIndex, m_WhichIdSet ) ); + } + else if (const SfxPoolItem* pItem = SfxItemIter(rSet).GetCurItem()) + { + if ( m_WhichIdSet.count( pItem->Which() ) ) + { + pNewHstr.reset( new SwHistorySetFormat( pItem, m_nNodeIndex ) ); + } + else + { + pNewHstr.reset( new SwHistoryResetFormat( pItem, m_nNodeIndex ) ); + } + } + + if (pNewHstr) + m_pHistory->m_SwpHstry.push_back( std::move(pNewHstr) ); + } +} + +void SwRegHistory::AddHint( SwTextAttr* pHt, const bool bNew ) +{ + m_pHistory->AddTextAttr(pHt, m_nNodeIndex, bNew); +} + +bool SwRegHistory::InsertItems( const SfxItemSet& rSet, + sal_Int32 const nStart, sal_Int32 const nEnd, SetAttrMode const nFlags, + SwTextAttr **ppNewTextAttr ) +{ + if( !rSet.Count() ) + return false; + + SwTextNode * const pTextNode = + dynamic_cast(GetRegisteredIn()); + + OSL_ENSURE(pTextNode, "SwRegHistory not registered at text node?"); + if (!pTextNode) + return false; + + if (m_pHistory) + { + pTextNode->GetOrCreateSwpHints().Register(this); + } + + const bool bInserted = pTextNode->SetAttr( rSet, nStart, nEnd, nFlags, ppNewTextAttr ); + + // Caution: The array can be deleted when inserting an attribute! + // This can happen when the value that should be added first deletes + // an existing attribute but does not need to be added itself because + // the paragraph attributes are identical + // ( -> bForgetAttr in SwpHints::Insert ) + if ( pTextNode->GetpSwpHints() && m_pHistory ) + { + pTextNode->GetpSwpHints()->DeRegister(); + } + +#ifndef NDEBUG + if ( m_pHistory && bInserted ) + { + SfxItemIter aIter(rSet); + for (SfxPoolItem const* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { // check that the history recorded a hint to reset every item + sal_uInt16 const nWhich(pItem->Which()); + sal_uInt16 const nExpected( + (isCHRATR(nWhich) || RES_TXTATR_UNKNOWN_CONTAINER == nWhich) + ? RES_TXTATR_AUTOFMT + : nWhich); + if (RES_TXTATR_AUTOFMT == nExpected) + continue; // special case, may get set on text node itself + // tdf#105077 even worse, node's set could cause + // nothing at all to be inserted + assert(std::any_of( + m_pHistory->m_SwpHstry.begin(), m_pHistory->m_SwpHstry.end(), + [nExpected](std::unique_ptr const& pHint) -> bool { + SwHistoryResetText const*const pReset( + dynamic_cast(pHint.get())); + return pReset && (pReset->GetWhich() == nExpected); + })); + } + } +#endif + + return bInserted; +} + +void SwRegHistory::RegisterInModify( sw::BroadcastingModify* pRegIn, const SwNode& rNd ) +{ + if ( m_pHistory && pRegIn ) + { + pRegIn->Add( this ); + m_nNodeIndex = rNd.GetIndex(); + MakeSetWhichIds(); + } + else + { + m_WhichIdSet.clear(); + } +} + +void SwRegHistory::MakeSetWhichIds() +{ + if (!m_pHistory) return; + + m_WhichIdSet.clear(); + + if( !GetRegisteredIn() ) + return; + + const SfxItemSet* pSet = nullptr; + if( auto pContentNode = dynamic_cast< const SwContentNode *>( GetRegisteredIn() ) ) + { + pSet = pContentNode->GetpSwAttrSet(); + } + else if ( auto pSwFormat = dynamic_cast< const SwFormat *>( GetRegisteredIn() ) ) + { + pSet = &pSwFormat->GetAttrSet(); + } + if( pSet && pSet->Count() ) + { + SfxItemIter aIter( *pSet ); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + sal_uInt16 nW = pItem->Which(); + m_WhichIdSet.insert( nW ); + } + } +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unattr.cxx b/sw/source/core/undo/unattr.cxx new file mode 100644 index 0000000000..7e694e21b7 --- /dev/null +++ b/sw/source/core/undo/unattr.cxx @@ -0,0 +1,1155 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SwUndoFormatAttrHelper::SwUndoFormatAttrHelper(SwFormat& rFormat, bool bSvDrwPt) + : SwClient(&rFormat) + , m_rFormat(rFormat) + , m_bSaveDrawPt(bSvDrwPt) +{ +} + +void SwUndoFormatAttrHelper::SwClientNotify(const SwModify&, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast(&rHint); + if(!pLegacy->m_pOld) + return; + assert(pLegacy->m_pOld->Which() != RES_OBJECTDYING); + if(!pLegacy->m_pNew) + return; + const SwDoc& rDoc = *m_rFormat.GetDoc(); + auto pOld = pLegacy->m_pOld; + if(POOLATTR_END >= pLegacy->m_pOld->Which()) { + if(!GetUndo()) + m_pUndo.reset(new SwUndoFormatAttr(*pOld, m_rFormat, m_bSaveDrawPt)); + else + m_pUndo->PutAttr(*pOld, rDoc); + } else if(RES_ATTRSET_CHG == pOld->Which()) { + auto& rChgSet = *static_cast(pOld)->GetChgSet(); + if(!GetUndo()) + m_pUndo.reset(new SwUndoFormatAttr(SfxItemSet(rChgSet), m_rFormat, m_bSaveDrawPt)); + else { + SfxItemIter aIter(rChgSet); + for(auto pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + m_pUndo->PutAttr(*pItem, rDoc); + } + } +} + +SwDocModifyAndUndoGuard::SwDocModifyAndUndoGuard(SwFormat& format) + : doc(format.GetName().isEmpty() ? nullptr : format.GetDoc()) + , helper(doc ? new SwUndoFormatAttrHelper(format) : nullptr) +{ +} + +SwDocModifyAndUndoGuard::~SwDocModifyAndUndoGuard() +{ + if (helper && helper->GetUndo()) + { + // helper tracks changes, even when DoesUndo is false, to detect modified state + if (doc->GetIDocumentUndoRedo().DoesUndo()) + doc->GetIDocumentUndoRedo().AppendUndo(helper->ReleaseUndo()); + + doc->getIDocumentState().SetModified(); + } +} + +SwUndoFormatAttr::SwUndoFormatAttr( SfxItemSet&& rOldSet, + SwFormat& rChgFormat, + bool bSaveDrawPt ) + : SwUndo( SwUndoId::INSFMTATTR, rChgFormat.GetDoc() ) + , m_sFormatName ( rChgFormat.GetName() ) + // #i56253# + , m_oOldSet( std::move( rOldSet ) ) + , m_nAnchorContentOffset( 0 ) + , m_nNodeIndex( 0 ) + , m_nFormatWhich( rChgFormat.Which() ) + , m_bSaveDrawPt( bSaveDrawPt ) +{ + assert(m_sFormatName.getLength()); + + Init( rChgFormat ); +} + +SwUndoFormatAttr::SwUndoFormatAttr( const SfxPoolItem& rItem, SwFormat& rChgFormat, + bool bSaveDrawPt ) + : SwUndo( SwUndoId::INSFMTATTR, rChgFormat.GetDoc() ) + , m_sFormatName(rChgFormat.GetName()) + , m_oOldSet( rChgFormat.GetAttrSet().CloneAsValue( false ) ) + , m_nAnchorContentOffset( 0 ) + , m_nNodeIndex( 0 ) + , m_nFormatWhich( rChgFormat.Which() ) + , m_bSaveDrawPt( bSaveDrawPt ) +{ + assert(m_sFormatName.getLength()); + + m_oOldSet->Put( rItem ); + Init( rChgFormat ); +} + +void SwUndoFormatAttr::Init( const SwFormat & rFormat ) +{ + // tdf#126017 never save SwNodeIndex, it will go stale + m_oOldSet->ClearItem(RES_CNTNT); + // treat change of anchor specially + if ( SfxItemState::SET == m_oOldSet->GetItemState( RES_ANCHOR, false )) { + SaveFlyAnchor( &rFormat, m_bSaveDrawPt ); + } else if ( RES_FRMFMT == m_nFormatWhich ) { + const SwDoc* pDoc = rFormat.GetDoc(); + auto pTableFormat = dynamic_cast(&rFormat); + if (pTableFormat && pDoc->GetTableFrameFormats()->ContainsFormat(const_cast(pTableFormat))) + { + // Table Format: save table position, table formats are volatile! + SwTable * pTable = SwIterator( rFormat ).First(); + if ( pTable ) { + m_nNodeIndex = pTable->GetTabSortBoxes()[ 0 ]->GetSttNd() + ->FindTableNode()->GetIndex(); + } + } else if (dynamic_cast(&rFormat)) { + if (auto pContentIndex = rFormat.GetContent().GetContentIdx()) + m_nNodeIndex = pContentIndex->GetIndex(); + } else if(auto pBoxFormat = dynamic_cast(&rFormat)) + { + auto pTableBox = pBoxFormat->GetTableBox(); + if(pTableBox) + m_nNodeIndex = pTableBox->GetSttIdx(); + } + } +} + +SwUndoFormatAttr::~SwUndoFormatAttr() +{ +} + +void SwUndoFormatAttr::UndoImpl(::sw::UndoRedoContext & rContext) +{ + // OD 2004-10-26 #i35443# + // Important note: also called by + + if (!m_oOldSet) + return; + + SwFormat * pFormat = GetFormat(rContext.GetDoc()); + if (!pFormat) + return; + + // #i35443# - If anchor attribute has been successful + // restored, all other attributes are also restored. + // Thus, keep track of its restoration + bool bAnchorAttrRestored( false ); + if ( SfxItemState::SET == m_oOldSet->GetItemState( RES_ANCHOR, false )) { + bAnchorAttrRestored = RestoreFlyAnchor(rContext); + if ( bAnchorAttrRestored ) { + // Anchor attribute successful restored. + // Thus, keep anchor position for redo + SaveFlyAnchor(pFormat); + } else { + // Anchor attribute not restored due to invalid anchor position. + // Thus, delete anchor attribute. + m_oOldSet->ClearItem( RES_ANCHOR ); + } + } + + if ( bAnchorAttrRestored ) return; + + SwUndoFormatAttrHelper aTmp( *pFormat, m_bSaveDrawPt ); + pFormat->SetFormatAttr( *m_oOldSet ); + if ( aTmp.GetUndo() ) { + // transfer ownership of helper object's old set + if (aTmp.GetUndo()->m_oOldSet) + m_oOldSet.emplace(std::move(*aTmp.GetUndo()->m_oOldSet)); + else + m_oOldSet.reset(); + } else { + m_oOldSet->ClearItem(); + } + + if ( RES_FLYFRMFMT == m_nFormatWhich || RES_DRAWFRMFMT == m_nFormatWhich ) { + rContext.SetSelections(static_cast(pFormat), nullptr); + } + + SfxStyleFamily nFamily = SfxStyleFamily::None; + if (RES_TXTFMTCOLL == m_nFormatWhich || RES_CONDTXTFMTCOLL == m_nFormatWhich) + nFamily = SfxStyleFamily::Para; + else if (RES_CHRFMT == m_nFormatWhich) + nFamily = SfxStyleFamily::Char; + + if (m_oOldSet && m_oOldSet->Count() > 0 && nFamily != SfxStyleFamily::None) + rContext.GetDoc().BroadcastStyleOperation(pFormat->GetName(), nFamily, SfxHintId::StyleSheetModified); +} + +// Check if it is still in Doc +SwFormat* SwUndoFormatAttr::GetFormat( const SwDoc& rDoc ) +{ + switch (m_nFormatWhich) + { + case RES_TXTFMTCOLL: + case RES_CONDTXTFMTCOLL: + return rDoc.FindTextFormatCollByName(m_sFormatName); + + case RES_GRFFMTCOLL: + return rDoc.GetGrfFormatColls()->FindFormatByName(m_sFormatName); + + case RES_CHRFMT: + return rDoc.FindCharFormatByName(m_sFormatName); + + case RES_FRMFMT: + if (m_nNodeIndex && (m_nNodeIndex < rDoc.GetNodes().Count())) + { + SwNode* pNd = rDoc.GetNodes()[m_nNodeIndex]; + if (pNd->IsTableNode()) + { + return static_cast(pNd)->GetTable().GetFrameFormat(); + } + else if (pNd->IsSectionNode()) + { + return static_cast(pNd)->GetSection().GetFormat(); + } + else if (pNd->IsStartNode() && (SwTableBoxStartNode == + static_cast(pNd)->GetStartNodeType())) + { + SwTableNode* pTableNode = pNd->FindTableNode(); + if (pTableNode) + { + SwTableBox* pBox = pTableNode->GetTable().GetTableBox(m_nNodeIndex); + if (pBox) + { + return pBox->GetFrameFormat(); + } + } + } + } + [[fallthrough]]; + case RES_DRAWFRMFMT: + case RES_FLYFRMFMT: + { + auto it = rDoc.GetSpzFrameFormats()->findByTypeAndName( m_nFormatWhich, m_sFormatName ); + if( it != rDoc.GetSpzFrameFormats()->typeAndNameEnd() ) + return *it; + SwFormat* pFormat = rDoc.GetFrameFormats()->FindFormatByName(m_sFormatName); + if (pFormat) + return pFormat; + } + break; + } + + return nullptr; +} + +void SwUndoFormatAttr::RedoImpl(::sw::UndoRedoContext & rContext) +{ + // #i35443# - Because the undo stores the attributes for + // redo, the same code as for can be applied for + UndoImpl(rContext); +} + +void SwUndoFormatAttr::RepeatImpl(::sw::RepeatContext & rContext) +{ + if (!m_oOldSet) + return; + + SwDoc & rDoc(rContext.GetDoc()); + + SwFormat * pFormat = GetFormat(rDoc); + if (!pFormat) + return; + + switch ( m_nFormatWhich ) { + case RES_GRFFMTCOLL: { + SwNoTextNode *const pNd = + rContext.GetRepeatPaM().GetPointNode().GetNoTextNode(); + if( pNd ) { + rDoc.SetAttr( pFormat->GetAttrSet(), *pNd->GetFormatColl() ); + } + } + break; + + case RES_TXTFMTCOLL: + case RES_CONDTXTFMTCOLL: + { + SwTextNode *const pNd = + rContext.GetRepeatPaM().GetPointNode().GetTextNode(); + if( pNd ) { + rDoc.SetAttr( pFormat->GetAttrSet(), *pNd->GetFormatColl() ); + } + } + break; + + case RES_FLYFRMFMT: { + // Check if the cursor is in a flying frame + // Steps: search in all FlyFrameFormats for the FlyContent attribute + // and validate if the cursor is in the respective section + SwFrameFormat *const pFly = + rContext.GetRepeatPaM().GetPointNode().GetFlyFormat(); + if( pFly ) { + // Bug 43672: do not set all attributes! + if (SfxItemState::SET == + pFormat->GetAttrSet().GetItemState( RES_CNTNT )) { + SfxItemSet aTmpSet( pFormat->GetAttrSet() ); + aTmpSet.ClearItem( RES_CNTNT ); + if( aTmpSet.Count() ) { + rDoc.SetAttr( aTmpSet, *pFly ); + } + } else { + rDoc.SetAttr( pFormat->GetAttrSet(), *pFly ); + } + } + break; + } + } +} + +SwRewriter SwUndoFormatAttr::GetRewriter() const +{ + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, m_sFormatName); + + return aRewriter; +} + +void SwUndoFormatAttr::PutAttr( const SfxPoolItem& rItem, const SwDoc& rDoc ) +{ + if (RES_CNTNT == rItem.Which()) + { + return; // tdf#126017 never save SwNodeIndex, it will go stale + } + m_oOldSet->Put( rItem ); + if ( RES_ANCHOR == rItem.Which() ) + { + SwFormat * pFormat = GetFormat( rDoc ); + SaveFlyAnchor( pFormat, m_bSaveDrawPt ); + } +} + +void SwUndoFormatAttr::SaveFlyAnchor( const SwFormat * pFormat, bool bSvDrwPt ) +{ + // Format is valid, otherwise you would not reach this point here + if( bSvDrwPt ) { + if ( RES_DRAWFRMFMT == pFormat->Which() ) { + Point aPt( static_cast(pFormat)->FindSdrObject() + ->GetRelativePos() ); + // store old value as attribute, to keep SwUndoFormatAttr small + m_oOldSet->Put( SwFormatFrameSize( SwFrameSize::Variable, aPt.X(), aPt.Y() ) ); + } + } + + const SwFormatAnchor& rAnchor = + m_oOldSet->Get( RES_ANCHOR, false ); + if( !rAnchor.GetAnchorNode() || rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PAGE) + return; + + switch( rAnchor.GetAnchorId() ) { + case RndStdIds::FLY_AS_CHAR: + case RndStdIds::FLY_AT_CHAR: + m_nAnchorContentOffset = rAnchor.GetAnchorContentOffset(); + [[fallthrough]]; + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_FLY: + m_nNodeIndex = rAnchor.GetAnchorNode()->GetIndex(); + break; + default: + assert(false); + } + + SwFormatAnchor aAnchor( rAnchor.GetAnchorId(), 0 ); + m_oOldSet->Put( aAnchor ); +} + +// #i35443# - Add return value, type . +// Return value indicates, if anchor attribute is restored. +// Note: If anchor attribute is restored, all other existing attributes +// are also restored. +bool SwUndoFormatAttr::RestoreFlyAnchor(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwFrameFormat* pFrameFormat = static_cast( GetFormat( *pDoc ) ); + const SwFormatAnchor& rAnchor = + m_oOldSet->Get( RES_ANCHOR, false ); + + SwFormatAnchor aNewAnchor( rAnchor.GetAnchorId() ); + if (RndStdIds::FLY_AT_PAGE != rAnchor.GetAnchorId()) { + SwNode* pNd = pDoc->GetNodes()[ m_nNodeIndex ]; + + if ( (RndStdIds::FLY_AT_FLY == rAnchor.GetAnchorId()) + ? ( !pNd->IsStartNode() || (SwFlyStartNode != + static_cast(pNd)->GetStartNodeType()) ) + : !pNd->IsTextNode() ) { + // #i35443# - invalid position. + // Thus, anchor attribute not restored + return false; + } + + SwPosition aPos( *pNd ); + if ((RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId())) { + aPos.SetContent( m_nAnchorContentOffset ); + if ( aPos.GetContentIndex() > pNd->GetTextNode()->GetText().getLength()) { + // #i35443# - invalid position. + // Thus, anchor attribute not restored + return false; + } + } + aNewAnchor.SetAnchor( &aPos ); + } else + aNewAnchor.SetPageNum( rAnchor.GetPageNum() ); + + Point aDrawSavePt, aDrawOldPt; + if( pDoc->getIDocumentLayoutAccess().GetCurrentViewShell() ) { + if( RES_DRAWFRMFMT == pFrameFormat->Which() ) { + // get the old cached value + const SwFormatFrameSize& rOldSize = m_oOldSet->Get( RES_FRM_SIZE ); + aDrawSavePt.setX( rOldSize.GetWidth() ); + aDrawSavePt.setY( rOldSize.GetHeight() ); + m_oOldSet->ClearItem( RES_FRM_SIZE ); + + // write the current value into cache + aDrawOldPt = pFrameFormat->FindSdrObject()->GetRelativePos(); + } else { + pFrameFormat->DelFrames(); // delete Frames + } + } + + const SwFormatAnchor &rOldAnch = pFrameFormat->GetAnchor(); + // #i54336# + // Consider case, that as-character anchored object has moved its anchor position. + if (RndStdIds::FLY_AS_CHAR == rOldAnch.GetAnchorId()) { + // With InContents it's tricky: the text attribute needs to be deleted. + // Unfortunately, this not only destroys the Frames but also the format. + // To prevent that, first detach the connection between attribute and + // format. + SwTextNode *pTextNode = static_cast(rOldAnch.GetAnchorNode()); + OSL_ENSURE( pTextNode->HasHints(), "Missing FlyInCnt-Hint." ); + const sal_Int32 nIdx = rOldAnch.GetAnchorContentOffset(); + SwTextAttr * const pHint = + pTextNode->GetTextAttrForCharAt( nIdx, RES_TXTATR_FLYCNT ); + assert(pHint && "Missing Hint."); + OSL_ENSURE( pHint->Which() == RES_TXTATR_FLYCNT, + "Missing FlyInCnt-Hint." ); + OSL_ENSURE( pHint->GetFlyCnt().GetFrameFormat() == pFrameFormat, + "Wrong TextFlyCnt-Hint." ); + const_cast(pHint->GetFlyCnt()).SetFlyFormat(); + + // Connection is now detached, therefore the attribute can be deleted + pTextNode->DeleteAttributes( RES_TXTATR_FLYCNT, nIdx, nIdx ); + } + + { + m_oOldSet->Put( aNewAnchor ); + SwUndoFormatAttrHelper aTmp( *pFrameFormat, m_bSaveDrawPt ); + pFrameFormat->SetFormatAttr( *m_oOldSet ); + if ( aTmp.GetUndo() ) { + m_nNodeIndex = aTmp.GetUndo()->m_nNodeIndex; + // transfer ownership of helper object's old set + if (aTmp.GetUndo()->m_oOldSet) + m_oOldSet.emplace(std::move(*aTmp.GetUndo()->m_oOldSet)); + else + m_oOldSet.reset(); + } else { + m_oOldSet->ClearItem(); + } + } + + if ( RES_DRAWFRMFMT == pFrameFormat->Which() ) + { + // The Draw model also prepared an Undo object for its right positioning + // which unfortunately is relative. Therefore block here a position + // change of the Contact object by setting the anchor. + const SwFormatVertOrient& rVertOrient = pFrameFormat->GetVertOrient(); + const SwFormatHoriOrient& rHoriOrient = pFrameFormat->GetHoriOrient(); + Point aFormatPos(rHoriOrient.GetPos(), rVertOrient.GetPos()); + if (aDrawSavePt != aFormatPos) + { + // If the position would be the same, then skip the call: either it would do nothing or + // it would just go wrong. + pFrameFormat->CallSwClientNotify(sw::RestoreFlyAnchorHint(aDrawSavePt)); + } + + // cache the old value again + m_oOldSet->Put(SwFormatFrameSize(SwFrameSize::Variable, aDrawOldPt.X(), aDrawOldPt.Y())); + } + + if (RndStdIds::FLY_AS_CHAR == aNewAnchor.GetAnchorId()) { + SwTextNode* pTextNd = aNewAnchor.GetAnchorNode()->GetTextNode(); + OSL_ENSURE( pTextNd, "no Text Node at position." ); + SwFormatFlyCnt aFormat( pFrameFormat ); + pTextNd->InsertItem( aFormat, aNewAnchor.GetAnchorContentOffset(), 0 ); + } + + if (RES_DRAWFRMFMT != pFrameFormat->Which()) + pFrameFormat->MakeFrames(); + else + { + pFrameFormat->CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::POST_RESTORE_FLY_ANCHOR)); + } + + rContext.SetSelections(pFrameFormat, nullptr); + + // #i35443# - anchor attribute restored. + return true; +} + +SwUndoFormatResetAttr::SwUndoFormatResetAttr( SwFormat& rChangedFormat, + const std::vector& rIds ) + : SwUndo( SwUndoId::RESETATTR, rChangedFormat.GetDoc() ) + , m_pChangedFormat( &rChangedFormat ) + , m_aSet(*rChangedFormat.GetAttrSet().GetPool()) +{ + for (const auto& nWhichId : rIds) + { + const SfxPoolItem* pItem = nullptr; + if (rChangedFormat.GetItemState(nWhichId, false, &pItem ) == SfxItemState::SET && pItem) + m_aSet.Put(*pItem); + } +} + +SwUndoFormatResetAttr::~SwUndoFormatResetAttr() +{ +} + +void SwUndoFormatResetAttr::UndoImpl(::sw::UndoRedoContext &) +{ + m_pChangedFormat->SetFormatAttr(m_aSet); + BroadcastStyleChange(); +} + +void SwUndoFormatResetAttr::RedoImpl(::sw::UndoRedoContext &) +{ + SfxItemIter aIter(m_aSet); + for (auto pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + m_pChangedFormat->ResetFormatAttr(pItem->Which()); + BroadcastStyleChange(); +} + +void SwUndoFormatResetAttr::BroadcastStyleChange() +{ + auto nWhich = m_pChangedFormat->Which(); + SfxStyleFamily nFamily = SfxStyleFamily::None; + + if (RES_TXTFMTCOLL == nWhich || RES_CONDTXTFMTCOLL == nWhich) + nFamily = SfxStyleFamily::Para; + else if (RES_CHRFMT == nWhich) + nFamily = SfxStyleFamily::Char; + + if (nFamily != SfxStyleFamily::None) + m_pChangedFormat->GetDoc()->BroadcastStyleOperation(m_pChangedFormat->GetName(), nFamily, SfxHintId::StyleSheetModified); +} + +SwUndoResetAttr::SwUndoResetAttr( const SwPaM& rRange, sal_uInt16 nFormatId ) + : SwUndo( SwUndoId::RESETATTR, &rRange.GetDoc() ), SwUndRng( rRange ) + , m_pHistory( new SwHistory ) + , m_nFormatId( nFormatId ) +{ +} + +SwUndoResetAttr::SwUndoResetAttr( const SwPosition& rPos, sal_uInt16 nFormatId ) + : SwUndo( SwUndoId::RESETATTR, &rPos.GetDoc() ) + , m_pHistory( new SwHistory ) + , m_nFormatId( nFormatId ) +{ + m_nSttNode = m_nEndNode = rPos.GetNodeIndex(); + m_nSttContent = m_nEndContent = rPos.GetContentIndex(); +} + +SwUndoResetAttr::~SwUndoResetAttr() +{ +} + +void SwUndoResetAttr::UndoImpl(::sw::UndoRedoContext & rContext) +{ + // reset old values + SwDoc & rDoc = rContext.GetDoc(); + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + + if ((RES_CONDTXTFMTCOLL == m_nFormatId) && + (m_nSttNode == m_nEndNode) && (m_nSttContent == m_nEndContent)) { + SwTextNode* pTNd = rDoc.GetNodes()[ m_nSttNode ]->GetTextNode(); + if( pTNd ) + pTNd->DontExpandFormat( m_nSttContent, false ); + } + else if (m_nFormatId == RES_TXTATR_REFMARK) + { + rDoc.GetEditShell()->SwViewShell::UpdateFields(); + } + + AddUndoRedoPaM(rContext); +} + +void SwUndoResetAttr::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPaM & rPam = AddUndoRedoPaM(rContext); + + switch ( m_nFormatId ) { + case RES_CHRFMT: + rDoc.RstTextAttrs(rPam); + break; + case RES_TXTFMTCOLL: + rDoc.ResetAttrs(rPam, false, m_Ids ); + break; + case RES_CONDTXTFMTCOLL: + rDoc.ResetAttrs(rPam, true, m_Ids ); + + break; + case RES_TXTATR_TOXMARK: + // special treatment for TOXMarks + { + SwTOXMarks aArr; + SwNodeIndex aIdx( rDoc.GetNodes(), m_nSttNode ); + SwPosition aPos( aIdx, aIdx.GetNode().GetContentNode(), m_nSttContent ); + + sal_uInt16 nCnt = SwDoc::GetCurTOXMark( aPos, aArr ); + if( nCnt ) { + if( 1 < nCnt ) { + // search for the right one + SwHistoryHint* pHHint = (GetHistory())[ 0 ]; + if( pHHint && HSTRY_SETTOXMARKHNT == pHHint->Which() ) { + while( nCnt ) { + if ( static_cast(pHHint) + ->IsEqual( *aArr[ --nCnt ] ) ) { + ++nCnt; + break; + } + } + } else + nCnt = 0; + } + // found one, thus delete it + if( nCnt-- ) { + rDoc.DeleteTOXMark( aArr[ nCnt ] ); + } + } + } + break; + case RES_TXTATR_REFMARK: + { + const registeredSfxPoolItems& aRange(rDoc.GetAttrPool().GetItemSurrogates(RES_TXTATR_REFMARK)); + SwHistoryHint* pHistoryHint = GetHistory()[0]; + if (pHistoryHint && HSTRY_SETREFMARKHNT == pHistoryHint->Which()) + { + for (const SfxPoolItem* pItem : aRange) + { + assert(dynamic_cast(pItem)); + const auto pFormatRefMark = static_cast(pItem); + if (static_cast(pHistoryHint)->GetRefName() == + pFormatRefMark->GetRefName()) + { + rDoc.DeleteFormatRefMark(pFormatRefMark); + rDoc.GetEditShell()->SwViewShell::UpdateFields(); + break; + } + } + } + } + break; + } +} + +void SwUndoResetAttr::RepeatImpl(::sw::RepeatContext & rContext) +{ + if (m_nFormatId < RES_FMT_BEGIN) { + return; + } + + switch ( m_nFormatId ) { + case RES_CHRFMT: + rContext.GetDoc().RstTextAttrs(rContext.GetRepeatPaM()); + break; + case RES_TXTFMTCOLL: + rContext.GetDoc().ResetAttrs(rContext.GetRepeatPaM(), false, m_Ids); + break; + case RES_CONDTXTFMTCOLL: + rContext.GetDoc().ResetAttrs(rContext.GetRepeatPaM(), true, m_Ids); + break; + } +} + +void SwUndoResetAttr::SetAttrs( o3tl::sorted_vector && rAttrs ) +{ + m_Ids = std::move(rAttrs); +} + +SwUndoAttr::SwUndoAttr( const SwPaM& rRange, const SfxPoolItem& rAttr, + const SetAttrMode nFlags ) + : SwUndo( SwUndoId::INSATTR, &rRange.GetDoc() ), SwUndRng( rRange ) + , m_AttrSet( rRange.GetDoc().GetAttrPool(), rAttr.Which(), rAttr.Which() ) + , m_pHistory( new SwHistory ) + , m_nNodeIndex( NODE_OFFSET_MAX ) + , m_nInsertFlags( nFlags ) +{ + m_AttrSet.Put( rAttr ); + + // Save character style as a style name, not as a reference + const SfxPoolItem* pItem = m_AttrSet.GetItem(RES_TXTATR_CHARFMT); + if (pItem) + { + uno::Any aValue; + pItem->QueryValue(aValue, RES_TXTATR_CHARFMT); + aValue >>= m_aChrFormatName; + } +} + +SwUndoAttr::SwUndoAttr( const SwPaM& rRange, SfxItemSet aSet, + const SetAttrMode nFlags ) + : SwUndo( SwUndoId::INSATTR, &rRange.GetDoc() ), SwUndRng( rRange ) + , m_AttrSet(std::move( aSet )) + , m_pHistory( new SwHistory ) + , m_nNodeIndex( NODE_OFFSET_MAX ) + , m_nInsertFlags( nFlags ) +{ + // Save character style as a style name, not as a reference + const SfxPoolItem* pItem = m_AttrSet.GetItem(RES_TXTATR_CHARFMT); + if (pItem) + { + uno::Any aValue; + pItem->QueryValue(aValue, RES_TXTATR_CHARFMT); + aValue >>= m_aChrFormatName; + } +} + +SwUndoAttr::~SwUndoAttr() +{ +} + +void SwUndoAttr::SaveRedlineData( const SwPaM& rPam, bool bIsContent ) +{ + SwDoc& rDoc = rPam.GetDoc(); + if ( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) { + m_pRedlineData.reset( new SwRedlineData( bIsContent + ? RedlineType::Insert + : RedlineType::Format, + rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ) ); + } + + m_pRedlineSaveData.reset( new SwRedlineSaveDatas ); + if ( !FillSaveDataForFormat( rPam, *m_pRedlineSaveData )) + m_pRedlineSaveData.reset(); + + SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ); + if ( bIsContent ) { + m_nNodeIndex = rPam.GetPoint()->GetNodeIndex(); + } +} + +void SwUndoAttr::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + + RemoveIdx( *pDoc ); + + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) ) { + SwPaM aPam(pDoc->GetNodes().GetEndOfContent()); + if ( NODE_OFFSET_MAX != m_nNodeIndex ) { + aPam.DeleteMark(); + aPam.GetPoint()->Assign( m_nNodeIndex, m_nSttContent ); + aPam.SetMark(); + aPam.GetPoint()->AdjustContent(+1); + pDoc->getIDocumentRedlineAccess().DeleteRedline(aPam, false, RedlineType::Any); + } else { + // remove all format redlines, will be recreated if needed + SetPaM(aPam); + pDoc->getIDocumentRedlineAccess().DeleteRedline(aPam, false, RedlineType::Format); + if (m_pRedlineSaveData) + { + SetSaveData( *pDoc, *m_pRedlineSaveData ); + } + } + } + + const bool bToLast = (1 == m_AttrSet.Count()) + && (RES_TXTATR_FIELD <= m_AttrSet.GetRanges()[0].first) + && (m_AttrSet.GetRanges()[0].first <= RES_TXTATR_ANNOTATION); + + // restore old values + m_pHistory->TmpRollback( pDoc, 0, !bToLast ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + + // set cursor onto Undo area + if (!(m_nInsertFlags & SetAttrMode::NO_CURSOR_CHANGE)) + AddUndoRedoPaM(rContext); +} + +void SwUndoAttr::RepeatImpl(::sw::RepeatContext & rContext) +{ + // RefMarks are not repeat capable + if ( SfxItemState::SET != m_AttrSet.GetItemState( RES_TXTATR_REFMARK, false ) ) { + rContext.GetDoc().getIDocumentContentOperations().InsertItemSet( rContext.GetRepeatPaM(), + m_AttrSet, m_nInsertFlags ); + } else if ( 1 < m_AttrSet.Count() ) { + SfxItemSet aTmpSet( m_AttrSet ); + aTmpSet.ClearItem( RES_TXTATR_REFMARK ); + rContext.GetDoc().getIDocumentContentOperations().InsertItemSet( rContext.GetRepeatPaM(), + aTmpSet, m_nInsertFlags ); + } +} + +void SwUndoAttr::redoAttribute(SwPaM& rPam, sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + // Restore pointer to char format from name + if (!m_aChrFormatName.isEmpty()) + { + SwCharFormat* pCharFormat = rDoc.FindCharFormatByName(m_aChrFormatName); + if (pCharFormat) + { + SwFormatCharFormat aFormat(pCharFormat); + m_AttrSet.Put(aFormat); + } + } + + if ( m_pRedlineData && + IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) ) { + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags::Ignore ); + rDoc.getIDocumentContentOperations().InsertItemSet( rPam, m_AttrSet, m_nInsertFlags ); + + if ( NODE_OFFSET_MAX != m_nNodeIndex ) { + rPam.SetMark(); + if ( rPam.Move( fnMoveBackward ) ) { + rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlineData, rPam ), + true); + } + rPam.DeleteMark(); + } else { + rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlineData, rPam ), true); + } + + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } else { + rDoc.getIDocumentContentOperations().InsertItemSet( rPam, m_AttrSet, m_nInsertFlags ); + } +} + +void SwUndoAttr::RedoImpl(sw::UndoRedoContext & rContext) +{ + if (m_nInsertFlags & SetAttrMode::NO_CURSOR_CHANGE) + { + SwPaM aPam(rContext.GetDoc().GetNodes().GetEndOfContent()); + SetPaM(aPam, false); + redoAttribute(aPam, rContext); + } + else + { + SwPaM& rPam = AddUndoRedoPaM(rContext); + redoAttribute(rPam, rContext); + } +} + +void SwUndoAttr::RemoveIdx( SwDoc& rDoc ) +{ + if ( SfxItemState::SET != m_AttrSet.GetItemState( RES_TXTATR_FTN, false )) + return ; + + SwNodes& rNds = rDoc.GetNodes(); + for ( sal_uInt16 n = 0; n < m_pHistory->Count(); ++n ) { + sal_Int32 nContent = 0; + SwNodeOffset nNode(0); + SwHistoryHint* pHstHint = (*m_pHistory)[ n ]; + switch ( pHstHint->Which() ) { + case HSTRY_RESETTXTHNT: { + SwHistoryResetText * pHistoryHint + = static_cast(pHstHint); + if ( RES_TXTATR_FTN == pHistoryHint->GetWhich() ) { + nNode = pHistoryHint->GetNode(); + nContent = pHistoryHint->GetContent(); + } + } + break; + + default: + break; + } + + if( nNode ) { + SwTextNode* pTextNd = rNds[ nNode ]->GetTextNode(); + if( pTextNd ) { + SwTextAttr *const pTextHt = + pTextNd->GetTextAttrForCharAt(nContent, RES_TXTATR_FTN); + if( pTextHt ) { + // ok, so get values + SwTextFootnote* pFootnote = static_cast(pTextHt); + RemoveIdxFromSection( rDoc, pFootnote->GetStartNode()->GetIndex() ); + return ; + } + } + } + } +} + +SwUndoDefaultAttr::SwUndoDefaultAttr( const SfxItemSet& rSet, const SwDoc& rDoc ) + : SwUndo( SwUndoId::SETDEFTATTR, &rDoc ) +{ + const SvxTabStopItem* pItem = rSet.GetItemIfSet( RES_PARATR_TABSTOP, false ); + if( pItem ) + { + // store separately, because it may change! + m_pTabStop.reset(pItem->Clone()); + if ( 1 != rSet.Count() ) { // are there more attributes? + m_oOldSet.emplace( rSet ); + } + } else { + m_oOldSet.emplace( rSet ); + } +} + +SwUndoDefaultAttr::~SwUndoDefaultAttr() +{ +} + +void SwUndoDefaultAttr::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + if (m_oOldSet) + { + SwUndoFormatAttrHelper aTmp( + *rDoc.GetDfltTextFormatColl() ); + rDoc.SetDefault( *m_oOldSet ); + m_oOldSet.reset(); + if ( aTmp.GetUndo() ) { + // transfer ownership of helper object's old set + if (aTmp.GetUndo()->m_oOldSet) + m_oOldSet.emplace(std::move(*aTmp.GetUndo()->m_oOldSet)); + } + } + if (m_pTabStop) + { + std::unique_ptr pOld(rDoc.GetDefault(RES_PARATR_TABSTOP).Clone()); + rDoc.SetDefault( *m_pTabStop ); + m_pTabStop = std::move( pOld ); + } +} + +void SwUndoDefaultAttr::RedoImpl(::sw::UndoRedoContext & rContext) +{ + UndoImpl(rContext); +} + +SwUndoMoveLeftMargin::SwUndoMoveLeftMargin( + const SwPaM& rPam, bool bFlag, bool bMod ) + : SwUndo( bFlag ? SwUndoId::INC_LEFTMARGIN : SwUndoId::DEC_LEFTMARGIN, &rPam.GetDoc() ) + , SwUndRng( rPam ) + , m_pHistory( new SwHistory ) + , m_bModulus( bMod ) +{ +} + +SwUndoMoveLeftMargin::~SwUndoMoveLeftMargin() +{ +} + +void SwUndoMoveLeftMargin::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + // restore old values + m_pHistory->TmpRollback( & rDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + + AddUndoRedoPaM(rContext); +} + +void SwUndoMoveLeftMargin::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPaM & rPam = AddUndoRedoPaM(rContext); + + rDoc.MoveLeftMargin( rPam, + GetId() == SwUndoId::INC_LEFTMARGIN, m_bModulus, + rDoc.getIDocumentLayoutAccess().GetCurrentLayout() ); +} + +void SwUndoMoveLeftMargin::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + rDoc.MoveLeftMargin(rContext.GetRepeatPaM(), GetId() == SwUndoId::INC_LEFTMARGIN, + m_bModulus, rDoc.getIDocumentLayoutAccess().GetCurrentLayout()); +} + +SwUndoChangeFootNote::SwUndoChangeFootNote( + const SwPaM& rRange, OUString aText, + bool const bIsEndNote) + : SwUndo( SwUndoId::CHGFTN, &rRange.GetDoc() ), SwUndRng( rRange ) + , m_pHistory( new SwHistory() ) + , m_Text(std::move( aText )) + , m_bEndNote( bIsEndNote ) +{ +} + +SwUndoChangeFootNote::~SwUndoChangeFootNote() +{ +} + +void SwUndoChangeFootNote::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + + rDoc.GetFootnoteIdxs().UpdateAllFootnote(); + + AddUndoRedoPaM(rContext); +} + +void SwUndoChangeFootNote::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc( rContext.GetDoc() ); + SwPaM & rPaM = AddUndoRedoPaM(rContext); + rDoc.SetCurFootnote(rPaM, m_Text, m_bEndNote); + SetPaM(rPaM); +} + +void SwUndoChangeFootNote::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + rDoc.SetCurFootnote(rContext.GetRepeatPaM(), m_Text, m_bEndNote); +} + +SwUndoFootNoteInfo::SwUndoFootNoteInfo( const SwFootnoteInfo &rInfo, const SwDoc& rDoc ) + : SwUndo( SwUndoId::FTNINFO, &rDoc ) + , m_pFootNoteInfo( new SwFootnoteInfo( rInfo ) ) +{ +} + +SwUndoFootNoteInfo::~SwUndoFootNoteInfo() +{ +} + +void SwUndoFootNoteInfo::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwFootnoteInfo *pInf = new SwFootnoteInfo( rDoc.GetFootnoteInfo() ); + rDoc.SetFootnoteInfo( *m_pFootNoteInfo ); + m_pFootNoteInfo.reset( pInf ); +} + +void SwUndoFootNoteInfo::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwFootnoteInfo *pInf = new SwFootnoteInfo( rDoc.GetFootnoteInfo() ); + rDoc.SetFootnoteInfo( *m_pFootNoteInfo ); + m_pFootNoteInfo.reset( pInf ); +} + +SwUndoEndNoteInfo::SwUndoEndNoteInfo( const SwEndNoteInfo &rInfo, const SwDoc& rDoc ) + : SwUndo( SwUndoId::FTNINFO, &rDoc ) + , m_pEndNoteInfo( new SwEndNoteInfo( rInfo ) ) +{ +} + +SwUndoEndNoteInfo::~SwUndoEndNoteInfo() +{ +} + +void SwUndoEndNoteInfo::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwEndNoteInfo *pInf = new SwEndNoteInfo( rDoc.GetEndNoteInfo() ); + rDoc.SetEndNoteInfo( *m_pEndNoteInfo ); + m_pEndNoteInfo.reset( pInf ); +} + +void SwUndoEndNoteInfo::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwEndNoteInfo *pInf = new SwEndNoteInfo( rDoc.GetEndNoteInfo() ); + rDoc.SetEndNoteInfo( *m_pEndNoteInfo ); + m_pEndNoteInfo.reset( pInf ); +} + +SwUndoDontExpandFormat::SwUndoDontExpandFormat( const SwPosition& rPos ) + : SwUndo( SwUndoId::DONTEXPAND, &rPos.GetDoc() ) + , m_nNodeIndex( rPos.GetNodeIndex() ) + , m_nContentIndex( rPos.GetContentIndex() ) +{ +} + +void SwUndoDontExpandFormat::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwCursor *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + SwDoc *const pDoc = & rContext.GetDoc(); + + SwPosition& rPos = *pPam->GetPoint(); + rPos.Assign( m_nNodeIndex, m_nContentIndex ); + pDoc->DontExpandFormat( rPos, false ); +} + +void SwUndoDontExpandFormat::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + SwDoc *const pDoc = & rContext.GetDoc(); + + SwPosition& rPos = *pPam->GetPoint(); + rPos.Assign( m_nNodeIndex, m_nContentIndex ); + pDoc->DontExpandFormat( rPos ); +} + +void SwUndoDontExpandFormat::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwPaM & rPam = rContext.GetRepeatPaM(); + SwDoc & rDoc = rContext.GetDoc(); + rDoc.DontExpandFormat( *rPam.GetPoint() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unbkmk.cxx b/sw/source/core/undo/unbkmk.cxx new file mode 100644 index 0000000000..8288453307 --- /dev/null +++ b/sw/source/core/undo/unbkmk.cxx @@ -0,0 +1,223 @@ +/* -*- 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 + +SwUndoBookmark::SwUndoBookmark( SwUndoId nUndoId, + const ::sw::mark::IMark& rBkmk ) + : SwUndo( nUndoId, &rBkmk.GetMarkPos().GetDoc() ) + , m_pHistoryBookmark(new SwHistoryBookmark(rBkmk, true, rBkmk.IsExpanded())) +{ +} + +SwUndoBookmark::~SwUndoBookmark() +{ +} + +void SwUndoBookmark::SetInDoc( SwDoc* pDoc ) +{ + m_pHistoryBookmark->SetInDoc( pDoc, false ); +} + +void SwUndoBookmark::ResetInDoc( SwDoc& rDoc ) +{ + IDocumentMarkAccess* const pMarkAccess = rDoc.getIDocumentMarkAccess(); + for ( IDocumentMarkAccess::const_iterator_t ppBkmk = pMarkAccess->getAllMarksBegin(); + ppBkmk != pMarkAccess->getAllMarksEnd(); + ++ppBkmk ) + { + if ( m_pHistoryBookmark->IsEqualBookmark( **ppBkmk ) ) + { + pMarkAccess->deleteMark(ppBkmk, false); + break; + } + } +} + +SwRewriter SwUndoBookmark::GetRewriter() const +{ + SwRewriter aResult; + + aResult.AddRule(UndoArg1, m_pHistoryBookmark->GetName()); + + return aResult; +} + +SwUndoInsBookmark::SwUndoInsBookmark( const ::sw::mark::IMark& rBkmk ) + : SwUndoBookmark( SwUndoId::INSBOOKMARK, rBkmk ) +{ +} + +void SwUndoInsBookmark::UndoImpl(::sw::UndoRedoContext & rContext) +{ + ResetInDoc( rContext.GetDoc() ); +} + +void SwUndoInsBookmark::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SetInDoc( &rContext.GetDoc() ); +} + +SwUndoDeleteBookmark::SwUndoDeleteBookmark( const ::sw::mark::IMark& rBkmk ) + : SwUndoBookmark( SwUndoId::DELBOOKMARK, rBkmk ) +{ +} + +void SwUndoDeleteBookmark::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SetInDoc( &rContext.GetDoc() ); +} + +void SwUndoDeleteBookmark::RedoImpl(::sw::UndoRedoContext & rContext) +{ + ResetInDoc( rContext.GetDoc() ); +} + +SwUndoRenameBookmark::SwUndoRenameBookmark( OUString aOldName, OUString aNewName, const SwDoc& rDoc ) + : SwUndo( SwUndoId::BOOKMARK_RENAME, &rDoc ) + , m_sOldName(std::move( aOldName )) + , m_sNewName(std::move( aNewName )) +{ +} + +SwUndoRenameBookmark::~SwUndoRenameBookmark() +{ +} + +static OUString lcl_QuoteName(std::u16string_view rName) +{ + static const OUString sStart = SwResId(STR_START_QUOTE); + static const OUString sEnd = SwResId(STR_END_QUOTE); + return sStart + rName + sEnd; +} + +SwRewriter SwUndoRenameBookmark::GetRewriter() const +{ + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, lcl_QuoteName(m_sOldName)); + aRewriter.AddRule(UndoArg2, SwResId(STR_YIELDS)); + aRewriter.AddRule(UndoArg3, lcl_QuoteName(m_sNewName)); + return aRewriter; +} + +void SwUndoRenameBookmark::Rename(::sw::UndoRedoContext const & rContext, const OUString& sFrom, const OUString& sTo) +{ + IDocumentMarkAccess* const pMarkAccess = rContext.GetDoc().getIDocumentMarkAccess(); + IDocumentMarkAccess::const_iterator_t ppBkmk = pMarkAccess->findMark(sFrom); + if (ppBkmk != pMarkAccess->getAllMarksEnd()) + { + pMarkAccess->renameMark( *ppBkmk, sTo ); + } +} + +void SwUndoRenameBookmark::UndoImpl(::sw::UndoRedoContext & rContext) +{ + Rename(rContext, m_sNewName, m_sOldName); +} + +void SwUndoRenameBookmark::RedoImpl(::sw::UndoRedoContext & rContext) +{ + Rename(rContext, m_sOldName, m_sNewName); +} + +SwUndoInsNoTextFieldmark::SwUndoInsNoTextFieldmark(const ::sw::mark::IFieldmark& rFieldmark) + : SwUndo(SwUndoId::INSERT, &rFieldmark.GetMarkPos().GetDoc()) + , m_pHistoryNoTextFieldmark(new SwHistoryNoTextFieldmark(rFieldmark)) +{ +} + +void SwUndoInsNoTextFieldmark::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryNoTextFieldmark->ResetInDoc(rContext.GetDoc()); +} + +void SwUndoInsNoTextFieldmark::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryNoTextFieldmark->SetInDoc(&rContext.GetDoc(), false); +} + +SwUndoDelNoTextFieldmark::SwUndoDelNoTextFieldmark(const ::sw::mark::IFieldmark& rFieldmark) + : SwUndo(SwUndoId::DELETE, &rFieldmark.GetMarkPos().GetDoc()) + , m_pHistoryNoTextFieldmark(new SwHistoryNoTextFieldmark(rFieldmark)) +{ +} + +SwUndoDelNoTextFieldmark::~SwUndoDelNoTextFieldmark() = default; + +void SwUndoDelNoTextFieldmark::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryNoTextFieldmark->SetInDoc(&rContext.GetDoc(), false); +} + +void SwUndoDelNoTextFieldmark::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryNoTextFieldmark->ResetInDoc(rContext.GetDoc()); +} + +SwUndoInsTextFieldmark::SwUndoInsTextFieldmark(const ::sw::mark::IFieldmark& rFieldmark) + : SwUndo(SwUndoId::INSERT, &rFieldmark.GetMarkPos().GetDoc()) + , m_pHistoryTextFieldmark(new SwHistoryTextFieldmark(rFieldmark)) +{ +} + +void SwUndoInsTextFieldmark::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryTextFieldmark->ResetInDoc(rContext.GetDoc()); +} + +void SwUndoInsTextFieldmark::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryTextFieldmark->SetInDoc(&rContext.GetDoc(), false); +} + +SwUndoDelTextFieldmark::SwUndoDelTextFieldmark(const ::sw::mark::IFieldmark& rFieldmark) + : SwUndo(SwUndoId::DELETE, &rFieldmark.GetMarkPos().GetDoc()) + , m_pHistoryTextFieldmark(new SwHistoryTextFieldmark(rFieldmark)) +{ +} + +SwUndoDelTextFieldmark::~SwUndoDelTextFieldmark() = default; + +void SwUndoDelTextFieldmark::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryTextFieldmark->SetInDoc(&rContext.GetDoc(), false); +} + +void SwUndoDelTextFieldmark::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_pHistoryTextFieldmark->ResetInDoc(rContext.GetDoc()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/undel.cxx b/sw/source/core/undo/undel.cxx new file mode 100644 index 0000000000..4c199c8ac3 --- /dev/null +++ b/sw/source/core/undo/undel.cxx @@ -0,0 +1,1361 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// DELETE +/* lcl_MakeAutoFrames has to call MakeFrames for objects bounded "AtChar" + ( == AUTO ), if the anchor frame has be moved via MoveNodes(..) and + DelFrames(..) +*/ +static void lcl_MakeAutoFrames(const sw::FrameFormats& rSpzs, SwNodeOffset nMovedIndex ) +{ + for(auto pSpz: rSpzs) + { + const SwFormatAnchor* pAnchor = &pSpz->GetAnchor(); + if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + const SwNode* pAnchorNode = pAnchor->GetAnchorNode(); + if( pAnchorNode && nMovedIndex == pAnchorNode->GetIndex() ) + pSpz->MakeFrames(); + } + } +} + +static SwTextNode * FindFirstAndNextNode(SwDoc & rDoc, SwUndRng const& rRange, + SwRedlineSaveDatas const& rRedlineSaveData, + SwTextNode *& o_rpFirstMergedDeletedTextNode) +{ + // redlines are corrected now to exclude the deleted node + assert(rRange.m_nEndContent == 0); + SwNodeOffset nEndOfRedline(0); + for (size_t i = 0; i < rRedlineSaveData.size(); ++i) + { + auto const& rRedline(rRedlineSaveData[i]); + if (rRedline.m_nSttNode <= rRange.m_nSttNode + // coverity[copy_paste_error : FALSE] : m_nEndNode is intentional here + && rRedline.m_nSttNode < rRange.m_nEndNode + && rRange.m_nEndNode <= rRedline.m_nEndNode + && rRedline.GetType() == RedlineType::Delete) + { + nEndOfRedline = rRedline.m_nEndNode; + o_rpFirstMergedDeletedTextNode = rDoc.GetNodes()[rRedline.m_nSttNode]->GetTextNode(); + assert(rRange.m_nSttNode == rRange.m_nEndNode - 1); // otherwise this needs to iterate more RL to find the first node? + break; + } + } + if (nEndOfRedline) + { + assert(o_rpFirstMergedDeletedTextNode); + SwTextNode * pNextNode(nullptr); + for (SwNodeOffset i = rRange.m_nEndNode; /* i <= nEndOfRedline */; ++i) + { + SwNode *const pNode(rDoc.GetNodes()[i]); + assert(!pNode->IsEndNode()); // cannot be both leaving section here *and* overlapping redline + if (pNode->IsStartNode()) + { + i = pNode->EndOfSectionIndex(); // will be incremented again + } + else if (pNode->IsTextNode()) + { + pNextNode = pNode->GetTextNode(); + break; + } + } + assert(pNextNode); + return pNextNode; + } + else + { + return nullptr; + } +} + +static void DelFullParaMoveFrames(SwDoc & rDoc, SwUndRng const& rRange, + SwRedlineSaveDatas const& rRedlineSaveData) +{ + SwTextNode * pFirstMergedDeletedTextNode(nullptr); + SwTextNode *const pNextNode = FindFirstAndNextNode(rDoc, rRange, + rRedlineSaveData, pFirstMergedDeletedTextNode); + if (!pNextNode) + return; + + std::vector frames; + SwIterator aIter(*pFirstMergedDeletedTextNode); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->HasMergedParas()) + { + assert(pFrame->GetMergedPara()); + assert(pFrame->GetMergedPara()->pFirstNode == pFirstMergedDeletedTextNode); + assert(pNextNode->GetIndex() <= pFrame->GetMergedPara()->pLastNode->GetIndex()); + frames.push_back(pFrame); + } + } + for (SwTextFrame *const pFrame : frames) + { + // sw_redlinehide: don't need FrameMode::Existing here + // because everything from pNextNode onwards is already + // correctly hidden + pFrame->RegisterToNode(*pNextNode, true); + } +} + +// SwUndoDelete has to perform a deletion and to record anything that is needed +// to restore the situation before the deletion. Unfortunately a part of the +// deletion will be done after calling this Ctor, this has to be kept in mind! +// In this Ctor only the complete paragraphs will be deleted, the joining of +// the first and last paragraph of the selection will be handled outside this +// function. +// Here are the main steps of the function: +// 1. Deletion/recording of content indices of the selection: footnotes, fly +// frames and bookmarks +// Step 1 could shift all nodes by deletion of footnotes => nNdDiff will be set. +// 2. If the paragraph where the selection ends, is the last content of a +// section so that this section becomes empty when the paragraphs will be +// joined we have to do some smart actions ;-) The paragraph will be moved +// outside the section and replaced by a dummy text node, the complete +// section will be deleted in step 3. The difference between replacement +// dummy and original is nReplacementDummy. +// 3. Moving complete selected nodes into the UndoArray. Before this happens the +// selection has to be extended if there are sections which would become +// empty otherwise. BTW: sections will be moved into the UndoArray if they +// are complete part of the selection. Sections starting or ending outside +// of the selection will not be removed from the DocNodeArray even they got +// a "dummy"-copy in the UndoArray. +// 4. We have to anticipate the joining of the two paragraphs if the start +// paragraph is inside a section and the end paragraph not. Then we have to +// move the paragraph into this section and to record this in nSectDiff. +SwUndoDelete::SwUndoDelete( + SwPaM& rPam, + SwDeleteFlags const flags, + bool bFullPara, + bool bCalledByTableCpy ) + : SwUndo(SwUndoId::DELETE, &rPam.GetDoc()), + SwUndRng( rPam ), + m_nNode(0), + m_nNdDiff(0), + m_nSectDiff(0), + m_nReplaceDummy(0), + m_nSetPos(0), + m_bGroup( false ), + m_bBackSp( false ), + m_bJoinNext( false ), + m_bTableDelLastNd( false ), + // bFullPara is set e.g. if an empty paragraph before a table is deleted + m_bDelFullPara( bFullPara ), + m_bResetPgDesc( false ), + m_bResetPgBrk( false ), + m_bFromTableCopy( bCalledByTableCpy ) + , m_DeleteFlags(flags) +{ + assert(!m_bDelFullPara || !(m_DeleteFlags & SwDeleteFlags::ArtificialSelection)); + + m_bCacheComment = false; + + SwDoc& rDoc = rPam.GetDoc(); + + if( !rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + m_pRedlSaveData.reset(new SwRedlineSaveDatas); + if( !FillSaveData( rPam, *m_pRedlSaveData )) + { + m_pRedlSaveData.reset(); + } + } + + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + + // delete all footnotes for now + auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition* + + // Step 1. deletion/record of content indices + if( m_bDelFullPara ) + { + OSL_ENSURE( rPam.HasMark(), "PaM without Mark" ); + DelContentIndex( *rPam.GetMark(), *rPam.GetPoint(), + DelContentType(DelContentType::AllMask | DelContentType::CheckNoCntnt) ); + + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + DelBookmarks(pStt->GetNode(), pEnd->GetNode()); + } + else + { + DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(), + DelContentType::AllMask + | ((m_DeleteFlags & SwDeleteFlags::ArtificialSelection) ? DelContentType::Replace : DelContentType(0))); + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + if (m_nEndNode - m_nSttNode > SwNodeOffset(1)) // check for fully selected nodes + { + SwNodeIndex const start(pStt->GetNode(), +1); + DelBookmarks(start.GetNode(), pEnd->GetNode()); + } + } + + m_nSetPos = m_pHistory ? m_pHistory->Count() : 0; + + // Is already anything deleted? + m_nNdDiff = m_nSttNode - pStt->GetNodeIndex(); + + m_bJoinNext = !bFullPara && pEnd == rPam.GetPoint(); + m_bBackSp = !bFullPara && !m_bJoinNext; + + SwTextNode *pSttTextNd = nullptr, *pEndTextNd = nullptr; + if( !bFullPara ) + { + pSttTextNd = pStt->GetNode().GetTextNode(); + pEndTextNd = m_nSttNode == m_nEndNode + ? pSttTextNd + : pEnd->GetNode().GetTextNode(); + } + else if (m_pRedlSaveData) + { + DelFullParaMoveFrames(rDoc, *this, *m_pRedlSaveData); + } + + bool bMoveNds = *pStt != *pEnd // any area still existent? + && ( SaveContent( pStt, pEnd, pSttTextNd, pEndTextNd ) || m_bFromTableCopy ); + + if( pSttTextNd && pEndTextNd && pSttTextNd != pEndTextNd ) + { + // two different TextNodes, thus save also the TextFormatCollection + m_pHistory->AddColl(pSttTextNd->GetTextColl(), pStt->GetNodeIndex(), SwNodeType::Text); + m_pHistory->AddColl(pEndTextNd->GetTextColl(), pEnd->GetNodeIndex(), SwNodeType::Text); + + if( !m_bJoinNext ) // Selection from bottom to top + { + // When using JoinPrev() all AUTO-PageBreak's will be copied + // correctly. To restore them with UNDO, Auto-PageBreak of the + // EndNode needs to be reset. Same for PageDesc and ColBreak. + if( pEndTextNd->HasSwAttrSet() ) + { + SwRegHistory aRegHist( *pEndTextNd, m_pHistory.get() ); + if( SfxItemState::SET == pEndTextNd->GetpSwAttrSet()->GetItemState( + RES_BREAK, false ) ) + pEndTextNd->ResetAttr( RES_BREAK ); + if( pEndTextNd->HasSwAttrSet() && + SfxItemState::SET == pEndTextNd->GetpSwAttrSet()->GetItemState( + RES_PAGEDESC, false ) ) + pEndTextNd->ResetAttr( RES_PAGEDESC ); + } + } + } + + // Move now also the PaM. The SPoint is at the beginning of a SSelection. + if( pEnd == rPam.GetPoint() && ( !bFullPara || pSttTextNd || pEndTextNd ) ) + rPam.Exchange(); + + if( !pSttTextNd && !pEndTextNd ) + rPam.GetPoint()->Adjust(SwNodeOffset(-1)); + rPam.DeleteMark(); // the SPoint is in the selection + + if( !pEndTextNd ) + m_nEndContent = 0; + if( !pSttTextNd ) + m_nSttContent = 0; + + if( bMoveNds ) // Do Nodes exist that need to be moved? + { + SwNodes& rNds = rDoc.GetUndoManager().GetUndoNodes(); + SwNodes& rDocNds = rDoc.GetNodes(); + SwNodeRange aRg( rDocNds, m_nSttNode - m_nNdDiff, m_nEndNode - m_nNdDiff ); + if( !bFullPara && !pEndTextNd && + aRg.aEnd.GetNode() != rDoc.GetNodes().GetEndOfContent() ) + { + SwNode* pNode = aRg.aEnd.GetNode().StartOfSectionNode(); + if( pNode->GetIndex() >= m_nSttNode - m_nNdDiff ) + ++aRg.aEnd; // Deletion of a complete table + } + SwNode* pTmpNd; + // Step 2: Expand selection if necessary + if( m_bJoinNext || bFullPara ) + { + // If all content of a section will be moved into Undo, the section + // itself should be moved completely. + while( aRg.aEnd.GetIndex() + 2 < rDocNds.Count() && + ( (pTmpNd = rDocNds[ aRg.aEnd.GetIndex()+1 ])->IsEndNode() && + pTmpNd->StartOfSectionNode()->IsSectionNode() && + pTmpNd->StartOfSectionNode()->GetIndex() >= aRg.aStart.GetIndex() ) ) + ++aRg.aEnd; + m_nReplaceDummy = aRg.aEnd.GetIndex() + m_nNdDiff - m_nEndNode; + if( m_nReplaceDummy ) + { // The selection has been expanded, because + ++aRg.aEnd; + if( pEndTextNd ) + { + // The end text node has to leave the (expanded) selection + // The dummy is needed because MoveNodes deletes empty + // sections + ++m_nReplaceDummy; + SwNodeRange aMvRg( *pEndTextNd, SwNodeOffset(0), *pEndTextNd, SwNodeOffset(1) ); + SwPosition aSplitPos( *pEndTextNd ); + ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo()); + rDoc.getIDocumentContentOperations().SplitNode( aSplitPos, false ); + rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aEnd.GetNode() ); + --aRg.aEnd; + } + else + m_nReplaceDummy = SwNodeOffset(0); + } + } + if( m_bBackSp || bFullPara ) + { + // See above, the selection has to be expanded if there are "nearly + // empty" sections and a replacement dummy has to be set if needed. + while( SwNodeOffset(1) < aRg.aStart.GetIndex() && + ( (pTmpNd = rDocNds[ aRg.aStart.GetIndex()-1 ])->IsSectionNode() && + pTmpNd->EndOfSectionIndex() < aRg.aEnd.GetIndex() ) ) + --aRg.aStart; + if( pSttTextNd ) + { + m_nReplaceDummy = m_nSttNode - m_nNdDiff - aRg.aStart.GetIndex(); + if( m_nReplaceDummy ) + { + SwNodeRange aMvRg( *pSttTextNd, SwNodeOffset(0), *pSttTextNd, SwNodeOffset(1) ); + SwPosition aSplitPos( *pSttTextNd ); + ::sw::UndoGuard const ug(rDoc.GetIDocumentUndoRedo()); + rDoc.getIDocumentContentOperations().SplitNode( aSplitPos, false ); + rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aStart.GetNode() ); + --aRg.aStart; + } + } + } + + if( m_bFromTableCopy ) + { + if( !pEndTextNd ) + { + if( pSttTextNd ) + ++aRg.aStart; + else if( !bFullPara && !aRg.aEnd.GetNode().IsContentNode() ) + --aRg.aEnd; + } + } + else if (pSttTextNd && (pEndTextNd || pSttTextNd->GetText().getLength())) + ++aRg.aStart; + + // Step 3: Moving into UndoArray... + m_nNode = rNds.GetEndOfContent().GetIndex(); + rDocNds.MoveNodes( aRg, rNds, rNds.GetEndOfContent() ); + m_oMvStt.emplace( rNds, m_nNode ); + // remember difference! + m_nNode = rNds.GetEndOfContent().GetIndex() - m_nNode; + + if( pSttTextNd && pEndTextNd ) + { + //Step 4: Moving around sections + m_nSectDiff = aRg.aEnd.GetIndex() - aRg.aStart.GetIndex(); + // nSect is the number of sections which starts(ends) between start + // and end node of the selection. The "loser" paragraph has to be + // moved into the section(s) of the "winner" paragraph + if( m_nSectDiff ) + { + if( m_bJoinNext ) + { + SwNodeRange aMvRg( *pEndTextNd, SwNodeOffset(0), *pEndTextNd, SwNodeOffset(1) ); + rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aStart.GetNode() ); + } + else + { + SwNodeRange aMvRg( *pSttTextNd, SwNodeOffset(0), *pSttTextNd, SwNodeOffset(1) ); + rDocNds.MoveNodes( aMvRg, rDocNds, aRg.aEnd.GetNode() ); + } + } + } + if( m_nSectDiff || m_nReplaceDummy ) + lcl_MakeAutoFrames( *rDoc.GetSpzFrameFormats(), + m_bJoinNext ? pEndTextNd->GetIndex() : pSttTextNd->GetIndex() ); + } + else + m_nNode = SwNodeOffset(0); // moved no node -> no difference at the end + + // Are there any Nodes that got deleted before that (FootNotes + // have ContentNodes)? + if( !pSttTextNd && !pEndTextNd ) + { + m_nNdDiff = m_nSttNode - rPam.GetPoint()->GetNodeIndex() - (bFullPara ? 0 : 1); + rPam.Move( fnMoveForward, GoInNode ); + } + else + { + m_nNdDiff = m_nSttNode; + if( m_nSectDiff && m_bBackSp ) + m_nNdDiff += m_nSectDiff; + m_nNdDiff -= rPam.GetPoint()->GetNodeIndex(); + } + + // is a history necessary here at all? + if( m_pHistory && !m_pHistory->Count() ) + m_pHistory.reset(); +} + +bool SwUndoDelete::SaveContent( const SwPosition* pStt, const SwPosition* pEnd, + SwTextNode* pSttTextNd, SwTextNode* pEndTextNd ) +{ + SwNodeOffset nNdIdx = pStt->GetNodeIndex(); + // 1 - copy start in Start-String + if( pSttTextNd ) + { + bool bOneNode = m_nSttNode == m_nEndNode; + SwRegHistory aRHst( *pSttTextNd, m_pHistory.get() ); + // always save all text atttibutes because of possibly overlapping + // areas of on/off + m_pHistory->CopyAttr( pSttTextNd->GetpSwpHints(), nNdIdx, + 0, pSttTextNd->GetText().getLength(), true ); + if( !bOneNode && pSttTextNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pSttTextNd->GetpSwAttrSet(), nNdIdx ); + + // the length might have changed (!!Fields!!) + sal_Int32 nLen = (bOneNode + ? pEnd->GetContentIndex() + : pSttTextNd->GetText().getLength()) + - pStt->GetContentIndex(); + + // delete now also the text (all attribute changes are added to + // UNDO history) + m_aSttStr = pSttTextNd->GetText().copy(m_nSttContent, nLen); + pSttTextNd->EraseText( *pStt, nLen ); + if( pSttTextNd->GetpSwpHints() ) + pSttTextNd->GetpSwpHints()->DeRegister(); + + // METADATA: store + bool emptied( !m_aSttStr->isEmpty() && !pSttTextNd->Len() ); + if (!bOneNode || emptied) // merging may overwrite xmlids... + { + m_pMetadataUndoStart = emptied + ? pSttTextNd->CreateUndoForDelete() + : pSttTextNd->CreateUndo(); + } + + if( bOneNode ) + return false; // stop moving more nodes + } + + // 2 - copy end into End-String + if( pEndTextNd ) + { + SwContentIndex aEndIdx( pEndTextNd ); + nNdIdx = pEnd->GetNodeIndex(); + SwRegHistory aRHst( *pEndTextNd, m_pHistory.get() ); + + // always save all text atttibutes because of possibly overlapping + // areas of on/off + m_pHistory->CopyAttr( pEndTextNd->GetpSwpHints(), nNdIdx, 0, + pEndTextNd->GetText().getLength(), true ); + + if( pEndTextNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pEndTextNd->GetpSwAttrSet(), nNdIdx ); + + // delete now also the text (all attribute changes are added to + // UNDO history) + m_aEndStr = pEndTextNd->GetText().copy( 0, pEnd->GetContentIndex() ); + pEndTextNd->EraseText( aEndIdx, pEnd->GetContentIndex() ); + if( pEndTextNd->GetpSwpHints() ) + pEndTextNd->GetpSwpHints()->DeRegister(); + + // METADATA: store + bool emptied = !m_aEndStr->isEmpty() && !pEndTextNd->Len(); + + m_pMetadataUndoEnd = emptied + ? pEndTextNd->CreateUndoForDelete() + : pEndTextNd->CreateUndo(); + } + + // if there are only two Nodes then we're done + if( ( pSttTextNd || pEndTextNd ) && m_nSttNode + 1 == m_nEndNode ) + return false; // do not move any Node + + return true; // move Nodes lying in between +} + +bool SwUndoDelete::CanGrouping( SwDoc& rDoc, const SwPaM& rDelPam ) +{ + // Is Undo greater than one Node (that is Start and EndString)? + if( !m_aSttStr || m_aSttStr->isEmpty() || m_aEndStr ) + return false; + + // only the deletion of single char's can be condensed + if( m_nSttNode != m_nEndNode || ( !m_bGroup && m_nSttContent+1 != m_nEndContent )) + return false; + + auto [pStt, pEnd] = rDelPam.StartEnd(); // SwPosition* + + if( pStt->GetNode() != pEnd->GetNode() || + pStt->GetContentIndex()+1 != pEnd->GetContentIndex() || + pEnd->GetNodeIndex() != m_nSttNode ) + return false; + + // Distinguish between BackSpace and Delete because the Undo array needs to + // be constructed differently! + if( pEnd->GetContentIndex() == m_nSttContent ) + { + if( m_bGroup && !m_bBackSp ) return false; + m_bBackSp = true; + } + // note: compare m_nSttContent here because the text isn't there any more! + else if( pStt->GetContentIndex() == m_nSttContent ) + { + if( m_bGroup && m_bBackSp ) return false; + m_bBackSp = false; + } + else + return false; + + // are both Nodes (Node/Undo array) TextNodes at all? + SwTextNode * pDelTextNd = pStt->GetNode().GetTextNode(); + if( !pDelTextNd ) return false; + + sal_Int32 nUChrPos = m_bBackSp ? 0 : m_aSttStr->getLength()-1; + sal_Unicode cDelChar = pDelTextNd->GetText()[ pStt->GetContentIndex() ]; + CharClass& rCC = GetAppCharClass(); + if( ( CH_TXTATR_BREAKWORD == cDelChar || CH_TXTATR_INWORD == cDelChar ) || + rCC.isLetterNumeric( OUString( cDelChar ), 0 ) != + rCC.isLetterNumeric( *m_aSttStr, nUChrPos ) ) + return false; + + // tdf#132725 - if at-char/at-para flys would be deleted, don't group! + // DelContentIndex() would be called at the wrong time here, the indexes + // in the stored SwHistoryTextFlyCnt would be wrong when Undo is invoked + if (IsFlySelectedByCursor(rDoc, *pStt, *pEnd)) + { + return false; + } + + if ((m_DeleteFlags & SwDeleteFlags::ArtificialSelection) && m_pHistory) + { + IDocumentMarkAccess const& rIDMA(*rDoc.getIDocumentMarkAccess()); + for (auto i = m_pHistory->Count(); 0 < i; ) + { + --i; + SwHistoryHint const*const pHistory((*m_pHistory)[i]); + if (pHistory->Which() == HSTRY_BOOKMARK) + { + SwHistoryBookmark const*const pHistoryBM( + static_cast(pHistory)); + auto const ppMark(rIDMA.findMark(pHistoryBM->GetName())); + if (ppMark != rIDMA.getAllMarksEnd() + && (m_bBackSp + ? ((**ppMark).GetMarkPos() == *pStt) + : ((**ppMark).IsExpanded() + && (**ppMark).GetOtherMarkPos() == *pEnd))) + { // prevent grouping that would delete this mark on Redo() + return false; + } + } + } + } + + { + SwRedlineSaveDatas aTmpSav; + const bool bSaved = FillSaveData( rDelPam, aTmpSav, false ); + + bool bOk = ( !m_pRedlSaveData && !bSaved ) || + ( m_pRedlSaveData && bSaved && + SwUndo::CanRedlineGroup( *m_pRedlSaveData, aTmpSav, m_bBackSp )); + // aTmpSav.DeleteAndDestroyAll(); + if( !bOk ) + return false; + + rDoc.getIDocumentRedlineAccess().DeleteRedline( rDelPam, false, RedlineType::Any ); + } + + // Both 'deletes' can be consolidated, so 'move' the related character + if( m_bBackSp ) + m_nSttContent--; // BackSpace: add char to array! + else + { + m_nEndContent++; // Delete: attach char at the end + nUChrPos++; + } + m_aSttStr = m_aSttStr->replaceAt( nUChrPos, 0, rtl::OUStringChar(cDelChar) ); + pDelTextNd->EraseText( *pStt, 1 ); + + m_bGroup = true; + return true; +} + +SwUndoDelete::~SwUndoDelete() +{ + if( m_oMvStt ) // Delete also the selection from UndoNodes array + { + // Insert saves content in IconSection + m_oMvStt->GetNode().GetNodes().Delete( *m_oMvStt, m_nNode ); + m_oMvStt.reset(); + } + m_pRedlSaveData.reset(); +} + +static SwRewriter lcl_RewriterFromHistory(SwHistory & rHistory) +{ + SwRewriter aRewriter; + + bool bDone = false; + + for ( sal_uInt16 n = 0; n < rHistory.Count(); n++) + { + OUString aDescr = rHistory[n]->GetDescription(); + + if (!aDescr.isEmpty()) + { + aRewriter.AddRule(UndoArg2, aDescr); + + bDone = true; + break; + } + } + + if (! bDone) + { + aRewriter.AddRule(UndoArg2, SwResId(STR_FIELD)); + } + + return aRewriter; +} + +static bool lcl_IsSpecialCharacter(sal_Unicode nChar) +{ + switch (nChar) + { + case CH_TXTATR_BREAKWORD: + case CH_TXTATR_INWORD: + case CH_TXTATR_TAB: + case CH_TXTATR_NEWLINE: + case CH_TXT_ATR_INPUTFIELDSTART: + case CH_TXT_ATR_INPUTFIELDEND: + case CH_TXT_ATR_FORMELEMENT: + case CH_TXT_ATR_FIELDSTART: + case CH_TXT_ATR_FIELDSEP: + case CH_TXT_ATR_FIELDEND: + return true; + + default: + break; + } + + return false; +} + +static OUString lcl_DenotedPortion(std::u16string_view rStr, sal_Int32 nStart, sal_Int32 nEnd, bool bQuoted) +{ + OUString aResult; + + auto nCount = nEnd - nStart; + if (nCount > 0) + { + sal_Unicode cLast = rStr[nEnd - 1]; + if (lcl_IsSpecialCharacter(cLast)) + { + switch(cLast) + { + case CH_TXTATR_TAB: + aResult = SwResId(STR_UNDO_TABS, nCount); + + break; + case CH_TXTATR_NEWLINE: + aResult = SwResId(STR_UNDO_NLS, nCount); + + break; + + case CH_TXTATR_INWORD: + case CH_TXTATR_BREAKWORD: + aResult = SwRewriter::GetPlaceHolder(UndoArg2); + break; + + case CH_TXT_ATR_INPUTFIELDSTART: + case CH_TXT_ATR_INPUTFIELDEND: + case CH_TXT_ATR_FORMELEMENT: + case CH_TXT_ATR_FIELDSTART: + case CH_TXT_ATR_FIELDSEP: + case CH_TXT_ATR_FIELDEND: + break; // nothing? + + default: + assert(!"unexpected special character"); + break; + } + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, OUString::number(nCount)); + aResult = aRewriter.Apply(aResult); + } + else if (bQuoted) + { + aResult = SwResId(STR_START_QUOTE) + + rStr.substr(nStart, nCount) + + SwResId(STR_END_QUOTE); + } + else + aResult = rStr.substr(nStart, nCount); + } + + return aResult; +} + +OUString DenoteSpecialCharacters(std::u16string_view aStr, bool bQuoted) +{ + OUStringBuffer aResult; + + if (!aStr.empty()) + { + bool bStart = false; + sal_Int32 nStart = 0; + sal_Unicode cLast = 0; + + for( size_t i = 0; i < aStr.size(); i++) + { + if (lcl_IsSpecialCharacter(aStr[i])) + { + if (cLast != aStr[i]) + bStart = true; + + } + else + { + if (lcl_IsSpecialCharacter(cLast)) + bStart = true; + } + + if (bStart) + { + aResult.append(lcl_DenotedPortion(aStr, nStart, i, bQuoted)); + + nStart = i; + bStart = false; + } + + cLast = aStr[i]; + } + + aResult.append(lcl_DenotedPortion(aStr, nStart, aStr.size(), bQuoted)); + } + else + aResult = SwRewriter::GetPlaceHolder(UndoArg2); + + return aResult.makeStringAndClear(); +} + +SwRewriter SwUndoDelete::GetRewriter() const +{ + SwRewriter aResult; + + if (m_nNode != SwNodeOffset(0)) + { + if (!m_sTableName.isEmpty()) + { + + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, SwResId(STR_START_QUOTE)); + aRewriter.AddRule(UndoArg2, m_sTableName); + aRewriter.AddRule(UndoArg3, SwResId(STR_END_QUOTE)); + + OUString sTmp = aRewriter.Apply(SwResId(STR_TABLE_NAME)); + aResult.AddRule(UndoArg1, sTmp); + } + else + aResult.AddRule(UndoArg1, SwResId(STR_PARAGRAPHS)); + } + else + { + OUString aStr; + + if (m_aSttStr && m_aEndStr && m_aSttStr->isEmpty() && + m_aEndStr->isEmpty()) + { + aStr = SwResId(STR_PARAGRAPH_UNDO); + } + else + { + std::optional aTmpStr; + if (m_aSttStr) + aTmpStr = m_aSttStr; + else if (m_aEndStr) + aTmpStr = m_aEndStr; + + if (aTmpStr) + { + aStr = DenoteSpecialCharacters(*aTmpStr); + } + else + { + aStr = SwRewriter::GetPlaceHolder(UndoArg2); + } + } + + aStr = ShortenString(aStr, nUndoStringLength, SwResId(STR_LDOTS)); + if (m_pHistory) + { + SwRewriter aRewriter = lcl_RewriterFromHistory(*m_pHistory); + aStr = aRewriter.Apply(aStr); + } + + aResult.AddRule(UndoArg1, aStr); + } + + return aResult; +} + +// Every object, anchored "AtContent" will be reanchored at rPos +static void lcl_ReAnchorAtContentFlyFrames(const sw::FrameFormats& rSpzs, const SwPosition &rPos, SwNodeOffset nOldIdx ) +{ + const SwFormatAnchor* pAnchor; + for(auto pSpz: rSpzs) + { + pAnchor = &pSpz->GetAnchor(); + if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA) + { + SwNode* pAnchorNode = pAnchor->GetAnchorNode(); + if( pAnchorNode && nOldIdx == pAnchorNode->GetIndex() ) + { + SwFormatAnchor aAnch( *pAnchor ); + aAnch.SetAnchor( &rPos ); + pSpz->SetFormatAttr( aAnch ); + } + } + } +} + +void SwUndoDelete::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc& rDoc = rContext.GetDoc(); + + SwNodeOffset nCalcStt = m_nSttNode - m_nNdDiff; + + if( m_nSectDiff && m_bBackSp ) + nCalcStt += m_nSectDiff; + + SwNodeIndex aIdx(rDoc.GetNodes(), nCalcStt); + SwNode* pInsNd = &aIdx.GetNode(); + SwNode* pMovedNode = nullptr; + + { // code block so that SwPosition is detached when deleting a Node + SwPosition aPos( aIdx ); + if( !m_bDelFullPara ) + { + assert(!m_bTableDelLastNd || pInsNd->IsTextNode()); + if( pInsNd->IsTableNode() ) + { + pInsNd = rDoc.GetNodes().MakeTextNode( aIdx.GetNode(), + rDoc.GetDfltTextFormatColl() ); + --aIdx; + aPos.Assign( *pInsNd->GetContentNode(), m_nSttContent ); + } + else + { + if( pInsNd->IsContentNode() ) + aPos.SetContent( m_nSttContent ); + if( !m_bTableDelLastNd ) + pInsNd = nullptr; // do not delete Node! + } + } + else + pInsNd = nullptr; // do not delete Node! + + bool bNodeMove = SwNodeOffset(0) != m_nNode; + + if( m_aEndStr ) + { + // discard attributes since they all saved! + SwTextNode * pTextNd; + if (!m_bDelFullPara && aPos.GetNode().IsSectionNode()) + { // tdf#134250 section node wasn't deleted; but aPos must point to it in bNodeMove case below + assert(m_nSttContent == 0); + assert(!m_aSttStr); + pTextNd = rDoc.GetNodes()[aPos.GetNodeIndex() + 1]->GetTextNode(); + } + else + { + pTextNd = aPos.GetNode().GetTextNode(); + } + + if( pTextNd && pTextNd->HasSwAttrSet() ) + pTextNd->ResetAllAttr(); + + if( pTextNd && pTextNd->GetpSwpHints() ) + pTextNd->ClearSwpHintsArr( true ); + + if( m_aSttStr && !m_bFromTableCopy ) + { + SwNodeOffset nOldIdx = aPos.GetNodeIndex(); + rDoc.getIDocumentContentOperations().SplitNode( aPos, false ); + // After the split all objects are anchored at the first + // paragraph, but the pHistory of the fly frame formats relies + // on anchoring at the start of the selection + // => selection backwards needs a correction. + if( m_bBackSp ) + lcl_ReAnchorAtContentFlyFrames(*rDoc.GetSpzFrameFormats(), aPos, nOldIdx); + pTextNd = aPos.GetNode().GetTextNode(); + } + assert(pTextNd); // else where does m_aEndStr come from? + if( pTextNd ) + { + SwContentIndex aTmpIdx(pTextNd, aPos.GetContentIndex()); + OUString const ins( pTextNd->InsertText(*m_aEndStr, aTmpIdx, + SwInsertFlags::NOHINTEXPAND) ); + assert(ins.getLength() == m_aEndStr->getLength()); // must succeed + (void) ins; + // METADATA: restore + pTextNd->RestoreMetadata(m_pMetadataUndoEnd); + } + } + else if (m_aSttStr && bNodeMove && pInsNd == nullptr) + { + SwTextNode * pNd = aPos.GetNode().GetTextNode(); + if( pNd ) + { + if (m_nSttContent < pNd->GetText().getLength()) + { + SwNodeOffset nOldIdx = aPos.GetNodeIndex(); + rDoc.getIDocumentContentOperations().SplitNode( aPos, false ); + if( m_bBackSp ) + lcl_ReAnchorAtContentFlyFrames(*rDoc.GetSpzFrameFormats(), aPos, nOldIdx); + } + else + aPos.Adjust(SwNodeOffset(+1)); + } + } + if( m_nSectDiff ) + { + SwNodeOffset nMoveIndex = aPos.GetNodeIndex(); + SwNodeOffset nDiff(0); + if( m_bJoinNext ) + { + nMoveIndex += m_nSectDiff + 1; + pMovedNode = &aPos.GetNode(); + } + else + { + nMoveIndex -= m_nSectDiff + 1; + ++nDiff; + } + SwNodeIndex aMvIdx(rDoc.GetNodes(), nMoveIndex); + SwNodeRange aRg( aPos.GetNode(), SwNodeOffset(0) - nDiff, aPos.GetNode(), SwNodeOffset(1) - nDiff ); + aPos.Adjust(SwNodeOffset(-1)); + if( !m_bJoinNext ) + pMovedNode = &aPos.GetNode(); + rDoc.GetNodes().MoveNodes(aRg, rDoc.GetNodes(), aMvIdx.GetNode()); + aPos.Adjust(SwNodeOffset(+1)); + } + + if( bNodeMove ) + { + SwNodeRange aRange( *m_oMvStt, SwNodeOffset(0), *m_oMvStt, m_nNode ); + SwNodeIndex aCopyIndex( aPos.GetNode(), -1 ); + rDoc.GetUndoManager().GetUndoNodes().Copy_(aRange, aPos.GetNode(), + // sw_redlinehide: delay creating frames: the flags on the + // nodes aren't necessarily up-to-date, and the redlines + // from m_pRedlSaveData aren't applied yet... + false); + + if( m_nReplaceDummy ) + { + SwNodeOffset nMoveIndex; + if( m_bJoinNext ) + { + nMoveIndex = m_nEndNode - m_nNdDiff; + aPos.Assign( nMoveIndex + m_nReplaceDummy ); + } + else + { + aPos.Assign( aCopyIndex ); + nMoveIndex = aPos.GetNodeIndex() + m_nReplaceDummy + 1; + } + SwNodeIndex aMvIdx(rDoc.GetNodes(), nMoveIndex); + SwNodeRange aRg( aPos.GetNode(), SwNodeOffset(0), aPos.GetNode(), SwNodeOffset(1) ); + pMovedNode = &aPos.GetNode(); + // tdf#131684 without deleting frames + rDoc.GetNodes().MoveNodes(aRg, rDoc.GetNodes(), aMvIdx.GetNode(), false); + rDoc.GetNodes().Delete( aMvIdx); + } + } + + if( m_aSttStr ) + { + aPos.Assign( m_nSttNode - m_nNdDiff + ( m_bJoinNext ? SwNodeOffset(0) : m_nReplaceDummy ) ); + SwTextNode * pTextNd = aPos.GetNode().GetTextNode(); + // If more than a single Node got deleted, also all "Node" + // attributes were saved + if (pTextNd != nullptr) + { + if( pTextNd->HasSwAttrSet() && bNodeMove && !m_aEndStr ) + pTextNd->ResetAllAttr(); + + if( pTextNd->GetpSwpHints() ) + pTextNd->ClearSwpHintsArr( true ); + + // SectionNode mode and selection from top to bottom: + // -> in StartNode is still the rest of the Join => delete + aPos.SetContent( m_nSttContent ); + pTextNd->SetInSwUndo(true); + OUString const ins( pTextNd->InsertText(*m_aSttStr, aPos, + SwInsertFlags::NOHINTEXPAND) ); + pTextNd->SetInSwUndo(false); + assert(ins.getLength() == m_aSttStr->getLength()); // must succeed + (void) ins; + // METADATA: restore + pTextNd->RestoreMetadata(m_pMetadataUndoStart); + } + } + + if( m_pHistory ) + { + m_pHistory->TmpRollback(&rDoc, m_nSetPos, false); + if( m_nSetPos ) // there were Footnodes/FlyFrames + { + // are there others than these ones? + if( m_nSetPos < m_pHistory->Count() ) + { + // if so save the attributes of the others + SwHistory aHstr; + aHstr.Move( 0, m_pHistory.get(), m_nSetPos ); + m_pHistory->Rollback(&rDoc); + m_pHistory->Move( 0, &aHstr ); + } + else + { + m_pHistory->Rollback(&rDoc); + m_pHistory.reset(); + } + } + } + + if( m_bResetPgDesc || m_bResetPgBrk ) + { + sal_uInt16 nStt = m_bResetPgDesc ? sal_uInt16(RES_PAGEDESC) : sal_uInt16(RES_BREAK); + sal_uInt16 nEnd = m_bResetPgBrk ? sal_uInt16(RES_BREAK) : sal_uInt16(RES_PAGEDESC); + + SwNode* pNode = rDoc.GetNodes()[ m_nEndNode + 1 ]; + if( pNode->IsContentNode() ) + static_cast(pNode)->ResetAttr( nStt, nEnd ); + else if( pNode->IsTableNode() ) + static_cast(pNode)->GetTable().GetFrameFormat()->ResetFormatAttr( nStt, nEnd ); + } + } + // delete the temporarily added Node + if (pInsNd && !m_bTableDelLastNd) + { + assert(&aIdx.GetNode() == pInsNd); + rDoc.GetNodes().Delete( aIdx ); + } + if( m_pRedlSaveData ) + SetSaveData(rDoc, *m_pRedlSaveData); + + SwNodeOffset delFullParaEndNode(m_nEndNode); + if (m_bDelFullPara && m_pRedlSaveData) + { + SwTextNode * pFirstMergedDeletedTextNode(nullptr); + SwTextNode *const pNextNode = FindFirstAndNextNode(rDoc, *this, + *m_pRedlSaveData, pFirstMergedDeletedTextNode); + if (pNextNode) + { + bool bNonMerged(false); + std::vector frames; + SwIterator aIter(*pNextNode); + for (SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next()) + { + if (pFrame->getRootFrame()->HasMergedParas()) + { + frames.push_back(pFrame); + } + else + { + bNonMerged = true; + } + } + for (SwTextFrame *const pFrame : frames) + { + // could either destroy the text frames, or move them... + // destroying them would have the advantage that we don't + // need special code to *exclude* pFirstMergedDeletedTextNode + // from MakeFrames for the layouts in Hide mode but not + // layouts in Show mode ... + // ... except that MakeFrames won't create them then :( + pFrame->RegisterToNode(*pFirstMergedDeletedTextNode); + assert(pFrame->GetMergedPara()); + assert(!bNonMerged); // delFullParaEndNode is such an awful hack + (void) bNonMerged; + delFullParaEndNode = pFirstMergedDeletedTextNode->GetIndex(); + } + } + } + else if (m_aSttStr && (!m_bFromTableCopy || SwNodeOffset(0) != m_nNode)) + { + // only now do we have redlines in the document again; fix up the split + // frames + SwTextNode *const pStartNode(aIdx.GetNodes()[m_nSttNode]->GetTextNode()); + assert(pStartNode); + sw::RecreateStartTextFrames(*pStartNode); + } + + // create frames after SetSaveData has recreated redlines + if (SwNodeOffset(0) != m_nNode) + { + // tdf#136453 only if section nodes at the start + if (m_bBackSp && m_nReplaceDummy != SwNodeOffset(0)) + { + // tdf#134252 *first* create outer section frames + // note: text node m_nSttNode currently has frame with an upper; + // there's a hack in InsertCnt_() to move it below new section frame + SwNode& start(*rDoc.GetNodes()[m_nSttNode - m_nReplaceDummy]); + SwNode& end(*rDoc.GetNodes()[m_nSttNode]); // exclude m_nSttNode + ::MakeFrames(&rDoc, start, end); + } + // tdf#121031 if the start node is a text node, it already has a frame; + // if it's a table, it does not + // tdf#109376 exception: end on non-text-node -> start node was inserted + assert(!m_bDelFullPara || (m_nSectDiff == SwNodeOffset(0))); + SwNode& start(*rDoc.GetNodes()[m_nSttNode + + ((m_bDelFullPara || !rDoc.GetNodes()[m_nSttNode]->IsTextNode() || pInsNd) + ? 0 : 1)]); + // don't include end node in the range: it may have been merged already + // by the start node, or it may be merged by one of the moved nodes, + // but if it isn't merged, its current frame(s) should be good... + SwNode& end(*rDoc.GetNodes()[ m_bDelFullPara + ? delFullParaEndNode + // tdf#147310 SwDoc::DeleteRowCol() may delete whole table - end must be node following table! + : (m_nEndNode + (rDoc.GetNodes()[m_nSttNode]->IsTableNode() && rDoc.GetNodes()[m_nEndNode]->IsEndNode() ? 1 : 0))]); + ::MakeFrames(&rDoc, start, end); + } + + if (pMovedNode) + { // probably better do this after creating all frames + lcl_MakeAutoFrames(*rDoc.GetSpzFrameFormats(), pMovedNode->GetIndex()); + } + + // tdf#134021 only after MakeFrames(), because it may be the only node + // that has layout frames + if (pInsNd && m_bTableDelLastNd) + { + assert(&aIdx.GetNode() == pInsNd); + SwPaM tmp(aIdx, aIdx); + rDoc.getIDocumentContentOperations().DelFullPara(tmp); + } + + AddUndoRedoPaM(rContext, true); +} + +void SwUndoDelete::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam = AddUndoRedoPaM(rContext); + SwDoc& rDoc = rPam.GetDoc(); + + if( m_pRedlSaveData ) + { + const bool bSuccess = FillSaveData(rPam, *m_pRedlSaveData); + OSL_ENSURE(bSuccess, + "SwUndoDelete::Redo: used to have redline data, but now none?"); + if (!bSuccess) + { + m_pRedlSaveData.reset(); + } + } + + if( !m_bDelFullPara ) + { + // tdf#128739 correct cursors but do not delete bookmarks yet + ::PaMCorrAbs(rPam, *rPam.End()); + SetPaM(rPam); + + if( !m_bJoinNext ) // then restore selection from bottom to top + rPam.Exchange(); + } + + if( m_pHistory ) // are the attributes saved? + { + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + SwHistory aHstr; + aHstr.Move( 0, m_pHistory.get() ); + + if( m_bDelFullPara ) + { + OSL_ENSURE( rPam.HasMark(), "PaM without Mark" ); + DelContentIndex( *rPam.GetMark(), *rPam.GetPoint(), + DelContentType(DelContentType::AllMask | DelContentType::CheckNoCntnt) ); + + DelBookmarks(rPam.GetMark()->GetNode(), rPam.GetPoint()->GetNode()); + } + else + { + DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(), + DelContentType::AllMask + | ((m_DeleteFlags & SwDeleteFlags::ArtificialSelection) ? DelContentType::Replace : DelContentType(0))); + } + m_nSetPos = m_pHistory ? m_pHistory->Count() : 0; + + m_pHistory->Move( m_nSetPos, &aHstr ); + } + else + { + if( m_bDelFullPara ) + { + OSL_ENSURE( rPam.HasMark(), "PaM without Mark" ); + DelContentIndex( *rPam.GetMark(), *rPam.GetPoint(), + DelContentType(DelContentType::AllMask | DelContentType::CheckNoCntnt) ); + + DelBookmarks( rPam.GetMark()->GetNode(), rPam.GetPoint()->GetNode() ); + } + else + { + DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(), + DelContentType::AllMask + | ((m_DeleteFlags & SwDeleteFlags::ArtificialSelection) ? DelContentType::Replace : DelContentType(0))); + } + m_nSetPos = m_pHistory ? m_pHistory->Count() : 0; + } + + if( !m_aSttStr && !m_aEndStr ) + { + if (m_bDelFullPara && m_pRedlSaveData) + { + DelFullParaMoveFrames(rDoc, *this, *m_pRedlSaveData); + } + + SwNode& rSttNd = ( m_bDelFullPara || m_bJoinNext ) + ? rPam.GetMark()->GetNode() + : rPam.GetPoint()->GetNode(); + SwTableNode* pTableNd = rSttNd.GetTableNode(); + if( pTableNd ) + { + if( m_bTableDelLastNd ) + { + // than add again a Node at the end + const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 ); + rDoc.GetNodes().MakeTextNode( aTmpIdx.GetNode(), + rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) ); + } + + SwContentNode* pNextNd = rDoc.GetNodes()[ + pTableNd->EndOfSectionIndex()+1 ]->GetContentNode(); + if( pNextNd ) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + + if( const SwFormatPageDesc* pItem = pTableFormat->GetItemIfSet( RES_PAGEDESC, + false ) ) + pNextNd->SetAttr( *pItem ); + + if( const SvxFormatBreakItem* pItem = pTableFormat->GetItemIfSet( RES_BREAK, + false ) ) + pNextNd->SetAttr( *pItem ); + } + pTableNd->DelFrames(); + } + else if (*rPam.GetMark() == *rPam.GetPoint()) + { // paragraph with only footnote or as-char fly, delete that + // => DelContentIndex has already deleted it! nothing to do here + assert(m_nEndNode == m_nSttNode); + return; + } + + // avoid asserts from ~SwContentIndexReg for deleted nodes + SwPaM aTmp(*rPam.End()); + if (!aTmp.Move(fnMoveForward, GoInNode)) + { + *aTmp.GetPoint() = *rPam.Start(); + aTmp.Move(fnMoveBackward, GoInNode); + } + // coverity[copy_paste_error : FALSE] : GetNode() is intentional on both branches + assert(aTmp.GetPoint()->GetNode() != rPam.GetPoint()->GetNode() + && aTmp.GetPoint()->GetNode() != rPam.GetMark()->GetNode()); + ::PaMCorrAbs(rPam, *aTmp.GetPoint()); + + rPam.DeleteMark(); + + rDoc.GetNodes().Delete( rSttNd, m_nEndNode - m_nSttNode ); + } + else if( m_bDelFullPara ) + { + assert(!"dead code"); + // The Pam was incremented by one at Point (== end) to provide space + // for UNDO. This now needs to be reverted! + rPam.End()->Adjust(SwNodeOffset(-1)); + if( rPam.GetPoint()->GetNode() == rPam.GetMark()->GetNode() ) + *rPam.GetMark() = *rPam.GetPoint(); + rDoc.getIDocumentContentOperations().DelFullPara( rPam ); + } + else + rDoc.getIDocumentContentOperations().DeleteAndJoin(rPam, m_DeleteFlags); +} + +void SwUndoDelete::RepeatImpl(::sw::RepeatContext & rContext) +{ + // this action does not seem idempotent, + // so make sure it is only executed once on repeat + if (rContext.m_bDeleteRepeated) + return; + + SwPaM & rPam = rContext.GetRepeatPaM(); + SwDoc& rDoc = rPam.GetDoc(); + ::sw::GroupUndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + if( !rPam.HasMark() ) + { + rPam.SetMark(); + rPam.Move( fnMoveForward, GoInContent ); + } + if( m_bDelFullPara ) + rDoc.getIDocumentContentOperations().DelFullPara( rPam ); + else + rDoc.getIDocumentContentOperations().DeleteAndJoin( rPam ); + rContext.m_bDeleteRepeated = true; +} + +void SwUndoDelete::SetTableName(const OUString & rName) +{ + m_sTableName = rName; +} + +void SwUndoDelete::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwUndoDelete")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + SwUndo::dumpAsXml(pWriter); + SwUndoSaveContent::dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/undobj.cxx b/sw/source/core/undo/undobj.cxx new file mode 100644 index 0000000000..ce27962aa3 --- /dev/null +++ b/sw/source/core/undo/undobj.cxx @@ -0,0 +1,1768 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// This class saves the Pam as integers and can recompose those into a PaM +SwUndRng::SwUndRng() + : m_nSttNode( 0 ), m_nEndNode( 0 ), m_nSttContent( 0 ), m_nEndContent( 0 ) +{ +} + +SwUndRng::SwUndRng( const SwPaM& rPam ) +{ + SetValues( rPam ); +} + +void SwUndRng::SetValues( const SwPaM& rPam ) +{ + const SwPosition *pStt = rPam.Start(); + if( rPam.HasMark() ) + { + const SwPosition *pEnd = rPam.End(); + m_nEndNode = pEnd->GetNodeIndex(); + m_nEndContent = pEnd->GetContentIndex(); + } + else + { + // no selection !! + m_nEndNode = SwNodeOffset(0); + m_nEndContent = COMPLETE_STRING; + } + + m_nSttNode = pStt->GetNodeIndex(); + m_nSttContent = pStt->GetContentIndex(); +} + +void SwUndRng::SetPaM( SwPaM & rPam, bool bCorrToContent ) const +{ + rPam.DeleteMark(); + rPam.GetPoint()->Assign( m_nSttNode, m_nSttContent ); + SwNode& rNd = rPam.GetPointNode(); + if( !rNd.IsContentNode() && bCorrToContent ) + rPam.Move( fnMoveForward, GoInContent ); + + if( !m_nEndNode && COMPLETE_STRING == m_nEndContent ) // no selection + return ; + + rPam.SetMark(); + if( m_nSttNode == m_nEndNode && m_nSttContent == m_nEndContent ) + return; // nothing left to do + + rPam.GetPoint()->Assign( m_nEndNode, m_nEndContent ); + if( !rPam.GetPointNode().IsContentNode() && bCorrToContent ) + rPam.Move( fnMoveBackward, GoInContent ); +} + +SwPaM & SwUndRng::AddUndoRedoPaM( + ::sw::UndoRedoContext & rContext, bool const bCorrToContent) const +{ + SwCursor & rPaM( rContext.GetCursorSupplier().CreateNewShellCursor() ); + SetPaM( rPaM, bCorrToContent ); + return rPaM; +} + +void SwUndo::RemoveIdxFromSection( SwDoc& rDoc, SwNodeOffset nSttIdx, + const SwNodeOffset* pEndIdx ) +{ + SwNodeIndex aIdx( rDoc.GetNodes(), nSttIdx ); + SwNodeIndex aEndIdx( rDoc.GetNodes(), pEndIdx ? *pEndIdx + : aIdx.GetNode().EndOfSectionIndex() ); + SwPosition aPos( rDoc.GetNodes().GetEndOfPostIts() ); + SwDoc::CorrAbs( aIdx, aEndIdx, aPos, true ); +} + +void SwUndo::RemoveIdxFromRange( SwPaM& rPam, bool bMoveNext ) +{ + const SwPosition* pEnd = rPam.End(); + if( bMoveNext ) + { + if( pEnd != rPam.GetPoint() ) + rPam.Exchange(); + + SwNodeIndex aStt( rPam.GetMark()->GetNode() ); + SwNodeIndex aEnd( rPam.GetPoint()->GetNode() ); + + if( !rPam.Move( fnMoveForward ) ) + { + rPam.Exchange(); + if( !rPam.Move( fnMoveBackward ) ) + { + rPam.GetPoint()->Assign( rPam.GetDoc().GetNodes().GetEndOfPostIts() ); + } + } + + SwDoc::CorrAbs( aStt, aEnd, *rPam.GetPoint(), true ); + } + else + SwDoc::CorrAbs( rPam, *pEnd, true ); +} + +void SwUndo::RemoveIdxRel( SwNodeOffset nIdx, const SwPosition& rPos ) +{ + // Move only the Cursor. Bookmarks/TOXMarks/etc. are done by the corresponding + // JoinNext/JoinPrev + ::PaMCorrRel( *rPos.GetNode().GetNodes()[nIdx], rPos ); +} + +SwUndo::SwUndo(SwUndoId const nId, const SwDoc* pDoc) + : m_nId(nId), m_nOrigRedlineFlags(RedlineFlags::NONE) + , m_nViewShellId(CreateViewShellId(pDoc)) + , m_isRepeatIgnored(false) + , m_bCacheComment(true) +{ +} + +ViewShellId SwUndo::CreateViewShellId(const SwDoc* pDoc) +{ + ViewShellId nRet(-1); + + if (const SwDocShell* pDocShell = pDoc->GetDocShell()) + { + if (const SwView* pView = pDocShell->GetView()) + nRet = pView->GetViewShellId(); + } + + return nRet; +} + +bool SwUndo::IsDelBox() const +{ + return GetId() == SwUndoId::COL_DELETE || GetId() == SwUndoId::ROW_DELETE || + GetId() == SwUndoId::TABLE_DELBOX; +} + +SwUndo::~SwUndo() +{ +} + +namespace { + +class UndoRedoRedlineGuard +{ +public: + UndoRedoRedlineGuard(::sw::UndoRedoContext const & rContext, SwUndo const & rUndo) + : m_rRedlineAccess(rContext.GetDoc().getIDocumentRedlineAccess()) + , m_eMode(m_rRedlineAccess.GetRedlineFlags()) + { + RedlineFlags const eTmpMode = rUndo.GetRedlineFlags(); + if ((RedlineFlags::ShowMask & eTmpMode) != (RedlineFlags::ShowMask & m_eMode)) + { + m_rRedlineAccess.SetRedlineFlags( eTmpMode ); + } + m_rRedlineAccess.SetRedlineFlags_intern( eTmpMode | RedlineFlags::Ignore ); + } + ~UndoRedoRedlineGuard() + { + m_rRedlineAccess.SetRedlineFlags(m_eMode); + } +private: + IDocumentRedlineAccess & m_rRedlineAccess; + RedlineFlags const m_eMode; +}; + +} + +void SwUndo::Undo() +{ + assert(false); // SwUndo::Undo(): ERROR: must call UndoWithContext instead +} + +void SwUndo::Redo() +{ + assert(false); // SwUndo::Redo(): ERROR: must call RedoWithContext instead +} + +void SwUndo::UndoWithContext(SfxUndoContext & rContext) +{ + ::sw::UndoRedoContext *const pContext( + dynamic_cast< ::sw::UndoRedoContext * >(& rContext)); + assert(pContext); + const UndoRedoRedlineGuard aUndoRedoRedlineGuard(*pContext, *this); + UndoImpl(*pContext); +} + +void SwUndo::RedoWithContext(SfxUndoContext & rContext) +{ + ::sw::UndoRedoContext *const pContext( + dynamic_cast< ::sw::UndoRedoContext * >(& rContext)); + assert(pContext); + const UndoRedoRedlineGuard aUndoRedoRedlineGuard(*pContext, *this); + RedoImpl(*pContext); +} + +void SwUndo::Repeat(SfxRepeatTarget & rContext) +{ + if (m_isRepeatIgnored) + { + return; // ignore Repeat for multi-selections + } + ::sw::RepeatContext *const pRepeatContext( + dynamic_cast< ::sw::RepeatContext * >(& rContext)); + assert(pRepeatContext); + RepeatImpl(*pRepeatContext); +} + +bool SwUndo::CanRepeat(SfxRepeatTarget & rContext) const +{ + assert(dynamic_cast< ::sw::RepeatContext * >(& rContext)); + (void)rContext; + // a MultiSelection action that doesn't do anything must still return true + return (SwUndoId::REPEAT_START <= GetId()) && (GetId() < SwUndoId::REPEAT_END); +} + +void SwUndo::RepeatImpl( ::sw::RepeatContext & ) +{ +} + +OUString GetUndoComment(SwUndoId eId) +{ + TranslateId pId; + switch (eId) + { + case SwUndoId::EMPTY: + pId = STR_CANT_UNDO; + break; + case SwUndoId::START: + case SwUndoId::END: + break; + case SwUndoId::DELETE: + pId = STR_DELETE_UNDO; + break; + case SwUndoId::INSERT: + pId = STR_INSERT_UNDO; + break; + case SwUndoId::OVERWRITE: + pId = STR_OVR_UNDO; + break; + case SwUndoId::SPLITNODE: + pId = STR_SPLITNODE_UNDO; + break; + case SwUndoId::INSATTR: + pId = STR_INSATTR_UNDO; + break; + case SwUndoId::SETFMTCOLL: + pId = STR_SETFMTCOLL_UNDO; + break; + case SwUndoId::RESETATTR: + pId = STR_RESET_ATTR_UNDO; + break; + case SwUndoId::INSFMTATTR: + pId = STR_INSFMT_ATTR_UNDO; + break; + case SwUndoId::INSDOKUMENT: + pId = STR_INSERT_DOC_UNDO; + break; + case SwUndoId::COPY: + pId = STR_COPY_UNDO; + break; + case SwUndoId::INSTABLE: + pId = STR_INSTABLE_UNDO; + break; + case SwUndoId::TABLETOTEXT: + pId = STR_TABLETOTEXT_UNDO; + break; + case SwUndoId::TEXTTOTABLE: + pId = STR_TEXTTOTABLE_UNDO; + break; + case SwUndoId::SORT_TXT: + pId = STR_SORT_TXT; + break; + case SwUndoId::INSLAYFMT: + pId = STR_INSERTFLY; + break; + case SwUndoId::TABLEHEADLINE: + pId = STR_TABLEHEADLINE; + break; + case SwUndoId::INSSECTION: + pId = STR_INSERTSECTION; + break; + case SwUndoId::OUTLINE_LR: + pId = STR_OUTLINE_LR; + break; + case SwUndoId::OUTLINE_UD: + pId = STR_OUTLINE_UD; + break; + case SwUndoId::OUTLINE_EDIT: + pId = STR_OUTLINE_EDIT; + break; + case SwUndoId::INSNUM: + pId = STR_INSNUM; + break; + case SwUndoId::NUMUP: + pId = STR_NUMUP; + break; + case SwUndoId::MOVENUM: + pId = STR_MOVENUM; + break; + case SwUndoId::INSDRAWFMT: + pId = STR_INSERTDRAW; + break; + case SwUndoId::NUMORNONUM: + pId = STR_NUMORNONUM; + break; + case SwUndoId::INC_LEFTMARGIN: + pId = STR_INC_LEFTMARGIN; + break; + case SwUndoId::DEC_LEFTMARGIN: + pId = STR_DEC_LEFTMARGIN; + break; + case SwUndoId::INSERTLABEL: + pId = STR_INSERTLABEL; + break; + case SwUndoId::SETNUMRULESTART: + pId = STR_SETNUMRULESTART; + break; + case SwUndoId::CHGFTN: + pId = STR_CHANGEFTN; + break; + case SwUndoId::REDLINE: + SAL_INFO("sw.core", "Should NEVER be used/translated"); + return "$1"; + case SwUndoId::ACCEPT_REDLINE: + pId = STR_ACCEPT_REDLINE; + break; + case SwUndoId::REJECT_REDLINE: + pId = STR_REJECT_REDLINE; + break; + case SwUndoId::SPLIT_TABLE: + pId = STR_SPLIT_TABLE; + break; + case SwUndoId::DONTEXPAND: + pId = STR_DONTEXPAND; + break; + case SwUndoId::AUTOCORRECT: + pId = STR_AUTOCORRECT; + break; + case SwUndoId::MERGE_TABLE: + pId = STR_MERGE_TABLE; + break; + case SwUndoId::TRANSLITERATE: + pId = STR_TRANSLITERATE; + break; + case SwUndoId::PASTE_CLIPBOARD: + pId = STR_PASTE_CLIPBOARD_UNDO; + break; + case SwUndoId::TYPING: + pId = STR_TYPING_UNDO; + break; + case SwUndoId::MOVE: + pId = STR_MOVE_UNDO; + break; + case SwUndoId::INSGLOSSARY: + pId = STR_INSERT_GLOSSARY; + break; + case SwUndoId::DELBOOKMARK: + pId = STR_DELBOOKMARK; + break; + case SwUndoId::INSBOOKMARK: + pId = STR_INSBOOKMARK; + break; + case SwUndoId::SORT_TBL: + pId = STR_SORT_TBL; + break; + case SwUndoId::DELLAYFMT: + pId = STR_DELETEFLY; + break; + case SwUndoId::AUTOFORMAT: + pId = STR_AUTOFORMAT; + break; + case SwUndoId::REPLACE: + pId = STR_REPLACE; + break; + case SwUndoId::DELSECTION: + pId = STR_DELETESECTION; + break; + case SwUndoId::CHGSECTION: + pId = STR_CHANGESECTION; + break; + case SwUndoId::SETDEFTATTR: + pId = STR_CHANGEDEFATTR; + break; + case SwUndoId::DELNUM: + pId = STR_DELNUM; + break; + case SwUndoId::DRAWUNDO: + pId = STR_DRAWUNDO; + break; + case SwUndoId::DRAWGROUP: + pId = STR_DRAWGROUP; + break; + case SwUndoId::DRAWUNGROUP: + pId = STR_DRAWUNGROUP; + break; + case SwUndoId::DRAWDELETE: + pId = STR_DRAWDELETE; + break; + case SwUndoId::REREAD: + pId = STR_REREAD; + break; + case SwUndoId::DELGRF: + pId = STR_DELGRF; + break; + case SwUndoId::TABLE_ATTR: + pId = STR_TABLE_ATTR; + break; + case SwUndoId::TABLE_AUTOFMT: + pId = STR_UNDO_TABLE_AUTOFMT; + break; + case SwUndoId::TABLE_INSCOL: + pId = STR_UNDO_TABLE_INSCOL; + break; + case SwUndoId::TABLE_INSROW: + pId = STR_UNDO_TABLE_INSROW; + break; + case SwUndoId::TABLE_DELBOX: + pId = STR_UNDO_TABLE_DELBOX; + break; + case SwUndoId::TABLE_SPLIT: + pId = STR_UNDO_TABLE_SPLIT; + break; + case SwUndoId::TABLE_MERGE: + pId = STR_UNDO_TABLE_MERGE; + break; + case SwUndoId::TBLNUMFMT: + pId = STR_TABLE_NUMFORMAT; + break; + case SwUndoId::INSTOX: + pId = STR_INSERT_TOX; + break; + case SwUndoId::CLEARTOXRANGE: + pId = STR_CLEAR_TOX_RANGE; + break; + case SwUndoId::TBLCPYTBL: + pId = STR_TABLE_TBLCPYTBL; + break; + case SwUndoId::CPYTBL: + pId = STR_TABLE_CPYTBL; + break; + case SwUndoId::INS_FROM_SHADOWCRSR: + pId = STR_INS_FROM_SHADOWCRSR; + break; + case SwUndoId::CHAINE: + pId = STR_UNDO_CHAIN; + break; + case SwUndoId::UNCHAIN: + pId = STR_UNDO_UNCHAIN; + break; + case SwUndoId::FTNINFO: + pId = STR_UNDO_FTNINFO; + break; + case SwUndoId::COMPAREDOC: + pId = STR_UNDO_COMPAREDOC; + break; + case SwUndoId::SETFLYFRMFMT: + pId = STR_UNDO_SETFLYFRMFMT; + break; + case SwUndoId::SETRUBYATTR: + pId = STR_UNDO_SETRUBYATTR; + break; + case SwUndoId::TOXCHANGE: + pId = STR_TOXCHANGE; + break; + case SwUndoId::CREATE_PAGEDESC: + pId = STR_UNDO_PAGEDESC_CREATE; + break; + case SwUndoId::CHANGE_PAGEDESC: + pId = STR_UNDO_PAGEDESC; + break; + case SwUndoId::DELETE_PAGEDESC: + pId = STR_UNDO_PAGEDESC_DELETE; + break; + case SwUndoId::HEADER_FOOTER: + pId = STR_UNDO_HEADER_FOOTER; + break; + case SwUndoId::FIELD: + pId = STR_UNDO_FIELD; + break; + case SwUndoId::TXTFMTCOL_CREATE: + pId = STR_UNDO_TXTFMTCOL_CREATE; + break; + case SwUndoId::TXTFMTCOL_DELETE: + pId = STR_UNDO_TXTFMTCOL_DELETE; + break; + case SwUndoId::TXTFMTCOL_RENAME: + pId = STR_UNDO_TXTFMTCOL_RENAME; + break; + case SwUndoId::CHARFMT_CREATE: + pId = STR_UNDO_CHARFMT_CREATE; + break; + case SwUndoId::CHARFMT_DELETE: + pId = STR_UNDO_CHARFMT_DELETE; + break; + case SwUndoId::CHARFMT_RENAME: + pId = STR_UNDO_CHARFMT_RENAME; + break; + case SwUndoId::FRMFMT_CREATE: + pId = STR_UNDO_FRMFMT_CREATE; + break; + case SwUndoId::FRMFMT_DELETE: + pId = STR_UNDO_FRMFMT_DELETE; + break; + case SwUndoId::FRMFMT_RENAME: + pId = STR_UNDO_FRMFMT_RENAME; + break; + case SwUndoId::NUMRULE_CREATE: + pId = STR_UNDO_NUMRULE_CREATE; + break; + case SwUndoId::NUMRULE_DELETE: + pId = STR_UNDO_NUMRULE_DELETE; + break; + case SwUndoId::NUMRULE_RENAME: + pId = STR_UNDO_NUMRULE_RENAME; + break; + case SwUndoId::BOOKMARK_RENAME: + pId = STR_UNDO_BOOKMARK_RENAME; + break; + case SwUndoId::INDEX_ENTRY_INSERT: + pId = STR_UNDO_INDEX_ENTRY_INSERT; + break; + case SwUndoId::INDEX_ENTRY_DELETE: + pId = STR_UNDO_INDEX_ENTRY_DELETE; + break; + case SwUndoId::COL_DELETE: + pId = STR_UNDO_COL_DELETE; + break; + case SwUndoId::ROW_DELETE: + pId = STR_UNDO_ROW_DELETE; + break; + case SwUndoId::RENAME_PAGEDESC: + pId = STR_UNDO_PAGEDESC_RENAME; + break; + case SwUndoId::NUMDOWN: + pId = STR_NUMDOWN; + break; + case SwUndoId::FLYFRMFMT_TITLE: + pId = STR_UNDO_FLYFRMFMT_TITLE; + break; + case SwUndoId::FLYFRMFMT_DESCRIPTION: + pId = STR_UNDO_FLYFRMFMT_DESCRIPTION; + break; + case SwUndoId::TBLSTYLE_CREATE: + pId = STR_UNDO_TBLSTYLE_CREATE; + break; + case SwUndoId::TBLSTYLE_DELETE: + pId = STR_UNDO_TBLSTYLE_DELETE; + break; + case SwUndoId::TBLSTYLE_UPDATE: + pId = STR_UNDO_TBLSTYLE_UPDATE; + break; + case SwUndoId::UI_REPLACE: + pId = STR_REPLACE_UNDO; + break; + case SwUndoId::UI_INSERT_PAGE_BREAK: + pId = STR_INSERT_PAGE_BREAK_UNDO; + break; + case SwUndoId::UI_INSERT_COLUMN_BREAK: + pId = STR_INSERT_COLUMN_BREAK_UNDO; + break; + case SwUndoId::UI_INSERT_ENVELOPE: + pId = STR_INSERT_ENV_UNDO; + break; + case SwUndoId::UI_DRAG_AND_COPY: + pId = STR_DRAG_AND_COPY; + break; + case SwUndoId::UI_DRAG_AND_MOVE: + pId = STR_DRAG_AND_MOVE; + break; + case SwUndoId::UI_INSERT_CHART: + pId = STR_INSERT_CHART; + break; + case SwUndoId::UI_INSERT_FOOTNOTE: + pId = STR_INSERT_FOOTNOTE; + break; + case SwUndoId::UI_INSERT_URLBTN: + pId = STR_INSERT_URLBTN; + break; + case SwUndoId::UI_INSERT_URLTXT: + pId = STR_INSERT_URLTXT; + break; + case SwUndoId::UI_DELETE_INVISIBLECNTNT: + pId = STR_DELETE_INVISIBLECNTNT; + break; + case SwUndoId::UI_REPLACE_STYLE: + pId = STR_REPLACE_STYLE; + break; + case SwUndoId::UI_DELETE_PAGE_BREAK: + pId = STR_DELETE_PAGE_BREAK; + break; + case SwUndoId::UI_TEXT_CORRECTION: + pId = STR_TEXT_CORRECTION; + break; + case SwUndoId::UI_TABLE_DELETE: + pId = STR_UNDO_TABLE_DELETE; + break; + case SwUndoId::CONFLICT: + break; + case SwUndoId::PARA_SIGN_ADD: + pId = STR_PARAGRAPH_SIGN_UNDO; + break; + case SwUndoId::INSERT_FORM_FIELD: + pId = STR_UNDO_INSERT_FORM_FIELD; + break; + case SwUndoId::INSERT_PAGE_NUMBER: + pId = STR_UNDO_INSERT_PAGE_NUMBER; + break; + case SwUndoId::UPDATE_FORM_FIELD: + pId = STR_UNDO_UPDATE_FORM_FIELD; + break; + case SwUndoId::UPDATE_FORM_FIELDS: + pId = STR_UNDO_UPDATE_FORM_FIELDS; + break; + case SwUndoId::DELETE_FORM_FIELDS: + pId = STR_UNDO_DELETE_FORM_FIELDS; + break; + case SwUndoId::UPDATE_BOOKMARK: + pId = STR_UPDATE_BOOKMARK; + break; + case SwUndoId::UPDATE_BOOKMARKS: + pId = STR_UPDATE_BOOKMARKS; + break; + case SwUndoId::DELETE_BOOKMARKS: + pId = STR_DELETE_BOOKMARKS; + break; + case SwUndoId::UPDATE_FIELD: + pId = STR_UPDATE_FIELD; + break; + case SwUndoId::UPDATE_FIELDS: + pId = STR_UPDATE_FIELDS; + break; + case SwUndoId::DELETE_FIELDS: + pId = STR_DELETE_FIELDS; + break; + case SwUndoId::UPDATE_SECTIONS: + pId = STR_UPDATE_SECTIONS; + break; + case SwUndoId::CHANGE_THEME: + pId = STR_UNDO_CHANGE_THEME_COLORS; + break; + case SwUndoId::DELETE_SECTIONS: + pId = STR_DELETE_SECTIONS; + break; + case SwUndoId::FLYFRMFMT_DECORATIVE: + pId = STR_UNDO_FLYFRMFMT_DECORATIVE; + break; + } + + assert(pId); + return SwResId(pId); +} + +OUString SwUndo::GetComment() const +{ + OUString aResult; + + if (m_bCacheComment) + { + if (! maComment) + { + maComment = GetUndoComment(GetId()); + + SwRewriter aRewriter = GetRewriter(); + + maComment = aRewriter.Apply(*maComment); + } + + aResult = *maComment; + } + else + { + aResult = GetUndoComment(GetId()); + + SwRewriter aRewriter = GetRewriter(); + + aResult = aRewriter.Apply(aResult); + } + + return aResult; +} + +ViewShellId SwUndo::GetViewShellId() const +{ + return m_nViewShellId; +} + +SwRewriter SwUndo::GetRewriter() const +{ + SwRewriter aResult; + + return aResult; +} + +SwUndoSaveContent::SwUndoSaveContent() +{} + +SwUndoSaveContent::~SwUndoSaveContent() COVERITY_NOEXCEPT_FALSE +{ +} + +void SwUndoSaveContent::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwUndoSaveContent")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + + if (m_pHistory) + { + m_pHistory->dumpAsXml(pWriter); + } + + (void)xmlTextWriterEndElement(pWriter); +} + +// This is needed when deleting content. For REDO all contents will be moved +// into the UndoNodesArray. These methods always create a new node to insert +// content. As a result, the attributes will not be expanded. +// - MoveTo moves from NodesArray into UndoNodesArray +// - MoveFrom moves from UndoNodesArray into NodesArray + +// If pEndNdIdx is given, Undo/Redo calls -Ins/DelFly. In that case the whole +// section should be moved. +void SwUndoSaveContent::MoveToUndoNds( SwPaM& rPaM, SwNodeIndex* pNodeIdx, + SwNodeOffset* pEndNdIdx ) +{ + SwDoc& rDoc = rPaM.GetDoc(); + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + + SwNoTextNode* pCpyNd = rPaM.GetPointNode().GetNoTextNode(); + + // here comes the actual delete (move) + SwNodes & rNds = rDoc.GetUndoManager().GetUndoNodes(); + SwPosition aPos( pEndNdIdx ? rNds.GetEndOfPostIts() + : rNds.GetEndOfExtras() ); + + const SwPosition* pStt = rPaM.Start(), *pEnd = rPaM.End(); + + SwNodeOffset nTmpMvNode = aPos.GetNodeIndex(); + + if( pCpyNd || pEndNdIdx ) + { + SwNodeRange aRg( pStt->GetNode(), SwNodeOffset(0), pEnd->GetNode(), SwNodeOffset(1) ); + rDoc.GetNodes().MoveNodes( aRg, rNds, aPos.GetNode(), true ); + aPos.Adjust(SwNodeOffset(-1)); + } + else + { + rDoc.GetNodes().MoveRange( rPaM, aPos, rNds ); + } + if( pEndNdIdx ) + *pEndNdIdx = aPos.GetNodeIndex(); + + // old position + aPos.Assign(nTmpMvNode); + if( pNodeIdx ) + *pNodeIdx = aPos.GetNode(); +} + +void SwUndoSaveContent::MoveFromUndoNds( SwDoc& rDoc, SwNodeOffset nNodeIdx, + SwPosition& rInsPos, + const SwNodeOffset* pEndNdIdx, bool const bForceCreateFrames) +{ + // here comes the recovery + SwNodes & rNds = rDoc.GetUndoManager().GetUndoNodes(); + if( nNodeIdx == rNds.GetEndOfPostIts().GetIndex() ) + return; // nothing saved + + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + + SwPaM aPaM( rInsPos ); + if( pEndNdIdx ) // than get the section from it + aPaM.GetPoint()->Assign( *rNds[SwNodeOffset(0)], *pEndNdIdx ); + else + { + aPaM.GetPoint()->Assign( rNds.GetEndOfExtras() ); + GoInContent( aPaM, fnMoveBackward ); + } + + SwTextNode* pTextNd = aPaM.GetPointNode().GetTextNode(); + if (!pEndNdIdx && pTextNd) + { + aPaM.SetMark(); + aPaM.GetPoint()->Assign(nNodeIdx, 0); + + SaveRedlEndPosForRestore aRedlRest( rInsPos.GetNode(), rInsPos.GetContentIndex() ); + + rNds.MoveRange( aPaM, rInsPos, rDoc.GetNodes() ); + + // delete the last Node as well + bool bDeleteLastNode = false; + if( !aPaM.GetPoint()->GetContentIndex() ) + bDeleteLastNode = true; + else + { + // still empty Nodes at the end? + aPaM.GetPoint()->Adjust(SwNodeOffset(1)); + if ( &rNds.GetEndOfExtras() != &aPaM.GetPoint()->GetNode() ) + bDeleteLastNode = true; + } + if( bDeleteLastNode ) + { + SwNode& rDelNode = aPaM.GetPoint()->GetNode(); + SwNodeOffset nDelOffset = rNds.GetEndOfExtras().GetIndex() - + aPaM.GetPoint()->GetNodeIndex(); + //move it so we don't have SwContentIndex pointing at a node when it is deleted. + aPaM.GetPoint()->Adjust(SwNodeOffset(-1)); + aPaM.SetMark(); + rNds.Delete( rDelNode, nDelOffset ); + } + + aRedlRest.Restore(); + } + else + { + SwNodeRange aRg( rNds, nNodeIdx, (pEndNdIdx + ? ((*pEndNdIdx) + 1) + : rNds.GetEndOfExtras().GetIndex() ) ); + rNds.MoveNodes(aRg, rDoc.GetNodes(), rInsPos.GetNode(), nullptr == pEndNdIdx || bForceCreateFrames); + + } +} + +// These two methods save and restore the Point of PaM. +// If the point cannot be moved, a "backup" is created on the previous node. +// Either way, returned, inserting at its original position will not move it. +::std::optional SwUndoSaveContent::MovePtBackward(SwPaM & rPam) +{ + rPam.SetMark(); + if( rPam.Move( fnMoveBackward )) + return {}; + + return { SwNodeIndex(rPam.GetPoint()->GetNode(), -1) }; +} + +void SwUndoSaveContent::MovePtForward(SwPaM& rPam, ::std::optional && oMvBkwrd) +{ + // Was there content before this position? + if (!oMvBkwrd) + rPam.Move( fnMoveForward ); + else + { + *rPam.GetPoint() = SwPosition(*oMvBkwrd); + rPam.GetPoint()->Adjust(SwNodeOffset(1)); + SwContentNode* pCNd = rPam.GetPointContentNode(); + if( !pCNd ) + rPam.Move( fnMoveForward ); + } +} + +// Delete all objects that have ContentIndices to the given area. +// Currently (1994) these exist: +// - Footnotes +// - Flys +// - Bookmarks + +// #i81002# - extending method +// delete certain (not all) cross-reference bookmarks at text node of +// and at text node of , if these text nodes aren't the same. +void SwUndoSaveContent::DelContentIndex( const SwPosition& rMark, + const SwPosition& rPoint, + DelContentType nDelContentType ) +{ + const SwPosition *pStt = rMark < rPoint ? &rMark : &rPoint, + *pEnd = &rMark == pStt ? &rPoint : &rMark; + + SwDoc& rDoc = rMark.GetNode().GetDoc(); + + // if it's not in the doc array, probably missing some invalidation somewhere + assert(&rPoint.GetNodes() == &rDoc.GetNodes()); + assert(&rMark.GetNodes() == &rDoc.GetNodes()); + + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + + // 1. Footnotes + if( DelContentType::Ftn & nDelContentType ) + { + SwFootnoteIdxs& rFootnoteArr = rDoc.GetFootnoteIdxs(); + if( !rFootnoteArr.empty() ) + { + const SwNode* pFootnoteNd; + size_t nPos = 0; + rFootnoteArr.SeekEntry( pStt->GetNode(), &nPos ); + SwTextFootnote* pSrch; + + // for now delete all that come afterwards + while( nPos < rFootnoteArr.size() && ( pFootnoteNd = + &( pSrch = rFootnoteArr[ nPos ] )->GetTextNode())->GetIndex() + <= pEnd->GetNodeIndex() ) + { + const sal_Int32 nFootnoteSttIdx = pSrch->GetStart(); + if( (DelContentType::CheckNoCntnt & nDelContentType ) + ? (&pEnd->GetNode() == pFootnoteNd ) + : (( &pStt->GetNode() == pFootnoteNd && + pStt->GetContentIndex() > nFootnoteSttIdx) || + ( &pEnd->GetNode() == pFootnoteNd && + nFootnoteSttIdx >= pEnd->GetContentIndex() )) ) + { + ++nPos; // continue searching + continue; + } + +// FIXME: duplicated code here and below -> refactor? + // Unfortunately an index needs to be created. Otherwise there + // will be problems with TextNode because the index will be + // deleted in the DTOR of SwFootnote! + SwTextNode* pTextNd = const_cast(static_cast(pFootnoteNd)); + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + SwTextAttr* const pFootnoteHint = + pTextNd->GetTextAttrForCharAt( nFootnoteSttIdx ); + assert(pFootnoteHint); + SwContentIndex aIdx( pTextNd, nFootnoteSttIdx ); + m_pHistory->AddTextAttr(pFootnoteHint, pTextNd->GetIndex(), false); + pTextNd->EraseText( aIdx, 1 ); + } + + while( nPos-- && ( pFootnoteNd = &( pSrch = rFootnoteArr[ nPos ] )-> + GetTextNode())->GetIndex() >= pStt->GetNodeIndex() ) + { + const sal_Int32 nFootnoteSttIdx = pSrch->GetStart(); + if( !(DelContentType::CheckNoCntnt & nDelContentType) && ( + ( &pStt->GetNode() == pFootnoteNd && + pStt->GetContentIndex() > nFootnoteSttIdx ) || + ( &pEnd->GetNode() == pFootnoteNd && + nFootnoteSttIdx >= pEnd->GetContentIndex() ))) + continue; // continue searching + + // Unfortunately an index needs to be created. Otherwise there + // will be problems with TextNode because the index will be + // deleted in the DTOR of SwFootnote! + SwTextNode* pTextNd = const_cast(static_cast(pFootnoteNd)); + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + SwTextAttr* const pFootnoteHint = + pTextNd->GetTextAttrForCharAt( nFootnoteSttIdx ); + assert(pFootnoteHint); + SwContentIndex aIdx( pTextNd, nFootnoteSttIdx ); + m_pHistory->AddTextAttr(pFootnoteHint, pTextNd->GetIndex(), false); + pTextNd->EraseText( aIdx, 1 ); + } + } + } + + // 2. Flys + if( DelContentType::Fly & nDelContentType ) + { + sal_uInt16 nChainInsPos = m_pHistory ? m_pHistory->Count() : 0; + const sw::SpzFrameFormats& rSpzArr = *rDoc.GetSpzFrameFormats(); + if( !rSpzArr.empty() ) + { + sw::SpzFrameFormat* pFormat; + const SwFormatAnchor* pAnchor; + size_t n = rSpzArr.size(); + const SwPosition* pAPos; + + while( n && !rSpzArr.empty() ) + { + pFormat = rSpzArr[--n]; + pAnchor = &pFormat->GetAnchor(); + switch( pAnchor->GetAnchorId() ) + { + case RndStdIds::FLY_AS_CHAR: + if( nullptr != (pAPos = pAnchor->GetContentAnchor() ) && + (( DelContentType::CheckNoCntnt & nDelContentType ) + ? ( pStt->GetNode() <= pAPos->GetNode() && + pAPos->GetNode() < pEnd->GetNode() ) + : ( *pStt <= *pAPos && *pAPos < *pEnd )) ) + { + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + SwTextNode *const pTextNd = + pAPos->GetNode().GetTextNode(); + SwTextAttr* const pFlyHint = pTextNd->GetTextAttrForCharAt( + pAPos->GetContentIndex()); + assert(pFlyHint); + m_pHistory->AddTextAttr(pFlyHint, SwNodeOffset(0), false); + // reset n so that no Format is skipped + n = n >= rSpzArr.size() ? rSpzArr.size() : n+1; + } + break; + case RndStdIds::FLY_AT_PARA: + { + pAPos = pAnchor->GetContentAnchor(); + if (pAPos && + pStt->GetNode() <= pAPos->GetNode() && pAPos->GetNode() <= pEnd->GetNode()) + { + if (!m_pHistory) + m_pHistory.reset( new SwHistory ); + + if (!(DelContentType::Replace & nDelContentType) + && IsSelectFrameAnchoredAtPara(*pAPos, *pStt, *pEnd, nDelContentType)) + { + m_pHistory->AddDeleteFly(*pFormat, nChainInsPos); + // reset n so that no Format is skipped + n = n >= rSpzArr.size() ? rSpzArr.size() : n+1; + } + // Moving the anchor? + else if (!((DelContentType::CheckNoCntnt|DelContentType::ExcludeFlyAtStartEnd) + & nDelContentType) && + // for SwUndoDelete: rPoint is the node that + // will be Joined - so anchor should be moved + // off it - but UndoImpl() split will insert + // new node *before* existing one so a no-op + // may need to be done here to add it to + // history for Undo. + (rPoint.GetNodeIndex() == pAPos->GetNodeIndex() + || pStt->GetNodeIndex() == pAPos->GetNodeIndex()) + // Do not try to move the anchor to a table! + && rMark.GetNode().IsTextNode()) + { + m_pHistory->AddChangeFlyAnchor(*pFormat); + SwFormatAnchor aAnch( *pAnchor ); + SwPosition aPos( rMark.GetNode() ); + aAnch.SetAnchor( &aPos ); + pFormat->SetFormatAttr( aAnch ); + } + } + } + break; + case RndStdIds::FLY_AT_CHAR: + if( nullptr != (pAPos = pAnchor->GetContentAnchor() ) && + ( pStt->GetNode() <= pAPos->GetNode() && pAPos->GetNode() <= pEnd->GetNode() ) ) + { + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + if (!(DelContentType::Replace & nDelContentType) + && IsDestroyFrameAnchoredAtChar( + *pAPos, *pStt, *pEnd, nDelContentType)) + { + m_pHistory->AddDeleteFly(*pFormat, nChainInsPos); + n = n >= rSpzArr.size() ? rSpzArr.size() : n+1; + } + else if (!((DelContentType::CheckNoCntnt | + DelContentType::ExcludeFlyAtStartEnd) + & nDelContentType)) + { + if( *pStt <= *pAPos && *pAPos < *pEnd ) + { + // These are the objects anchored + // between section start and end position + // Do not try to move the anchor to a table! + if( rMark.GetNode().GetTextNode() ) + { + m_pHistory->AddChangeFlyAnchor(*pFormat); + SwFormatAnchor aAnch( *pAnchor ); + aAnch.SetAnchor( &rMark ); + pFormat->SetFormatAttr( aAnch ); + } + } + } + } + break; + case RndStdIds::FLY_AT_FLY: + + if( nullptr != (pAPos = pAnchor->GetContentAnchor() ) && + pStt->GetNode() == pAPos->GetNode() ) + { + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + + m_pHistory->AddDeleteFly(*pFormat, nChainInsPos); + + // reset n so that no Format is skipped + n = n >= rSpzArr.size() ? rSpzArr.size() : n+1; + } + break; + default: break; + } + } + } + } + + // 3. Bookmarks + if( !(DelContentType::Bkm & nDelContentType) ) + return; + + IDocumentMarkAccess* const pMarkAccess = rDoc.getIDocumentMarkAccess(); + if( !pMarkAccess->getAllMarksCount() ) + return; + + for( sal_Int32 n = 0; n < pMarkAccess->getAllMarksCount(); ++n ) + { + // #i81002# + bool bSavePos = false; + bool bSaveOtherPos = false; + bool bDelete = false; + const ::sw::mark::IMark *const pBkmk = pMarkAccess->getAllMarksBegin()[n]; + auto const type(IDocumentMarkAccess::GetType(*pBkmk)); + + if( DelContentType::CheckNoCntnt & nDelContentType ) + { + if ( pStt->GetNode() <= pBkmk->GetMarkPos().GetNode() + && pBkmk->GetMarkPos().GetNode() < pEnd->GetNode() ) + { + bSavePos = true; + } + if ( pBkmk->IsExpanded() + && pStt->GetNode() <= pBkmk->GetOtherMarkPos().GetNode() + && pBkmk->GetOtherMarkPos().GetNode() < pEnd->GetNode() ) + { + bSaveOtherPos = true; + } + bDelete = bSavePos && bSaveOtherPos; + } + else + { + // #i92125# + // keep cross-reference bookmarks, if content inside one paragraph is deleted. + if ( rMark.GetNode() == rPoint.GetNode() + && ( type == IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK + || type == IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK)) + { + continue; + } + + bool bMaybe = false; + if ( *pStt <= pBkmk->GetMarkPos() && pBkmk->GetMarkPos() <= *pEnd ) + { + if ( pBkmk->GetMarkPos() == *pEnd + || ( *pStt == pBkmk->GetMarkPos() && pBkmk->IsExpanded() ) ) + bMaybe = true; + else + bSavePos = true; + } + if( pBkmk->IsExpanded() && + *pStt <= pBkmk->GetOtherMarkPos() && pBkmk->GetOtherMarkPos() <= *pEnd ) + { + assert(!bSaveOtherPos); + if ( bSavePos + || (*pStt < pBkmk->GetOtherMarkPos() && pBkmk->GetOtherMarkPos() < *pEnd) + || (bMaybe + && ( type == IDocumentMarkAccess::MarkType::TEXT_FIELDMARK + || type == IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK + || type == IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK + || type == IDocumentMarkAccess::MarkType::DATE_FIELDMARK)) + || (bMaybe + && !(nDelContentType & DelContentType::Replace) + && type == IDocumentMarkAccess::MarkType::BOOKMARK + && pStt->GetContentIndex() == 0 // entire paragraph deleted? + && pEnd->GetContentIndex() == pEnd->GetNode().GetTextNode()->Len())) + { + if( bMaybe ) + bSavePos = true; + bDelete = true; + } + if (bDelete || pBkmk->GetOtherMarkPos() == *pEnd) + { + bSaveOtherPos = true; // tdf#148389 always undo if at end + } + } + if (!bSavePos && bMaybe && pBkmk->IsExpanded() && *pStt == pBkmk->GetMarkPos()) + { + bSavePos = true; // tdf#148389 always undo if at start + } + + if ( !bSavePos && !bSaveOtherPos + && dynamic_cast< const ::sw::mark::CrossRefBookmark* >(pBkmk) ) + { + // certain special handling for cross-reference bookmarks + const bool bDifferentTextNodesAtMarkAndPoint = + rMark.GetNode() != rPoint.GetNode() + && rMark.GetNode().GetTextNode() + && rPoint.GetNode().GetTextNode(); + if ( bDifferentTextNodesAtMarkAndPoint ) + { + // delete cross-reference bookmark at , if only part of + // text node content is deleted. + if( pStt->GetNode() == pBkmk->GetMarkPos().GetNode() + && pEnd->GetContentIndex() != pEnd->GetNode().GetTextNode()->Len() ) + { + bSavePos = true; + bSaveOtherPos = false; // cross-reference bookmarks are not expanded + } + // delete cross-reference bookmark at , if only part of + // text node content is deleted. + else if( pEnd->GetNode() == pBkmk->GetMarkPos().GetNode() && + pStt->GetContentIndex() != 0 ) + { + bSavePos = true; + bSaveOtherPos = false; // cross-reference bookmarks are not expanded + } + } + } + else if (type == IDocumentMarkAccess::MarkType::ANNOTATIONMARK) + { + // delete annotation marks, if its end position is covered by the deletion + const SwPosition& rAnnotationEndPos = pBkmk->GetMarkEnd(); + if ( *pStt < rAnnotationEndPos && rAnnotationEndPos <= *pEnd ) + { + bSavePos = true; + bSaveOtherPos = pBkmk->IsExpanded(); //tdf#90138, only save the other pos if there is one + bDelete = true; + } + } + } + + if ( bSavePos || bSaveOtherPos ) + { + if (type != IDocumentMarkAccess::MarkType::UNO_BOOKMARK) + { + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + m_pHistory->AddIMark(*pBkmk, bSavePos, bSaveOtherPos); + } + if ( bSavePos + && (bDelete || !pBkmk->IsExpanded())) + { + pMarkAccess->deleteMark(pMarkAccess->getAllMarksBegin()+n, false); + n--; + } + } + } +} + +// save a complete section into UndoNodes array +SwUndoSaveSection::SwUndoSaveSection() + : m_nMoveLen( 0 ), m_nStartPos( NODE_OFFSET_MAX ) +{ +} + +SwUndoSaveSection::~SwUndoSaveSection() +{ + if (m_oMovedStart) // delete also the section from UndoNodes array + { + // SaveSection saves the content in the PostIt section. + SwNodes& rUNds = m_oMovedStart->GetNode().GetNodes(); + // cid#1486004 Uncaught exception + suppress_fun_call_w_exception(rUNds.Delete(*m_oMovedStart, m_nMoveLen)); + + m_oMovedStart.reset(); + } + m_pRedlineSaveData.reset(); +} + +void SwUndoSaveSection::SaveSection( const SwNodeIndex& rSttIdx ) +{ + SwNodeRange aRg( rSttIdx.GetNode(), *rSttIdx.GetNode().EndOfSectionNode() ); + SaveSection( aRg ); +} + +void SwUndoSaveSection::SaveSection( + const SwNodeRange& rRange, bool const bExpandNodes) +{ + SwPaM aPam( rRange.aStart, rRange.aEnd ); + + // delete all footnotes, fly frames, bookmarks + DelContentIndex( *aPam.GetMark(), *aPam.GetPoint() ); + + // redlines *before* CorrAbs, because DelBookmarks will make them 0-length + // but *after* DelContentIndex because that also may use FillSaveData (in + // flys) and that will be restored *after* this one... + m_pRedlineSaveData.reset( new SwRedlineSaveDatas ); + if (!SwUndo::FillSaveData( aPam, *m_pRedlineSaveData )) + { + m_pRedlineSaveData.reset(); + } + + { + // move certain indexes out of deleted range + SwNodeIndex aSttIdx( aPam.Start()->GetNode() ); + SwNodeIndex aEndIdx( aPam.End()->GetNode() ); + SwNodeIndex aMvStt( aEndIdx, 1 ); + SwDoc::CorrAbs( aSttIdx, aEndIdx, SwPosition( aMvStt ), true ); + } + + m_nStartPos = rRange.aStart.GetIndex(); + + if (bExpandNodes) + { + aPam.GetPoint()->Adjust(SwNodeOffset(-1)); + aPam.GetMark()->Adjust(SwNodeOffset(+1)); + } + + SwContentNode* pCNd = aPam.GetMarkContentNode(); + if( pCNd ) + aPam.GetMark()->SetContent( 0 ); + pCNd = aPam.GetPointContentNode(); + if( nullptr != pCNd ) + aPam.GetPoint()->SetContent( pCNd->Len() ); + + // Keep positions as SwContentIndex so that this section can be deleted in DTOR + SwNodeOffset nEnd; + m_oMovedStart = rRange.aStart; + MoveToUndoNds(aPam, &*m_oMovedStart, &nEnd); + m_nMoveLen = nEnd - m_oMovedStart->GetIndex() + 1; +} + +void SwUndoSaveSection::RestoreSection( SwDoc* pDoc, SwNodeIndex* pIdx, + sal_uInt16 nSectType ) +{ + if( NODE_OFFSET_MAX == m_nStartPos ) // was there any content? + return; + + // check if the content is at the old position + SwNodeIndex aSttIdx( pDoc->GetNodes(), m_nStartPos ); + + // move the content from UndoNodes array into Fly + SwStartNode* pSttNd = SwNodes::MakeEmptySection( aSttIdx.GetNode(), + static_cast(nSectType) ); + + RestoreSection( pDoc, *pSttNd->EndOfSectionNode() ); + + if( pIdx ) + *pIdx = *pSttNd; +} + +void SwUndoSaveSection::RestoreSection( + SwDoc *const pDoc, const SwNode& rInsPos, bool bForceCreateFrames) +{ + if( NODE_OFFSET_MAX == m_nStartPos ) // was there any content? + return; + + SwPosition aInsPos( rInsPos ); + SwNodeOffset nEnd = m_oMovedStart->GetIndex() + m_nMoveLen - 1; + MoveFromUndoNds(*pDoc, m_oMovedStart->GetIndex(), aInsPos, &nEnd, bForceCreateFrames); + + // destroy indices again, content was deleted from UndoNodes array + m_oMovedStart.reset(); + m_nMoveLen = SwNodeOffset(0); + + if( m_pRedlineSaveData ) + { + SwUndo::SetSaveData( *pDoc, *m_pRedlineSaveData ); + m_pRedlineSaveData.reset(); + } +} + +void SwUndoSaveSection::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + SwUndoSaveContent::dumpAsXml(pWriter); +} + +// save and set the RedlineData +SwRedlineSaveData::SwRedlineSaveData( + SwComparePosition eCmpPos, + const SwPosition& rSttPos, + const SwPosition& rEndPos, + SwRangeRedline& rRedl, + bool bCopyNext ) + : SwUndRng( rRedl ) + , SwRedlineData( rRedl.GetRedlineData(), bCopyNext ) +{ + assert( SwComparePosition::Outside == eCmpPos || + !rRedl.GetContentIdx() ); // "Redline with Content" + + switch (eCmpPos) + { + case SwComparePosition::OverlapBefore: // Pos1 overlaps Pos2 at the beginning + m_nEndNode = rEndPos.GetNodeIndex(); + m_nEndContent = rEndPos.GetContentIndex(); + break; + + case SwComparePosition::OverlapBehind: // Pos1 overlaps Pos2 at the end + m_nSttNode = rSttPos.GetNodeIndex(); + m_nSttContent = rSttPos.GetContentIndex(); + break; + + case SwComparePosition::Inside: // Pos1 lays completely in Pos2 + m_nSttNode = rSttPos.GetNodeIndex(); + m_nSttContent = rSttPos.GetContentIndex(); + m_nEndNode = rEndPos.GetNodeIndex(); + m_nEndContent = rEndPos.GetContentIndex(); + break; + + case SwComparePosition::Outside: // Pos2 lays completely in Pos1 + if ( rRedl.GetContentIdx() ) + { + // than move section into UndoArray and memorize it + SaveSection( *rRedl.GetContentIdx() ); + rRedl.ClearContentIdx(); + } + break; + + case SwComparePosition::Equal: // Pos1 is exactly as big as Pos2 + break; + + default: + assert(false); + } + +#if OSL_DEBUG_LEVEL > 0 + m_nRedlineCount = rSttPos.GetNode().GetDoc().getIDocumentRedlineAccess().GetRedlineTable().size(); + m_bRedlineCountDontCheck = false; + m_bRedlineMoved = rRedl.IsMoved(); +#endif +} + +SwRedlineSaveData::~SwRedlineSaveData() +{ +} + +void SwRedlineSaveData::RedlineToDoc( SwPaM const & rPam ) +{ + SwDoc& rDoc = rPam.GetDoc(); + SwRangeRedline* pRedl = new SwRangeRedline( *this, rPam ); + + if( GetMvSttIdx() ) + { + SwNodeIndex aIdx( rDoc.GetNodes() ); + RestoreSection( &rDoc, &aIdx, SwNormalStartNode ); + if( GetHistory() ) + GetHistory()->Rollback( &rDoc ); + pRedl->SetContentIdx( aIdx ); + } + SetPaM( *pRedl ); + // First, delete the "old" so that in an Append no unexpected things will + // happen, e.g. a delete in an insert. In the latter case the just restored + // content will be deleted and not the one you originally wanted. + rDoc.getIDocumentRedlineAccess().DeleteRedline( *pRedl, false, RedlineType::Any ); + + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld | RedlineFlags::DontCombineRedlines ); + //#i92154# let UI know about a new redline with comment + if (rDoc.GetDocShell() && (!pRedl->GetComment().isEmpty()) ) + rDoc.GetDocShell()->Broadcast(SwRedlineHint()); + + auto const result(rDoc.getIDocumentRedlineAccess().AppendRedline(pRedl, true)); + assert(result != IDocumentRedlineAccess::AppendResult::IGNORED); // SwRedlineSaveData::RedlineToDoc: insert redline failed + (void) result; // unused in non-debug + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +bool SwUndo::FillSaveData( + const SwPaM& rRange, + SwRedlineSaveDatas& rSData, + bool bDelRange, + bool bCopyNext ) +{ + rSData.clear(); + + auto [pStt, pEnd] = rRange.StartEnd(); // SwPosition* + const SwRedlineTable& rTable = rRange.GetDoc().getIDocumentRedlineAccess().GetRedlineTable(); + SwRedlineTable::size_type n = 0; + rRange.GetDoc().getIDocumentRedlineAccess().GetRedline( *pStt, &n ); + for ( ; n < rTable.size(); ++n ) + { + SwRangeRedline* pRedl = rTable[n]; + + const SwComparePosition eCmpPos = + ComparePosition( *pStt, *pEnd, *pRedl->Start(), *pRedl->End() ); + if ( eCmpPos != SwComparePosition::Before + && eCmpPos != SwComparePosition::Behind + && eCmpPos != SwComparePosition::CollideEnd + && eCmpPos != SwComparePosition::CollideStart ) + { + + rSData.push_back(std::unique_ptr(new SwRedlineSaveData(eCmpPos, *pStt, *pEnd, *pRedl, bCopyNext))); + } + } + if( !rSData.empty() && bDelRange ) + { + rRange.GetDoc().getIDocumentRedlineAccess().DeleteRedline( rRange, false, RedlineType::Any ); + } + return !rSData.empty(); +} + +bool SwUndo::FillSaveDataForFormat( + const SwPaM& rRange, + SwRedlineSaveDatas& rSData ) +{ + rSData.clear(); + + const SwPosition *pStt = rRange.Start(), *pEnd = rRange.End(); + const SwRedlineTable& rTable = rRange.GetDoc().getIDocumentRedlineAccess().GetRedlineTable(); + SwRedlineTable::size_type n = 0; + rRange.GetDoc().getIDocumentRedlineAccess().GetRedline( *pStt, &n ); + for ( ; n < rTable.size(); ++n ) + { + SwRangeRedline* pRedl = rTable[n]; + if ( RedlineType::Format == pRedl->GetType() ) + { + const SwComparePosition eCmpPos = ComparePosition( *pStt, *pEnd, *pRedl->Start(), *pRedl->End() ); + if ( eCmpPos != SwComparePosition::Before + && eCmpPos != SwComparePosition::Behind + && eCmpPos != SwComparePosition::CollideEnd + && eCmpPos != SwComparePosition::CollideStart ) + { + rSData.push_back(std::unique_ptr(new SwRedlineSaveData(eCmpPos, *pStt, *pEnd, *pRedl, true))); + } + + } + } + return !rSData.empty(); +} + + +void SwUndo::SetSaveData( SwDoc& rDoc, SwRedlineSaveDatas& rSData ) +{ + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + SwPaM aPam( rDoc.GetNodes().GetEndOfContent() ); + + for( size_t n = rSData.size(); n; ) + rSData[ --n ].RedlineToDoc( aPam ); + +#if OSL_DEBUG_LEVEL > 0 + // check redline count against count saved in RedlineSaveData object + // except in the case of moved redlines + assert( + rSData.empty() || rSData[0].m_bRedlineMoved || rSData[0].m_bRedlineCountDontCheck || + (rSData[0].m_nRedlineCount == rDoc.getIDocumentRedlineAccess().GetRedlineTable().size())); + // "redline count not restored properly" +#endif + + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +bool SwUndo::HasHiddenRedlines( const SwRedlineSaveDatas& rSData ) +{ + for( size_t n = rSData.size(); n; ) + if( rSData[ --n ].GetMvSttIdx() ) + return true; + return false; +} + +bool SwUndo::CanRedlineGroup( SwRedlineSaveDatas& rCurr, + const SwRedlineSaveDatas& rCheck, bool bCurrIsEnd ) +{ + if( rCurr.size() != rCheck.size() ) + return false; + + for( size_t n = 0; n < rCurr.size(); ++n ) + { + const SwRedlineSaveData& rSet = rCurr[ n ]; + const SwRedlineSaveData& rGet = rCheck[ n ]; + if( rSet.m_nSttNode != rGet.m_nSttNode || + rSet.GetMvSttIdx() || rGet.GetMvSttIdx() || + ( bCurrIsEnd ? rSet.m_nSttContent != rGet.m_nEndContent + : rSet.m_nEndContent != rGet.m_nSttContent ) || + !rGet.CanCombine( rSet ) ) + { + return false; + } + } + + for( size_t n = 0; n < rCurr.size(); ++n ) + { + SwRedlineSaveData& rSet = rCurr[ n ]; + const SwRedlineSaveData& rGet = rCheck[ n ]; + if( bCurrIsEnd ) + rSet.m_nSttContent = rGet.m_nSttContent; + else + rSet.m_nEndContent = rGet.m_nEndContent; + } + return true; +} + +OUString ShortenString(const OUString & rStr, sal_Int32 nLength, std::u16string_view aFillStr) +{ + assert(nLength - aFillStr.size() >= 2); + + if (rStr.getLength() <= nLength) + return rStr; + + nLength -= aFillStr.size(); + if ( nLength < 2 ) + nLength = 2; + + const sal_Int32 nFrontLen = nLength - nLength / 2; + const sal_Int32 nBackLen = nLength - nFrontLen; + + return OUString::Concat(rStr.subView(0, nFrontLen)) + + aFillStr + + rStr.subView(rStr.getLength() - nBackLen); +} + +static bool IsAtEndOfSection(SwPosition const& rAnchorPos) +{ + SwNodeIndex node(*rAnchorPos.GetNode().EndOfSectionNode()); + SwContentNode *const pNode(SwNodes::GoPrevious(&node)); + assert(pNode); + assert(rAnchorPos.GetNode() <= node.GetNode()); // last valid anchor pos is last content + return node == rAnchorPos.GetNode() + // at-para fly has no SwContentIndex! + && (rAnchorPos.GetContentIndex() == pNode->Len() || rAnchorPos.GetContentNode() == nullptr); +} + +static bool IsAtStartOfSection(SwPosition const& rAnchorPos) +{ + SwNodes const& rNodes(rAnchorPos.GetNodes()); + SwNodeIndex node(*rAnchorPos.GetNode().StartOfSectionNode()); + SwContentNode *const pNode(rNodes.GoNext(&node)); + assert(pNode); + (void) pNode; + assert(node <= rAnchorPos.GetNode()); + return node == rAnchorPos.GetNode() && rAnchorPos.GetContentIndex() == 0; +} + +/// passed start / end position could be on section start / end node +static bool IsAtEndOfSection2(SwPosition const& rPos) +{ + return rPos.GetNode().IsEndNode() + || IsAtEndOfSection(rPos); +} + +static bool IsAtStartOfSection2(SwPosition const& rPos) +{ + return rPos.GetNode().IsStartNode() + || IsAtStartOfSection(rPos); +} + +static bool IsNotBackspaceHeuristic( + SwPosition const& rStart, SwPosition const& rEnd) +{ + // check if the selection is backspace/delete created by DelLeft/DelRight + if (rStart.GetNodeIndex() + 1 != rEnd.GetNodeIndex()) + return true; + if (rEnd.GetContentIndex() != 0) + return true; + const SwTextNode* pTextNode = rStart.GetNode().GetTextNode(); + if (!pTextNode || rStart.GetContentIndex() != pTextNode->Len()) + return true; + return false; +} + +bool IsDestroyFrameAnchoredAtChar(SwPosition const & rAnchorPos, + SwPosition const & rStart, SwPosition const & rEnd, + DelContentType const nDelContentType) +{ + assert(rStart <= rEnd); + + // CheckNoCntnt means DelFullPara which is obvious to handle + if (DelContentType::CheckNoCntnt & nDelContentType) + { // exclude selection end node because it won't be deleted + return (rAnchorPos.GetNode() < rEnd.GetNode()) + && (rStart.GetNode() <= rAnchorPos.GetNode()); + } + + if ((nDelContentType & DelContentType::WriterfilterHack) + && rAnchorPos.GetDoc().IsInWriterfilterImport()) + { // FIXME hack for writerfilter RemoveLastParagraph() and MakeFlyAndMove(); can't test file format more specific? + return (rStart < rAnchorPos) && (rAnchorPos < rEnd); + } + + if (nDelContentType & DelContentType::ExcludeFlyAtStartEnd) + { // exclude selection start and end node + return (rAnchorPos.GetNode() < rEnd.GetNode()) + && (rStart.GetNode() < rAnchorPos.GetNode()); + } + + // in general, exclude the start and end position + return ((rStart < rAnchorPos) + || (rStart == rAnchorPos + // special case: fully deleted node + && ((rStart.GetNode() != rEnd.GetNode() && rStart.GetContentIndex() == 0 + // but not if the selection is backspace/delete! + && IsNotBackspaceHeuristic(rStart, rEnd)) + || (IsAtStartOfSection(rAnchorPos) && IsAtEndOfSection2(rEnd))))) + && ((rAnchorPos < rEnd) + || (rAnchorPos == rEnd + // special case: fully deleted node + && ((rEnd.GetNode() != rStart.GetNode() && rEnd.GetContentIndex() == rEnd.GetNode().GetTextNode()->Len() + && IsNotBackspaceHeuristic(rStart, rEnd)) + || (IsAtEndOfSection(rAnchorPos) && IsAtStartOfSection2(rStart))))); +} + +bool IsSelectFrameAnchoredAtPara(SwPosition const & rAnchorPos, + SwPosition const & rStart, SwPosition const & rEnd, + DelContentType const nDelContentType) +{ + assert(rStart <= rEnd); + + // CheckNoCntnt means DelFullPara which is obvious to handle + if (DelContentType::CheckNoCntnt & nDelContentType) + { // exclude selection end node because it won't be deleted + return (rAnchorPos.GetNode() < rEnd.GetNode()) + && (rStart.GetNode() <= rAnchorPos.GetNode()); + } + + if ((nDelContentType & DelContentType::WriterfilterHack) + && rAnchorPos.GetDoc().IsInWriterfilterImport()) + { // FIXME hack for writerfilter RemoveLastParagraph() and MakeFlyAndMove(); can't test file format more specific? + // but it MUST NOT be done during the SetRedlineFlags at the end of ODF + // import, where the IsInXMLImport() cannot be checked because the + // stupid code temp. overrides it - instead rely on setting the ALLFLYS + // flag in MoveFromSection() and converting that to CheckNoCntnt with + // adjusted cursor! + return (rStart.GetNode() < rAnchorPos.GetNode()) && (rAnchorPos.GetNode() < rEnd.GetNode()); + } + + // in general, exclude the start and end position + return ((rStart.GetNode() < rAnchorPos.GetNode()) + || (rStart.GetNode() == rAnchorPos.GetNode() + && !(nDelContentType & DelContentType::ExcludeFlyAtStartEnd) + // special case: fully deleted node + && ((rStart.GetNode() != rEnd.GetNode() && rStart.GetContentIndex() == 0 + // but not if the selection is backspace/delete! + && IsNotBackspaceHeuristic(rStart, rEnd)) + || (IsAtStartOfSection2(rStart) && IsAtEndOfSection2(rEnd))))) + && ((rAnchorPos.GetNode() < rEnd.GetNode()) + || (rAnchorPos.GetNode() == rEnd.GetNode() + && !(nDelContentType & DelContentType::ExcludeFlyAtStartEnd) + // special case: fully deleted node + && ((rEnd.GetNode() != rStart.GetNode() && rEnd.GetContentIndex() == rEnd.GetNode().GetTextNode()->Len() + && IsNotBackspaceHeuristic(rStart, rEnd)) + || (IsAtEndOfSection2(rEnd) && IsAtStartOfSection2(rStart))))); +} + +bool IsFlySelectedByCursor(SwDoc const & rDoc, + SwPosition const & rStart, SwPosition const & rEnd) +{ + for (SwFrameFormat const*const pFly : *rDoc.GetSpzFrameFormats()) + { + SwFormatAnchor const& rAnchor(pFly->GetAnchor()); + switch (rAnchor.GetAnchorId()) + { + case RndStdIds::FLY_AT_CHAR: + case RndStdIds::FLY_AT_PARA: + { + SwPosition const*const pAnchorPos(rAnchor.GetContentAnchor()); + // can this really be null? + if (pAnchorPos != nullptr + && ((rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + ? IsDestroyFrameAnchoredAtChar(*pAnchorPos, rStart, rEnd) + : IsSelectFrameAnchoredAtPara(*pAnchorPos, rStart, rEnd))) + { + return true; + } + } + break; + default: // other types not relevant + break; + } + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/undobj1.cxx b/sw/source/core/undo/undobj1.cxx new file mode 100644 index 0000000000..f0430b965b --- /dev/null +++ b/sw/source/core/undo/undobj1.cxx @@ -0,0 +1,720 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include + +SwUndoFlyBase::SwUndoFlyBase( SwFrameFormat* pFormat, SwUndoId nUndoId ) + : SwUndo(nUndoId, pFormat->GetDoc()) + , m_pFrameFormat(pFormat) + , m_nNodePagePos(0) + , m_nContentPos(0) + , m_nRndId(RndStdIds::FLY_AT_PARA) + , m_bDelFormat(false) +{ +} + +SwUndoFlyBase::~SwUndoFlyBase() +{ + if( m_bDelFormat ) // delete during an Undo? + { + if (const auto& pTextBoxes = m_pFrameFormat->GetOtherTextBoxFormats()) + { + // Clear and unregister before release. + if (m_pFrameFormat->Which() == RES_FLYFRMFMT) + pTextBoxes->DelTextBox(m_pFrameFormat); + + if (m_pFrameFormat->Which() == RES_DRAWFRMFMT) + pTextBoxes->ClearAll(); + + // clear that before delete + m_pFrameFormat->SetOtherTextBoxFormats(nullptr); + } + delete m_pFrameFormat; + } +} + +void SwUndoFlyBase::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwUndoFlyBase")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nNodePagePos"), + BAD_CAST(OString::number(sal_Int32(m_nNodePagePos)).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nContentPos"), + BAD_CAST(OString::number(m_nContentPos).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_nRndId"), + BAD_CAST(OString::number(static_cast(m_nRndId)).getStr())); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("m_bDelFormat"), + BAD_CAST(OString::boolean(m_bDelFormat).getStr())); + + SwUndo::dumpAsXml(pWriter); + SwUndoSaveSection::dumpAsXml(pWriter); + + if (m_pFrameFormat) + { + m_pFrameFormat->dumpAsXml(pWriter); + } + + (void)xmlTextWriterEndElement(pWriter); +} + +void SwUndoFlyBase::InsFly(::sw::UndoRedoContext & rContext, bool bShowSelFrame) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + + // add again into array + sw::SpzFrameFormats& rFlyFormats = *pDoc->GetSpzFrameFormats(); + rFlyFormats.push_back( static_cast(m_pFrameFormat)); + + // OD 26.06.2003 #108784# - insert 'master' drawing object into drawing page + if ( RES_DRAWFRMFMT == m_pFrameFormat->Which() ) + m_pFrameFormat->CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::PREP_INSERT_FLY)); + + SwFormatAnchor aAnchor( m_nRndId ); + + if (RndStdIds::FLY_AT_PAGE == m_nRndId) + { + aAnchor.SetPageNum( o3tl::narrowing(sal_Int32(m_nNodePagePos)) ); + } + else + { + SwPosition aNewPos(*pDoc->GetNodes()[m_nNodePagePos]); + if ((RndStdIds::FLY_AS_CHAR == m_nRndId) || (RndStdIds::FLY_AT_CHAR == m_nRndId)) + { + aNewPos.SetContent( m_nContentPos ); + } + aAnchor.SetAnchor( &aNewPos ); + } + + m_pFrameFormat->SetFormatAttr( aAnchor ); // reset anchor + + if( RES_DRAWFRMFMT != m_pFrameFormat->Which() ) + { + // get Content and reset ContentAttribute + SwNodeIndex aIdx( pDoc->GetNodes() ); + RestoreSection( pDoc, &aIdx, SwFlyStartNode ); + m_pFrameFormat->SetFormatAttr( SwFormatContent( aIdx.GetNode().GetStartNode() )); + } + + // Set InContentAttribute not until there is content! + // Otherwise the layout would format the Fly beforehand but would not find + // content; this happened with graphics from the internet. + if (RndStdIds::FLY_AS_CHAR == m_nRndId) + { + // there must be at least the attribute in a TextNode + SwContentNode* pCNd = aAnchor.GetAnchorNode()->GetContentNode(); + OSL_ENSURE( pCNd->IsTextNode(), "no Text Node at position." ); + SwFormatFlyCnt aFormat( m_pFrameFormat ); + pCNd->GetTextNode()->InsertItem(aFormat, m_nContentPos, m_nContentPos, SetAttrMode::NOHINTEXPAND); + } + + if (m_pFrameFormat->GetOtherTextBoxFormats()) + { + // recklessly assume that this thing will live longer than the + // SwUndoFlyBase - not sure what could be done if that isn't the case... + m_pFrameFormat->GetOtherTextBoxFormats()->GetOwnerShape()->SetOtherTextBoxFormats( + m_pFrameFormat->GetOtherTextBoxFormats()); + + SdrObject* pSdrObject + = m_pFrameFormat->GetOtherTextBoxFormats()->GetOwnerShape()->FindSdrObject(); + if (pSdrObject && m_pFrameFormat->Which() == RES_FLYFRMFMT) + m_pFrameFormat->GetOtherTextBoxFormats()->AddTextBox(pSdrObject, m_pFrameFormat); + + if (m_pFrameFormat->GetOtherTextBoxFormats()->GetOwnerShape()->Which() == RES_DRAWFRMFMT) + { + + if (pSdrObject) + { + // Make sure the old UNO wrapper is no longer cached after changing the shape + + // textframe pair. Otherwise we would have a wrapper which doesn't know about its + // textframe, even if it's there. + pSdrObject->setUnoShape(nullptr); + } + } + if (m_pFrameFormat->Which() == RES_FLYFRMFMT) + { + SwFrameFormat* pShapeFormat = m_pFrameFormat->GetOtherTextBoxFormats()->GetOwnerShape(); + pShapeFormat->SetFormatAttr(m_pFrameFormat->GetContent()); + } + } + + m_pFrameFormat->MakeFrames(); + + if( bShowSelFrame ) + { + rContext.SetSelections(m_pFrameFormat, nullptr); + } + + if( GetHistory() ) + GetHistory()->Rollback( pDoc ); + + switch( m_nRndId ) + { + case RndStdIds::FLY_AS_CHAR: + case RndStdIds::FLY_AT_CHAR: + { + const SwFormatAnchor& rAnchor = m_pFrameFormat->GetAnchor(); + m_nNodePagePos = rAnchor.GetAnchorNode()->GetIndex(); + m_nContentPos = rAnchor.GetAnchorContentOffset(); + } + break; + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_FLY: + { + const SwFormatAnchor& rAnchor = m_pFrameFormat->GetAnchor(); + m_nNodePagePos = rAnchor.GetAnchorNode()->GetIndex(); + } + break; + case RndStdIds::FLY_AT_PAGE: + break; + default: break; + } + m_bDelFormat = false; +} + +void SwUndoFlyBase::DelFly( SwDoc* pDoc ) +{ + m_bDelFormat = true; // delete Format in DTOR + m_pFrameFormat->DelFrames(); // destroy Frames + + if (m_pFrameFormat->GetOtherTextBoxFormats()) + { // tdf#108867 clear that pointer + m_pFrameFormat->GetOtherTextBoxFormats()->GetOwnerShape()->SetOtherTextBoxFormats(nullptr); + } + + // all Uno objects should now log themselves off + m_pFrameFormat->RemoveAllUnos(); + + if ( RES_DRAWFRMFMT != m_pFrameFormat->Which() ) + { + // if there is content than save it + const SwFormatContent& rContent = m_pFrameFormat->GetContent(); + OSL_ENSURE( rContent.GetContentIdx(), "Fly without content" ); + + SaveSection( *rContent.GetContentIdx() ); + const_cast(rContent).SetNewContentIdx( nullptr ); + } + // OD 02.07.2003 #108784# - remove 'master' drawing object from drawing page + else + m_pFrameFormat->CallSwClientNotify(sw::DrawFrameFormatHint(sw::DrawFrameFormatHintId::PREP_DELETE_FLY)); + + const SwFormatAnchor& rAnchor = m_pFrameFormat->GetAnchor(); + SwNode* pAnchorNode = rAnchor.GetAnchorNode(); + // The positions in Nodes array got shifted. + m_nRndId = rAnchor.GetAnchorId(); + if (RndStdIds::FLY_AS_CHAR == m_nRndId) + { + m_nNodePagePos = pAnchorNode->GetIndex(); + m_nContentPos = rAnchor.GetAnchorContentOffset(); + SwTextNode *const pTextNd = pAnchorNode->GetTextNode(); + OSL_ENSURE( pTextNd, "No Textnode found" ); + SwTextFlyCnt* const pAttr = static_cast( + pTextNd->GetTextAttrForCharAt( m_nContentPos, RES_TXTATR_FLYCNT ) ); + // attribute is still in TextNode, delete + if( pAttr && pAttr->GetFlyCnt().GetFrameFormat() == m_pFrameFormat ) + { + // Pointer to 0, do not delete + const_cast(pAttr->GetFlyCnt()).SetFlyFormat(); + pTextNd->EraseText( *rAnchor.GetContentAnchor(), 1 ); + } + } + else if (RndStdIds::FLY_AT_CHAR == m_nRndId) + { + m_nNodePagePos = pAnchorNode->GetIndex(); + m_nContentPos = rAnchor.GetAnchorContentOffset(); + } + else if ((RndStdIds::FLY_AT_PARA == m_nRndId) || (RndStdIds::FLY_AT_FLY == m_nRndId)) + { + m_nNodePagePos = pAnchorNode->GetIndex(); + } + else + { + m_nNodePagePos = SwNodeOffset(rAnchor.GetPageNum()); + } + + m_pFrameFormat->ResetFormatAttr( RES_ANCHOR ); // delete anchor + + // delete from array + sw::SpzFrameFormats& rFlyFormats = *pDoc->GetSpzFrameFormats(); + rFlyFormats.erase(static_cast(m_pFrameFormat)); +} + +SwUndoInsLayFormat::SwUndoInsLayFormat( SwFrameFormat* pFormat, SwNodeOffset nNodeIdx, sal_Int32 nCntIdx ) + : SwUndoFlyBase( pFormat, RES_DRAWFRMFMT == pFormat->Which() ? + SwUndoId::INSDRAWFMT : SwUndoId::INSLAYFMT ), + mnCursorSaveIndexPara( nNodeIdx ), mnCursorSaveIndexPos( nCntIdx ) +{ + const SwFormatAnchor& rAnchor = m_pFrameFormat->GetAnchor(); + m_nRndId = rAnchor.GetAnchorId(); + m_bDelFormat = false; + // note: SwUndoInsLayFormat is called with the content being fully inserted + // from most places but with only an empty content section from + // CopyLayoutFormat(); it's not necessary here to init m_nNodePagePos + // because Undo will do it. +} + +SwUndoInsLayFormat::~SwUndoInsLayFormat() +{ +} + +void SwUndoInsLayFormat::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc(rContext.GetDoc()); + const SwFormatContent& rContent = m_pFrameFormat->GetContent(); + if( rContent.GetContentIdx() ) // no content + { + assert(&rContent.GetContentIdx()->GetNodes() == &rDoc.GetNodes()); + if( mnCursorSaveIndexPara > SwNodeOffset(0) ) + { + SwTextNode *const pNode = + rDoc.GetNodes()[mnCursorSaveIndexPara]->GetTextNode(); + if( pNode ) + { + SwNodeIndex aIdx( rDoc.GetNodes(), + rContent.GetContentIdx()->GetIndex() ); + SwNodeIndex aEndIdx( rDoc.GetNodes(), + aIdx.GetNode().EndOfSectionIndex() ); + SwPosition aPos( *pNode, mnCursorSaveIndexPos ); + // don't delete bookmarks here, DelFly() will save them in history + ::PaMCorrAbs(SwPaM(aIdx, aEndIdx), aPos); + // TODO: is aPos actually a sensible pos for e.g. SwXText* ? + } + } + } + DelFly(& rDoc); +} + +void SwUndoInsLayFormat::RedoImpl(::sw::UndoRedoContext & rContext) +{ + InsFly(rContext); +} + +void SwUndoInsLayFormat::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + // get anchor and reset it + SwFormatAnchor aAnchor( m_pFrameFormat->GetAnchor() ); + if ((RndStdIds::FLY_AT_PARA == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == aAnchor.GetAnchorId()) || + (RndStdIds::FLY_AS_CHAR == aAnchor.GetAnchorId())) + { + SwPosition aPos( *rContext.GetRepeatPaM().GetPoint() ); + aAnchor.SetAnchor( &aPos ); + } + else if( RndStdIds::FLY_AT_FLY == aAnchor.GetAnchorId() ) + { + SwStartNode const*const pSttNd = + rContext.GetRepeatPaM().GetPointNode().FindFlyStartNode(); + if( pSttNd ) + { + SwPosition aPos( *pSttNd ); + aAnchor.SetAnchor( &aPos ); + } + else + { + return ; + } + } + else if (RndStdIds::FLY_AT_PAGE == aAnchor.GetAnchorId()) + { + aAnchor.SetPageNum( pDoc->getIDocumentLayoutAccess().GetCurrentLayout()->GetCurrPage( &rContext.GetRepeatPaM() )); + } + else { + OSL_FAIL( "What kind of anchor is this?" ); + } + + (void) pDoc->getIDocumentLayoutAccess().CopyLayoutFormat( *m_pFrameFormat, aAnchor, true, true ); +} + +OUString SwUndoInsLayFormat::GetComment() const +{ + OUString aResult; + + // HACK: disable caching: + // the SfxUndoManager calls GetComment() too early: the pFrameFormat does not + // have a SwDrawContact yet, so it will fall back to SwUndo::GetComment(), + // which sets pComment to a wrong value. +// if (! pComment) + if ((true)) + { + /* + If frame format is present and has an SdrObject use the undo + comment of the SdrObject. Otherwise use the default comment. + */ + bool bDone = false; + if (m_pFrameFormat) + { + const SdrObject * pSdrObj = m_pFrameFormat->FindSdrObject(); + if ( pSdrObj ) + { + aResult = SdrUndoNewObj::GetComment( *pSdrObj ); + bDone = true; + } + } + + if (! bDone) + aResult = SwUndo::GetComment(); + } + else + aResult = *maComment; + + return aResult; +} + +static SwUndoId +lcl_GetSwUndoId(SwFrameFormat const *const pFrameFormat) +{ + if (RES_DRAWFRMFMT != pFrameFormat->Which()) + { + const SwFormatContent& rContent = pFrameFormat->GetContent(); + OSL_ENSURE( rContent.GetContentIdx(), "Fly without content" ); + + SwNodeIndex firstNode(*rContent.GetContentIdx(), 1); + SwNoTextNode *const pNoTextNode(firstNode.GetNode().GetNoTextNode()); + if (pNoTextNode && pNoTextNode->IsGrfNode()) + { + return SwUndoId::DELGRF; + } + else if (pNoTextNode && pNoTextNode->IsOLENode()) + { + // surprisingly not SwUndoId::DELOLE, which does not seem to work + return SwUndoId::DELETE; + } + } + return SwUndoId::DELLAYFMT; +} + +SwUndoDelLayFormat::SwUndoDelLayFormat( SwFrameFormat* pFormat ) + : SwUndoFlyBase( pFormat, lcl_GetSwUndoId(pFormat) ) + , m_bShowSelFrame( true ) +{ + SwDoc* pDoc = pFormat->GetDoc(); + DelFly( pDoc ); +} + +SwRewriter SwUndoDelLayFormat::GetRewriter() const +{ + SwRewriter aRewriter; + + SwDoc * pDoc = m_pFrameFormat->GetDoc(); + + if (pDoc) + { + const SwNodeIndex* pIdx = GetMvSttIdx(); + if( SwNodeOffset(1) == GetMvNodeCnt() && pIdx) + { + SwNode *const pNd = & pIdx->GetNode(); + + if ( pNd->IsNoTextNode() && pNd->IsOLENode()) + { + SwOLENode * pOLENd = pNd->GetOLENode(); + + aRewriter.AddRule(UndoArg1, pOLENd->GetDescription()); + } + } + } + + return aRewriter; +} + +void SwUndoDelLayFormat::UndoImpl(::sw::UndoRedoContext & rContext) +{ + InsFly( rContext, m_bShowSelFrame ); +} + +void SwUndoDelLayFormat::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc(rContext.GetDoc()); + const SwFormatContent& rContent = m_pFrameFormat->GetContent(); + if( rContent.GetContentIdx() ) // no content + { + RemoveIdxFromSection(rDoc, rContent.GetContentIdx()->GetIndex()); + } + + DelFly(& rDoc); +} + +void SwUndoDelLayFormat::RedoForRollback() +{ + const SwFormatContent& rContent = m_pFrameFormat->GetContent(); + if( rContent.GetContentIdx() ) // no content + RemoveIdxFromSection( *m_pFrameFormat->GetDoc(), + rContent.GetContentIdx()->GetIndex() ); + + DelFly( m_pFrameFormat->GetDoc() ); +} + +SwUndoSetFlyFormat::SwUndoSetFlyFormat( SwFrameFormat& rFlyFormat, const SwFrameFormat& rNewFrameFormat ) + : SwUndo( SwUndoId::SETFLYFRMFMT, rFlyFormat.GetDoc() ), SwClient( &rFlyFormat ), m_pFrameFormat( &rFlyFormat ), + m_DerivedFromFormatName( rFlyFormat.IsDefault() ? "" : rFlyFormat.DerivedFrom()->GetName() ), + m_NewFormatName( rNewFrameFormat.GetName() ), + m_oItemSet( std::in_place, *rFlyFormat.GetAttrSet().GetPool(), + rFlyFormat.GetAttrSet().GetRanges() ), + m_nOldNode( 0 ), m_nNewNode( 0 ), + m_nOldContent( 0 ), m_nNewContent( 0 ), + m_nOldAnchorType( RndStdIds::FLY_AT_PARA ), m_nNewAnchorType( RndStdIds::FLY_AT_PARA ), m_bAnchorChanged( false ) +{ +} + +SwRewriter SwUndoSetFlyFormat::GetRewriter() const +{ + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, m_NewFormatName); + + return aRewriter; +} + +SwUndoSetFlyFormat::~SwUndoSetFlyFormat() +{ +} + +void SwUndoSetFlyFormat::GetAnchor( SwFormatAnchor& rAnchor, + SwNodeOffset nNode, sal_Int32 nContent ) +{ + RndStdIds nAnchorTyp = rAnchor.GetAnchorId(); + if (RndStdIds::FLY_AT_PAGE != nAnchorTyp) + { + SwNode* pNd = m_pFrameFormat->GetDoc()->GetNodes()[ nNode ]; + + if( RndStdIds::FLY_AT_FLY == nAnchorTyp + ? ( !pNd->IsStartNode() || SwFlyStartNode != + static_cast(pNd)->GetStartNodeType() ) + : !pNd->IsTextNode() ) + { + pNd = nullptr; // invalid position + } + else + { + SwPosition aPos( *pNd ); + if ((RndStdIds::FLY_AS_CHAR == nAnchorTyp) || + (RndStdIds::FLY_AT_CHAR == nAnchorTyp)) + { + if (nContent > pNd->GetTextNode()->GetText().getLength()) + { + pNd = nullptr; // invalid position + } + else + { + aPos.SetContent(nContent); + } + } + if ( pNd ) + { + rAnchor.SetAnchor( &aPos ); + } + } + + if( !pNd ) + { + // invalid position - assign first page + rAnchor.SetType( RndStdIds::FLY_AT_PAGE ); + rAnchor.SetPageNum( 1 ); + } + } + else + rAnchor.SetPageNum( nContent ); +} + +void SwUndoSetFlyFormat::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + // Is the new Format still existent? + SwFrameFormat* pDerivedFromFrameFormat = rDoc.FindFrameFormatByName(m_DerivedFromFormatName); + if (!pDerivedFromFrameFormat) + return; + + if( m_bAnchorChanged ) + m_pFrameFormat->DelFrames(); + + if( m_pFrameFormat->DerivedFrom() != pDerivedFromFrameFormat) + m_pFrameFormat->SetDerivedFrom(pDerivedFromFrameFormat); + + SfxItemIter aIter( *m_oItemSet ); + for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + { + if( IsInvalidItem( pItem )) + m_pFrameFormat->ResetFormatAttr( m_oItemSet->GetWhichByOffset( + aIter.GetCurPos() )); + else + m_pFrameFormat->SetFormatAttr( *pItem ); + } + + if( m_bAnchorChanged ) + { + const SwFormatAnchor& rOldAnch = m_pFrameFormat->GetAnchor(); + if (RndStdIds::FLY_AS_CHAR == rOldAnch.GetAnchorId()) + { + // With InContents it's tricky: the text attribute needs to be + // deleted. Unfortunately, this not only destroys the Frames but + // also the format. To prevent that, first detach the + // connection between attribute and format. + SwNode *pAnchorNode = rOldAnch.GetAnchorNode(); + SwTextNode *pTextNode = pAnchorNode->GetTextNode(); + OSL_ENSURE( pTextNode->HasHints(), "Missing FlyInCnt-Hint." ); + const sal_Int32 nIdx = rOldAnch.GetAnchorContentOffset(); + SwTextAttr * pHint = pTextNode->GetTextAttrForCharAt( + nIdx, RES_TXTATR_FLYCNT ); + assert(pHint && "Missing Hint."); + OSL_ENSURE( pHint->Which() == RES_TXTATR_FLYCNT, + "Missing FlyInCnt-Hint." ); + OSL_ENSURE( pHint->GetFlyCnt().GetFrameFormat() == m_pFrameFormat, + "Wrong TextFlyCnt-Hint." ); + const_cast(pHint->GetFlyCnt()).SetFlyFormat(); + + // Connection is now detached, therefore the attribute can be + // deleted + pTextNode->DeleteAttributes( RES_TXTATR_FLYCNT, nIdx, nIdx ); + } + + // reposition anchor + SwFormatAnchor aNewAnchor( m_nOldAnchorType ); + GetAnchor( aNewAnchor, m_nOldNode, m_nOldContent ); + m_pFrameFormat->SetFormatAttr( aNewAnchor ); + + if (RndStdIds::FLY_AS_CHAR == aNewAnchor.GetAnchorId()) + { + SwNode* pAnchorNode = aNewAnchor.GetAnchorNode(); + SwFormatFlyCnt aFormat( m_pFrameFormat ); + pAnchorNode->GetTextNode()->InsertItem( aFormat, + m_nOldContent, 0 ); + } + + m_pFrameFormat->MakeFrames(); + } + rContext.SetSelections(m_pFrameFormat, nullptr); +} + +void SwUndoSetFlyFormat::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + // Is the new Format still existent? + SwFrameFormat* pNewFrameFormat = rDoc.FindFrameFormatByName(m_NewFormatName); + if (!pNewFrameFormat) + return; + + if( m_bAnchorChanged ) + { + SwFormatAnchor aNewAnchor( m_nNewAnchorType ); + GetAnchor( aNewAnchor, m_nNewNode, m_nNewContent ); + SfxItemSet aSet( rDoc.GetAttrPool(), aFrameFormatSetRange ); + aSet.Put( aNewAnchor ); + rDoc.SetFrameFormatToFly( *m_pFrameFormat, *pNewFrameFormat, &aSet ); + } + else + rDoc.SetFrameFormatToFly( *m_pFrameFormat, *pNewFrameFormat); + + rContext.SetSelections(m_pFrameFormat, nullptr); +} + +void SwUndoSetFlyFormat::PutAttr( sal_uInt16 nWhich, const SfxPoolItem* pItem ) +{ + if( pItem && !SfxPoolItem::areSame(pItem, GetDfltAttr( nWhich ) ) ) + { + // Special treatment for this anchor + if( RES_ANCHOR == nWhich ) + { + // only keep the first change + OSL_ENSURE( !m_bAnchorChanged, "multiple changes of an anchor are not allowed!" ); + + m_bAnchorChanged = true; + + const SwFormatAnchor* pAnchor = static_cast(pItem); + m_nOldAnchorType = pAnchor->GetAnchorId(); + switch( m_nOldAnchorType ) + { + case RndStdIds::FLY_AS_CHAR: + case RndStdIds::FLY_AT_CHAR: + m_nOldContent = pAnchor->GetAnchorContentOffset(); + [[fallthrough]]; + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_FLY: + m_nOldNode = pAnchor->GetAnchorNode()->GetIndex(); + break; + + default: + m_nOldContent = pAnchor->GetPageNum(); + } + + pAnchor = &m_pFrameFormat->GetAnchor(); + m_nNewAnchorType = pAnchor->GetAnchorId(); + switch( m_nNewAnchorType ) + { + case RndStdIds::FLY_AS_CHAR: + case RndStdIds::FLY_AT_CHAR: + m_nNewContent = pAnchor->GetAnchorContentOffset(); + [[fallthrough]]; + case RndStdIds::FLY_AT_PARA: + case RndStdIds::FLY_AT_FLY: + m_nNewNode = pAnchor->GetAnchorNode()->GetIndex(); + break; + + default: + m_nNewContent = pAnchor->GetPageNum(); + } + } + else + m_oItemSet->Put( *pItem ); + } + else + m_oItemSet->InvalidateItem( nWhich ); +} + +void SwUndoSetFlyFormat::SwClientNotify(const SwModify&, const SfxHint& rHint) +{ + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast(&rHint); + if(!pLegacy->m_pOld) + return; + const sal_uInt16 nWhich = pLegacy->m_pOld->Which(); + if(nWhich < POOLATTR_END) + PutAttr(nWhich, pLegacy->m_pOld); + else if(RES_ATTRSET_CHG == nWhich) + { + SfxItemIter aIter(*static_cast(pLegacy->m_pOld)->GetChgSet()); + for(const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem()) + PutAttr(pItem->Which(), pItem); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/undoflystrattr.cxx b/sw/source/core/undo/undoflystrattr.cxx new file mode 100644 index 0000000000..60d8da4e95 --- /dev/null +++ b/sw/source/core/undo/undoflystrattr.cxx @@ -0,0 +1,122 @@ +/* -*- 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 + + +SwUndoFlyStrAttr::SwUndoFlyStrAttr( SwFlyFrameFormat& rFlyFrameFormat, + const SwUndoId eUndoId, + OUString sOldStr, + OUString sNewStr ) + : SwUndo( eUndoId, rFlyFrameFormat.GetDoc() ), + mrFlyFrameFormat( rFlyFrameFormat ), + msOldStr(std::move( sOldStr )), + msNewStr(std::move( sNewStr )) +{ + assert(eUndoId == SwUndoId::FLYFRMFMT_TITLE + || eUndoId == SwUndoId::FLYFRMFMT_DESCRIPTION); +} + +SwUndoFlyStrAttr::~SwUndoFlyStrAttr() +{ +} + +void SwUndoFlyStrAttr::UndoImpl(::sw::UndoRedoContext &) +{ + switch ( GetId() ) + { + case SwUndoId::FLYFRMFMT_TITLE: + { + mrFlyFrameFormat.SetObjTitle( msOldStr, true ); + } + break; + case SwUndoId::FLYFRMFMT_DESCRIPTION: + { + mrFlyFrameFormat.SetObjDescription( msOldStr, true ); + } + break; + default: + { + } + } +} + +void SwUndoFlyStrAttr::RedoImpl(::sw::UndoRedoContext &) +{ + switch ( GetId() ) + { + case SwUndoId::FLYFRMFMT_TITLE: + { + mrFlyFrameFormat.SetObjTitle( msNewStr, true ); + } + break; + case SwUndoId::FLYFRMFMT_DESCRIPTION: + { + mrFlyFrameFormat.SetObjDescription( msNewStr, true ); + } + break; + default: + { + } + } +} + +SwRewriter SwUndoFlyStrAttr::GetRewriter() const +{ + SwRewriter aResult; + + aResult.AddRule( UndoArg1, mrFlyFrameFormat.GetName() ); + + return aResult; +} + +SwUndoFlyDecorative::SwUndoFlyDecorative(SwFlyFrameFormat& rFlyFrameFormat, + bool const isDecorative) + : SwUndo(SwUndoId::FLYFRMFMT_DECORATIVE, rFlyFrameFormat.GetDoc()) + , m_rFlyFrameFormat(rFlyFrameFormat) + , m_IsDecorative(isDecorative) +{ +} + +SwUndoFlyDecorative::~SwUndoFlyDecorative() +{ +} + +void SwUndoFlyDecorative::UndoImpl(::sw::UndoRedoContext &) +{ + m_rFlyFrameFormat.SetObjDecorative(!m_IsDecorative); +} + +void SwUndoFlyDecorative::RedoImpl(::sw::UndoRedoContext &) +{ + m_rFlyFrameFormat.SetObjDecorative(m_IsDecorative); +} + +SwRewriter SwUndoFlyDecorative::GetRewriter() const +{ + SwRewriter aResult; + + aResult.AddRule(UndoArg1, m_rFlyFrameFormat.GetName()); + + return aResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/undraw.cxx b/sw/source/core/undo/undraw.cxx new file mode 100644 index 0000000000..134c686297 --- /dev/null +++ b/sw/source/core/undo/undraw.cxx @@ -0,0 +1,679 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include + +struct SwUndoGroupObjImpl +{ + SwDrawFrameFormat* pFormat; + SdrObject* pObj; + SwNodeOffset nNodeIdx; +}; + +// Draw-Objecte + +void SwDoc::AddDrawUndo( std::unique_ptr pUndo ) +{ + if (GetIDocumentUndoRedo().DoesUndo() && + GetIDocumentUndoRedo().DoesDrawUndo()) + { + const SdrMarkList* pMarkList = nullptr; + SwViewShell* pSh = getIDocumentLayoutAccess().GetCurrentViewShell(); + if( pSh && pSh->HasDrawView() ) + pMarkList = &pSh->GetDrawView()->GetMarkedObjectList(); + + GetIDocumentUndoRedo().AppendUndo( std::make_unique(std::move(pUndo), pMarkList, *this) ); + } +} + +SwSdrUndo::SwSdrUndo( std::unique_ptr pUndo, const SdrMarkList* pMrkLst, const SwDoc& rDoc ) + : SwUndo( SwUndoId::DRAWUNDO, &rDoc ), m_pSdrUndo( std::move(pUndo) ) +{ + if( pMrkLst && pMrkLst->GetMarkCount() ) + m_pMarkList.reset( new SdrMarkList( *pMrkLst ) ); +} + +SwSdrUndo::~SwSdrUndo() +{ + m_pSdrUndo.reset(); + m_pMarkList.reset(); +} + +void SwSdrUndo::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_pSdrUndo->Undo(); + rContext.SetSelections(nullptr, m_pMarkList.get()); +} + +void SwSdrUndo::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_pSdrUndo->Redo(); + rContext.SetSelections(nullptr, m_pMarkList.get()); +} + +OUString SwSdrUndo::GetComment() const +{ + return m_pSdrUndo->GetComment(); +} + +static void lcl_SaveAnchor( SwFrameFormat* pFormat, SwNodeOffset& rNodePos ) +{ + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + if (!((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_FLY == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId()))) + return; + + rNodePos = rAnchor.GetAnchorNode()->GetIndex(); + sal_Int32 nContentPos = 0; + + if (RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId()) + { + nContentPos = rAnchor.GetAnchorContentOffset(); + + // destroy TextAttribute + SwTextNode *pTextNd = pFormat->GetDoc()->GetNodes()[ rNodePos ]->GetTextNode(); + OSL_ENSURE( pTextNd, "No text node found!" ); + SwTextFlyCnt* pAttr = static_cast( + pTextNd->GetTextAttrForCharAt( nContentPos, RES_TXTATR_FLYCNT )); + // attribute still in text node, delete + if( pAttr && pAttr->GetFlyCnt().GetFrameFormat() == pFormat ) + { + // just set pointer to 0, don't delete + const_cast(pAttr->GetFlyCnt()).SetFlyFormat(); + SwContentIndex aIdx( pTextNd, nContentPos ); + pTextNd->EraseText( aIdx, 1 ); + } + } + else if (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) + { + nContentPos = rAnchor.GetAnchorContentOffset(); + } + + pFormat->SetFormatAttr( SwFormatAnchor( rAnchor.GetAnchorId(), nContentPos ) ); +} + +static void lcl_RestoreAnchor( SwFrameFormat* pFormat, SwNodeOffset nNodePos ) +{ + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + if (!((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_FLY == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId()))) + return; + + const sal_Int32 nContentPos = rAnchor.GetPageNum(); + SwNodes& rNds = pFormat->GetDoc()->GetNodes(); + + SwNodeIndex aIdx( rNds, nNodePos ); + SwPosition aPos( aIdx ); + + SwFormatAnchor aTmp( rAnchor.GetAnchorId() ); + if ((RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId())) + { + aPos.SetContent( nContentPos ); + } + aTmp.SetAnchor( &aPos ); + RndStdIds nAnchorId = rAnchor.GetAnchorId(); + pFormat->SetFormatAttr( aTmp ); + + if (RndStdIds::FLY_AS_CHAR == nAnchorId) + { + SwTextNode *pTextNd = aIdx.GetNode().GetTextNode(); + OSL_ENSURE( pTextNd, "no Text Node" ); + SwFormatFlyCnt aFormat( pFormat ); + pTextNd->InsertItem( aFormat, nContentPos, nContentPos ); + } +} + +SwUndoDrawGroup::SwUndoDrawGroup( sal_uInt16 nCnt, const SwDoc& rDoc ) + : SwUndo( SwUndoId::DRAWGROUP, &rDoc ), m_nSize( nCnt + 1 ), m_bDeleteFormat( true ) +{ + m_pObjArray.reset( new SwUndoGroupObjImpl[ m_nSize ] ); +} + +SwUndoDrawGroup::~SwUndoDrawGroup() +{ + if( m_bDeleteFormat ) + { + SwUndoGroupObjImpl* pTmp = m_pObjArray.get() + 1; + for( sal_uInt16 n = 1; n < m_nSize; ++n, ++pTmp ) + delete pTmp->pFormat; + } + else + delete m_pObjArray[0].pFormat; +} + +void SwUndoDrawGroup::UndoImpl(::sw::UndoRedoContext &) +{ + m_bDeleteFormat = false; + + // save group object + SwDrawFrameFormat* pFormat = m_pObjArray[0].pFormat; + + pFormat->CallSwClientNotify(sw::ContactChangedHint(&m_pObjArray[0].pObj)); + auto pObj = m_pObjArray[0].pObj; + pObj->SetUserCall(nullptr); + + // This will store the textboxes what were owned by this group + std::vector> vTextBoxes; + if (auto pOldTextBoxNode = pFormat->GetOtherTextBoxFormats()) + { + if (auto pChildren = pObj->getChildrenOfSdrObject()) + { + for (const rtl::Reference& pChild : *pChildren) + { + if (auto pTextBox = pOldTextBoxNode->GetTextBox(pChild.get())) + vTextBoxes.push_back(std::pair(pChild.get(), pTextBox)); + } + } + } + + ::lcl_SaveAnchor( pFormat, m_pObjArray[0].nNodeIdx ); + + pFormat->RemoveAllUnos(); + + // remove from array + SwDoc* pDoc = pFormat->GetDoc(); + sw::SpzFrameFormats& rFlyFormats = *pDoc->GetSpzFrameFormats(); + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), pFormat )); + + for( sal_uInt16 n = 1; n < m_nSize; ++n ) + { + SwUndoGroupObjImpl& rSave = m_pObjArray[n]; + + ::lcl_RestoreAnchor( rSave.pFormat, rSave.nNodeIdx ); + rFlyFormats.push_back(static_cast(rSave.pFormat)); + + pObj = rSave.pObj; + + SwDrawContact *pContact = new SwDrawContact( rSave.pFormat, pObj ); + pContact->ConnectToLayout(); + // #i45718# - follow-up of #i35635# move object to visible layer + pContact->MoveObjToVisibleLayer( pObj ); + + for (auto& rElem : vTextBoxes) + { + if (rElem.first == pObj) + { + auto pNewTextBoxNode = std::make_shared(SwTextBoxNode(rSave.pFormat)); + rSave.pFormat->SetOtherTextBoxFormats(pNewTextBoxNode); + pNewTextBoxNode->AddTextBox(rElem.first, rElem.second); + rElem.second->SetOtherTextBoxFormats(pNewTextBoxNode); + break; + } + } + + SwDrawFrameFormat* pDrawFrameFormat = rSave.pFormat; + + // #i45952# - notify that position attributes are already set + OSL_ENSURE(pDrawFrameFormat, + " - wrong type of frame format for drawing object"); + if (pDrawFrameFormat) + pDrawFrameFormat->PosAttrSet(); + } +} + +void SwUndoDrawGroup::RedoImpl(::sw::UndoRedoContext &) +{ + m_bDeleteFormat = true; + + // remove from array + SwDoc* pDoc = m_pObjArray[0].pFormat->GetDoc(); + sw::SpzFrameFormats& rFlyFormats = *pDoc->GetSpzFrameFormats(); + + // This will store the textboxes from the ex-group-shapes + std::vector> vTextBoxes; + + for( sal_uInt16 n = 1; n < m_nSize; ++n ) + { + SwUndoGroupObjImpl& rSave = m_pObjArray[n]; + + SdrObject* pObj = rSave.pObj; + + SwDrawContact *pContact = static_cast(GetUserCall(pObj)); + + // Save the textboxes + if (auto pOldTextBoxNode = rSave.pFormat->GetOtherTextBoxFormats()) + { + if (auto pTextBox = pOldTextBoxNode->GetTextBox(pObj)) + vTextBoxes.push_back(std::pair(pObj, pTextBox)); + } + + // object will destroy itself + pContact->Changed( *pObj, SdrUserCallType::Delete, pObj->GetLastBoundRect() ); + pObj->SetUserCall( nullptr ); + + ::lcl_SaveAnchor( rSave.pFormat, rSave.nNodeIdx ); + + rSave.pFormat->RemoveAllUnos(); + + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), rSave.pFormat )); + } + + // re-insert group object + ::lcl_RestoreAnchor( m_pObjArray[0].pFormat, m_pObjArray[0].nNodeIdx ); + rFlyFormats.push_back(static_cast(m_pObjArray[0].pFormat )); + + SwDrawContact *pContact = new SwDrawContact( m_pObjArray[0].pFormat, m_pObjArray[0].pObj ); + // #i26791# - correction: connect object to layout + pContact->ConnectToLayout(); + // #i45718# - follow-up of #i35635# move object to visible layer + pContact->MoveObjToVisibleLayer( m_pObjArray[0].pObj ); + + SwDrawFrameFormat* pDrawFrameFormat = m_pObjArray[0].pFormat; + + // Restore the textboxes + if (vTextBoxes.size()) + { + auto pNewTextBoxNode = std::make_shared(SwTextBoxNode(m_pObjArray[0].pFormat)); + for (auto& rElem : vTextBoxes) + { + pNewTextBoxNode->AddTextBox(rElem.first, rElem.second); + rElem.second->SetOtherTextBoxFormats(pNewTextBoxNode); + } + m_pObjArray[0].pFormat->SetOtherTextBoxFormats(pNewTextBoxNode); + } + + // #i45952# - notify that position attributes are already set + OSL_ENSURE(pDrawFrameFormat, + " - wrong type of frame format for drawing object"); + if (pDrawFrameFormat) + pDrawFrameFormat->PosAttrSet(); +} + +void SwUndoDrawGroup::AddObj( sal_uInt16 nPos, SwDrawFrameFormat* pFormat, SdrObject* pObj ) +{ + SwUndoGroupObjImpl& rSave = m_pObjArray[nPos + 1]; + rSave.pObj = pObj; + rSave.pFormat = pFormat; + ::lcl_SaveAnchor( pFormat, rSave.nNodeIdx ); + + pFormat->RemoveAllUnos(); + + // remove from array + sw::SpzFrameFormats& rFlyFormats = *pFormat->GetDoc()->GetSpzFrameFormats(); + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), pFormat )); +} + +void SwUndoDrawGroup::SetGroupFormat( SwDrawFrameFormat* pFormat ) +{ + m_pObjArray[0].pObj = nullptr; + m_pObjArray[0].pFormat = pFormat; +} + +SwUndoDrawUnGroup::SwUndoDrawUnGroup( SdrObjGroup* pObj, const SwDoc& rDoc ) + : SwUndo( SwUndoId::DRAWUNGROUP, &rDoc ), m_bDeleteFormat( false ) +{ + m_nSize = o3tl::narrowing(pObj->GetSubList()->GetObjCount()) + 1; + m_pObjArray.reset( new SwUndoGroupObjImpl[ m_nSize ] ); + + SwDrawContact *pContact = static_cast(GetUserCall(pObj)); + SwDrawFrameFormat* pFormat = static_cast(pContact->GetFormat()); + + m_pObjArray[0].pObj = pObj; + m_pObjArray[0].pFormat = pFormat; + + // object will destroy itself + pContact->Changed( *pObj, SdrUserCallType::Delete, pObj->GetLastBoundRect() ); + pObj->SetUserCall( nullptr ); + + ::lcl_SaveAnchor( pFormat, m_pObjArray[0].nNodeIdx ); + + pFormat->RemoveAllUnos(); + + // remove from array + sw::SpzFrameFormats& rFlyFormats = *pFormat->GetDoc()->GetSpzFrameFormats(); + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), pFormat )); +} + +SwUndoDrawUnGroup::~SwUndoDrawUnGroup() +{ + if( m_bDeleteFormat ) + { + SwUndoGroupObjImpl* pTmp = m_pObjArray.get() + 1; + for( sal_uInt16 n = 1; n < m_nSize; ++n, ++pTmp ) + delete pTmp->pFormat; + } + else + delete m_pObjArray[0].pFormat; +} + +void SwUndoDrawUnGroup::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_bDeleteFormat = true; + + SwDoc *const pDoc = & rContext.GetDoc(); + sw::SpzFrameFormats& rFlyFormats = *pDoc->GetSpzFrameFormats(); + + // This will store the textboxes what were owned by this group + std::vector> vTextBoxes; + + // remove from array + for( sal_uInt16 n = 1; n < m_nSize; ++n ) + { + SwUndoGroupObjImpl& rSave = m_pObjArray[n]; + + ::lcl_SaveAnchor( rSave.pFormat, rSave.nNodeIdx ); + + // copy the textboxes for later use to this vector + if (auto pTxBxNd = rSave.pFormat->GetOtherTextBoxFormats()) + { + if (auto pGroupObj = m_pObjArray[0].pObj) + { + if (auto pChildren = pGroupObj->getChildrenOfSdrObject()) + { + for (const rtl::Reference& pChild : *pChildren) + { + if (auto pTextBox = pTxBxNd->GetTextBox(pChild.get())) + vTextBoxes.push_back(std::pair(pChild.get(), pTextBox)); + } + } + } + } + + rSave.pFormat->RemoveAllUnos(); + + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), rSave.pFormat )); + } + + // re-insert group object + ::lcl_RestoreAnchor( m_pObjArray[0].pFormat, m_pObjArray[0].nNodeIdx ); + rFlyFormats.push_back(static_cast(m_pObjArray[0].pFormat )); + + SwDrawContact *pContact = new SwDrawContact( m_pObjArray[0].pFormat, m_pObjArray[0].pObj ); + pContact->ConnectToLayout(); + // #i45718# - follow-up of #i35635# move object to visible layer + pContact->MoveObjToVisibleLayer( m_pObjArray[0].pObj ); + + SwDrawFrameFormat* pDrawFrameFormat = m_pObjArray[0].pFormat; + + // Restore the vector content for the new formats + if (vTextBoxes.size()) + { + auto pNewTxBxNd = std::make_shared( SwTextBoxNode(m_pObjArray[0].pFormat)); + for (auto& rElem : vTextBoxes) + { + pNewTxBxNd->AddTextBox(rElem.first, rElem.second); + rElem.second->SetOtherTextBoxFormats(pNewTxBxNd); + } + m_pObjArray[0].pFormat->SetOtherTextBoxFormats(pNewTxBxNd); + } + + + // #i45952# - notify that position attributes are already set + OSL_ENSURE(pDrawFrameFormat, + " - wrong type of frame format for drawing object"); + if (pDrawFrameFormat) + pDrawFrameFormat->PosAttrSet(); +} + +void SwUndoDrawUnGroup::RedoImpl(::sw::UndoRedoContext &) +{ + m_bDeleteFormat = false; + + // save group object + SwDrawFrameFormat* pFormat = m_pObjArray[0].pFormat; + pFormat->CallSwClientNotify(sw::ContactChangedHint(&(m_pObjArray[0].pObj))); + m_pObjArray[0].pObj->SetUserCall( nullptr ); + + ::lcl_SaveAnchor( pFormat, m_pObjArray[0].nNodeIdx ); + + // Store the textboxes in this vector for later use. + std::vector> vTextBoxes; + if (auto pTextBoxNode = pFormat->GetOtherTextBoxFormats()) + { + auto pMasterObj = m_pObjArray[0].pObj; + + if (auto pObjList = pMasterObj->getChildrenOfSdrObject()) + for (const rtl::Reference& pObj : *pObjList) + { + vTextBoxes.push_back(std::pair(pObj.get(), pTextBoxNode->GetTextBox(pObj.get()))); + } + } + + pFormat->RemoveAllUnos(); + + // remove from array + SwDoc* pDoc = pFormat->GetDoc(); + sw::SpzFrameFormats& rFlyFormats = *pDoc->GetSpzFrameFormats(); + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), pFormat )); + + for( sal_uInt16 n = 1; n < m_nSize; ++n ) + { + SwUndoGroupObjImpl& rSave = m_pObjArray[n]; + + ::lcl_RestoreAnchor( rSave.pFormat, rSave.nNodeIdx ); + rFlyFormats.push_back(static_cast(rSave.pFormat)); + + SwDrawFrameFormat* pDrawFrameFormat = rSave.pFormat; + + // Restore the textboxes for the restored group shape. + for (auto& pElem : vTextBoxes) + { + if (pElem.first == rSave.pObj) + { + auto pTmpTxBxNd = std::make_shared(SwTextBoxNode(rSave.pFormat)); + pTmpTxBxNd->AddTextBox(rSave.pObj, pElem.second); + pFormat->SetOtherTextBoxFormats(pTmpTxBxNd); + pElem.second->SetOtherTextBoxFormats(pTmpTxBxNd); + break; + } + } + + // #i45952# - notify that position attributes are already set + OSL_ENSURE(pDrawFrameFormat, + " - wrong type of frame format for drawing object" ); + if (pDrawFrameFormat) + pDrawFrameFormat->PosAttrSet(); + } +} + +void SwUndoDrawUnGroup::AddObj( sal_uInt16 nPos, SwDrawFrameFormat* pFormat ) +{ + SwUndoGroupObjImpl& rSave = m_pObjArray[ nPos + 1 ]; + rSave.pFormat = pFormat; + rSave.pObj = nullptr; +} + +SwUndoDrawUnGroupConnectToLayout::SwUndoDrawUnGroupConnectToLayout(const SwDoc& rDoc) + : SwUndo( SwUndoId::DRAWUNGROUP, &rDoc ) +{ +} + +SwUndoDrawUnGroupConnectToLayout::~SwUndoDrawUnGroupConnectToLayout() +{ +} + +void +SwUndoDrawUnGroupConnectToLayout::UndoImpl(::sw::UndoRedoContext &) +{ + for (const std::pair< SwDrawFrameFormat*, SdrObject* > & rPair : m_aDrawFormatsAndObjs) + { + SdrObject* pObj( rPair.second ); + SwDrawContact* pDrawContact( dynamic_cast(pObj->GetUserCall()) ); + OSL_ENSURE( pDrawContact, + " -- missing SwDrawContact instance" ); + if ( pDrawContact ) + { + // deletion of instance and thus disconnection from + // the Writer layout. + pDrawContact->Changed( *pObj, SdrUserCallType::Delete, pObj->GetLastBoundRect() ); + pObj->SetUserCall( nullptr ); + } + } +} + +void +SwUndoDrawUnGroupConnectToLayout::RedoImpl(::sw::UndoRedoContext &) +{ + for (const std::pair< SwDrawFrameFormat*, SdrObject* > & rPair : m_aDrawFormatsAndObjs) + { + SwDrawFrameFormat* pFormat( rPair.first ); + SdrObject* pObj( rPair.second ); + SwDrawContact *pContact = new SwDrawContact( pFormat, pObj ); + pContact->ConnectToLayout(); + pContact->MoveObjToVisibleLayer( pObj ); + } +} + +void SwUndoDrawUnGroupConnectToLayout::AddFormatAndObj( SwDrawFrameFormat* pDrawFrameFormat, + SdrObject* pDrawObject ) +{ + m_aDrawFormatsAndObjs.emplace_back( pDrawFrameFormat, pDrawObject ); +} + +SwUndoDrawDelete::SwUndoDrawDelete( sal_uInt16 nCnt, const SwDoc& rDoc ) + : SwUndo( SwUndoId::DRAWDELETE, &rDoc ), m_bDeleteFormat( true ) +{ + m_pObjArray.reset( new SwUndoGroupObjImpl[ nCnt ] ); + m_pMarkList.reset( new SdrMarkList() ); +} + +SwUndoDrawDelete::~SwUndoDrawDelete() +{ + if( m_bDeleteFormat ) + { + SwUndoGroupObjImpl* pTmp = m_pObjArray.get(); + for( size_t n = 0; n < m_pMarkList->GetMarkCount(); ++n, ++pTmp ) + delete pTmp->pFormat; + } +} + +void SwUndoDrawDelete::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_bDeleteFormat = false; + sw::SpzFrameFormats& rFlyFormats = *rContext.GetDoc().GetSpzFrameFormats(); + for( size_t n = 0; n < m_pMarkList->GetMarkCount(); ++n ) + { + SwUndoGroupObjImpl& rSave = m_pObjArray[n]; + ::lcl_RestoreAnchor( rSave.pFormat, rSave.nNodeIdx ); + rFlyFormats.push_back(static_cast(rSave.pFormat)); + SdrObject *pObj = rSave.pObj; + SwDrawContact *pContact = new SwDrawContact( rSave.pFormat, pObj ); + pContact->Changed_( *pObj, SdrUserCallType::Inserted, nullptr ); + // #i45718# - follow-up of #i35635# move object to visible layer + pContact->MoveObjToVisibleLayer( pObj ); + + SwDrawFrameFormat* pDrawFrameFormat = rSave.pFormat; + if (pDrawFrameFormat->GetOtherTextBoxFormats()) + { + SwTextBoxHelper::synchronizeGroupTextBoxProperty( + SwTextBoxHelper::changeAnchor, pDrawFrameFormat, pObj); + } + + // #i45952# - notify that position attributes are already set + OSL_ENSURE(pDrawFrameFormat, + " - wrong type of frame format for drawing object"); + if (pDrawFrameFormat) + pDrawFrameFormat->PosAttrSet(); + } + rContext.SetSelections(nullptr, m_pMarkList.get()); +} + +void SwUndoDrawDelete::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_bDeleteFormat = true; + sw::SpzFrameFormats& rFlyFormats = *rContext.GetDoc().GetSpzFrameFormats(); + for( size_t n = 0; n < m_pMarkList->GetMarkCount(); ++n ) + { + SwUndoGroupObjImpl& rSave = m_pObjArray[n]; + SdrObject *pObj = rSave.pObj; + SwDrawContact *pContact = static_cast(GetUserCall(pObj)); + SwDrawFrameFormat *pFormat = static_cast(pContact->GetFormat()); + + // object will destroy itself + pContact->Changed( *pObj, SdrUserCallType::Delete, pObj->GetLastBoundRect() ); + pObj->SetUserCall( nullptr ); + + pFormat->RemoveAllUnos(); + + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), pFormat )); + ::lcl_SaveAnchor( pFormat, rSave.nNodeIdx ); + } +} + +void SwUndoDrawDelete::AddObj( SwDrawFrameFormat* pFormat, + const SdrMark& rMark ) +{ + SwUndoGroupObjImpl& rSave = m_pObjArray[ m_pMarkList->GetMarkCount() ]; + rSave.pObj = rMark.GetMarkedSdrObj(); + rSave.pFormat = pFormat; + ::lcl_SaveAnchor( pFormat, rSave.nNodeIdx ); + + pFormat->RemoveAllUnos(); + + // remove from array + SwDoc* pDoc = pFormat->GetDoc(); + sw::SpzFrameFormats& rFlyFormats = *pDoc->GetSpzFrameFormats(); + rFlyFormats.erase( std::find( rFlyFormats.begin(), rFlyFormats.end(), pFormat )); + + m_pMarkList->InsertEntry( rMark ); +} + +void SwUndoDrawDelete::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwUndoDrawDelete")); + + for (size_t i = 0; i < m_pMarkList->GetMarkCount(); ++i) + { + SwUndoGroupObjImpl& rObj = m_pObjArray[i]; + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwUndoGroupObjImpl")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("index"), + BAD_CAST(OString::number(i).getStr())); + + if (rObj.pFormat) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("pFormat")); + rObj.pFormat->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); + } + + (void)xmlTextWriterEndElement(pWriter); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unfmco.cxx b/sw/source/core/undo/unfmco.cxx new file mode 100644 index 0000000000..7ea43d0d5f --- /dev/null +++ b/sw/source/core/undo/unfmco.cxx @@ -0,0 +1,87 @@ +/* -*- 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 + +SwUndoFormatColl::SwUndoFormatColl( const SwPaM& rRange, + const SwFormatColl* pColl, + const bool bReset, + const bool bResetListAttrs ) + : SwUndo( SwUndoId::SETFMTCOLL, &rRange.GetDoc() ), + SwUndRng( rRange ), + mpHistory( new SwHistory ), + mbReset( bReset ), + mbResetListAttrs( bResetListAttrs ) +{ + // #i31191# + if ( pColl ) + maFormatName = pColl->GetName(); +} + +SwUndoFormatColl::~SwUndoFormatColl() +{ +} + +void SwUndoFormatColl::UndoImpl(::sw::UndoRedoContext & rContext) +{ + // restore old values + mpHistory->TmpRollback(& rContext.GetDoc(), 0); + mpHistory->SetTmpEnd( mpHistory->Count() ); + + // create cursor for undo range + AddUndoRedoPaM(rContext); +} + +void SwUndoFormatColl::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam = AddUndoRedoPaM(rContext); + + DoSetFormatColl(rContext.GetDoc(), rPam); +} + +void SwUndoFormatColl::RepeatImpl(::sw::RepeatContext & rContext) +{ + DoSetFormatColl(rContext.GetDoc(), rContext.GetRepeatPaM()); +} + +void SwUndoFormatColl::DoSetFormatColl(SwDoc & rDoc, SwPaM const & rPaM) +{ + // Only one TextFrameColl can be applied to a section, thus request only in + // this array. + SwTextFormatColl * pFormatColl = rDoc.FindTextFormatCollByName(maFormatName); + if (pFormatColl) + { + rDoc.SetTextFormatColl(rPaM, pFormatColl, mbReset, mbResetListAttrs); + } +} + +SwRewriter SwUndoFormatColl::GetRewriter() const +{ + SwRewriter aResult; + + aResult.AddRule(UndoArg1, maFormatName ); + + return aResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unins.cxx b/sw/source/core/undo/unins.cxx new file mode 100644 index 0000000000..bf6c2877b2 --- /dev/null +++ b/sw/source/core/undo/unins.cxx @@ -0,0 +1,1035 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace ::com::sun::star; + +// INSERT + +std::optional SwUndoInsert::GetTextFromDoc() const +{ + std::optional aResult; + + SwNodeIndex aNd( m_pDoc->GetNodes(), m_nNode); + SwContentNode* pCNd = aNd.GetNode().GetContentNode(); + + if( pCNd->IsTextNode() ) + { + OUString sText = pCNd->GetTextNode()->GetText(); + + sal_Int32 nStart = m_nContent-m_nLen; + sal_Int32 nLength = m_nLen; + + if (nStart < 0) + { + nLength += nStart; + nStart = 0; + } + + aResult = sText.copy(nStart, nLength); + } + + return aResult; +} + +void SwUndoInsert::Init(const SwNode & rNd) +{ + // consider Redline + m_pDoc = const_cast(&rNd.GetDoc()); + if( m_pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + { + m_pRedlData.reset( new SwRedlineData( RedlineType::Insert, + m_pDoc->getIDocumentRedlineAccess().GetRedlineAuthor() ) ); + SetRedlineFlags( m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags() ); + } + + maUndoText = GetTextFromDoc(); + + m_bCacheComment = false; +} + +SwUndoInsert::SwUndoInsert( const SwNode& rNd, sal_Int32 nCnt, + sal_Int32 nL, + const SwInsertFlags nInsertFlags, + bool bWDelim ) + : SwUndo(SwUndoId::TYPING, &rNd.GetDoc()), + m_nNode( rNd.GetIndex() ), m_nContent(nCnt), m_nLen(nL), + m_bIsWordDelim( bWDelim ), m_bIsAppend( false ) + , m_bWithRsid(false) + , m_nInsertFlags(nInsertFlags) +{ + Init(rNd); +} + +SwUndoInsert::SwUndoInsert( const SwNode& rNd ) + : SwUndo(SwUndoId::SPLITNODE, &rNd.GetDoc()), + m_nNode( rNd.GetIndex() ), m_nContent(0), m_nLen(1), + m_bIsWordDelim( false ), m_bIsAppend( true ) + , m_bWithRsid(false) + , m_nInsertFlags(SwInsertFlags::EMPTYEXPAND) +{ + Init(rNd); +} + +// Check if the next Insert can be combined with the current one. If so +// change the length and InsPos. As a result, SwDoc::Insert will not add a +// new object into the Undo list. + +bool SwUndoInsert::CanGrouping( sal_Unicode cIns ) +{ + if( !m_bIsAppend && m_bIsWordDelim == + !GetAppCharClass().isLetterNumeric( OUString( cIns )) ) + { + m_nLen++; + m_nContent++; + + if (maUndoText) + (*maUndoText) += OUStringChar(cIns); + + return true; + } + return false; +} + +bool SwUndoInsert::CanGrouping( const SwPosition& rPos ) +{ + bool bRet = false; + if( m_nNode == rPos.GetNodeIndex() && + m_nContent == rPos.GetContentIndex() ) + { + // consider Redline + SwDoc& rDoc = rPos.GetNode().GetDoc(); + if( ( ~RedlineFlags::ShowMask & rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ) == + ( ~RedlineFlags::ShowMask & GetRedlineFlags() ) ) + { + bRet = true; + + // then there is or was still an active Redline: + // Check if there is another Redline at the InsPosition. If the + // same exists only once, it can be combined. + const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable(); + if( !rTable.empty() ) + { + SwRedlineData aRData( RedlineType::Insert, rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ); + const SwContentNode* pIReg = rPos.GetContentNode(); + for(SwRangeRedline* pRedl : rTable) + { + const SwPosition& rIdx = *pRedl->End(); + if( pIReg == rIdx.GetContentNode() && + m_nContent == rIdx.GetContentIndex() ) + { + if( !pRedl->HasMark() || !m_pRedlData || + *pRedl != *m_pRedlData || *pRedl != aRData ) + { + bRet = false; + break; + } + } + } + } + } + } + return bRet; +} + +SwUndoInsert::~SwUndoInsert() +{ + if (m_oUndoNodeIndex) // delete the section from UndoNodes array + { + // Insert saves the content in IconSection + SwNodes& rUNds = m_oUndoNodeIndex->GetNodes(); + rUNds.Delete(*m_oUndoNodeIndex, + rUNds.GetEndOfExtras().GetIndex() - m_oUndoNodeIndex->GetIndex()); + m_oUndoNodeIndex.reset(); + } + else // the inserted text + { + maText.reset(); + } + m_pRedlData.reset(); +} + +void SwUndoInsert::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pTmpDoc = & rContext.GetDoc(); + SwCursor *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + if( m_bIsAppend ) + { + pPam->GetPoint()->Assign(m_nNode); + + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + pPam->SetMark(); + pPam->Move( fnMoveBackward ); + pPam->Exchange(); + pTmpDoc->getIDocumentRedlineAccess().DeleteRedline( *pPam, true, RedlineType::Any ); + } + pPam->DeleteMark(); + pTmpDoc->getIDocumentContentOperations().DelFullPara( *pPam ); + pPam->GetPoint()->SetContent( 0 ); + } + else + { + SwNodeOffset nNd = m_nNode; + sal_Int32 nCnt = m_nContent; + if( m_nLen ) + { + SwNodeIndex aNd( pTmpDoc->GetNodes(), m_nNode); + SwContentNode* pCNd = aNd.GetNode().GetContentNode(); + SwPaM aPaM( *pCNd, m_nContent ); + + aPaM.SetMark(); + + SwTextNode * const pTextNode( pCNd->GetTextNode() ); + if ( pTextNode ) + { + aPaM.GetPoint()->AdjustContent( - m_nLen ); + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + pTmpDoc->getIDocumentRedlineAccess().DeleteRedline( aPaM, true, RedlineType::Any ); + if (m_bWithRsid) + { + // RSID was added: remove any CHARFMT/AUTOFMT that may be + // set on the deleted text; EraseText will leave empty + // ones behind otherwise + pTextNode->DeleteAttributes(RES_TXTATR_AUTOFMT, + aPaM.GetPoint()->GetContentIndex(), + aPaM.GetMark()->GetContentIndex()); + pTextNode->DeleteAttributes(RES_TXTATR_CHARFMT, + aPaM.GetPoint()->GetContentIndex(), + aPaM.GetMark()->GetContentIndex()); + } + RemoveIdxFromRange( aPaM, false ); + maText = pTextNode->GetText().copy(m_nContent-m_nLen, m_nLen); + pTextNode->EraseText( *aPaM.GetPoint(), m_nLen ); + } + else // otherwise Graphics/OLE/Text/... + { + aPaM.Move(fnMoveBackward); + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + pTmpDoc->getIDocumentRedlineAccess().DeleteRedline( aPaM, true, RedlineType::Any ); + RemoveIdxFromRange( aPaM, false ); + } + + nNd = aPaM.GetPoint()->GetNodeIndex(); + nCnt = aPaM.GetPoint()->GetContentIndex(); + + if (!maText) + { + m_oUndoNodeIndex.emplace(m_pDoc->GetNodes().GetEndOfContent()); + MoveToUndoNds(aPaM, &*m_oUndoNodeIndex); + } + m_nNode = aPaM.GetPoint()->GetNodeIndex(); + m_nContent = aPaM.GetPoint()->GetContentIndex(); + } + + // set cursor to Undo range + pPam->DeleteMark(); + + pPam->GetPoint()->Assign( nNd, nCnt ); + } + + maUndoText.reset(); +} + +void SwUndoInsert::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pTmpDoc = & rContext.GetDoc(); + SwCursor *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + pPam->DeleteMark(); + + if( m_bIsAppend ) + { + pPam->GetPoint()->Assign( m_nNode - 1 ); + pTmpDoc->getIDocumentContentOperations().AppendTextNode( *pPam->GetPoint() ); + + pPam->SetMark(); + pPam->Move( fnMoveBackward ); + pPam->Exchange(); + + if( m_pRedlData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + RedlineFlags eOld = pTmpDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pTmpDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags::Ignore); + pTmpDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlData, *pPam ), true); + pTmpDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) && + !pTmpDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() ) + pTmpDoc->getIDocumentRedlineAccess().SplitRedline( *pPam ); + + pPam->DeleteMark(); + } + else + { + pPam->GetPoint()->Assign( m_nNode ); + SwContentNode *const pCNd = + pPam->GetPoint()->GetNode().GetContentNode(); + pPam->GetPoint()->SetContent( m_nContent ); + + if( m_nLen ) + { + ::std::optional oMvBkwrd = MovePtBackward(*pPam); + + if (maText) + { + SwTextNode *const pTextNode = pCNd->GetTextNode(); + OSL_ENSURE( pTextNode, "where is my textnode ?" ); + OUString const ins( + pTextNode->InsertText( *maText, *pPam->GetMark(), + m_nInsertFlags) ); + assert(ins.getLength() == maText->getLength()); // must succeed + maText.reset(); + if (m_bWithRsid) // re-insert RSID + { + SwPaM pam(*pPam->GetMark(), nullptr); // mark -> point + pTmpDoc->UpdateRsid(pam, ins.getLength()); + } + } + else + { + // re-insert content again (first detach m_oUndoNodeIndex!) + SwNodeOffset const nMvNd = m_oUndoNodeIndex->GetIndex(); + m_oUndoNodeIndex.reset(); + MoveFromUndoNds(*pTmpDoc, nMvNd, *pPam->GetMark()); + } + m_nNode = pPam->GetMark()->GetNodeIndex(); + m_nContent = pPam->GetMark()->GetContentIndex(); + + MovePtForward(*pPam, ::std::move(oMvBkwrd)); + pPam->Exchange(); + if( m_pRedlData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + RedlineFlags eOld = pTmpDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pTmpDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags::Ignore); + pTmpDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlData, + *pPam ), true); + pTmpDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) && + !pTmpDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() ) + pTmpDoc->getIDocumentRedlineAccess().SplitRedline(*pPam); + } + } + + maUndoText = GetTextFromDoc(); +} + +void SwUndoInsert::RepeatImpl(::sw::RepeatContext & rContext) +{ + if( !m_nLen ) + return; + + SwDoc & rDoc = rContext.GetDoc(); + SwNodeIndex aNd( rDoc.GetNodes(), m_nNode ); + SwContentNode* pCNd = aNd.GetNode().GetContentNode(); + + if( !m_bIsAppend && 1 == m_nLen ) // >1 than always Text, otherwise Graphics/OLE/Text/... + { + SwPaM aPaM( *pCNd, m_nContent ); + aPaM.SetMark(); + aPaM.Move(fnMoveBackward); + pCNd = aPaM.GetPointContentNode(); + } + +// What happens with the possible selected range ??? + + switch( pCNd->GetNodeType() ) + { + case SwNodeType::Text: + if( m_bIsAppend ) + { + rDoc.getIDocumentContentOperations().AppendTextNode( *rContext.GetRepeatPaM().GetPoint() ); + } + else + { + OUString const aText( pCNd->GetTextNode()->GetText() ); + ::sw::GroupUndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + rDoc.getIDocumentContentOperations().InsertString( rContext.GetRepeatPaM(), + aText.copy(m_nContent - m_nLen, m_nLen) ); + } + break; + case SwNodeType::Grf: + { + SwGrfNode* pGrfNd = static_cast(pCNd); + OUString sFile; + OUString sFilter; + if( pGrfNd->IsGrfLink() ) + pGrfNd->GetFileFilterNms( &sFile, &sFilter ); + + rDoc.getIDocumentContentOperations().InsertGraphic( + rContext.GetRepeatPaM(), sFile, sFilter, + &pGrfNd->GetGrf(), + nullptr/* Graphics collection*/, nullptr, nullptr ); + } + break; + + case SwNodeType::Ole: + { + // StarView does not yet provide an option to copy a StarOBJ + SwOLEObj& rSwOLE = static_cast(pCNd)->GetOLEObj(); + + // temporary storage until object is inserted + // TODO/MBA: seems that here a physical copy is done - not as in drawing layer! Testing! + // TODO/LATER: Copying through the container would copy the replacement image as well + comphelper::EmbeddedObjectContainer aCnt; + OUString aName = aCnt.CreateUniqueObjectName(); + if (aCnt.StoreEmbeddedObject(rSwOLE.GetOleRef(), aName, true, OUString(), OUString())) + { + uno::Reference < embed::XEmbeddedObject > aNew = aCnt.GetEmbeddedObject( aName ); + rDoc.getIDocumentContentOperations().InsertEmbObject( + rContext.GetRepeatPaM(), + svt::EmbeddedObjectRef( aNew, + static_cast(pCNd)->GetAspect() ), + nullptr ); + } + + break; + } + + default: break; + } +} + +SwRewriter SwUndoInsert::GetRewriter() const +{ + SwRewriter aResult; + std::optional aStr; + bool bDone = false; + + if (maText) + aStr = maText; + else if (maUndoText) + aStr = maUndoText; + + if (aStr) + { + OUString aString = ShortenString(DenoteSpecialCharacters(*aStr), + nUndoStringLength, + SwResId(STR_LDOTS)); + + aResult.AddRule(UndoArg1, aString); + + bDone = true; + } + + if ( ! bDone ) + { + aResult.AddRule(UndoArg1, "??"); + } + + return aResult; +} + +bool SwUndoInsert::IsIndependent(const SwUndoInsert& rOther) const +{ + return m_nNode != rOther.m_nNode; +} + +class SwUndoReplace::Impl + : private SwUndoSaveContent +{ + OUString m_sOld; + OUString m_sIns; + SwNodeOffset m_nSttNd, m_nEndNd, m_nOffset; + sal_Int32 m_nSttCnt, m_nEndCnt, m_nSetPos, m_nSelEnd; + bool m_bSplitNext : 1; + bool m_bRegExp : 1; + // metadata references for paragraph and following para (if m_bSplitNext) + std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndoStart; + std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndoEnd; + +public: + Impl(SwPaM const& rPam, OUString aIns, bool const bRegExp); + + void UndoImpl( ::sw::UndoRedoContext & ); + void RedoImpl( ::sw::UndoRedoContext & ); + + void SetEnd(SwPaM const& rPam); + + OUString const& GetOld() const { return m_sOld; } + OUString const& GetIns() const { return m_sIns; } +}; + +SwUndoReplace::SwUndoReplace(SwPaM const& rPam, + OUString const& rIns, bool const bRegExp) + : SwUndo( SwUndoId::REPLACE, &rPam.GetDoc() ) + , m_pImpl(std::make_unique(rPam, rIns, bRegExp)) +{ +} + +SwUndoReplace::~SwUndoReplace() +{ +} + +void SwUndoReplace::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_pImpl->UndoImpl(rContext); +} + +void SwUndoReplace::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_pImpl->RedoImpl(rContext); +} + +SwRewriter +MakeUndoReplaceRewriter(sal_uLong const occurrences, + OUString const& sOld, OUString const& sNew) +{ + SwRewriter aResult; + + if (1 < occurrences) + { + aResult.AddRule(UndoArg1, OUString::number(occurrences)); + aResult.AddRule(UndoArg2, SwResId(STR_OCCURRENCES_OF)); + + OUString aTmpStr = + SwResId(STR_START_QUOTE) + + ShortenString(sOld, nUndoStringLength, SwResId(STR_LDOTS)) + + SwResId(STR_END_QUOTE); + aResult.AddRule(UndoArg3, aTmpStr); + } + else if (1 == occurrences) + { + { + // #i33488 # + OUString aTmpStr = + SwResId(STR_START_QUOTE) + + ShortenString(sOld, nUndoStringLength, SwResId(STR_LDOTS)) + + SwResId(STR_END_QUOTE); + aResult.AddRule(UndoArg1, aTmpStr); + } + + aResult.AddRule(UndoArg2, SwResId(STR_YIELDS)); + + { + // #i33488 # + OUString aTmpStr = + SwResId(STR_START_QUOTE) + + ShortenString(sNew, nUndoStringLength, SwResId(STR_LDOTS)) + + SwResId(STR_END_QUOTE); + aResult.AddRule(UndoArg3, aTmpStr); + } + } + + return aResult; +} + +SwRewriter SwUndoReplace::GetRewriter() const +{ + return MakeUndoReplaceRewriter(1, m_pImpl->GetOld(), m_pImpl->GetIns()); +} + +void SwUndoReplace::SetEnd(SwPaM const& rPam) +{ + m_pImpl->SetEnd(rPam); +} + +SwUndoReplace::Impl::Impl( + SwPaM const& rPam, OUString aIns, bool const bRegExp) + : m_sIns(std::move( aIns )) + , m_nOffset( 0 ) + , m_bRegExp(bRegExp) +{ + + auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition* + + m_nSttNd = m_nEndNd = pStt->GetNodeIndex(); + m_nSttCnt = pStt->GetContentIndex(); + m_nSelEnd = m_nEndCnt = pEnd->GetContentIndex(); + + m_bSplitNext = m_nSttNd != pEnd->GetNodeIndex(); + + SwTextNode* pNd = pStt->GetNode().GetTextNode(); + OSL_ENSURE( pNd, "Dude, where's my TextNode?" ); + + m_pHistory.reset( new SwHistory ); + DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(), DelContentType::AllMask | DelContentType::Replace); + + m_nSetPos = m_pHistory->Count(); + + SwNodeOffset nNewPos = pStt->GetNodeIndex(); + m_nOffset = m_nSttNd - nNewPos; + + if ( pNd->GetpSwpHints() ) + { + m_pHistory->CopyAttr( pNd->GetpSwpHints(), nNewPos, 0, + pNd->GetText().getLength(), true ); + } + + if ( m_bSplitNext ) + { + if( pNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pNd->GetpSwAttrSet(), nNewPos ); + m_pHistory->AddColl(pNd->GetTextColl(), nNewPos, SwNodeType::Text); + + SwTextNode* pNext = pEnd->GetNode().GetTextNode(); + SwNodeOffset nTmp = pNext->GetIndex(); + m_pHistory->CopyAttr( pNext->GetpSwpHints(), nTmp, 0, + pNext->GetText().getLength(), true ); + if( pNext->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pNext->GetpSwAttrSet(), nTmp ); + m_pHistory->AddColl(pNext->GetTextColl(),nTmp, SwNodeType::Text); + // METADATA: store + m_pMetadataUndoStart = pNd ->CreateUndo(); + m_pMetadataUndoEnd = pNext->CreateUndo(); + } + + if( !m_pHistory->Count() ) + { + m_pHistory.reset(); + } + + const sal_Int32 nECnt = m_bSplitNext ? pNd->GetText().getLength() + : pEnd->GetContentIndex(); + m_sOld = pNd->GetText().copy( m_nSttCnt, nECnt - m_nSttCnt ); +} + +void SwUndoReplace::Impl::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwCursor & rPam(rContext.GetCursorSupplier().CreateNewShellCursor()); + rPam.DeleteMark(); + + SwTextNode* pNd = pDoc->GetNodes()[ m_nSttNd - m_nOffset ]->GetTextNode(); + OSL_ENSURE( pNd, "Dude, where's my TextNode?" ); + + SwAutoCorrExceptWord* pACEWord = pDoc->GetAutoCorrExceptWord(); + if( pACEWord ) + { + if ((1 == m_sIns.getLength()) && (1 == m_sOld.getLength())) + { + SwPosition aPos( *pNd, m_nSttCnt ); + pACEWord->CheckChar( aPos, m_sOld[ 0 ] ); + } + pDoc->SetAutoCorrExceptWord( nullptr ); + } + + // don't look at m_sIns for deletion, maybe it was not completely inserted + { + rPam.GetPoint()->Assign(*pNd, m_nSttCnt ); + rPam.SetMark(); + rPam.GetPoint()->Assign( m_nSttNd - m_nOffset, m_nSttNd == m_nEndNd ? m_nEndCnt : pNd->Len()); + + // replace only in start node, without regex + bool const ret = pDoc->getIDocumentContentOperations().ReplaceRange(rPam, m_sOld, false); + assert(ret); (void)ret; + if (m_nSttNd != m_nEndNd) + { // in case of regex inserting paragraph breaks, join nodes... + assert(rPam.GetMark()->GetContentIndex() == rPam.GetMark()->GetNode().GetTextNode()->Len()); + rPam.GetPoint()->Assign( m_nEndNd - m_nOffset, m_nEndCnt ); + pDoc->getIDocumentContentOperations().DeleteAndJoin(rPam); + } + rPam.DeleteMark(); + pNd = pDoc->GetNodes()[ m_nSttNd - m_nOffset ]->GetTextNode(); + OSL_ENSURE( pNd, "Dude, where's my TextNode?" ); + } + + if( m_bSplitNext ) + { + assert(m_nSttCnt + m_sOld.getLength() <= pNd->Len()); + SwPosition aPos(*pNd, m_nSttCnt + m_sOld.getLength()); + pDoc->getIDocumentContentOperations().SplitNode( aPos, false ); + pNd->RestoreMetadata(m_pMetadataUndoEnd); + pNd = pDoc->GetNodes()[ m_nSttNd - m_nOffset ]->GetTextNode(); + // METADATA: restore + pNd->RestoreMetadata(m_pMetadataUndoStart); + } + + if( m_pHistory ) + { + if( pNd->GetpSwpHints() ) + pNd->ClearSwpHintsArr( true ); + + m_pHistory->TmpRollback( pDoc, m_nSetPos, false ); + if ( m_nSetPos ) // there were footnotes/FlyFrames + { + // are there others than these? + if( m_nSetPos < m_pHistory->Count() ) + { + // than save those attributes as well + SwHistory aHstr; + aHstr.Move( 0, m_pHistory.get(), m_nSetPos ); + m_pHistory->Rollback( pDoc ); + m_pHistory->Move( 0, &aHstr ); + } + else + { + m_pHistory->Rollback( pDoc ); + m_pHistory.reset(); + } + } + } + + rPam.GetPoint()->Assign( m_nSttNd, m_nSttCnt ); +} + +void SwUndoReplace::Impl::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwCursor & rPam(rContext.GetCursorSupplier().CreateNewShellCursor()); + rPam.DeleteMark(); + rPam.GetPoint()->Assign( m_nSttNd, m_nSttCnt ); + + rPam.SetMark(); + if( m_bSplitNext ) + rPam.GetPoint()->Assign( m_nSttNd + 1 ); + rPam.GetPoint()->SetContent( m_nSelEnd ); + + if( m_pHistory ) + { + auto xSave = std::make_unique(); + std::swap(m_pHistory, xSave); + + DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(), DelContentType::AllMask | DelContentType::Replace); + m_nSetPos = m_pHistory->Count(); + + std::swap(xSave, m_pHistory); + m_pHistory->Move(0, xSave.get()); + } + else + { + m_pHistory.reset( new SwHistory ); + DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(), DelContentType::AllMask | DelContentType::Replace); + m_nSetPos = m_pHistory->Count(); + if( !m_nSetPos ) + { + m_pHistory.reset(); + } + } + + rDoc.getIDocumentContentOperations().ReplaceRange( rPam, m_sIns, m_bRegExp ); + rPam.DeleteMark(); +} + +void SwUndoReplace::Impl::SetEnd(SwPaM const& rPam) +{ + const SwPosition* pEnd = rPam.End(); + m_nEndNd = m_nOffset + pEnd->GetNodeIndex(); + m_nEndCnt = pEnd->GetContentIndex(); +} + +SwUndoReRead::SwUndoReRead( const SwPaM& rPam, const SwGrfNode& rGrfNd ) + : SwUndo( SwUndoId::REREAD, &rPam.GetDoc() ), mnPosition( rPam.GetPoint()->GetNodeIndex() ) +{ + SaveGraphicData( rGrfNd ); +} + +SwUndoReRead::~SwUndoReRead() +{ +} + +void SwUndoReRead::SetAndSave(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwGrfNode* pGrfNd = rDoc.GetNodes()[ mnPosition ]->GetGrfNode(); + + if( !pGrfNd ) + return ; + + // cache the old values + std::optional oOldGrf(moGraphic); + std::optional aOldNm = maNm; + MirrorGraph nOldMirr = mnMirror; + // since all of them are cleared/modified by SaveGraphicData: + SaveGraphicData( *pGrfNd ); + + if( aOldNm ) + { + pGrfNd->ReRead( *aOldNm, maFltr ? *maFltr : OUString() ); + } + else + { + pGrfNd->ReRead( OUString(), OUString(), oOldGrf ? &*oOldGrf : nullptr ); + } + + if( MirrorGraph::Dont != nOldMirr ) + pGrfNd->SetAttr( SwMirrorGrf() ); + + rContext.SetSelections(pGrfNd->GetFlyFormat(), nullptr); +} + +void SwUndoReRead::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SetAndSave(rContext); +} + +void SwUndoReRead::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SetAndSave(rContext); +} + +void SwUndoReRead::SaveGraphicData( const SwGrfNode& rGrfNd ) +{ + if( rGrfNd.IsGrfLink() ) + { + maNm = OUString(); + maFltr = OUString(); + rGrfNd.GetFileFilterNms(&*maNm, &*maFltr); + moGraphic.reset(); + } + else + { + moGraphic.emplace( rGrfNd.GetGrf(true) ); + maNm.reset(); + maFltr.reset(); + } + mnMirror = rGrfNd.GetSwAttrSet().GetMirrorGrf().GetValue(); +} + +SwUndoInsertLabel::SwUndoInsertLabel( const SwLabelType eTyp, + OUString aText, + OUString aSeparator, + OUString aNumberSeparator, + const bool bBef, + const sal_uInt16 nInitId, + OUString aCharacterStyle, + const bool bCpyBorder, + const SwDoc* pDoc ) + : SwUndo( SwUndoId::INSERTLABEL, pDoc ), + m_sText(std::move( aText )), + m_sSeparator(std::move( aSeparator )), + m_sNumberSeparator(std::move( aNumberSeparator )),//#i61007# order of captions + m_sCharacterStyle(std::move( aCharacterStyle )), + m_nFieldId( nInitId ), + m_eType( eTyp ), + m_nLayerId( 0 ), + m_bBefore( bBef ), + m_bCopyBorder( bCpyBorder ) +{ + m_bUndoKeep = false; + OBJECT.pUndoFly = nullptr; + OBJECT.pUndoAttr = nullptr; +} + +SwUndoInsertLabel::~SwUndoInsertLabel() +{ + if( SwLabelType::Object == m_eType || SwLabelType::Draw == m_eType ) + { + delete OBJECT.pUndoFly; + delete OBJECT.pUndoAttr; + } + else + delete NODE.pUndoInsNd; +} + +void SwUndoInsertLabel::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + if( SwLabelType::Object == m_eType || SwLabelType::Draw == m_eType ) + { + OSL_ENSURE( OBJECT.pUndoAttr && OBJECT.pUndoFly, "Pointer not initialized" ); + SwFrameFormat* pFormat; + SdrObject *pSdrObj = nullptr; + if( OBJECT.pUndoAttr && + nullptr != (pFormat = static_cast(OBJECT.pUndoAttr->GetFormat( rDoc ))) && + ( SwLabelType::Draw != m_eType || + nullptr != (pSdrObj = pFormat->FindSdrObject()) ) ) + { + OBJECT.pUndoAttr->UndoImpl(rContext); + OBJECT.pUndoFly->UndoImpl(rContext); + if( SwLabelType::Draw == m_eType ) + { + pSdrObj->SetLayer( m_nLayerId ); + } + } + } + else if( NODE.nNode ) + { + if ( m_eType == SwLabelType::Table && m_bUndoKeep ) + { + SwTableNode *pNd = rDoc.GetNodes()[ + rDoc.GetNodes()[NODE.nNode-1]->StartOfSectionIndex()]->GetTableNode(); + if ( pNd ) + pNd->GetTable().GetFrameFormat()->ResetFormatAttr( RES_KEEP ); + } + SwPaM aPam( rDoc.GetNodes().GetEndOfContent() ); + aPam.GetPoint()->Assign( NODE.nNode ); + aPam.SetMark(); + aPam.GetPoint()->Assign( NODE.nNode + 1 ); + NODE.pUndoInsNd = new SwUndoDelete(aPam, SwDeleteFlags::Default, true); + } +} + +void SwUndoInsertLabel::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + if( SwLabelType::Object == m_eType || SwLabelType::Draw == m_eType ) + { + OSL_ENSURE( OBJECT.pUndoAttr && OBJECT.pUndoFly, "Pointer not initialized" ); + SwFrameFormat* pFormat; + SdrObject *pSdrObj = nullptr; + if( OBJECT.pUndoAttr && + nullptr != (pFormat = static_cast(OBJECT.pUndoAttr->GetFormat( rDoc ))) && + ( SwLabelType::Draw != m_eType || + nullptr != (pSdrObj = pFormat->FindSdrObject()) ) ) + { + OBJECT.pUndoFly->RedoImpl(rContext); + OBJECT.pUndoAttr->RedoImpl(rContext); + if( SwLabelType::Draw == m_eType ) + { + pSdrObj->SetLayer( m_nLayerId ); + if( pSdrObj->GetLayer() == rDoc.getIDocumentDrawModelAccess().GetHellId() ) + pSdrObj->SetLayer( rDoc.getIDocumentDrawModelAccess().GetHeavenId() ); + // OD 02.07.2003 #108784# + else if( pSdrObj->GetLayer() == rDoc.getIDocumentDrawModelAccess().GetInvisibleHellId() ) + pSdrObj->SetLayer( rDoc.getIDocumentDrawModelAccess().GetInvisibleHeavenId() ); + } + } + } + else if( NODE.pUndoInsNd ) + { + if ( m_eType == SwLabelType::Table && m_bUndoKeep ) + { + SwTableNode *pNd = rDoc.GetNodes()[ + rDoc.GetNodes()[NODE.nNode-1]->StartOfSectionIndex()]->GetTableNode(); + if ( pNd ) + pNd->GetTable().GetFrameFormat()->SetFormatAttr( SvxFormatKeepItem(true, RES_KEEP) ); + } + NODE.pUndoInsNd->UndoImpl(rContext); + delete NODE.pUndoInsNd; + NODE.pUndoInsNd = nullptr; + } +} + +void SwUndoInsertLabel::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + const SwPosition& rPos = *rContext.GetRepeatPaM().GetPoint(); + + SwNodeOffset nIdx(0); + + SwContentNode* pCNd = rPos.GetNode().GetContentNode(); + if( pCNd ) + switch( m_eType ) + { + case SwLabelType::Table: + { + const SwTableNode* pTNd = pCNd->FindTableNode(); + if( pTNd ) + nIdx = pTNd->GetIndex(); + } + break; + + case SwLabelType::Fly: + case SwLabelType::Object: + { + SwFlyFrame* pFly; + SwContentFrame *pCnt = pCNd->getLayoutFrame( rDoc.getIDocumentLayoutAccess().GetCurrentLayout() ); + if( pCnt && nullptr != ( pFly = pCnt->FindFlyFrame() ) ) + nIdx = pFly->GetFormat()->GetContent().GetContentIdx()->GetIndex(); + } + break; + case SwLabelType::Draw: + break; + } + + if( nIdx ) + { + rDoc.InsertLabel( m_eType, m_sText, m_sSeparator, m_sNumberSeparator, m_bBefore, + m_nFieldId, nIdx, m_sCharacterStyle, m_bCopyBorder ); + } +} + +SwRewriter SwUndoInsertLabel::GetRewriter() const +{ + return CreateRewriter(m_sText); +} + +SwRewriter SwUndoInsertLabel::CreateRewriter(const OUString &rStr) +{ + SwRewriter aRewriter; + + OUString aTmpStr; + + if (!rStr.isEmpty()) + { + aTmpStr = + SwResId(STR_START_QUOTE) + + ShortenString(rStr, nUndoStringLength, SwResId(STR_LDOTS)) + + SwResId(STR_END_QUOTE); + } + + aRewriter.AddRule(UndoArg1, aTmpStr); + + return aRewriter; +} + +void SwUndoInsertLabel::SetFlys( SwFrameFormat& rOldFly, SfxItemSet const & rChgSet, + SwFrameFormat& rNewFly ) +{ + if( SwLabelType::Object == m_eType || SwLabelType::Draw == m_eType ) + { + SwUndoFormatAttrHelper aTmp( rOldFly, false ); + rOldFly.SetFormatAttr( rChgSet ); + if ( aTmp.GetUndo() ) + { + OBJECT.pUndoAttr = aTmp.ReleaseUndo().release(); + } + OBJECT.pUndoFly = new SwUndoInsLayFormat( &rNewFly, SwNodeOffset(0), 0 ); + } +} + +void SwUndoInsertLabel::SetDrawObj( SdrLayerID nLId ) +{ + if( SwLabelType::Draw == m_eType ) + { + m_nLayerId = nLId; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unmove.cxx b/sw/source/core/undo/unmove.cxx new file mode 100644 index 0000000000..0ea6443f61 --- /dev/null +++ b/sw/source/core/undo/unmove.cxx @@ -0,0 +1,305 @@ +/* -*- 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 + +// MOVE +SwUndoMove::SwUndoMove( const SwPaM& rRange, const SwPosition& rMvPos ) + : SwUndo( SwUndoId::MOVE, &rRange.GetDoc() ) + , SwUndRng( rRange ) + , m_nDestStartNode(0) + , m_nDestEndNode(0) + , m_nInsPosNode(0) + , m_nMoveDestNode(rMvPos.GetNodeIndex()) + , m_nDestStartContent(0) + , m_nDestEndContent(0) + , m_nInsPosContent(0) + , m_nMoveDestContent(rMvPos.GetContentIndex()) + , m_bJoinNext(false) + , m_bMoveRange(false) + , m_bMoveRedlines(false) +{ + // get StartNode from footnotes before delete! + SwDoc& rDoc = rRange.GetDoc(); + SwTextNode* pTextNd = rDoc.GetNodes()[ m_nSttNode ]->GetTextNode(); + SwTextNode* pEndTextNd = rDoc.GetNodes()[ m_nEndNode ]->GetTextNode(); + + m_pHistory.reset( new SwHistory ); + + if( pTextNd ) + { + m_pHistory->AddColl(pTextNd->GetTextColl(), m_nSttNode, SwNodeType::Text); + if ( pTextNd->GetpSwpHints() ) + { + m_pHistory->CopyAttr( pTextNd->GetpSwpHints(), m_nSttNode, + 0, pTextNd->GetText().getLength(), false ); + } + if( pTextNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pTextNd->GetpSwAttrSet(), m_nSttNode ); + } + if( pEndTextNd && pEndTextNd != pTextNd ) + { + m_pHistory->AddColl(pEndTextNd->GetTextColl(), m_nEndNode, SwNodeType::Text); + if ( pEndTextNd->GetpSwpHints() ) + { + m_pHistory->CopyAttr( pEndTextNd->GetpSwpHints(), m_nEndNode, + 0, pEndTextNd->GetText().getLength(), false ); + } + if( pEndTextNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pEndTextNd->GetpSwAttrSet(), m_nEndNode ); + } + + pTextNd = rMvPos.GetNode().GetTextNode(); + if (nullptr != pTextNd) + { + m_pHistory->AddColl(pTextNd->GetTextColl(), m_nMoveDestNode, SwNodeType::Text); + if ( pTextNd->GetpSwpHints() ) + { + m_pHistory->CopyAttr( pTextNd->GetpSwpHints(), m_nMoveDestNode, + 0, pTextNd->GetText().getLength(), false ); + } + if( pTextNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pTextNd->GetpSwAttrSet(), m_nMoveDestNode ); + } + + m_nFootnoteStart = m_pHistory->Count(); + DelFootnote( rRange ); + + if( m_pHistory && !m_pHistory->Count() ) + m_pHistory.reset(); +} + +SwUndoMove::SwUndoMove( SwDoc& rDoc, const SwNodeRange& rRg, + const SwNode& rMvPos ) + : SwUndo(SwUndoId::MOVE, &rDoc) + , m_nDestStartNode(0) + , m_nDestEndNode(0) + , m_nInsPosNode(0) + , m_nMoveDestNode(rMvPos.GetIndex()) + , m_nDestStartContent(0) + , m_nDestEndContent(0) + , m_nInsPosContent(0) + , m_bMoveRedlines(false) +{ + m_bMoveRange = true; + m_bJoinNext = false; + + m_nSttContent = m_nEndContent = m_nMoveDestContent = COMPLETE_STRING; + + m_nSttNode = rRg.aStart.GetIndex(); + m_nEndNode = rRg.aEnd.GetIndex(); + +// DelFootnote( rRange ); +// FIXME: duplication of the method body of DelFootnote below + + // is the current move from ContentArea into the special section? + SwNodeOffset nContentStt = rDoc.GetNodes().GetEndOfAutotext().GetIndex(); + if( m_nMoveDestNode < nContentStt && rRg.aStart.GetIndex() > nContentStt ) + { + // delete all footnotes since they are undesired there + SwPosition aPtPos( rRg.aEnd ); + SwContentNode* pCNd = rRg.aEnd.GetNode().GetContentNode(); + if( pCNd ) + aPtPos.SetContent( pCNd->Len() ); + SwPosition aMkPos( rRg.aStart ); + + DelContentIndex( aMkPos, aPtPos, DelContentType::Ftn ); + + if( m_pHistory && !m_pHistory->Count() ) + m_pHistory.reset(); + } + + m_nFootnoteStart = 0; +} + +void SwUndoMove::SetDestRange( const SwNode& rStt, + const SwNode& rEnd, + const SwNodeIndex& rInsPos ) +{ + m_nDestStartNode = rStt.GetIndex(); + m_nDestEndNode = rEnd.GetIndex(); + if( m_nDestStartNode > m_nDestEndNode ) + { + m_nDestStartNode = m_nDestEndNode; + m_nDestEndNode = rStt.GetIndex(); + } + m_nInsPosNode = rInsPos.GetIndex(); + + m_nDestStartContent = m_nDestEndContent = m_nInsPosContent = COMPLETE_STRING; +} + +void SwUndoMove::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + + // Block so that we can jump out of it + do { + // create index position and section based on the existing values + SwNodeIndex aIdx( pDoc->GetNodes(), m_nDestStartNode ); + + if( m_bMoveRange ) + { + // only a move with SwRange + SwNodeRange aRg( aIdx, aIdx ); + aRg.aEnd = m_nDestEndNode; + aIdx = m_nInsPosNode; + bool bSuccess = pDoc->getIDocumentContentOperations().MoveNodeRange( aRg, aIdx.GetNode(), + SwMoveFlags::DEFAULT ); + if (!bSuccess) + break; + } + else + { + SwPaM aPam( aIdx.GetNode(), m_nDestStartContent, + *pDoc->GetNodes()[ m_nDestEndNode ], m_nDestEndContent ); + + // #i17764# if redlines are to be moved, we may not remove them + // before pDoc->Move gets a chance to handle them + if( ! m_bMoveRedlines ) + RemoveIdxFromRange( aPam, false ); + + SwPosition aPos( *pDoc->GetNodes()[ m_nInsPosNode] ); + SwContentNode* pCNd = aPos.GetNode().GetContentNode(); + aPos.SetContent( m_nInsPosContent ); + + if( pCNd->HasSwAttrSet() ) + pCNd->ResetAllAttr(); + + if( pCNd->IsTextNode() && static_cast(pCNd)->GetpSwpHints() ) + static_cast(pCNd)->ClearSwpHintsArr( false ); + + // first delete all attributes at InsertPos + const bool bSuccess = pDoc->getIDocumentContentOperations().MoveRange( aPam, aPos, m_bMoveRedlines + ? SwMoveFlags::REDLINES + : SwMoveFlags::DEFAULT ); + if (!bSuccess) + break; + + aPam.Exchange(); + aPam.DeleteMark(); + if( aPam.GetPointNode().IsContentNode() ) + aPam.GetPointNode().GetContentNode()->ResetAllAttr(); + // the Pam will be dropped now + } + + SwTextNode* pTextNd = aIdx.GetNode().GetTextNode(); + if( m_bJoinNext ) + { + { + RemoveIdxRel( aIdx.GetIndex() + 1, + SwPosition( aIdx, pTextNd, pTextNd->GetText().getLength()) ); + } + // Are there any Pams in the next TextNode? + pTextNd->JoinNext(); + } + + } while( false ); + + if( m_pHistory ) + { + if( m_nFootnoteStart != m_pHistory->Count() ) + m_pHistory->Rollback( pDoc, m_nFootnoteStart ); + m_pHistory->TmpRollback( pDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + } + + // set the cursor onto Undo area + if( !m_bMoveRange ) + { + AddUndoRedoPaM(rContext); + } +} + +void SwUndoMove::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM& rPam = AddUndoRedoPaM(rContext); + SwDoc& rDoc = rContext.GetDoc(); + + SwNodes& rNds = rDoc.GetNodes(); + SwNodeIndex aIdx( rNds, m_nMoveDestNode ); + + if( m_bMoveRange ) + { + // only a move with SwRange + SwNodeRange aRg( rNds, m_nSttNode, m_nEndNode ); + rDoc.getIDocumentContentOperations().MoveNodeRange( aRg, aIdx.GetNode(), m_bMoveRedlines + ? SwMoveFlags::REDLINES + : SwMoveFlags::DEFAULT ); + } + else + { + SwPaM aPam(*rPam.GetPoint()); + SetPaM( aPam ); + SwPosition aMvPos( aIdx, aIdx.GetNode().GetContentNode(), m_nMoveDestContent ); + + DelFootnote( aPam ); + RemoveIdxFromRange( aPam, false ); + + aIdx = aPam.Start()->GetNode(); + bool bJoinText = aIdx.GetNode().IsTextNode(); + + --aIdx; + rDoc.getIDocumentContentOperations().MoveRange( aPam, aMvPos, + SwMoveFlags::DEFAULT ); + + if( m_nSttNode != m_nEndNode && bJoinText ) + { + ++aIdx; + SwTextNode * pTextNd = aIdx.GetNode().GetTextNode(); + if( pTextNd && pTextNd->CanJoinNext() ) + { + { + RemoveIdxRel( aIdx.GetIndex() + 1, + SwPosition( *pTextNd, pTextNd->GetText().getLength()) ); + } + pTextNd->JoinNext(); + } + } + *rPam.GetPoint() = *aPam.GetPoint(); + rPam.SetMark(); + *rPam.GetMark() = *aPam.GetMark(); + } +} + +void SwUndoMove::DelFootnote( const SwPaM& rRange ) +{ + // is the current move from ContentArea into the special section? + SwDoc& rDoc = rRange.GetDoc(); + SwNodeOffset nContentStt = rDoc.GetNodes().GetEndOfAutotext().GetIndex(); + if( m_nMoveDestNode < nContentStt && + rRange.GetPoint()->GetNodeIndex() >= nContentStt ) + { + // delete all footnotes since they are undesired there + DelContentIndex( *rRange.GetMark(), *rRange.GetPoint(), + DelContentType::Ftn ); + + if( m_pHistory && !m_pHistory->Count() ) + { + m_pHistory.reset(); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unnum.cxx b/sw/source/core/undo/unnum.cxx new file mode 100644 index 0000000000..1251635a31 --- /dev/null +++ b/sw/source/core/undo/unnum.cxx @@ -0,0 +1,397 @@ +/* -*- 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 + +SwUndoInsNum::SwUndoInsNum( const SwNumRule& rOldRule, + const SwNumRule& rNewRule, + const SwDoc& rDoc, + SwUndoId nUndoId) + : SwUndo( nUndoId, &rDoc ), + m_aNumRule( rNewRule ), + m_pOldNumRule( new SwNumRule( rOldRule )), m_nLRSavePos( 0 ) +{ +} + +SwUndoInsNum::SwUndoInsNum( const SwPaM& rPam, const SwNumRule& rRule ) + : SwUndo( SwUndoId::INSNUM, &rPam.GetDoc() ), SwUndRng( rPam ), + m_aNumRule( rRule ), + m_nLRSavePos( 0 ) +{ +} + +SwUndoInsNum::SwUndoInsNum( const SwPosition& rPos, const SwNumRule& rRule, + OUString aReplaceRule ) + : SwUndo( SwUndoId::INSNUM, &rPos.GetNode().GetDoc() ), + m_aNumRule( rRule ), + m_sReplaceRule(std::move( aReplaceRule )), m_nLRSavePos( 0 ) +{ + // No selection! + m_nEndNode = SwNodeOffset(0); + m_nEndContent = COMPLETE_STRING; + m_nSttNode = rPos.GetNodeIndex(); + m_nSttContent = rPos.GetContentIndex(); +} + +SwUndoInsNum::~SwUndoInsNum() +{ + m_pHistory.reset(); + m_pOldNumRule.reset(); +} + +SwRewriter SwUndoInsNum::GetRewriter() const +{ + SwRewriter aResult; + if( SwUndoId::INSFMTATTR == GetId() ) + aResult.AddRule(UndoArg1, m_aNumRule.GetName()); + return aResult; +} + +void SwUndoInsNum::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + if( m_pOldNumRule ) + rDoc.ChgNumRuleFormats( *m_pOldNumRule ); + + if( m_pHistory ) + { + if( m_nLRSavePos ) + { + // Update immediately so that potential "old" LRSpaces will be valid again. + m_pHistory->TmpRollback( &rDoc, m_nLRSavePos ); + + } + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + } + + if (m_nSttNode) + { + AddUndoRedoPaM(rContext); + } +} + +void SwUndoInsNum::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + if ( m_pOldNumRule ) + rDoc.ChgNumRuleFormats( m_aNumRule ); + else if ( m_pHistory ) + { + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + if( !m_sReplaceRule.isEmpty() ) + { + rDoc.ReplaceNumRule( *rPam.GetPoint(), m_sReplaceRule, m_aNumRule.GetName() ); + } + else + { + // #i42921# - adapt to changed signature + rDoc.SetNumRule(rPam, m_aNumRule, false); + } + } +} + +void SwUndoInsNum::SetLRSpaceEndPos() +{ + if( m_pHistory ) + m_nLRSavePos = m_pHistory->Count(); +} + +void SwUndoInsNum::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc( rContext.GetDoc() ); + if ( m_nSttNode ) + { + if( m_sReplaceRule.isEmpty() ) + { + // #i42921# - adapt to changed signature + rDoc.SetNumRule(rContext.GetRepeatPaM(), m_aNumRule, false); + } + } + else + { + rDoc.ChgNumRuleFormats( m_aNumRule ); + } +} + +SwHistory* SwUndoInsNum::GetHistory() +{ + if( !m_pHistory ) + m_pHistory.reset(new SwHistory); + return m_pHistory.get(); +} + +void SwUndoInsNum::SaveOldNumRule( const SwNumRule& rOld ) +{ + if( !m_pOldNumRule ) + m_pOldNumRule.reset(new SwNumRule( rOld )); +} + +SwUndoDelNum::SwUndoDelNum( const SwPaM& rPam ) + : SwUndo( SwUndoId::DELNUM, &rPam.GetDoc() ), SwUndRng( rPam ) +{ + if (m_nEndNode > m_nSttNode) + m_aNodes.reserve( std::min(sal_Int32(m_nEndNode - m_nSttNode), 255) ); + m_pHistory.reset( new SwHistory ); +} + +SwUndoDelNum::~SwUndoDelNum() +{ +} + +void SwUndoDelNum::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + + for( const auto& rNode : m_aNodes ) + { + SwTextNode* pNd = rDoc.GetNodes()[ rNode.index ]->GetTextNode(); + OSL_ENSURE( pNd, "Where has the TextNode gone?" ); + pNd->SetAttrListLevel( rNode.level ); + + if( pNd->GetCondFormatColl() ) + pNd->ChkCondColl(); + } + + AddUndoRedoPaM(rContext); +} + +void SwUndoDelNum::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + rContext.GetDoc().DelNumRules(rPam); +} + +void SwUndoDelNum::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().DelNumRules(rContext.GetRepeatPaM()); +} + +void SwUndoDelNum::AddNode( const SwTextNode& rNd ) +{ + if( rNd.GetNumRule() ) + { + m_aNodes.emplace_back( rNd.GetIndex(), rNd.GetActualListLevel() ); + } +} + +SwUndoMoveNum::SwUndoMoveNum( const SwPaM& rPam, SwNodeOffset nOff, bool bIsOutlMv ) + : SwUndo( bIsOutlMv ? SwUndoId::OUTLINE_UD : SwUndoId::MOVENUM, &rPam.GetDoc() ), + SwUndRng( rPam ), + m_nNewStart( 0 ), m_nOffset( nOff ) +{ + // nOffset: Down => 1 + // Up => -1 +} + +void SwUndoMoveNum::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwNodeOffset nTmpStt = m_nSttNode, nTmpEnd = m_nEndNode; + + if (m_nEndNode || m_nEndContent != COMPLETE_STRING) // section? + { + if( m_nNewStart < m_nSttNode ) // moved forwards + m_nEndNode = m_nEndNode - ( m_nSttNode - m_nNewStart ); + else + m_nEndNode = m_nEndNode + ( m_nNewStart - m_nSttNode ); + } + m_nSttNode = m_nNewStart; + + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + rContext.GetDoc().MoveParagraph( rPam, -m_nOffset, + SwUndoId::OUTLINE_UD == GetId() ); + m_nSttNode = nTmpStt; + m_nEndNode = nTmpEnd; +} + +void SwUndoMoveNum::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + rContext.GetDoc().MoveParagraph(rPam, m_nOffset, SwUndoId::OUTLINE_UD == GetId()); +} + +void SwUndoMoveNum::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + if( SwUndoId::OUTLINE_UD == GetId() ) + { + rDoc.MoveOutlinePara(rContext.GetRepeatPaM(), + SwNodeOffset(0) < m_nOffset ? 1 : -1 ); + } + else + { + rDoc.MoveParagraph(rContext.GetRepeatPaM(), m_nOffset); + } +} + +SwUndoNumUpDown::SwUndoNumUpDown( const SwPaM& rPam, short nOff ) + : SwUndo( nOff > 0 ? SwUndoId::NUMUP : SwUndoId::NUMDOWN, &rPam.GetDoc() ), + SwUndRng( rPam ), + m_nOffset( nOff ) +{ + // nOffset: Down => 1 + // Up => -1 +} + +void SwUndoNumUpDown::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + rContext.GetDoc().NumUpDown(rPam, 1 != m_nOffset ); +} + +void SwUndoNumUpDown::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + rContext.GetDoc().NumUpDown(rPam, 1 == m_nOffset); +} + +void SwUndoNumUpDown::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().NumUpDown(rContext.GetRepeatPaM(), 1 == m_nOffset); +} + +SwUndoNumOrNoNum::SwUndoNumOrNoNum( const SwNode& rIdx, bool bOldNum, + bool bNewNum) + : SwUndo( SwUndoId::NUMORNONUM, &rIdx.GetDoc() ), + m_nIndex( rIdx.GetIndex() ), mbNewNum(bNewNum), + mbOldNum(bOldNum) +{ +} + +// #115901#, #i40034# +void SwUndoNumOrNoNum::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwNodeIndex aIdx( rContext.GetDoc().GetNodes(), m_nIndex ); + SwTextNode * pTextNd = aIdx.GetNode().GetTextNode(); + + if (nullptr != pTextNd) + { + pTextNd->SetCountedInList(mbOldNum); + } +} + +// #115901#, #i40034# +void SwUndoNumOrNoNum::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwNodeIndex aIdx( rContext.GetDoc().GetNodes(), m_nIndex ); + SwTextNode * pTextNd = aIdx.GetNode().GetTextNode(); + + if (nullptr != pTextNd) + { + pTextNd->SetCountedInList(mbNewNum); + } +} + +void SwUndoNumOrNoNum::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + if (mbOldNum && ! mbNewNum) + { + rDoc.NumOrNoNum(rContext.GetRepeatPaM().GetPoint()->GetNode()); + } + else if ( ! mbOldNum && mbNewNum ) + { + rDoc.NumOrNoNum(rContext.GetRepeatPaM().GetPoint()->GetNode(), true); + } +} + +SwUndoNumRuleStart::SwUndoNumRuleStart( const SwPosition& rPos, bool bFlg ) + : SwUndo( SwUndoId::SETNUMRULESTART, &rPos.GetDoc() ), + m_nIndex( rPos.GetNodeIndex() ), m_nOldStart( USHRT_MAX ), + m_nNewStart( USHRT_MAX ), m_bSetStartValue( false ), m_bFlag( bFlg ) +{ +} + +SwUndoNumRuleStart::SwUndoNumRuleStart( const SwPosition& rPos, sal_uInt16 nStt ) + : SwUndo(SwUndoId::SETNUMRULESTART, &rPos.GetDoc()) + , m_nIndex(rPos.GetNodeIndex()) + , m_nOldStart(USHRT_MAX) + , m_nNewStart(nStt) + , m_bSetStartValue(true) + , m_bFlag(false) +{ + SwTextNode* pTextNd = rPos.GetNode().GetTextNode(); + if ( pTextNd ) + { + if ( pTextNd->HasAttrListRestartValue() ) + { + m_nOldStart = o3tl::narrowing(pTextNd->GetAttrListRestartValue()); + } + else + { + m_nOldStart = USHRT_MAX; // indicating, that the list restart value is not set + } + } +} + +void SwUndoNumRuleStart::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPosition const aPos( *rDoc.GetNodes()[ m_nIndex ] ); + if( m_bSetStartValue ) + { + rDoc.SetNodeNumStart( aPos, m_nOldStart ); + } + else + { + rDoc.SetNumRuleStart( aPos, !m_bFlag ); + } +} + +void SwUndoNumRuleStart::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPosition const aPos( *rDoc.GetNodes()[ m_nIndex ] ); + if( m_bSetStartValue ) + { + rDoc.SetNodeNumStart( aPos, m_nNewStart ); + } + else + { + rDoc.SetNumRuleStart( aPos, m_bFlag ); + } +} + +void SwUndoNumRuleStart::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + if( m_bSetStartValue ) + { + rDoc.SetNodeNumStart(*rContext.GetRepeatPaM().GetPoint(), m_nNewStart); + } + else + { + rDoc.SetNumRuleStart(*rContext.GetRepeatPaM().GetPoint(), m_bFlag); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unoutl.cxx b/sw/source/core/undo/unoutl.cxx new file mode 100644 index 0000000000..2144c7dd78 --- /dev/null +++ b/sw/source/core/undo/unoutl.cxx @@ -0,0 +1,73 @@ +/* -*- 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 + +SwUndoOutlineLeftRight::SwUndoOutlineLeftRight( const SwPaM& rPam, + short nOff ) + : SwUndo( SwUndoId::OUTLINE_LR, &rPam.GetDoc() ), SwUndRng( rPam ), m_nOffset( nOff ) +{ +} + +void SwUndoOutlineLeftRight::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPaM( AddUndoRedoPaM(rContext) ); + rContext.GetDoc().OutlineUpDown(rPaM, -m_nOffset); +} + +void SwUndoOutlineLeftRight::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPaM( AddUndoRedoPaM(rContext) ); + rContext.GetDoc().OutlineUpDown(rPaM, m_nOffset); +} + +void SwUndoOutlineLeftRight::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().OutlineUpDown(rContext.GetRepeatPaM(), m_nOffset); +} + + +SwUndoOutlineEdit::SwUndoOutlineEdit(const SwNumRule& rOldRule, const SwNumRule& rNewRule, + const SwDoc& rDoc) + : SwUndo(SwUndoId::OUTLINE_EDIT, &rDoc) + , m_aNewNumRule(rNewRule) + , m_aOldNumRule(rOldRule) +{ +} + +void SwUndoOutlineEdit::UndoImpl(::sw::UndoRedoContext& rContext) +{ + rContext.GetDoc().SetOutlineNumRule(m_aOldNumRule); +} + +void SwUndoOutlineEdit::RedoImpl(::sw::UndoRedoContext& rContext) +{ + rContext.GetDoc().SetOutlineNumRule(m_aNewNumRule); +} + +void SwUndoOutlineEdit::RepeatImpl(::sw::RepeatContext& rContext) +{ + rContext.GetDoc().SetOutlineNumRule(m_aNewNumRule); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unovwr.cxx b/sw/source/core/undo/unovwr.cxx new file mode 100644 index 0000000000..7d14c89779 --- /dev/null +++ b/sw/source/core/undo/unovwr.cxx @@ -0,0 +1,475 @@ +/* -*- 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 +#include +#include +#include + +using namespace ::com::sun::star; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::uno; + +SwUndoOverwrite::SwUndoOverwrite( SwDoc& rDoc, SwPosition& rPos, + sal_Unicode cIns ) + : SwUndo(SwUndoId::OVERWRITE, &rDoc), + m_bGroup( false ) +{ + SwTextNode *const pTextNd = rPos.GetNode().GetTextNode(); + assert(pTextNd); + sal_Int32 const nTextNdLen = pTextNd->GetText().getLength(); + + m_nStartNode = rPos.GetNodeIndex(); + m_nStartContent = rPos.GetContentIndex(); + + if( !rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + SwPaM aPam( rPos.GetNode(), rPos.GetContentIndex(), + rPos.GetNode(), rPos.GetContentIndex()+1 ); + m_pRedlSaveData.reset( new SwRedlineSaveDatas ); + if( !FillSaveData( aPam, *m_pRedlSaveData, false )) + { + m_pRedlSaveData.reset(); + } + if (m_nStartContent < nTextNdLen) + { + rDoc.getIDocumentRedlineAccess().DeleteRedline(aPam, false, RedlineType::Any); + } + } + + m_bInsChar = true; + if( m_nStartContent < nTextNdLen ) // no pure insert? + { + m_aDelStr += OUStringChar( pTextNd->GetText()[m_nStartContent] ); + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + SwRegHistory aRHst( *pTextNd, m_pHistory.get() ); + m_pHistory->CopyAttr( pTextNd->GetpSwpHints(), m_nStartNode, 0, + nTextNdLen, false ); + rPos.AdjustContent(+1); + m_bInsChar = false; + } + + bool bOldExpFlg = pTextNd->IsIgnoreDontExpand(); + pTextNd->SetIgnoreDontExpand( true ); + + pTextNd->InsertText( OUString(cIns), rPos, SwInsertFlags::EMPTYEXPAND ); + m_aInsStr += OUStringChar( cIns ); + + if( !m_bInsChar ) + { + const SwContentIndex aTmpIndex( rPos.GetContentNode(), rPos.GetContentIndex() - 2 ); + pTextNd->EraseText( aTmpIndex, 1 ); + } + pTextNd->SetIgnoreDontExpand( bOldExpFlg ); + + m_bCacheComment = false; +} + +SwUndoOverwrite::~SwUndoOverwrite() +{ +} + +bool SwUndoOverwrite::CanGrouping( SwDoc& rDoc, SwPosition& rPos, + sal_Unicode cIns ) +{ +// What is with only inserted characters? + + // Only deletion of single chars can be combined. + if( rPos.GetNodeIndex() != m_nStartNode || m_aInsStr.isEmpty() || + ( !m_bGroup && m_aInsStr.getLength() != 1 )) + return false; + + // Is the node a TextNode at all? + SwTextNode * pDelTextNd = rPos.GetNode().GetTextNode(); + if( !pDelTextNd || + (pDelTextNd->GetText().getLength() != rPos.GetContentIndex() && + rPos.GetContentIndex() != ( m_nStartContent + m_aInsStr.getLength() ))) + return false; + + CharClass& rCC = GetAppCharClass(); + + // ask the char that should be inserted + if (( CH_TXTATR_BREAKWORD == cIns || CH_TXTATR_INWORD == cIns ) || + rCC.isLetterNumeric( OUString( cIns ), 0 ) != + rCC.isLetterNumeric( m_aInsStr, m_aInsStr.getLength()-1 ) ) + return false; + + if (!m_bInsChar && rPos.GetContentIndex() < pDelTextNd->GetText().getLength()) + { + SwRedlineSaveDatas aTmpSav; + SwPaM aPam( rPos.GetNode(), rPos.GetContentIndex(), + rPos.GetNode(), rPos.GetContentIndex()+1 ); + + const bool bSaved = FillSaveData( aPam, aTmpSav, false ); + + bool bOk = ( !m_pRedlSaveData && !bSaved ) || + ( m_pRedlSaveData && bSaved && + SwUndo::CanRedlineGroup( *m_pRedlSaveData, aTmpSav, + m_nStartContent > rPos.GetContentIndex() )); + // aTmpSav.DeleteAndDestroyAll(); + if( !bOk ) + return false; + + rDoc.getIDocumentRedlineAccess().DeleteRedline( aPam, false, RedlineType::Any ); + } + + // both 'overwrites' can be combined so 'move' the corresponding character + if( !m_bInsChar ) + { + if (rPos.GetContentIndex() < pDelTextNd->GetText().getLength()) + { + m_aDelStr += OUStringChar( pDelTextNd->GetText()[rPos.GetContentIndex()] ); + rPos.AdjustContent(+1); + } + else + m_bInsChar = true; + } + + bool bOldExpFlg = pDelTextNd->IsIgnoreDontExpand(); + pDelTextNd->SetIgnoreDontExpand( true ); + + OUString const ins( pDelTextNd->InsertText(OUString(cIns), rPos, + SwInsertFlags::EMPTYEXPAND) ); + assert(ins.getLength() == 1); // check in SwDoc::Overwrite => cannot fail + (void) ins; + m_aInsStr += OUStringChar( cIns ); + + if( !m_bInsChar ) + { + const SwContentIndex aTmpIndex( rPos.GetContentNode(), rPos.GetContentIndex() - 2 ); + pDelTextNd->EraseText( aTmpIndex, 1 ); + } + pDelTextNd->SetIgnoreDontExpand( bOldExpFlg ); + + m_bGroup = true; + return true; +} + +void SwUndoOverwrite::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc& rDoc = rContext.GetDoc(); + SwCursor& rCurrentPam(rContext.GetCursorSupplier().CreateNewShellCursor()); + + rCurrentPam.DeleteMark(); + rCurrentPam.GetPoint()->Assign( m_nStartNode ); + SwTextNode* pTextNd = rCurrentPam.GetPointNode().GetTextNode(); + assert(pTextNd); + SwPosition& rPtPos = *rCurrentPam.GetPoint(); + rPtPos.SetContent( m_nStartContent ); + + SwAutoCorrExceptWord* pACEWord = rDoc.GetAutoCorrExceptWord(); + if( pACEWord ) + { + if( 1 == m_aInsStr.getLength() && 1 == m_aDelStr.getLength() ) + pACEWord->CheckChar( *rCurrentPam.GetPoint(), m_aDelStr[0] ); + rDoc.SetAutoCorrExceptWord( nullptr ); + } + + // If there was not only an overwrite but also an insert, delete the surplus + if( m_aInsStr.getLength() > m_aDelStr.getLength() ) + { + rPtPos.AdjustContent( m_aDelStr.getLength() ); + pTextNd->EraseText( rPtPos, m_aInsStr.getLength() - m_aDelStr.getLength() ); + rPtPos.SetContent( m_nStartContent ); + } + + if( !m_aDelStr.isEmpty() ) + { + bool bOldExpFlg = pTextNd->IsIgnoreDontExpand(); + pTextNd->SetIgnoreDontExpand( true ); + + rPtPos.AdjustContent(+1); + for( sal_Int32 n = 0; n < m_aDelStr.getLength(); n++ ) + { + // do it individually, to keep the attributes! + OUString aTmpStr(m_aDelStr[n]); + OUString const ins( pTextNd->InsertText(aTmpStr, rPtPos) ); + assert(ins.getLength() == 1); // cannot fail + (void) ins; + rPtPos.AdjustContent(-2); + pTextNd->EraseText( rPtPos, 1 ); + rPtPos.AdjustContent(+2); + } + pTextNd->SetIgnoreDontExpand( bOldExpFlg ); + rPtPos.AdjustContent(-1); + } + + if( m_pHistory ) + { + if( pTextNd->GetpSwpHints() ) + pTextNd->ClearSwpHintsArr( false ); + m_pHistory->TmpRollback( &rDoc, 0, false ); + } + + if( rCurrentPam.GetMark()->GetContentIndex() != m_nStartContent ) + { + rCurrentPam.SetMark(); + rCurrentPam.GetMark()->SetContent( m_nStartContent ); + } + + if( m_pRedlSaveData ) + SetSaveData( rDoc, *m_pRedlSaveData ); +} + +void SwUndoOverwrite::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwPaM& rCurrentPam = rContext.GetRepeatPaM(); + if( m_aInsStr.isEmpty() || rCurrentPam.HasMark() ) + return; + + SwDoc & rDoc = rContext.GetDoc(); + + { + ::sw::GroupUndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + rDoc.getIDocumentContentOperations().Overwrite(rCurrentPam, OUString(m_aInsStr[0])); + } + for( sal_Int32 n = 1; n < m_aInsStr.getLength(); ++n ) + rDoc.getIDocumentContentOperations().Overwrite(rCurrentPam, OUString(m_aInsStr[n])); +} + +void SwUndoOverwrite::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc& rDoc = rContext.GetDoc(); + SwCursor& rCurrentPam(rContext.GetCursorSupplier().CreateNewShellCursor()); + + rCurrentPam.DeleteMark(); + rCurrentPam.GetPoint()->Assign(m_nStartNode); + SwTextNode* pTextNd = rCurrentPam.GetPointNode().GetTextNode(); + assert(pTextNd); + SwPosition& rPtPos = *rCurrentPam.GetPoint(); + + if( m_pRedlSaveData ) + { + rPtPos.SetContent( m_nStartContent ); + rCurrentPam.SetMark(); + rCurrentPam.GetMark()->AdjustContent( m_aDelStr.getLength() ); + rDoc.getIDocumentRedlineAccess().DeleteRedline( rCurrentPam, false, RedlineType::Any ); + rCurrentPam.DeleteMark(); + } + rPtPos.SetContent( !m_aDelStr.isEmpty() ? m_nStartContent+1 : m_nStartContent ); + + bool bOldExpFlg = pTextNd->IsIgnoreDontExpand(); + pTextNd->SetIgnoreDontExpand( true ); + + for( sal_Int32 n = 0; n < m_aInsStr.getLength(); n++ ) + { + // do it individually, to keep the attributes! + OUString const ins( + pTextNd->InsertText( OUString(m_aInsStr[n]), rPtPos, + SwInsertFlags::EMPTYEXPAND) ); + assert(ins.getLength() == 1); // cannot fail + (void) ins; + if( n < m_aDelStr.getLength() ) + { + rPtPos.AdjustContent(-2); + pTextNd->EraseText( rPtPos, 1 ); + rPtPos.AdjustContent( n+1 < m_aDelStr.getLength() ? 2 : 1 ); + } + } + pTextNd->SetIgnoreDontExpand( bOldExpFlg ); + + // get back old start position from UndoNodes array + if( m_pHistory ) + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + if( rCurrentPam.GetMark()->GetContentIndex() != m_nStartContent ) + { + rCurrentPam.SetMark(); + rCurrentPam.GetMark()->SetContent( m_nStartContent ); + } +} + +SwRewriter SwUndoOverwrite::GetRewriter() const +{ + SwRewriter aResult; + + OUString aString = SwResId(STR_START_QUOTE) + + ShortenString(m_aInsStr, nUndoStringLength, SwResId(STR_LDOTS)) + + SwResId(STR_END_QUOTE); + + aResult.AddRule(UndoArg1, aString); + + return aResult; +} + +struct UndoTransliterate_Data +{ + OUString sText; + std::unique_ptr pHistory; + std::optional> oOffsets; + SwNodeOffset nNdIdx; + sal_Int32 nStart, nLen; + + UndoTransliterate_Data( SwNodeOffset nNd, sal_Int32 nStt, sal_Int32 nStrLen, OUString aText ) + : sText(std::move( aText )), + nNdIdx( nNd ), nStart( nStt ), nLen( nStrLen ) + {} + + void SetChangeAtNode( SwDoc& rDoc ); +}; + +SwUndoTransliterate::SwUndoTransliterate( + const SwPaM& rPam, + const utl::TransliterationWrapper& rTrans ) + : SwUndo( SwUndoId::TRANSLITERATE, &rPam.GetDoc() ), SwUndRng( rPam ), m_nType( rTrans.getType() ) +{ +} + +SwUndoTransliterate::~SwUndoTransliterate() +{ +} + +void SwUndoTransliterate::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + // since the changes were added to the vector from the end of the string/node towards + // the start, we need to revert them from the start towards the end now to keep the + // offset information of the undo data in sync with the changing text. + // Thus we need to iterate from the end of the vector to the start + for (sal_Int32 i = m_aChanges.size() - 1; i >= 0; --i) + m_aChanges[i]->SetChangeAtNode( rDoc ); + + AddUndoRedoPaM(rContext, true); +} + +void SwUndoTransliterate::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + DoTransliterate(rContext.GetDoc(), rPam); +} + +void SwUndoTransliterate::RepeatImpl(::sw::RepeatContext & rContext) +{ + DoTransliterate(rContext.GetDoc(), rContext.GetRepeatPaM()); +} + +void SwUndoTransliterate::DoTransliterate(SwDoc & rDoc, SwPaM const & rPam) +{ + utl::TransliterationWrapper aTrans( ::comphelper::getProcessComponentContext(), m_nType ); + rDoc.getIDocumentContentOperations().TransliterateText( rPam, aTrans ); +} + +void SwUndoTransliterate::AddChanges( SwTextNode& rTNd, + sal_Int32 nStart, sal_Int32 nLen, + uno::Sequence const & rOffsets ) +{ + tools::Long nOffsLen = rOffsets.getLength(); + UndoTransliterate_Data* pNew = new UndoTransliterate_Data( + rTNd.GetIndex(), nStart, static_cast(nOffsLen), + rTNd.GetText().copy(nStart, nLen)); + + m_aChanges.push_back( std::unique_ptr(pNew) ); + + const sal_Int32* pOffsets = rOffsets.getConstArray(); + // where did we need less memory ? + const sal_Int32* p = pOffsets; + for( tools::Long n = 0; n < nOffsLen; ++n, ++p ) + if( *p != ( nStart + n )) + { + // create the Offset array + pNew->oOffsets.emplace( nLen ); + sal_Int32* pIdx = pNew->oOffsets->getArray(); + p = pOffsets; + tools::Long nMyOff, nNewVal = nStart; + for( n = 0, nMyOff = nStart; n < nOffsLen; ++p, ++n, ++nMyOff ) + { + if( *p < nMyOff ) + { + // something is deleted + nMyOff = *p; + *(pIdx-1) = nNewVal++; + } + else if( *p > nMyOff ) + { + for( ; *p > nMyOff; ++nMyOff ) + *pIdx++ = nNewVal; + --nMyOff; + --n; + --p; + } + else + *pIdx++ = nNewVal++; + } + + // and then we need to save the attributes/bookmarks + // but this data must moved every time to the last in the chain! + for (size_t i = 0; i + 1 < m_aChanges.size(); ++i) // check all changes but not the current one + { + UndoTransliterate_Data* pD = m_aChanges[i].get(); + if( pD->nNdIdx == pNew->nNdIdx && pD->pHistory ) + { + // same node and have a history? + pNew->pHistory = std::move(pD->pHistory); + break; // more can't exist + } + } + + if( !pNew->pHistory ) + { + pNew->pHistory.reset( new SwHistory ); + SwRegHistory aRHst( rTNd, pNew->pHistory.get() ); + pNew->pHistory->CopyAttr( rTNd.GetpSwpHints(), + pNew->nNdIdx, 0, rTNd.GetText().getLength(), false ); + } + break; + } +} + +void UndoTransliterate_Data::SetChangeAtNode( SwDoc& rDoc ) +{ + SwTextNode* pTNd = rDoc.GetNodes()[ nNdIdx ]->GetTextNode(); + if( !pTNd ) + return; + + Sequence aOffsets( oOffsets ? oOffsets->getLength() : nLen ); + if( oOffsets ) + aOffsets = *oOffsets; + else + { + sal_Int32* p = aOffsets.getArray(); + for( sal_Int32 n = 0; n < nLen; ++n, ++p ) + *p = n + nStart; + } + pTNd->ReplaceTextOnly( nStart, nLen, sText, aOffsets ); + + if( pHistory ) + { + if( pTNd->GetpSwpHints() ) + pTNd->ClearSwpHintsArr( false ); + pHistory->TmpRollback( &rDoc, 0, false ); + pHistory->SetTmpEnd( pHistory->Count() ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unredln.cxx b/sw/source/core/undo/unredln.cxx new file mode 100644 index 0000000000..f77ceb3d26 --- /dev/null +++ b/sw/source/core/undo/unredln.cxx @@ -0,0 +1,602 @@ +/* -*- 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 +#include +#include +#include +#include + +SwUndoRedline::SwUndoRedline( SwUndoId nUsrId, const SwPaM& rRange, sal_Int8 nDepth ) + : SwUndo( SwUndoId::REDLINE, &rRange.GetDoc() ), SwUndRng( rRange ), + mnUserId( nUsrId ), + mbHiddenRedlines( false ), + mnDepth( nDepth ) +{ + // consider Redline + SwDoc& rDoc = rRange.GetDoc(); + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + switch( mnUserId ) + { + case SwUndoId::DELETE: + case SwUndoId::REPLACE: + mpRedlData.reset( new SwRedlineData( RedlineType::Delete, rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ) ); + break; + default: + ; + } + SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ); + } + + SwNodeOffset nEndExtra = rDoc.GetNodes().GetEndOfExtras().GetIndex(); + + mpRedlSaveData.reset( new SwRedlineSaveDatas ); + if( !FillSaveData( rRange, *mpRedlSaveData, false, SwUndoId::REJECT_REDLINE != mnUserId )) + { + mpRedlSaveData.reset(); + } + else + { + mbHiddenRedlines = HasHiddenRedlines( *mpRedlSaveData ); + if( mbHiddenRedlines ) // then the NodeIndices of SwUndRng need to be corrected + { + nEndExtra -= rDoc.GetNodes().GetEndOfExtras().GetIndex(); + m_nSttNode -= nEndExtra; + m_nEndNode -= nEndExtra; + } + } +} + +SwUndoRedline::~SwUndoRedline() +{ + mpRedlData.reset(); + mpRedlSaveData.reset(); +} + +sal_uInt16 SwUndoRedline::GetRedlSaveCount() const +{ + return mpRedlSaveData ? mpRedlSaveData->size() : 0; +} + +#if OSL_DEBUG_LEVEL > 0 +void SwUndoRedline::SetRedlineCountDontCheck(bool bCheck) +{ + if (mpRedlSaveData) + mpRedlSaveData->SetRedlineCountDontCheck(bCheck); +} +#endif + +void SwUndoRedline::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc& rDoc = rContext.GetDoc(); + SwPaM& rPam(AddUndoRedoPaM(rContext)); + + // fix PaM for deletions shown in margin + bool bIsDeletion = dynamic_cast(this); + const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable(); + sal_uInt32 nMaxId = SAL_MAX_UINT32; + if ( bIsDeletion && rTable.size() > 0 ) + { + // Nodes of the deletion range are in the newest invisible redlines. + // Set all redlines visible and recover the original deletion range. + for (SwNodeOffset nNodes(0); nNodes < m_nEndNode - m_nSttNode + 1; ++nNodes) + { + SwRedlineTable::size_type nCurRedlinePos = 0; + SwRangeRedline * pRedline(rTable[nCurRedlinePos]); + + // search last but nNodes redline by its nth biggest id + for( SwRedlineTable::size_type n = 1; n < rTable.size(); ++n ) + { + SwRangeRedline *pRed(rTable[n]); + if ( !pRed->HasMark() && pRedline->GetId() < pRed->GetId() && pRed->GetId() < nMaxId ) + { + nCurRedlinePos = n; + pRedline = pRed; + } + } + + nMaxId = pRedline->GetId(); + + if ( !pRedline->IsVisible() && !pRedline->HasMark() ) + { + // set it visible + pRedline->Show(0, rTable.GetPos(pRedline), /*bForced=*/true); + pRedline->Show(1, rTable.GetPos(pRedline), /*bForced=*/true); + + // extend the range + if ( nNodes == SwNodeOffset(0) ) + rPam = *pRedline; + else + { + rPam.SetMark(); + *rPam.GetMark() = *pRedline->GetMark(); + } + } + } + } + + UndoRedlineImpl(rDoc, rPam); + + if( mpRedlSaveData ) + { + SwNodeOffset nEndExtra = rDoc.GetNodes().GetEndOfExtras().GetIndex(); + SetSaveData(rDoc, *mpRedlSaveData); + if( mbHiddenRedlines ) + { + mpRedlSaveData->clear(); + + nEndExtra = rDoc.GetNodes().GetEndOfExtras().GetIndex() - nEndExtra; + m_nSttNode += nEndExtra; + m_nEndNode += nEndExtra; + } + SetPaM(rPam, true); + } + + // update frames after calling SetSaveData + if ( bIsDeletion ) + { + sw::UpdateFramesForRemoveDeleteRedline(rDoc, rPam); + } + else if (dynamic_cast(this) + || dynamic_cast(this)) + { // (can't check here if there's a delete redline being accepted) + sw::UpdateFramesForAddDeleteRedline(rDoc, rPam); + } +} + +void SwUndoRedline::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc& rDoc = rContext.GetDoc(); + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On ); + + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + if( mpRedlSaveData && mbHiddenRedlines ) + { + SwNodeOffset nEndExtra = rDoc.GetNodes().GetEndOfExtras().GetIndex(); + FillSaveData(rPam, *mpRedlSaveData, false, SwUndoId::REJECT_REDLINE != mnUserId ); + + nEndExtra -= rDoc.GetNodes().GetEndOfExtras().GetIndex(); + m_nSttNode -= nEndExtra; + m_nEndNode -= nEndExtra; + } + + RedoRedlineImpl(rDoc, rPam); + + SetPaM(rPam, true); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); +} + +void SwUndoRedline::UndoRedlineImpl(SwDoc &, SwPaM &) +{ +} + +// default: remove redlines +void SwUndoRedline::RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) +{ + rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any); +} + +SwUndoRedlineDelete::SwUndoRedlineDelete( + const SwPaM& rRange, SwUndoId const nUsrId, SwDeleteFlags const flags) + : SwUndoRedline( nUsrId != SwUndoId::EMPTY ? nUsrId : SwUndoId::DELETE, rRange ), + m_bCanGroup( false ), m_bIsDelim( false ), m_bIsBackspace( false ) +{ + const SwTextNode* pTNd; + SetRedlineText(rRange.GetText()); + if( SwUndoId::DELETE == mnUserId && + m_nSttNode == m_nEndNode && m_nSttContent + 1 == m_nEndContent && + nullptr != (pTNd = rRange.GetPointNode().GetTextNode()) ) + { + sal_Unicode const cCh = pTNd->GetText()[m_nSttContent]; + if( CH_TXTATR_BREAKWORD != cCh && CH_TXTATR_INWORD != cCh ) + { + m_bCanGroup = true; + m_bIsDelim = !GetAppCharClass().isLetterNumeric( pTNd->GetText(), + m_nSttContent ); + m_bIsBackspace = m_nSttContent == rRange.GetPoint()->GetContentIndex(); + } + } + + m_bCacheComment = false; + if (flags & SwDeleteFlags::ArtificialSelection) + { + InitHistory(rRange); + } +} + +void SwUndoRedlineDelete::InitHistory(SwPaM const& rRedline) +{ + m_pHistory.reset(new SwHistory); + // try to rely on direction of rPam here so it works for + // backspacing/deleting consecutive characters + SaveFlyArr flys; + SaveFlyInRange(rRedline, *rRedline.GetMark(), flys, false, m_pHistory.get()); + RestFlyInRange(flys, *rRedline.GetPoint(), &rRedline.GetPoint()->GetNode(), true); + if (m_pHistory->Count()) + { + m_bCanGroup = false; // how to group history? + } +} + +// bit of a hack, replace everything... +SwRewriter SwUndoRedlineDelete::GetRewriter() const +{ + SwRewriter aResult; + OUString aStr = DenoteSpecialCharacters(m_sRedlineText); + aStr = ShortenString(aStr, nUndoStringLength, SwResId(STR_LDOTS)); + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, aStr); + OUString ret = aRewriter.Apply(SwResId(STR_UNDO_REDLINE_DELETE)); + aResult.AddRule(UndoArg1, ret); + return aResult; +} + +void SwUndoRedlineDelete::SetRedlineText(const OUString & rText) +{ + m_sRedlineText = rText; +} + +void SwUndoRedlineDelete::UndoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) +{ + rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any); + if (m_pHistory) + { + m_pHistory->TmpRollback(&rDoc, 0); + } +} + +void SwUndoRedlineDelete::RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) +{ + if (rPam.GetPoint() != rPam.GetMark()) + { + if (m_pHistory) // if it was created before, it must be recreated now + { + rPam.Normalize(m_bIsBackspace); // to check the correct edge + InitHistory(rPam); + } + rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline(*mpRedlData, rPam), false ); + } + sw::UpdateFramesForAddDeleteRedline(rDoc, rPam); +} + +bool SwUndoRedlineDelete::CanGrouping( const SwUndoRedlineDelete& rNext ) +{ + bool bRet = false; + if( SwUndoId::DELETE == mnUserId && mnUserId == rNext.mnUserId && + m_bCanGroup && rNext.m_bCanGroup && + m_bIsDelim == rNext.m_bIsDelim && + m_bIsBackspace == rNext.m_bIsBackspace && + m_nSttNode == m_nEndNode && + rNext.m_nSttNode == m_nSttNode && + rNext.m_nEndNode == m_nEndNode ) + { + int bIsEnd = 0; + if( rNext.m_nSttContent == m_nEndContent ) + bIsEnd = 1; + else if( rNext.m_nEndContent == m_nSttContent ) + bIsEnd = -1; + + if( bIsEnd && + (( !mpRedlSaveData && !rNext.mpRedlSaveData ) || + ( mpRedlSaveData && rNext.mpRedlSaveData && + SwUndo::CanRedlineGroup( *mpRedlSaveData, + *rNext.mpRedlSaveData, 1 != bIsEnd ) + ))) + { + if( 1 == bIsEnd ) + m_nEndContent = rNext.m_nEndContent; + else + m_nSttContent = rNext.m_nSttContent; + bRet = true; + } + } + return bRet; +} + +SwUndoRedlineSort::SwUndoRedlineSort( const SwPaM& rRange, + const SwSortOptions& rOpt ) + : SwUndoRedline( SwUndoId::SORT_TXT, rRange ), + m_pOpt( new SwSortOptions( rOpt ) ), + m_nSaveEndNode( m_nEndNode ), m_nSaveEndContent( m_nEndContent ) +{ +} + +SwUndoRedlineSort::~SwUndoRedlineSort() +{ +} + +void SwUndoRedlineSort::UndoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) +{ + // rPam contains the sorted range + // aSaveRange contains copied (i.e. original) range + + auto [pStart, pEnd] = rPam.StartEnd(); // SwPosition* + + SwNodeIndex aPrevIdx( pStart->GetNode(), -1 ); + SwNodeOffset nOffsetTemp = pEnd->GetNodeIndex() - pStart->GetNodeIndex(); + + if( !( RedlineFlags::ShowDelete & rDoc.getIDocumentRedlineAccess().GetRedlineFlags()) ) + { + // Search both Redline objects and make them visible to make the nodes + // consistent again. The 'delete' one is hidden, thus search for the + // 'insert' Redline object. The former is located directly after the latter. + SwRedlineTable::size_type nFnd = rDoc.getIDocumentRedlineAccess().GetRedlinePos( + *rDoc.GetNodes()[ m_nSttNode + 1 ], + RedlineType::Insert ); + OSL_ENSURE( SwRedlineTable::npos != nFnd && nFnd+1 < rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(), + "could not find an Insert object" ); + ++nFnd; + rDoc.getIDocumentRedlineAccess().GetRedlineTable()[nFnd]->Show(1, nFnd); + } + + { + SwPaM aTmp( rPam.GetMark()->GetNode() ); + aTmp.SetMark(); + aTmp.GetPoint()->Assign( m_nSaveEndNode, m_nSaveEndContent ); + rDoc.getIDocumentRedlineAccess().DeleteRedline( aTmp, true, RedlineType::Any ); + } + + rDoc.getIDocumentContentOperations().DelFullPara(rPam); + + SwPaM *const pPam = & rPam; + pPam->DeleteMark(); + pPam->GetPoint()->Assign( aPrevIdx.GetNode(), SwNodeOffset(+1) ); + pPam->SetMark(); + + pPam->GetPoint()->Adjust(nOffsetTemp); + SwContentNode* pCNd = pPam->GetPointContentNode(); + pPam->GetPoint()->SetContent( pCNd->Len() ); + + SetValues( *pPam ); + + SetPaM(rPam); +} + +void SwUndoRedlineSort::RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) +{ + SwPaM* pPam = &rPam; + auto [pStart, pEnd] = pPam->StartEnd(); // SwPosition* + + SwNodeIndex aPrevIdx( pStart->GetNode(), -1 ); + SwNodeOffset nOffsetTemp = pEnd->GetNodeIndex() - pStart->GetNodeIndex(); + const sal_Int32 nCntStt = pStart->GetContentIndex(); + + rDoc.SortText(rPam, *m_pOpt); + + pPam->DeleteMark(); + pPam->GetPoint()->Assign( aPrevIdx.GetNode(), SwNodeOffset(+1) ); + SwContentNode* pCNd = pPam->GetPointContentNode(); + sal_Int32 nLen = pCNd->Len(); + if( nLen > nCntStt ) + nLen = nCntStt; + pPam->GetPoint()->SetContent( nLen ); + pPam->SetMark(); + + pPam->GetPoint()->Adjust(nOffsetTemp); + pCNd = pPam->GetPointContentNode(); + pPam->GetPoint()->SetContent( pCNd->Len() ); + + SetValues( rPam ); + + SetPaM( rPam ); + rPam.GetPoint()->Assign( m_nSaveEndNode, m_nSaveEndContent ); +} + +void SwUndoRedlineSort::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().SortText( rContext.GetRepeatPaM(), *m_pOpt ); +} + +void SwUndoRedlineSort::SetSaveRange( const SwPaM& rRange ) +{ + const SwPosition& rPos = *rRange.End(); + m_nSaveEndNode = rPos.GetNodeIndex(); + m_nSaveEndContent = rPos.GetContentIndex(); +} + +SwUndoAcceptRedline::SwUndoAcceptRedline( const SwPaM& rRange, sal_Int8 nDepth /* = 0 */ ) + : SwUndoRedline( SwUndoId::ACCEPT_REDLINE, rRange, nDepth ) +{ +} + +void SwUndoAcceptRedline::RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) +{ + rDoc.getIDocumentRedlineAccess().AcceptRedline(rPam, false, mnDepth); +} + +void SwUndoAcceptRedline::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().getIDocumentRedlineAccess().AcceptRedline(rContext.GetRepeatPaM(), true); +} + +SwUndoRejectRedline::SwUndoRejectRedline( const SwPaM& rRange, sal_Int8 nDepth /* = 0 */ ) + : SwUndoRedline( SwUndoId::REJECT_REDLINE, rRange, nDepth ) +{ +} + +void SwUndoRejectRedline::RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) +{ + rDoc.getIDocumentRedlineAccess().RejectRedline(rPam, false, mnDepth); +} + +void SwUndoRejectRedline::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().getIDocumentRedlineAccess().RejectRedline(rContext.GetRepeatPaM(), true); +} + +SwUndoCompDoc::SwUndoCompDoc( const SwPaM& rRg, bool bIns ) + : SwUndo( SwUndoId::COMPAREDOC, &rRg.GetDoc() ), SwUndRng( rRg ), + m_bInsert( bIns ) +{ + SwDoc& rDoc = rRg.GetDoc(); + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + RedlineType eTyp = m_bInsert ? RedlineType::Insert : RedlineType::Delete; + m_pRedlineData.reset( new SwRedlineData( eTyp, rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ) ); + SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ); + } +} + +SwUndoCompDoc::SwUndoCompDoc( const SwRangeRedline& rRedl ) + : SwUndo( SwUndoId::COMPAREDOC, &rRedl.GetDoc() ), SwUndRng( rRedl ), + // for MergeDoc the corresponding inverse is needed + m_bInsert( RedlineType::Delete == rRedl.GetType() ) +{ + SwDoc& rDoc = rRedl.GetDoc(); + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + m_pRedlineData.reset( new SwRedlineData( rRedl.GetRedlineData() ) ); + SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ); + } + + m_pRedlineSaveDatas.reset( new SwRedlineSaveDatas ); + if( !FillSaveData( rRedl, *m_pRedlineSaveDatas, false )) + { + m_pRedlineSaveDatas.reset(); + } +} + +SwUndoCompDoc::~SwUndoCompDoc() +{ + m_pRedlineData.reset(); + m_pUndoDelete.reset(); + m_pUndoDelete2.reset(); + m_pRedlineSaveDatas.reset(); +} + +void SwUndoCompDoc::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc& rDoc = rContext.GetDoc(); + SwPaM& rPam(AddUndoRedoPaM(rContext)); + + if( !m_bInsert ) + { + // delete Redlines + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(( eOld & ~RedlineFlags::Ignore) | RedlineFlags::On); + + rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any); + + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + + // per definition Point is end (in SwUndRng!) + SwContentNode* pCSttNd = rPam.GetMarkContentNode(); + SwContentNode* pCEndNd = rPam.GetPointContentNode(); + + // if start- and end-content is zero, then the doc-compare moves + // complete nodes into the current doc. And then the selection + // must be from end to start, so the delete join into the right + // direction. + if( !m_nSttContent && !m_nEndContent ) + rPam.Exchange(); + + bool bJoinText, bJoinPrev; + sw_GetJoinFlags(rPam, bJoinText, bJoinPrev); + + m_pUndoDelete.reset(new SwUndoDelete(rPam, SwDeleteFlags::Default, false)); + + if( bJoinText ) + sw_JoinText(rPam, bJoinPrev); + + if( pCSttNd && !pCEndNd) + { + // #112139# Do not step behind the end of content. + SwNode & rTmp = rPam.GetPointNode(); + SwNode * pEnd = rDoc.GetNodes().DocumentSectionEndNode(&rTmp); + + if (&rTmp != pEnd) + { + rPam.SetMark(); + rPam.GetPoint()->Adjust(SwNodeOffset(1)); + m_pUndoDelete2.reset(new SwUndoDelete(rPam, SwDeleteFlags::Default, true)); + } + } + rPam.DeleteMark(); + } + else + { + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any); + + if( m_pRedlineSaveDatas ) + SetSaveData(rDoc, *m_pRedlineSaveDatas); + } + SetPaM(rPam, true); + } +} + +void SwUndoCompDoc::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc& rDoc = rContext.GetDoc(); + + if( m_bInsert ) + { + SwPaM& rPam(AddUndoRedoPaM(rContext)); + if( m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + SwRangeRedline* pTmp = new SwRangeRedline(*m_pRedlineData, rPam); + rDoc.getIDocumentRedlineAccess().GetRedlineTable().Insert( pTmp ); + pTmp->InvalidateRange(SwRangeRedline::Invalidation::Add); + } + else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) && + !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + rDoc.getIDocumentRedlineAccess().SplitRedline(rPam); + } + SetPaM(rPam, true); + } + else + { + if( m_pUndoDelete2 ) + { + m_pUndoDelete2->UndoImpl(rContext); + m_pUndoDelete2.reset(); + } + m_pUndoDelete->UndoImpl(rContext); + m_pUndoDelete.reset(); + + // note: don't call SetPaM before executing Undo of members + SwPaM& rPam(AddUndoRedoPaM(rContext)); + + SwRangeRedline* pTmp = new SwRangeRedline(*m_pRedlineData, rPam); + rDoc.getIDocumentRedlineAccess().GetRedlineTable().Insert( pTmp ); + pTmp->InvalidateRange(SwRangeRedline::Invalidation::Add); + + SetPaM(rPam, true); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unsect.cxx b/sw/source/core/undo/unsect.cxx new file mode 100644 index 0000000000..4e75aa9adb --- /dev/null +++ b/sw/source/core/undo/unsect.cxx @@ -0,0 +1,613 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/// OD 04.10.2002 #102894# +/// class Calc needed for calculation of the hidden condition of a section. +#include + +static std::optional lcl_GetAttrSet( const SwSection& rSect ) +{ + // save attributes of the format (columns, color, ...) + // Content and Protect items are not interesting since they are already + // stored in Section, thus delete them. + std::optional oAttr; + if( rSect.GetFormat() ) + { + sal_uInt16 nCnt = 1; + if( rSect.IsProtect() ) + ++nCnt; + + if( nCnt < rSect.GetFormat()->GetAttrSet().Count() ) + { + oAttr.emplace( rSect.GetFormat()->GetAttrSet() ); + oAttr->ClearItem( RES_PROTECT ); + oAttr->ClearItem( RES_CNTNT ); + if( !oAttr->Count() ) + { + oAttr.reset(); + } + } + } + return oAttr; +} + +SwUndoInsSection::SwUndoInsSection( + SwPaM const& rPam, SwSectionData const& rNewData, + SfxItemSet const*const pSet, + std::tuple const*const pTOXBase) + : SwUndo( SwUndoId::INSSECTION, &rPam.GetDoc() ), SwUndRng( rPam ) + , m_pSectionData(new SwSectionData(rNewData)) + , m_pAttrSet( (pSet && pSet->Count()) ? new SfxItemSet( *pSet ) : nullptr ) + , m_nSectionNodePos(0) + , m_bSplitAtStart(false) + , m_bSplitAtEnd(false) + , m_bUpdateFootnote(false) +{ + if (pTOXBase) + m_xTOXBase.emplace( + std::make_unique(*std::get<0>(*pTOXBase)), + std::get<1>(*pTOXBase), + std::get<2>(*pTOXBase), + std::get<3>(*pTOXBase)); + + SwDoc& rDoc = rPam.GetDoc(); + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + m_pRedlData.reset(new SwRedlineData( RedlineType::Insert, + rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() )); + SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ); + } + m_pRedlineSaveData.reset( new SwRedlineSaveDatas ); + if( !FillSaveData( rPam, *m_pRedlineSaveData, false )) + m_pRedlineSaveData.reset(); + + if( rPam.HasMark() ) + return; + + const SwContentNode* pCNd = rPam.GetPoint()->GetNode().GetContentNode(); + if( pCNd && pCNd->HasSwAttrSet() && ( + !rPam.GetPoint()->GetContentIndex() || + rPam.GetPoint()->GetContentIndex() == pCNd->Len() )) + { + SfxItemSet aBrkSet( rDoc.GetAttrPool(), aBreakSetRange ); + aBrkSet.Put( *pCNd->GetpSwAttrSet() ); + if( aBrkSet.Count() ) + { + m_pHistory.reset( new SwHistory ); + m_pHistory->CopyFormatAttr( aBrkSet, pCNd->GetIndex() ); + } + } +} + +SwUndoInsSection::~SwUndoInsSection() +{ +} + +void SwUndoInsSection::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + RemoveIdxFromSection( rDoc, m_nSectionNodePos ); + + SwSectionNode *const pNd = + rDoc.GetNodes()[ m_nSectionNodePos ]->GetSectionNode(); + OSL_ENSURE( pNd, "where is my SectionNode?" ); + + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + rDoc.getIDocumentRedlineAccess().DeleteRedline( *pNd, true, RedlineType::Any ); + + // no selection? + SwNodeIndex aIdx( *pNd ); + if( ( !m_nEndNode && COMPLETE_STRING == m_nEndContent ) || + ( m_nSttNode == m_nEndNode && m_nSttContent == m_nEndContent )) + // delete simply all nodes + rDoc.GetNodes().Delete( aIdx, pNd->EndOfSectionIndex() - + aIdx.GetIndex() ); + else + // just delete format, rest happens automatically + rDoc.DelSectionFormat( pNd->GetSection().GetFormat() ); + + // do we need to consolidate? + if (m_bSplitAtStart) + { + Join( rDoc, m_nSttNode ); + } + + if (m_bSplitAtEnd) + { + Join( rDoc, m_nEndNode ); + } + + if (m_pHistory) + { + m_pHistory->TmpRollback( &rDoc, 0, false ); + } + + if (m_bUpdateFootnote) + { + rDoc.GetFootnoteIdxs().UpdateFootnote( aIdx.GetNode() ); + } + + AddUndoRedoPaM(rContext); + + if (m_pRedlineSaveData) + SetSaveData( rDoc, *m_pRedlineSaveData ); +} + +void SwUndoInsSection::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + + const SwTOXBaseSection* pUpdateTOX = nullptr; + if (m_xTOXBase) + { + SwRootFrame const* pLayout(nullptr); + SwRootFrame * pLayoutToReset(nullptr); + sw::FieldmarkMode eFieldmarkMode{}; + sw::ParagraphBreakMode eParagraphBreakMode{}; + comphelper::ScopeGuard g([&]() { + if (pLayoutToReset) + { + pLayoutToReset->SetHideRedlines(std::get<1>(*m_xTOXBase) == sw::RedlineMode::Shown); + pLayoutToReset->SetFieldmarkMode(eFieldmarkMode, eParagraphBreakMode); + } + }); + o3tl::sorted_vector layouts(rDoc.GetAllLayouts()); + for (SwRootFrame const*const p : layouts) + { + if ((std::get<1>(*m_xTOXBase) == sw::RedlineMode::Hidden) == p->IsHideRedlines() + && std::get<2>(*m_xTOXBase) == p->GetFieldmarkMode() + && std::get<3>(*m_xTOXBase) == p->GetParagraphBreakMode()) + { + pLayout = p; + break; + } + } + if (!pLayout) + { + assert(!layouts.empty()); // must have one layout + pLayoutToReset = *layouts.begin(); + eFieldmarkMode = pLayoutToReset->GetFieldmarkMode(); + eParagraphBreakMode = pLayoutToReset->GetParagraphBreakMode(); + pLayoutToReset->SetHideRedlines(std::get<1>(*m_xTOXBase) == sw::RedlineMode::Hidden); + pLayoutToReset->SetFieldmarkMode(std::get<2>(*m_xTOXBase), std::get<3>(*m_xTOXBase)); + pLayout = pLayoutToReset; + } + pUpdateTOX = rDoc.InsertTableOf( *rPam.GetPoint(), + // don't expand: will be done by SwUndoUpdateIndex::RedoImpl() + *std::get<0>(*m_xTOXBase), m_pAttrSet.get(), false, pLayout); + } + else + { + rDoc.InsertSwSection(rPam, *m_pSectionData, nullptr, m_pAttrSet.get()); + } + + if (m_pHistory) + { + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + } + + SwSectionNode *const pSectNd = + rDoc.GetNodes()[ m_nSectionNodePos ]->GetSectionNode(); + if (m_pRedlData && + IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags())) + { + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags::Ignore); + + SwPaM aPam( *pSectNd->EndOfSectionNode(), *pSectNd, SwNodeOffset(1) ); + rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlData, aPam ), true); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) && + !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + { + SwPaM aPam( *pSectNd->EndOfSectionNode(), *pSectNd, SwNodeOffset(1) ); + rDoc.getIDocumentRedlineAccess().SplitRedline( aPam ); + } + + if( pUpdateTOX ) + { + // initiate formatting + if (SwEditShell* pESh = rDoc.GetEditShell()) + pESh->CalcLayout(); + + // insert page numbers + const_cast(pUpdateTOX)->UpdatePageNum(); + } +} + +void SwUndoInsSection::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + if (m_xTOXBase) + { + rDoc.InsertTableOf(*rContext.GetRepeatPaM().GetPoint(), + *std::get<0>(*m_xTOXBase), m_pAttrSet.get(), true, + rDoc.getIDocumentLayoutAccess().GetCurrentLayout()); // TODO add shell to RepeatContext? + } + else + { + rDoc.InsertSwSection(rContext.GetRepeatPaM(), + *m_pSectionData, nullptr, m_pAttrSet.get()); + } +} + +void SwUndoInsSection::Join( SwDoc& rDoc, SwNodeOffset nNode ) +{ + SwNodeIndex aIdx( rDoc.GetNodes(), nNode ); + SwTextNode* pTextNd = aIdx.GetNode().GetTextNode(); + OSL_ENSURE( pTextNd, "Where is my TextNode?" ); + + { + RemoveIdxRel( + nNode + 1, + SwPosition( aIdx, pTextNd, pTextNd->GetText().getLength() ) ); + } + pTextNd->JoinNext(); + + if (m_pHistory) + pTextNd->RstTextAttr( 0, pTextNd->Len(), 0, nullptr, true ); +} + +void +SwUndoInsSection::SaveSplitNode(SwTextNode *const pTextNd, bool const bAtStart) +{ + if( pTextNd->GetpSwpHints() ) + { + if (!m_pHistory) + { + m_pHistory.reset( new SwHistory ); + } + m_pHistory->CopyAttr( pTextNd->GetpSwpHints(), pTextNd->GetIndex(), 0, + pTextNd->GetText().getLength(), false ); + } + + if (bAtStart) + { + m_bSplitAtStart = true; + } + else + { + m_bSplitAtEnd = true; + } +} + +class SwUndoDelSection + : public SwUndo +{ +private: + std::unique_ptr const m_pSectionData; /// section not TOX + std::unique_ptr const m_pTOXBase; /// set iff section is TOX + std::optional const m_oAttrSet; + std::shared_ptr< ::sfx2::MetadatableUndo > const m_pMetadataUndo; + SwNodeOffset const m_nStartNode; + SwNodeOffset const m_nEndNode; + +public: + SwUndoDelSection( + SwSectionFormat const&, SwSection const&, SwNodeIndex const*const); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +std::unique_ptr MakeUndoDelSection(SwSectionFormat const& rFormat) +{ + return std::make_unique(rFormat, *rFormat.GetSection(), + rFormat.GetContent().GetContentIdx()); +} + +SwUndoDelSection::SwUndoDelSection( + SwSectionFormat const& rSectionFormat, SwSection const& rSection, + SwNodeIndex const*const pIndex) + : SwUndo( SwUndoId::DELSECTION, rSectionFormat.GetDoc() ) + , m_pSectionData( new SwSectionData(rSection) ) + , m_pTOXBase( dynamic_cast( &rSection) != nullptr + ? new SwTOXBase(static_cast(rSection)) + : nullptr ) + , m_oAttrSet( ::lcl_GetAttrSet(rSection) ) + , m_pMetadataUndo( rSectionFormat.CreateUndo() ) + , m_nStartNode( pIndex->GetIndex() ) + , m_nEndNode( pIndex->GetNode().EndOfSectionIndex() ) +{ +} + +void SwUndoDelSection::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + if (m_pTOXBase) + { + // sw_redlinehide: this should work as-is; there will be another undo for the update + rDoc.InsertTableOf(m_nStartNode, m_nEndNode-2, *m_pTOXBase, + m_oAttrSet ? &*m_oAttrSet : nullptr); + } + else + { + SwNodeIndex aStt( rDoc.GetNodes(), m_nStartNode ); + SwNodeIndex aEnd( rDoc.GetNodes(), m_nEndNode-2 ); + SwSectionFormat* pFormat = rDoc.MakeSectionFormat(); + if (m_oAttrSet) + { + pFormat->SetFormatAttr( *m_oAttrSet ); + } + + /// OD 04.10.2002 #102894# + /// remember inserted section node for further calculations + SwSectionNode* pInsertedSectNd = rDoc.GetNodes().InsertTextSection( + aStt.GetNode(), *pFormat, *m_pSectionData, nullptr, & aEnd.GetNode() ); + + if( SfxItemState::SET == pFormat->GetItemState( RES_FTN_AT_TXTEND ) || + SfxItemState::SET == pFormat->GetItemState( RES_END_AT_TXTEND )) + { + rDoc.GetFootnoteIdxs().UpdateFootnote( aStt.GetNode() ); + } + + /// OD 04.10.2002 #102894# + /// consider that section is hidden by condition. + /// If section is hidden by condition, + /// recalculate condition and update hidden condition flag. + /// Recalculation is necessary, because fields, on which the hide + /// condition depends, can be changed - fields changes aren't undoable. + /// NOTE: setting hidden condition flag also creates/deletes corresponding + /// frames, if the hidden condition flag changes. + SwSection& aInsertedSect = pInsertedSectNd->GetSection(); + if ( aInsertedSect.IsHidden() && + !aInsertedSect.GetCondition().isEmpty() ) + { + SwCalc aCalc( rDoc ); + rDoc.getIDocumentFieldsAccess().FieldsToCalc(aCalc, pInsertedSectNd->GetIndex(), SAL_MAX_INT32); + bool bRecalcCondHidden = + aCalc.Calculate( aInsertedSect.GetCondition() ).GetBool(); + aInsertedSect.SetCondHidden( bRecalcCondHidden ); + } + + pFormat->RestoreMetadata(m_pMetadataUndo); + } +} + +void SwUndoDelSection::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + SwSectionNode *const pNd = + rDoc.GetNodes()[ m_nStartNode ]->GetSectionNode(); + OSL_ENSURE( pNd, "Where is my SectionNode?" ); + // just delete format, rest happens automatically + rDoc.DelSectionFormat( pNd->GetSection().GetFormat() ); +} + +namespace { + +class SwUndoUpdateSection + : public SwUndo +{ +private: + std::unique_ptr m_pSectionData; + std::optional m_oAttrSet; + SwNodeOffset const m_nStartNode; + bool const m_bOnlyAttrChanged; + +public: + SwUndoUpdateSection( + SwSection const&, SwNodeIndex const*const, bool const bOnlyAttr); + + virtual void UndoImpl( ::sw::UndoRedoContext & ) override; + virtual void RedoImpl( ::sw::UndoRedoContext & ) override; +}; + +} + +std::unique_ptr +MakeUndoUpdateSection(SwSectionFormat const& rFormat, bool const bOnlyAttr) +{ + return std::make_unique(*rFormat.GetSection(), + rFormat.GetContent().GetContentIdx(), bOnlyAttr); +} + +SwUndoUpdateSection::SwUndoUpdateSection( + SwSection const& rSection, SwNodeIndex const*const pIndex, + bool const bOnlyAttr) + : SwUndo( SwUndoId::CHGSECTION, &pIndex->GetNode().GetDoc() ) + , m_pSectionData( new SwSectionData(rSection) ) + , m_oAttrSet( ::lcl_GetAttrSet(rSection) ) + , m_nStartNode( pIndex->GetIndex() ) + , m_bOnlyAttrChanged( bOnlyAttr ) +{ +} + +void SwUndoUpdateSection::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwSectionNode *const pSectNd = + rDoc.GetNodes()[ m_nStartNode ]->GetSectionNode(); + OSL_ENSURE( pSectNd, "Where is my SectionNode?" ); + + SwSection& rNdSect = pSectNd->GetSection(); + SwFormat* pFormat = rNdSect.GetFormat(); + + std::optional oCur = ::lcl_GetAttrSet( rNdSect ); + if (m_oAttrSet) + { + // The Content and Protect items must persist + m_oAttrSet->Put( pFormat->GetFormatAttr( RES_CNTNT )); + if( const SvxProtectItem* pItem = pFormat->GetItemIfSet( RES_PROTECT )) + { + m_oAttrSet->Put( *pItem ); + } + pFormat->DelDiffs( *m_oAttrSet ); + m_oAttrSet->ClearItem( RES_CNTNT ); + pFormat->SetFormatAttr( *m_oAttrSet ); + } + else + { + // than the old ones need to be deleted + pFormat->ResetFormatAttr( RES_FRMATR_BEGIN, RES_BREAK ); + pFormat->ResetFormatAttr( RES_HEADER, RES_OPAQUE ); + pFormat->ResetFormatAttr( RES_SURROUND, RES_FRMATR_END-1 ); + } + if (oCur) + m_oAttrSet.emplace(std::move(*oCur)); + else + m_oAttrSet.reset(); + + if (m_bOnlyAttrChanged) + return; + + const bool bUpdate = + (!rNdSect.IsLinkType() && m_pSectionData->IsLinkType()) + || ( !m_pSectionData->GetLinkFileName().isEmpty() + && (m_pSectionData->GetLinkFileName() != + rNdSect.GetLinkFileName())); + + // swap stored section data with live section data + SwSectionData *const pOld( new SwSectionData(rNdSect) ); + rNdSect.SetSectionData(*m_pSectionData); + m_pSectionData.reset(pOld); + + if( bUpdate ) + rNdSect.CreateLink( LinkCreateType::Update ); + else if( SectionType::Content == rNdSect.GetType() && rNdSect.IsConnected() ) + { + rNdSect.Disconnect(); + rDoc.getIDocumentLinksAdministration().GetLinkManager().Remove( &rNdSect.GetBaseLink() ); + } +} + +void SwUndoUpdateSection::RedoImpl(::sw::UndoRedoContext & rContext) +{ + UndoImpl(rContext); +} + + +SwUndoUpdateIndex::SwUndoUpdateIndex(SwTOXBaseSection & rTOX) + : SwUndo(SwUndoId::INSSECTION, rTOX.GetFormat()->GetDoc()) + , m_pSaveSectionOriginal(new SwUndoSaveSection) + , m_pSaveSectionUpdated(new SwUndoSaveSection) + , m_nStartIndex(rTOX.GetFormat()->GetSectionNode()->GetIndex() + 1) +{ + SwDoc & rDoc(*rTOX.GetFormat()->GetDoc()); + assert(rDoc.GetNodes()[m_nStartIndex-1]->IsSectionNode()); + assert(rDoc.GetNodes()[rDoc.GetNodes()[m_nStartIndex]->EndOfSectionIndex()-1]->IsTextNode()); // -1 for extra empty node + // note: title is optional + assert(rDoc.GetNodes()[m_nStartIndex]->IsTextNode() + || rDoc.GetNodes()[m_nStartIndex]->IsSectionNode()); + SwNodeIndex const first(rDoc.GetNodes(), m_nStartIndex); + if (first.GetNode().IsSectionNode()) + { + SwSectionFormat & rSectionFormat(*first.GetNode().GetSectionNode()->GetSection().GetFormat()); + // note: DelSectionFormat will create & append SwUndoDelSection! + rDoc.DelSectionFormat(& rSectionFormat); // remove inner section nodes + } + assert(first.GetNode().IsTextNode()); // invariant: ToX section is *never* empty + SwNodeIndex const last(rDoc.GetNodes(), rDoc.GetNodes()[m_nStartIndex]->EndOfSectionIndex() - 2); // skip empty node + assert(last.GetNode().IsTextNode()); + m_pSaveSectionOriginal->SaveSection(SwNodeRange(first, last), false); +} + +SwUndoUpdateIndex::~SwUndoUpdateIndex() = default; + +void SwUndoUpdateIndex::TitleSectionInserted(SwSectionFormat & rFormat) +{ +#ifndef NDEBUG + SwNodeIndex const tmp(rFormat.GetDoc()->GetNodes(), m_nStartIndex); // title inserted before empty node + assert(tmp.GetNode().IsSectionNode()); + assert(tmp.GetNode().GetSectionNode()->GetSection().GetFormat() == &rFormat); +#endif + m_pTitleSectionUpdated.reset(static_cast(MakeUndoDelSection(rFormat).release())); +} + +void SwUndoUpdateIndex::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc(rContext.GetDoc()); + if (m_pTitleSectionUpdated) + { + m_pTitleSectionUpdated->RedoImpl(rContext); + } + SwNodeIndex const first(rDoc.GetNodes(), m_nStartIndex); + assert(first.GetNode().IsTextNode()); // invariant: ToX section is *never* empty + SwNodeIndex const last(rDoc.GetNodes(), rDoc.GetNodes()[m_nStartIndex]->EndOfSectionIndex() - 1); + assert(last.GetNode().IsTextNode()); + // dummy node so that SaveSection doesn't remove ToX section... + SwTextNode *const pDeletionPrevention = rDoc.GetNodes().MakeTextNode( + *rDoc.GetNodes()[m_nStartIndex]->EndOfSectionNode(), + rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_TEXT)); + m_pSaveSectionUpdated->SaveSection(SwNodeRange(first, last), false); + m_pSaveSectionOriginal->RestoreSection(&rDoc, first.GetNode(), true); + // delete before restoring nested undo, so its node indexes match + SwNodeIndex const del(*pDeletionPrevention); + SwDoc::CorrAbs(del, del, SwPosition(*rDoc.GetNodes()[m_nStartIndex]->EndOfSectionNode(), SwNodeOffset(0)), true); + rDoc.GetNodes().Delete(del); + // original title section will be restored by next Undo, see ctor! +} + +void SwUndoUpdateIndex::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc(rContext.GetDoc()); + // original title section was deleted by previous Undo, see ctor! + SwNodeIndex const first(rDoc.GetNodes(), m_nStartIndex); + assert(first.GetNode().IsTextNode()); // invariant: ToX section is *never* empty + SwNodeIndex const last(rDoc.GetNodes(), rDoc.GetNodes()[m_nStartIndex]->EndOfSectionIndex() - 1); + assert(last.GetNode().IsTextNode()); + // dummy node so that SaveSection doesn't remove ToX section... + SwTextNode *const pDeletionPrevention = rDoc.GetNodes().MakeTextNode( + *rDoc.GetNodes()[m_nStartIndex]->EndOfSectionNode(), + rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool(RES_POOLCOLL_TEXT)); + m_pSaveSectionOriginal->SaveSection(SwNodeRange(first, last), false); + m_pSaveSectionUpdated->RestoreSection(&rDoc, first.GetNode(), true); + // delete before restoring nested undo, so its node indexes match + SwNodeIndex const del(*pDeletionPrevention); + SwDoc::CorrAbs(del, del, SwPosition(*rDoc.GetNodes()[m_nStartIndex]->EndOfSectionNode(), SwNodeOffset(0)), true); + rDoc.GetNodes().Delete(del); + if (m_pTitleSectionUpdated) + { + m_pTitleSectionUpdated->UndoImpl(rContext); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unsort.cxx b/sw/source/core/undo/unsort.cxx new file mode 100644 index 0000000000..bf188918a4 --- /dev/null +++ b/sw/source/core/undo/unsort.cxx @@ -0,0 +1,236 @@ +/* -*- 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 + +SwUndoSort::SwUndoSort(const SwPaM& rRg, const SwSortOptions& rOpt) + : SwUndo(SwUndoId::SORT_TXT, &rRg.GetDoc()) + , SwUndRng(rRg) + , m_nTableNode(0) +{ + m_pSortOptions.reset( new SwSortOptions(rOpt) ); +} + +SwUndoSort::SwUndoSort( SwNodeOffset nStt, SwNodeOffset nEnd, const SwTableNode& rTableNd, + const SwSortOptions& rOpt, bool bSaveTable ) + : SwUndo(SwUndoId::SORT_TBL, &rTableNd.GetDoc()) +{ + m_nSttNode = nStt; + m_nEndNode = nEnd; + m_nTableNode = rTableNd.GetIndex(); + + m_pSortOptions.reset( new SwSortOptions(rOpt) ); + if( bSaveTable ) + m_pUndoAttrTable.reset( new SwUndoAttrTable( rTableNd ) ); +} + +SwUndoSort::~SwUndoSort() +{ + m_pSortOptions.reset(); + m_pUndoAttrTable.reset(); +} + +void SwUndoSort::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + if(m_pSortOptions->bTable) + { + // Undo for Table + RemoveIdxFromSection( rDoc, m_nSttNode, &m_nEndNode ); + + if( m_pUndoAttrTable ) + { + m_pUndoAttrTable->UndoImpl(rContext); + } + + SwTableNode* pTableNd = rDoc.GetNodes()[ m_nTableNode ]->GetTableNode(); + + // #i37739# A simple 'MakeFrames' after the node sorting + // does not work if the table is inside a frame and has no prev/next. + SwNode2LayoutSaveUpperFrames aNode2Layout(*pTableNd); + + pTableNd->DelFrames(); + const SwTable& rTable = pTableNd->GetTable(); + + SwMovedBoxes aMovedList; + for (std::unique_ptr const& pElement : m_SortList) + { + const SwTableBox* pSource = rTable.GetTableBox(pElement->maSourceString); + const SwTableBox* pTarget = rTable.GetTableBox(pElement->maTargetString); + + // move back + MoveCell(&rDoc, pTarget, pSource, + USHRT_MAX != aMovedList.GetPos(pSource) ); + + // store moved entry in list + aMovedList.push_back(pTarget); + } + + // Restore table frames: + // #i37739# A simple 'MakeFrames' after the node sorting + // does not work if the table is inside a frame and has no prev/next. + const SwNodeOffset nIdx = pTableNd->GetIndex(); + aNode2Layout.RestoreUpperFrames( rDoc.GetNodes(), nIdx, nIdx + 1 ); + } + else + { + // Undo for Text + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + RemoveIdxFromRange(rPam, true); + + // create index for (sorted) positions + // The IndexList must be created based on (asc.) sorted SourcePosition. + std::vector aIdxList; + aIdxList.reserve(m_SortList.size()); + + for (size_t i = 0; i < m_SortList.size(); ++i) + { + for (std::unique_ptr const& pElement : m_SortList) + { + if (pElement->mnSourceNodeOffset == (m_nSttNode + SwNodeOffset(i))) + { + aIdxList.push_back( SwNodeIndex(rDoc.GetNodes(), pElement->mnTargetNodeOffset)); + break; + } + } + } + + for (size_t i = 0; i < m_SortList.size(); ++i) + { + SwNodeIndex aIdx( rDoc.GetNodes(), m_nSttNode + SwNodeOffset(i) ); + SwNodeRange aRg( aIdxList[i], SwNodeOffset(0), aIdxList[i], SwNodeOffset(1) ); + rDoc.getIDocumentContentOperations().MoveNodeRange(aRg, aIdx.GetNode(), + SwMoveFlags::DEFAULT); + } + // delete indices + aIdxList.clear(); + SetPaM(rPam, true); + } +} + +void SwUndoSort::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + if(m_pSortOptions->bTable) + { + // Redo for Table + RemoveIdxFromSection( rDoc, m_nSttNode, &m_nEndNode ); + + SwTableNode* pTableNd = rDoc.GetNodes()[ m_nTableNode ]->GetTableNode(); + + // #i37739# A simple 'MakeFrames' after the node sorting + // does not work if the table is inside a frame and has no prev/next. + SwNode2LayoutSaveUpperFrames aNode2Layout(*pTableNd); + + pTableNd->DelFrames(); + const SwTable& rTable = pTableNd->GetTable(); + + SwMovedBoxes aMovedList; + for (std::unique_ptr const& pElement: m_SortList) + { + const SwTableBox* pSource = rTable.GetTableBox(pElement->maSourceString); + const SwTableBox* pTarget = rTable.GetTableBox(pElement->maTargetString); + + // move back + MoveCell(&rDoc, pSource, pTarget, + USHRT_MAX != aMovedList.GetPos( pTarget ) ); + // store moved entry in list + aMovedList.push_back( pSource ); + } + + if( m_pUndoAttrTable ) + { + m_pUndoAttrTable->RedoImpl(rContext); + } + + // Restore table frames: + // #i37739# A simple 'MakeFrames' after the node sorting + // does not work if the table is inside a frame and has no prev/next. + const SwNodeOffset nIdx = pTableNd->GetIndex(); + aNode2Layout.RestoreUpperFrames( rDoc.GetNodes(), nIdx, nIdx + 1 ); + } + else + { + // Redo for Text + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + SetPaM(rPam); + RemoveIdxFromRange(rPam, true); + + std::vector aIdxList; + aIdxList.reserve(m_SortList.size()); + + for (size_t i = 0; i < m_SortList.size(); ++i) + { // current position is starting point + aIdxList.push_back( SwNodeIndex(rDoc.GetNodes(), m_SortList[i]->mnSourceNodeOffset) ); + } + + for (size_t i = 0; i < m_SortList.size(); ++i) + { + SwNodeIndex aIdx( rDoc.GetNodes(), m_nSttNode + SwNodeOffset(i)); + SwNodeRange aRg( aIdxList[i], SwNodeOffset(0), aIdxList[i], SwNodeOffset(1) ); + rDoc.getIDocumentContentOperations().MoveNodeRange(aRg, aIdx.GetNode(), + SwMoveFlags::DEFAULT); + } + // delete indices + aIdxList.clear(); + SetPaM(rPam, true); + SwTextNode const*const pTNd = rPam.GetPointNode().GetTextNode(); + if( pTNd ) + { + rPam.GetPoint()->SetContent( pTNd->GetText().getLength() ); + } + } +} + +void SwUndoSort::RepeatImpl(::sw::RepeatContext & rContext) +{ + // table not repeat capable + if(!m_pSortOptions->bTable) + { + SwPaM *const pPam = & rContext.GetRepeatPaM(); + SwDoc& rDoc = pPam->GetDoc(); + + if( !SwDoc::IsInTable( pPam->Start()->GetNode() ) ) + rDoc.SortText(*pPam, *m_pSortOptions); + } +} + +void SwUndoSort::Insert( const OUString& rOrgPos, const OUString& rNewPos) +{ + m_SortList.push_back(std::make_unique(rOrgPos, rNewPos)); +} + +void SwUndoSort::Insert( SwNodeOffset nOrgPos, SwNodeOffset nNewPos) +{ + m_SortList.push_back(std::make_unique(nOrgPos, nNewPos)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/unspnd.cxx b/sw/source/core/undo/unspnd.cxx new file mode 100644 index 0000000000..8dc6533032 --- /dev/null +++ b/sw/source/core/undo/unspnd.cxx @@ -0,0 +1,192 @@ +/* -*- 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 +#include +#include + +// SPLITNODE + +SwUndoSplitNode::SwUndoSplitNode( SwDoc& rDoc, const SwPosition& rPos, + bool bChkTable ) + : SwUndo( SwUndoId::SPLITNODE, &rDoc ), m_nNode( rPos.GetNodeIndex() ), + m_nContent( rPos.GetContentIndex() ), + m_bTableFlag( false ), m_bCheckTableStart( bChkTable ) +{ + SwTextNode *const pTextNd = rPos.GetNode().GetTextNode(); + OSL_ENSURE( pTextNd, "only for TextNode" ); + if( pTextNd->GetpSwpHints() ) + { + m_pHistory.reset(new SwHistory); + m_pHistory->CopyAttr(pTextNd->GetpSwpHints(), m_nNode, 0, + pTextNd->GetText().getLength(), false ); + if (!m_pHistory->Count()) + { + m_pHistory.reset(); + } + } + // consider Redline + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + m_pRedlineData.reset( new SwRedlineData( RedlineType::Insert, rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ) ); + SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ); + } + + m_nParRsid = pTextNd->GetParRsid(); +} + +SwUndoSplitNode::~SwUndoSplitNode() +{ + m_pHistory.reset(); + m_pRedlineData.reset(); +} + +void SwUndoSplitNode::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwCursor & rPam( rContext.GetCursorSupplier().CreateNewShellCursor() ); + rPam.DeleteMark(); + if( m_bTableFlag ) + { + // than a TextNode was added directly before the current table + SwPosition& rPos = *rPam.GetPoint(); + rPos.Assign(m_nNode); + SwTextNode* pTNd; + SwNode* pCurrNd = pDoc->GetNodes()[ m_nNode + 1 ]; + SwTableNode* pTableNd = pCurrNd->FindTableNode(); + if( pCurrNd->IsContentNode() && pTableNd && + nullptr != ( pTNd = pDoc->GetNodes()[ pTableNd->GetIndex()-1 ]->GetTextNode() )) + { + // move break attributes + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + const SfxItemSet* pNdSet = pTNd->GetpSwAttrSet(); + if( pNdSet ) + { + if( const SwFormatPageDesc* pItem = pNdSet->GetItemIfSet( RES_PAGEDESC, false ) ) + pTableFormat->SetFormatAttr( *pItem ); + + if( const SvxFormatBreakItem* pItem = pNdSet->GetItemIfSet( RES_BREAK, false ) ) + pTableFormat->SetFormatAttr( *pItem ); + } + + // than delete it again + SwNodeIndex aDelNd( *pTableNd, -1 ); + RemoveIdxRel( aDelNd.GetIndex(), *rPam.GetPoint() ); + pDoc->GetNodes().Delete( aDelNd ); + } + } + else + { + SwTextNode * pTNd = pDoc->GetNodes()[ m_nNode ]->GetTextNode(); + if( pTNd ) + { + rPam.GetPoint()->Assign(*pTNd, pTNd->GetText().getLength()); + + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + rPam.SetMark(); + rPam.GetMark()->Adjust(SwNodeOffset(1)); + pDoc->getIDocumentRedlineAccess().DeleteRedline( rPam, true, RedlineType::Any ); + rPam.DeleteMark(); + } + + RemoveIdxRel( m_nNode+1, *rPam.GetPoint() ); + + pTNd->JoinNext(); + if (m_pHistory) + { + rPam.GetPoint()->SetContent(0); + rPam.SetMark(); + rPam.GetPoint()->SetContent(pTNd->GetText().getLength()); + + pDoc->RstTextAttrs( rPam, true ); + m_pHistory->TmpRollback( pDoc, 0, false ); + } + + pDoc->UpdateParRsid( pTNd, m_nParRsid ); + } + } + + // also set the cursor onto undo section + rPam.DeleteMark(); + rPam.GetPoint()->Assign( m_nNode, m_nContent ); +} + +void SwUndoSplitNode::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwCursor & rPam( rContext.GetCursorSupplier().CreateNewShellCursor() ); + rPam.GetPoint()->Assign(m_nNode); + SwTextNode * pTNd = rPam.GetPointNode().GetTextNode(); + OSL_ENSURE(pTNd, "SwUndoSplitNode::RedoImpl(): SwTextNode expected"); + if (!pTNd) + return; + + rPam.GetPoint()->SetContent( m_nContent ); + + SwDoc& rDoc = rPam.GetDoc(); + rDoc.getIDocumentContentOperations().SplitNode( *rPam.GetPoint(), m_bCheckTableStart ); + + if (m_pHistory) + { + m_pHistory->SetTmpEnd(m_pHistory->Count()); + } + + if( !(( m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) || + ( !( RedlineFlags::Ignore & GetRedlineFlags() ) && + !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ))) + return; + + rPam.SetMark(); + if( rPam.Move( fnMoveBackward )) + { + if( m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags::Ignore); + rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlineData, rPam ), true); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else + rDoc.getIDocumentRedlineAccess().SplitRedline( rPam ); + rPam.Exchange(); + } + rPam.DeleteMark(); +} + +void SwUndoSplitNode::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().getIDocumentContentOperations().SplitNode( + *rContext.GetRepeatPaM().GetPoint(), m_bCheckTableStart ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/untbl.cxx b/sw/source/core/undo/untbl.cxx new file mode 100644 index 0000000000..72f1c809e2 --- /dev/null +++ b/sw/source/core/undo/untbl.cxx @@ -0,0 +1,3179 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef DBG_UTIL +#define CHECK_TABLE(t) (t).CheckConsistency(); +#else +#define CHECK_TABLE(t) +#endif + +#ifdef DBG_UTIL + #define DEBUG_REDLINE( pDoc ) sw_DebugRedline( pDoc ); +#else + #define DEBUG_REDLINE( pDoc ) +#endif + +typedef std::vector > SfxItemSets; + +struct UndoTableCpyTable_Entry +{ + SwNodeOffset nBoxIdx, nOffset; + std::unique_ptr pBoxNumAttr; + std::unique_ptr pUndo; + + // Was the last paragraph of the new and the first paragraph of the old content joined? + bool bJoin; // For redlining only + + explicit UndoTableCpyTable_Entry( const SwTableBox& rBox ); + + void dumpAsXml(xmlTextWriterPtr pWriter) const; +}; + +namespace { + +class SaveBox; +class SaveLine; + +void KillEmptyFrameFormat(SwFrameFormat& rFormat) +{ + if(!rFormat.HasWriterListeners()) + delete &rFormat; +}; + +} + +class SaveTable +{ + friend SaveBox; + friend SaveLine; + SfxItemSet m_aTableSet; + std::unique_ptr m_pLine; + const SwTable* m_pSwTable; + SfxItemSets m_aSets; + SwFrameFormatsV m_aFrameFormats; + sal_uInt16 m_nLineCount; + bool m_bModifyBox : 1; + bool m_bSaveFormula : 1; + bool m_bNewModel : 1; + + SaveTable(const SaveTable&) = delete; + SaveTable& operator=(const SaveTable&) = delete; + SwFrameFormat& CreateNewFormat(SwFrameFormat& rFormat, sal_uInt16 nFormatPos); + +public: + SaveTable( const SwTable& rTable, sal_uInt16 nLnCnt = USHRT_MAX, + bool bSaveFormula = true ); + + sal_uInt16 AddFormat( SwFrameFormat* pFormat, bool bIsLine ); + void NewFrameFormatForLine(const SwTableLine&, sal_uInt16 nFormatPos, SwFrameFormat* pOldFormat); + void NewFrameFormatForBox(const SwTableBox&, sal_uInt16 nFormatPos, SwFrameFormat* pOldFormat); + + void RestoreAttr( SwTable& rTable, bool bModifyBox = false ); + void SaveContentAttrs( SwDoc* pDoc ); + void CreateNew( SwTable& rTable, bool bCreateFrames = true, + bool bRestoreChart = true ); + bool IsNewModel() const { return m_bNewModel; } +}; + +namespace { + +class SaveLine +{ + friend SaveTable; + friend class SaveBox; + + SaveLine* m_pNext; + SaveBox* m_pBox; + sal_uInt16 m_nItemSet; + + SaveLine(const SaveLine&) = delete; + SaveLine& operator=(const SaveLine&) = delete; + +public: + SaveLine( SaveLine* pPrev, const SwTableLine& rLine, SaveTable& rSTable ); + ~SaveLine(); + + void RestoreAttr( SwTableLine& rLine, SaveTable& rSTable ); + void SaveContentAttrs( SwDoc* pDoc ); + + void CreateNew( SwTable& rTable, SwTableBox& rParent, SaveTable& rSTable ); +}; + +class SaveBox +{ + friend class SaveLine; + + SaveBox* m_pNext; + SwNodeOffset m_nStartNode; + sal_Int32 m_nRowSpan; + sal_uInt16 m_nItemSet; + union + { + SfxItemSets* pContentAttrs; + SaveLine* pLine; + } m_Ptrs; + +public: + SaveBox( SaveBox* pPrev, const SwTableBox& rBox, SaveTable& rSTable ); + ~SaveBox(); + + void RestoreAttr( SwTableBox& rBox, SaveTable& rSTable ); + void SaveContentAttrs( SwDoc* pDoc ); + + void CreateNew( SwTable& rTable, SwTableLine& rParent, SaveTable& rSTable ); +}; + +} + +#if OSL_DEBUG_LEVEL > 0 +static void CheckTable( const SwTable& ); +#define CHECKTABLE(t) CheckTable( t ); +#else +#define CHECKTABLE(t) +#endif + +/* #130880: Crash in undo of table to text when the table has (freshly) merged cells +The order of cell content nodes in the nodes array is not given by the recursive table structure. +The algorithm must not rely on this even it holds for a fresh loaded table in odt file format. +So we need to remember not only the start node position but the end node position as well. +*/ + +struct SwTableToTextSave +{ + SwNodeOffset m_nSttNd; + SwNodeOffset m_nEndNd; + sal_Int32 m_nContent; + std::unique_ptr m_pHstry; + // metadata references for first and last paragraph in cell + std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndoStart; + std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndoEnd; + + SwTableToTextSave( SwDoc& rDoc, SwNodeOffset nNd, SwNodeOffset nEndIdx, sal_Int32 nContent ); + +private: + SwTableToTextSave(const SwTableToTextSave&) = delete; + SwTableToTextSave& operator=(const SwTableToTextSave&) = delete; + +}; + +WhichRangesContainer const aSave_BoxContentSet(svl::Items< + RES_CHRATR_COLOR, RES_CHRATR_CROSSEDOUT, + RES_CHRATR_FONT, RES_CHRATR_FONTSIZE, + RES_CHRATR_POSTURE, RES_CHRATR_POSTURE, + RES_CHRATR_SHADOWED, RES_CHRATR_WEIGHT, + RES_PARATR_ADJUST, RES_PARATR_ADJUST>); + +SwUndoInsTable::SwUndoInsTable( const SwPosition& rPos, sal_uInt16 nCl, sal_uInt16 nRw, + sal_uInt16 nAdj, const SwInsertTableOptions& rInsTableOpts, + const SwTableAutoFormat* pTAFormat, + const std::vector *pColArr, + const OUString & rName) + : SwUndo( SwUndoId::INSTABLE, &rPos.GetDoc() ), + m_aInsTableOptions( rInsTableOpts ), + m_nStartNode( rPos.GetNodeIndex() ), m_nRows( nRw ), m_nColumns( nCl ), m_nAdjust( nAdj ) +{ + if( pColArr ) + { + m_oColumnWidth.emplace( *pColArr ); + } + if( pTAFormat ) + m_pAutoFormat.reset( new SwTableAutoFormat( *pTAFormat ) ); + + // consider redline + SwDoc& rDoc = rPos.GetNode().GetDoc(); + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + m_pRedlineData.reset( new SwRedlineData( RedlineType::Insert, rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ) ); + SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ); + } + + m_sTableName = rName; +} + +SwUndoInsTable::~SwUndoInsTable() +{ + m_pDDEFieldType.reset(); + m_oColumnWidth.reset(); + m_pRedlineData.reset(); + m_pAutoFormat.reset(); +} + +void SwUndoInsTable::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwNodeIndex aIdx( rDoc.GetNodes(), m_nStartNode ); + + SwTableNode* pTableNd = aIdx.GetNode().GetTableNode(); + // tdf#159025 skip undo if SwTableNode is a nullptr + // I don't know what causes the SwTableNode to be a nullptr in the + // case of tdf#159025, but at least stop the crashing by skipping + // this undo request. + SAL_WARN_IF( !pTableNd, "sw.core", "no TableNode" ); + if( !pTableNd ) + return; + + pTableNd->DelFrames(); + + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + rDoc.getIDocumentRedlineAccess().DeleteRedline( *pTableNd, true, RedlineType::Any ); + RemoveIdxFromSection( rDoc, m_nStartNode ); + + // move hard page breaks into next node + SwContentNode* pNextNd = rDoc.GetNodes()[ pTableNd->EndOfSectionIndex()+1 ]->GetContentNode(); + if( pNextNd ) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + + if( const SwFormatPageDesc* pItem = pTableFormat->GetItemIfSet( RES_PAGEDESC, + false ) ) + pNextNd->SetAttr( *pItem ); + + if( const SvxFormatBreakItem* pItem = pTableFormat->GetItemIfSet( RES_BREAK, + false ) ) + pNextNd->SetAttr( *pItem ); + + ::sw::NotifyTableCollapsedParagraph(pNextNd, nullptr); + } + + m_sTableName = pTableNd->GetTable().GetFrameFormat()->GetName(); + if( auto pDDETable = dynamic_cast(&pTableNd->GetTable()) ) + m_pDDEFieldType.reset(static_cast(pDDETable->GetDDEFieldType()->Copy().release())); + + rDoc.GetNodes().Delete( aIdx, pTableNd->EndOfSectionIndex() - + aIdx.GetIndex() + 1 ); + + SwPaM & rPam( rContext.GetCursorSupplier().CreateNewShellCursor() ); + rPam.DeleteMark(); + rPam.GetPoint()->Assign(aIdx); +} + +void SwUndoInsTable::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + SwEditShell *const pEditShell(rDoc.GetEditShell()); + OSL_ENSURE(pEditShell, "SwUndoInsTable::RedoImpl needs a SwEditShell!"); + if (!pEditShell) + { + throw uno::RuntimeException(); + } + + SwPosition const aPos(rDoc.GetNodes(), m_nStartNode); + const SwTable* pTable = rDoc.InsertTable( m_aInsTableOptions, aPos, m_nRows, m_nColumns, + m_nAdjust, + m_pAutoFormat.get(), + m_oColumnWidth ? &*m_oColumnWidth : nullptr ); + pEditShell->MoveTable( GotoPrevTable, fnTableStart ); + static_cast(pTable->GetFrameFormat())->SetFormatName( m_sTableName ); + SwTableNode* pTableNode = rDoc.GetNodes()[m_nStartNode]->GetTableNode(); + + if( m_pDDEFieldType ) + { + SwDDEFieldType* pNewType = static_cast(rDoc.getIDocumentFieldsAccess().InsertFieldType( + *m_pDDEFieldType)); + std::unique_ptr pDDETable(new SwDDETable( pTableNode->GetTable(), pNewType )); + pTableNode->SetNewTable( std::move(pDDETable) ); + m_pDDEFieldType.reset(); + } + + if( !((m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) || + ( !( RedlineFlags::Ignore & GetRedlineFlags() ) && + !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ))) + return; + + SwPaM aPam( *pTableNode->EndOfSectionNode(), *pTableNode, SwNodeOffset(1) ); + + if( m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) ) + { + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags::Ignore); + + rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlineData, aPam ), true); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else + rDoc.getIDocumentRedlineAccess().SplitRedline( aPam ); +} + +void SwUndoInsTable::RepeatImpl(::sw::RepeatContext & rContext) +{ + rContext.GetDoc().InsertTable( + m_aInsTableOptions, *rContext.GetRepeatPaM().GetPoint(), + m_nRows, m_nColumns, m_nAdjust, m_pAutoFormat.get(), + m_oColumnWidth ? &*m_oColumnWidth : nullptr ); +} + +SwRewriter SwUndoInsTable::GetRewriter() const +{ + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, SwResId(STR_START_QUOTE)); + aRewriter.AddRule(UndoArg2, m_sTableName); + aRewriter.AddRule(UndoArg3, SwResId(STR_END_QUOTE)); + + return aRewriter; +} + +SwTableToTextSave::SwTableToTextSave( SwDoc& rDoc, SwNodeOffset nNd, SwNodeOffset nEndIdx, sal_Int32 nCnt ) + : m_nSttNd( nNd ), m_nEndNd( nEndIdx), m_nContent( nCnt ) +{ + // keep attributes of the joined node + SwTextNode* pNd = rDoc.GetNodes()[ nNd ]->GetTextNode(); + if( pNd ) + { + m_pHstry.reset( new SwHistory ); + + m_pHstry->AddColl(pNd->GetTextColl(), nNd, SwNodeType::Text); + if ( pNd->GetpSwpHints() ) + { + m_pHstry->CopyAttr( pNd->GetpSwpHints(), nNd, 0, + pNd->GetText().getLength(), false ); + } + if( pNd->HasSwAttrSet() ) + m_pHstry->CopyFormatAttr( *pNd->GetpSwAttrSet(), nNd ); + + if( !m_pHstry->Count() ) + { + m_pHstry.reset(); + } + + // METADATA: store + m_pMetadataUndoStart = pNd->CreateUndo(); + } + + // we also need to store the metadata reference of the _last_ paragraph + // we subtract 1 to account for the removed cell start/end node pair + // (after SectionUp, the end of the range points to the node after the cell) + if ( nEndIdx - 1 > nNd ) + { + SwTextNode* pLastNode( rDoc.GetNodes()[ nEndIdx - 1 ]->GetTextNode() ); + if( pLastNode ) + { + // METADATA: store + m_pMetadataUndoEnd = pLastNode->CreateUndo(); + } + } +} + +SwUndoTableToText::SwUndoTableToText( const SwTable& rTable, sal_Unicode cCh ) + : SwUndo( SwUndoId::TABLETOTEXT, rTable.GetFrameFormat()->GetDoc() ), + m_sTableName( rTable.GetFrameFormat()->GetName() ), + m_nStartNode( 0 ), m_nEndNode( 0 ), + m_cSeparator( cCh ), m_nHeadlineRepeat( rTable.GetRowsToRepeat() ) +{ + m_pTableSave.reset( new SaveTable( rTable ) ); + m_vBoxSaves.reserve(rTable.GetTabSortBoxes().size()); + + if( auto pDDETable = dynamic_cast(&rTable) ) + m_pDDEFieldType.reset(static_cast(pDDETable->GetDDEFieldType()->Copy().release())); + + m_bCheckNumFormat = rTable.GetFrameFormat()->GetDoc()->IsInsTableFormatNum(); + + m_pHistory.reset(new SwHistory); + const SwTableNode* pTableNd = rTable.GetTableNode(); + SwNodeOffset nTableStt = pTableNd->GetIndex(), nTableEnd = pTableNd->EndOfSectionIndex(); + + for(sw::SpzFrameFormat* pFormat: *pTableNd->GetDoc().GetSpzFrameFormats()) + { + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwNode const*const pAnchorNode = pAnchor->GetAnchorNode(); + if (pAnchorNode && + ((RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) || + (RndStdIds::FLY_AT_PARA == pAnchor->GetAnchorId())) && + nTableStt <= pAnchorNode->GetIndex() && + pAnchorNode->GetIndex() < nTableEnd ) + { + m_pHistory->AddChangeFlyAnchor(*pFormat); + } + } + + if( !m_pHistory->Count() ) + { + m_pHistory.reset(); + } +} + +SwUndoTableToText::~SwUndoTableToText() +{ + m_pDDEFieldType.reset(); + m_pTableSave.reset(); + m_vBoxSaves.clear(); + m_pHistory.reset(); +} + +void SwUndoTableToText::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + SwNodeIndex aFrameIdx( rDoc.GetNodes(), m_nStartNode ); + SwNodeIndex aEndIdx( rDoc.GetNodes(), m_nEndNode ); + + pPam->GetPoint()->Assign( aFrameIdx ); + pPam->SetMark(); + pPam->GetPoint()->Assign( aEndIdx ); + rDoc.DelNumRules( *pPam ); + pPam->DeleteMark(); + + // now collect all Uppers + SwNode2LayoutSaveUpperFrames aNode2Layout(aFrameIdx.GetNode()); + + // create TableNode structure + SwTableNode* pTableNd = rDoc.GetNodes().UndoTableToText( m_nStartNode, m_nEndNode, m_vBoxSaves ); + pTableNd->GetTable().SetTableModel( m_pTableSave->IsNewModel() ); + SwTableFormat* pTableFormat = rDoc.MakeTableFrameFormat( m_sTableName, rDoc.GetDfltFrameFormat() ); + pTableNd->GetTable().RegisterToFormat( *pTableFormat ); + pTableNd->GetTable().SetRowsToRepeat( m_nHeadlineRepeat ); + + // create old table structure + m_pTableSave->CreateNew( pTableNd->GetTable() ); + + if( m_pDDEFieldType ) + { + SwDDEFieldType* pNewType = static_cast(rDoc.getIDocumentFieldsAccess().InsertFieldType( + *m_pDDEFieldType)); + std::unique_ptr pDDETable( new SwDDETable( pTableNd->GetTable(), pNewType ) ); + pTableNd->SetNewTable( std::move(pDDETable), false ); + m_pDDEFieldType.reset(); + } + + if( m_bCheckNumFormat ) + { + SwTableSortBoxes& rBxs = pTableNd->GetTable().GetTabSortBoxes(); + for (size_t nBoxes = rBxs.size(); nBoxes; ) + { + rDoc.ChkBoxNumFormat( *rBxs[ --nBoxes ], false ); + } + } + + if( m_pHistory ) + { + sal_uInt16 nTmpEnd = m_pHistory->GetTmpEnd(); + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( nTmpEnd ); + } + + aNode2Layout.RestoreUpperFrames( rDoc.GetNodes(), + pTableNd->GetIndex(), pTableNd->GetIndex()+1 ); + + // Is a table selection requested? + pPam->DeleteMark(); + pPam->GetPoint()->Assign( *pTableNd->EndOfSectionNode() ); + pPam->SetMark(); + pPam->GetPoint()->Assign( *pPam->GetPointNode().StartOfSectionNode() ); + pPam->Move( fnMoveForward, GoInContent ); + pPam->Exchange(); + pPam->Move( fnMoveBackward, GoInContent ); + + ClearFEShellTabCols(rDoc, nullptr); +} + +// located in untbl.cxx and only an Undo object is allowed to call it +SwTableNode* SwNodes::UndoTableToText( SwNodeOffset nSttNd, SwNodeOffset nEndNd, + const SwTableToTextSaves& rSavedData ) +{ + SwNodeIndex aSttIdx( *this, nSttNd ); + SwNodeIndex aEndIdx( *this, nEndNd+1 ); + + SwTableNode * pTableNd = new SwTableNode( aSttIdx.GetNode() ); + SwEndNode* pEndNd = new SwEndNode( aEndIdx.GetNode(), *pTableNd ); + + aEndIdx = *pEndNd; + + /* Set pTableNd as start of section for all nodes in [nSttNd, nEndNd]. + Delete all Frames attached to the nodes in that range. */ + SwNode* pNd; + { + SwNodeOffset n, nTmpEnd = aEndIdx.GetIndex(); + for( n = pTableNd->GetIndex() + 1; n < nTmpEnd; ++n ) + { + pNd = (*this)[n]; + if (pNd->IsContentNode()) + { + static_cast(pNd)->DelFrames(nullptr); + } + // tdf#147938 reset merge flag in nodes + pNd->SetRedlineMergeFlag(SwNode::Merge::None); + pNd->m_pStartOfSection = pTableNd; + } + } + + // than create table structure partially. First a single line that contains + // all boxes. The correct structure is then taken from SaveStruct. + SwTableBoxFormat* pBoxFormat = GetDoc().MakeTableBoxFormat(); + SwTableLineFormat* pLineFormat = GetDoc().MakeTableLineFormat(); + SwTableLine* pLine = new SwTableLine( pLineFormat, rSavedData.size(), nullptr ); + pTableNd->GetTable().GetTabLines().insert( pTableNd->GetTable().GetTabLines().begin(), pLine ); + + for( size_t n = rSavedData.size(); n; ) + { + const SwTableToTextSave *const pSave = rSavedData[ --n ].get(); + // if the start node was merged with last from prev. cell, + // subtract 1 from index to get the merged paragraph, and split that + aSttIdx = pSave->m_nSttNd - ( ( SAL_MAX_INT32 != pSave->m_nContent ) ? 1 : 0); + SwTextNode* pTextNd = aSttIdx.GetNode().GetTextNode(); + + if( SAL_MAX_INT32 != pSave->m_nContent ) + { + // split at ContentPosition, delete previous char (= separator) + OSL_ENSURE( pTextNd, "Where is my TextNode?" ); + SwContentIndex aCntPos( pTextNd, pSave->m_nContent - 1 ); + + const std::shared_ptr pContentStore(sw::mark::ContentIdxStore::Create()); + pContentStore->Save(GetDoc(), aSttIdx.GetIndex(), aCntPos.GetIndex()); + + pTextNd->EraseText( aCntPos, 1 ); + + std::function restoreFunc( + [&](SwTextNode *const pNewNode, sw::mark::RestoreMode const eMode, bool) + { + if (!pContentStore->Empty()) + { + pContentStore->Restore(*pNewNode, pSave->m_nContent, pSave->m_nContent + 1, eMode); + } + }); + pTextNd->SplitContentNode( + SwPosition(aSttIdx, aCntPos), &restoreFunc); + } + + if( pTextNd ) + { + // METADATA: restore + pTextNd->GetTextNode()->RestoreMetadata(pSave->m_pMetadataUndoStart); + if( pTextNd->HasSwAttrSet() ) + pTextNd->ResetAllAttr(); + + if( pTextNd->GetpSwpHints() ) + pTextNd->ClearSwpHintsArr( false ); + } + + if( pSave->m_pHstry ) + { + sal_uInt16 nTmpEnd = pSave->m_pHstry->GetTmpEnd(); + pSave->m_pHstry->TmpRollback( &GetDoc(), 0 ); + pSave->m_pHstry->SetTmpEnd( nTmpEnd ); + } + + // METADATA: restore + // end points to node after cell + if ( pSave->m_nEndNd - 1 > pSave->m_nSttNd ) + { + SwTextNode* pLastNode = (*this)[ pSave->m_nEndNd - 1 ]->GetTextNode(); + if (pLastNode) + { + pLastNode->RestoreMetadata(pSave->m_pMetadataUndoEnd); + } + } + + aEndIdx = pSave->m_nEndNd; + SwStartNode* pSttNd = new SwStartNode( aSttIdx.GetNode(), SwNodeType::Start, + SwTableBoxStartNode ); + pSttNd->m_pStartOfSection = pTableNd; + new SwEndNode( aEndIdx.GetNode(), *pSttNd ); + + for( SwNodeOffset i = aSttIdx.GetIndex(); i < aEndIdx.GetIndex()-1; ++i ) + { + pNd = (*this)[ i ]; + pNd->m_pStartOfSection = pSttNd; + if( pNd->IsStartNode() ) + i = pNd->EndOfSectionIndex(); + } + + SwTableBox* pBox = new SwTableBox( pBoxFormat, *pSttNd, pLine ); + pLine->GetTabBoxes().insert( pLine->GetTabBoxes().begin(), pBox ); + } + return pTableNd; +} + +void SwUndoTableToText::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + pPam->GetPoint()->Assign( m_nStartNode ); + SwNodeIndex aSaveIdx( pPam->GetPoint()->GetNode(), -1 ); + + pPam->SetMark(); // log off all indices + pPam->DeleteMark(); + + SwTableNode* pTableNd = pPam->GetPointNode().GetTableNode(); + OSL_ENSURE( pTableNd, "Could not find any TableNode" ); + + if( auto pDDETable = dynamic_cast(&pTableNd->GetTable()) ) + m_pDDEFieldType.reset(static_cast(pDDETable->GetDDEFieldType()->Copy().release())); + + rDoc.TableToText( pTableNd, m_cSeparator ); + + ++aSaveIdx; + SwContentNode* pCNd = aSaveIdx.GetNode().GetContentNode(); + if( !pCNd && nullptr == ( pCNd = rDoc.GetNodes().GoNext( &aSaveIdx ) ) && + nullptr == ( pCNd = SwNodes::GoPrevious( &aSaveIdx )) ) + { + OSL_FAIL( "Where is the TextNode now?" ); + } + + pPam->GetPoint()->Assign( aSaveIdx ); + + pPam->SetMark(); // log off all indices + pPam->DeleteMark(); +} + +void SwUndoTableToText::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwPaM *const pPam = & rContext.GetRepeatPaM(); + SwTableNode *const pTableNd = pPam->GetPointNode().FindTableNode(); + if( pTableNd ) + { + // move cursor out of table + pPam->GetPoint()->Assign( *pTableNd->EndOfSectionNode() ); + pPam->Move( fnMoveForward, GoInContent ); + pPam->SetMark(); + pPam->DeleteMark(); + + rContext.GetDoc().TableToText( pTableNd, m_cSeparator ); + } +} + +void SwUndoTableToText::SetRange( const SwNodeRange& rRg ) +{ + m_nStartNode = rRg.aStart.GetIndex(); + m_nEndNode = rRg.aEnd.GetIndex(); +} + +void SwUndoTableToText::AddBoxPos( SwDoc& rDoc, SwNodeOffset nNdIdx, SwNodeOffset nEndIdx, sal_Int32 nContentIdx ) +{ + m_vBoxSaves.push_back(std::make_unique(rDoc, nNdIdx, nEndIdx, nContentIdx)); +} + +SwUndoTextToTable::SwUndoTextToTable( const SwPaM& rRg, + const SwInsertTableOptions& rInsTableOpts, + sal_Unicode cCh, sal_uInt16 nAdj, + const SwTableAutoFormat* pAFormat ) + : SwUndo( SwUndoId::TEXTTOTABLE, &rRg.GetDoc() ), SwUndRng( rRg ), m_aInsertTableOpts( rInsTableOpts ), + m_pHistory( nullptr ), m_cSeparator( cCh ), m_nAdjust( nAdj ) +{ + if( pAFormat ) + m_pAutoFormat.reset( new SwTableAutoFormat( *pAFormat ) ); + + const SwPosition* pEnd = rRg.End(); + SwNodes& rNds = rRg.GetDoc().GetNodes(); + m_bSplitEnd = pEnd->GetContentIndex() && ( pEnd->GetContentIndex() + != pEnd->GetNode().GetContentNode()->Len() || + pEnd->GetNodeIndex() >= rNds.GetEndOfContent().GetIndex()-1 ); +} + +SwUndoTextToTable::~SwUndoTextToTable() +{ + m_pAutoFormat.reset(); +} + +void SwUndoTextToTable::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + SwNodeOffset nTableNd = m_nSttNode; + if( m_nSttContent ) + ++nTableNd; // Node was split previously + SwNodeIndex aIdx( rDoc.GetNodes(), nTableNd ); + SwTableNode *const pTNd = aIdx.GetNode().GetTableNode(); + OSL_ENSURE( pTNd, "Could not find a TableNode" ); + + RemoveIdxFromSection( rDoc, nTableNd ); + + m_sTableName = pTNd->GetTable().GetFrameFormat()->GetName(); + + if( m_pHistory ) + { + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + } + + if( !mvDelBoxes.empty() ) + { + pTNd->DelFrames(); + SwTable& rTable = pTNd->GetTable(); + for( size_t n = mvDelBoxes.size(); n; ) + { + SwTableBox* pBox = rTable.GetTableBox( mvDelBoxes[ --n ] ); + if( pBox ) + ::DeleteBox_( rTable, pBox, nullptr, false, false ); + else { + OSL_ENSURE( false, "Where is my box?" ); + } + } + } + + rDoc.TableToText( pTNd, 0x0b == m_cSeparator ? 0x09 : m_cSeparator ); + + // join again at start? + if( m_nSttContent ) + { + SwPaM aPam(rDoc.GetNodes(), nTableNd); + if (aPam.Move(fnMoveBackward, GoInContent)) + { + SwNode & rIdx = aPam.GetPoint()->GetNode(); + + // than move, relatively, the Cursor/etc. again + RemoveIdxRel( rIdx.GetIndex()+1, *aPam.GetPoint() ); + + rIdx.GetContentNode()->JoinNext(); + } + } + + // join again at end? + if( m_bSplitEnd ) + { + SwPosition aEndPos( rDoc.GetNodes(), m_nEndNode ); + SwTextNode* pTextNd = aEndPos.GetNode().GetTextNode(); + if( pTextNd && pTextNd->CanJoinNext() ) + { + aEndPos.nContent.Assign( nullptr, 0 ); + + // than move, relatively, the Cursor/etc. again + aEndPos.SetContent(pTextNd->GetText().getLength()); + RemoveIdxRel( m_nEndNode + 1, aEndPos ); + + pTextNd->JoinNext(); + } + } + + AddUndoRedoPaM(rContext); +} + +void SwUndoTextToTable::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + RemoveIdxFromRange(rPam, false); + SetPaM(rPam); + + SwTable const*const pTable = rContext.GetDoc().TextToTable( + m_aInsertTableOpts, rPam, m_cSeparator, m_nAdjust, m_pAutoFormat.get() ); + static_cast(pTable->GetFrameFormat())->SetFormatName( m_sTableName ); +} + +void SwUndoTextToTable::RepeatImpl(::sw::RepeatContext & rContext) +{ + // no Table In Table + if (!rContext.GetRepeatPaM().GetPointNode().FindTableNode()) + { + rContext.GetDoc().TextToTable( m_aInsertTableOpts, rContext.GetRepeatPaM(), + m_cSeparator, m_nAdjust, + m_pAutoFormat.get() ); + } +} + +void SwUndoTextToTable::AddFillBox( const SwTableBox& rBox ) +{ + mvDelBoxes.push_back( rBox.GetSttIdx() ); +} + +SwHistory& SwUndoTextToTable::GetHistory() +{ + if( !m_pHistory ) + m_pHistory = new SwHistory; + return *m_pHistory; +} + +SwUndoTableHeadline::SwUndoTableHeadline( const SwTable& rTable, sal_uInt16 nOldHdl, + sal_uInt16 nNewHdl ) + : SwUndo( SwUndoId::TABLEHEADLINE, rTable.GetFrameFormat()->GetDoc() ), + m_nOldHeadline( nOldHdl ), + m_nNewHeadline( nNewHdl ) +{ + OSL_ENSURE( !rTable.GetTabSortBoxes().empty(), "Table without content" ); + const SwStartNode *pSttNd = rTable.GetTabSortBoxes()[ 0 ]->GetSttNd(); + OSL_ENSURE( pSttNd, "Box without content" ); + + m_nTableNode = pSttNd->StartOfSectionIndex(); +} + +void SwUndoTableHeadline::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwTableNode* pTNd = rDoc.GetNodes()[ m_nTableNode ]->GetTableNode(); + OSL_ENSURE( pTNd, "could not find any TableNode" ); + + rDoc.SetRowsToRepeat( pTNd->GetTable(), m_nOldHeadline ); +} + +void SwUndoTableHeadline::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + SwTableNode* pTNd = rDoc.GetNodes()[ m_nTableNode ]->GetTableNode(); + OSL_ENSURE( pTNd, "could not find any TableNode" ); + + rDoc.SetRowsToRepeat( pTNd->GetTable(), m_nNewHeadline ); +} + +void SwUndoTableHeadline::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwTableNode *const pTableNd = + rContext.GetRepeatPaM().GetPointNode().FindTableNode(); + if( pTableNd ) + { + rContext.GetDoc().SetRowsToRepeat( pTableNd->GetTable(), m_nNewHeadline ); + } +} + +SaveTable::SaveTable( const SwTable& rTable, sal_uInt16 nLnCnt, bool bSaveFormula ) + : m_aTableSet(*rTable.GetFrameFormat()->GetAttrSet().GetPool(), aTableSetRange), + m_pSwTable(&rTable), m_nLineCount(nLnCnt), m_bSaveFormula(bSaveFormula) +{ + m_bModifyBox = false; + m_bNewModel = rTable.IsNewModel(); + m_aTableSet.Put(rTable.GetFrameFormat()->GetAttrSet()); + m_pLine.reset( new SaveLine( nullptr, *rTable.GetTabLines()[ 0 ], *this ) ); + + SaveLine* pLn = m_pLine.get(); + if( USHRT_MAX == nLnCnt ) + nLnCnt = rTable.GetTabLines().size(); + for( sal_uInt16 n = 1; n < nLnCnt; ++n ) + pLn = new SaveLine( pLn, *rTable.GetTabLines()[ n ], *this ); + + m_aFrameFormats.clear(); + m_pSwTable = nullptr; +} + +sal_uInt16 SaveTable::AddFormat( SwFrameFormat* pFormat, bool bIsLine ) +{ + size_t nRet = m_aFrameFormats.GetPos(pFormat); + if( SIZE_MAX == nRet ) + { + // Create copy of ItemSet + auto pSet = std::make_shared( *pFormat->GetAttrSet().GetPool(), + bIsLine ? aTableLineSetRange : aTableBoxSetRange ); + pSet->Put( pFormat->GetAttrSet() ); + // When a formula is set, never save the value. It possibly must be + // recalculated. + // Save formulas always in plain text. + if( const SwTableBoxFormula* pItem = pSet->GetItemIfSet( RES_BOXATR_FORMULA )) + { + pSet->ClearItem( RES_BOXATR_VALUE ); + if (m_pSwTable && m_bSaveFormula) + { + const_cast(m_pSwTable)->SwitchFormulasToExternalRepresentation(); + SwTableBoxFormula* pFormulaItem = const_cast(pItem); + pFormulaItem->ChgDefinedIn(pFormat); + pFormulaItem->ToRelBoxNm(m_pSwTable); + pFormulaItem->ChgDefinedIn(nullptr); + } + } + nRet = m_aSets.size(); + m_aSets.push_back(pSet); + m_aFrameFormats.insert(m_aFrameFormats.begin() + nRet, pFormat); + } + return o3tl::narrowing(nRet); +} + +void SaveTable::RestoreAttr( SwTable& rTable, bool bMdfyBox ) +{ + m_bModifyBox = bMdfyBox; + + FndBox_ aTmpBox( nullptr, nullptr ); + bool bHideChanges = rTable.GetFrameFormat()->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout()->IsHideRedlines(); + // TODO delete/make frames only at changing line attribute TextChangesOnly (RES_PRINT) to true again + if ( bHideChanges ) + aTmpBox.DelFrames( rTable ); + + // first, get back attributes of TableFrameFormat + SwFrameFormat* pFormat = rTable.GetFrameFormat(); + SfxItemSet& rFormatSet = const_cast(static_cast(pFormat->GetAttrSet())); + rFormatSet.ClearItem(); + rFormatSet.Put(m_aTableSet); + + pFormat->InvalidateInSwCache(RES_ATTRSET_CHG); + + // table without table frame + bool bHiddenTable = true; + + // for safety, invalidate all TableFrames + SwIterator aIter( *pFormat ); + for( SwTabFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { + if( pLast->GetTable() == &rTable ) + { + pLast->InvalidateAll(); + pLast->SetCompletePaint(); + bHiddenTable = false; + } + } + + // fill FrameFormats with defaults (0) + pFormat = nullptr; + for (size_t n = m_aSets.size(); n; --n) + m_aFrameFormats.push_back(pFormat); + + const size_t nLnCnt = (USHRT_MAX == m_nLineCount) + ? rTable.GetTabLines().size() + : m_nLineCount; + + SaveLine* pLn = m_pLine.get(); + for (size_t n = 0; n < nLnCnt; ++n, pLn = pLn->m_pNext) + { + if( !pLn ) + { + OSL_ENSURE( false, "Number of lines changed" ); + break; + } + + pLn->RestoreAttr( *rTable.GetTabLines()[ n ], *this ); + } + + m_aFrameFormats.clear(); + m_bModifyBox = false; + + if ( bHideChanges ) + { + if ( bHiddenTable ) + { + SwTableNode* pTableNode = rTable.GetTableNode(); + pTableNode->DelFrames(); + pTableNode->MakeOwnFrames(); + } + else + { + aTmpBox.MakeFrames( rTable ); + } + } +} + +void SaveTable::SaveContentAttrs( SwDoc* pDoc ) +{ + m_pLine->SaveContentAttrs(pDoc); +} + +void SaveTable::CreateNew( SwTable& rTable, bool bCreateFrames, + bool bRestoreChart ) +{ + FndBox_ aTmpBox( nullptr, nullptr ); + aTmpBox.DelFrames( rTable ); + + // first, get back attributes of TableFrameFormat + SwFrameFormat* pFormat = rTable.GetFrameFormat(); + SfxItemSet& rFormatSet = const_cast(static_cast(pFormat->GetAttrSet())); + rFormatSet.ClearItem(); + rFormatSet.Put(m_aTableSet); + + pFormat->InvalidateInSwCache(RES_ATTRSET_CHG); + + // SwTableBox must have a format - the SwTableBox takes ownership of it + SwTableBoxFormat *const pNewFormat(pFormat->GetDoc()->MakeTableBoxFormat()); + SwTableBox aParent(pNewFormat, rTable.GetTabLines().size(), nullptr); + + // fill FrameFormats with defaults (0) + pFormat = nullptr; + for( size_t n = m_aSets.size(); n; --n ) + m_aFrameFormats.push_back(pFormat); + + m_pLine->CreateNew(rTable, aParent, *this); + m_aFrameFormats.clear(); + + // add new lines, delete old ones + const size_t nOldLines = (USHRT_MAX == m_nLineCount) + ? rTable.GetTabLines().size() + : m_nLineCount; + + SwDoc *pDoc = rTable.GetFrameFormat()->GetDoc(); + SwChartDataProvider *pPCD = pDoc->getIDocumentChartDataProviderAccess().GetChartDataProvider(); + size_t n = 0; + for( ; n < aParent.GetTabLines().size(); ++n ) + { + SwTableLine* pLn = aParent.GetTabLines()[ n ]; + pLn->SetUpper( nullptr ); + if( n < nOldLines ) + { + SwTableLine* pOld = rTable.GetTabLines()[ n ]; + + // TL_CHART2: notify chart about boxes to be removed + const SwTableBoxes &rBoxes = pOld->GetTabBoxes(); + const size_t nBoxes = rBoxes.size(); + for (size_t k = 0; k < nBoxes; ++k) + { + SwTableBox *pBox = rBoxes[k]; + if (pPCD) + pPCD->DeleteBox( &rTable, *pBox ); + } + + rTable.GetTabLines()[n] = pLn; + delete pOld; + } + else + rTable.GetTabLines().insert( rTable.GetTabLines().begin() + n, pLn ); + } + + if( n < nOldLines ) + { + // remove remaining lines... + for (size_t k1 = 0; k1 < nOldLines - n; ++k1) + { + const SwTableBoxes &rBoxes = rTable.GetTabLines()[n + k1]->GetTabBoxes(); + const size_t nBoxes = rBoxes.size(); + for (size_t k2 = 0; k2 < nBoxes; ++k2) + { + SwTableBox *pBox = rBoxes[k2]; + // TL_CHART2: notify chart about boxes to be removed + if (pPCD) + pPCD->DeleteBox( &rTable, *pBox ); + } + } + + for( SwTableLines::const_iterator it = rTable.GetTabLines().begin() + n; + it != rTable.GetTabLines().begin() + nOldLines; ++it ) + delete *it; + rTable.GetTabLines().erase( rTable.GetTabLines().begin() + n, rTable.GetTabLines().begin() + nOldLines ); + } + + aParent.GetTabLines().erase( aParent.GetTabLines().begin(), aParent.GetTabLines().begin() + n ); + assert(aParent.GetTabLines().empty()); + + if( bCreateFrames ) + aTmpBox.MakeFrames( rTable ); + if( bRestoreChart ) + { + // TL_CHART2: need to inform chart of probably changed cell names + pDoc->UpdateCharts( rTable.GetFrameFormat()->GetName() ); + } +} + +SwFrameFormat& SaveTable::CreateNewFormat(SwFrameFormat& rFormat, sal_uInt16 nFormatPos) +{ + rFormat.SetFormatAttr(*m_aSets[nFormatPos]); + m_aFrameFormats[nFormatPos] = &rFormat; + return rFormat; +} + +void SaveTable::NewFrameFormatForLine(const SwTableLine& rTableLn, sal_uInt16 nFormatPos, SwFrameFormat* pOldFormat) +{ + SwFrameFormat* pFormat = m_aFrameFormats[nFormatPos]; + if(!pFormat) + pFormat = &CreateNewFormat(*pOldFormat->GetDoc()->MakeTableLineFormat(), nFormatPos); + pOldFormat->CallSwClientNotify(sw::MoveTableLineHint(*pFormat, rTableLn)); + pFormat->Add(const_cast(&rTableLn)); + KillEmptyFrameFormat(*pOldFormat); +} + +void SaveTable::NewFrameFormatForBox(const SwTableBox& rTableBx, sal_uInt16 nFormatPos, SwFrameFormat* pOldFormat) +{ + SwFrameFormat* pFormat = m_aFrameFormats[nFormatPos]; + if(!pFormat) + pFormat = &CreateNewFormat(*pOldFormat->GetDoc()->MakeTableBoxFormat(), nFormatPos); + pOldFormat->CallSwClientNotify(sw::MoveTableBoxHint(*pFormat, rTableBx)); + pFormat->MoveTableBox(*const_cast(&rTableBx), m_bModifyBox ? pOldFormat : nullptr); + KillEmptyFrameFormat(*pOldFormat); +} + +SaveLine::SaveLine(SaveLine* pPrev, const SwTableLine& rLine, SaveTable& rSTable) + : m_pNext(nullptr) +{ + if( pPrev ) + pPrev->m_pNext = this; + + m_nItemSet = rSTable.AddFormat(rLine.GetFrameFormat(), true); + + m_pBox = new SaveBox(nullptr, *rLine.GetTabBoxes()[0], rSTable); + SaveBox* pBx = m_pBox; + for( size_t n = 1; n < rLine.GetTabBoxes().size(); ++n ) + pBx = new SaveBox( pBx, *rLine.GetTabBoxes()[ n ], rSTable ); +} + +SaveLine::~SaveLine() +{ + delete m_pBox; + delete m_pNext; +} + +void SaveLine::RestoreAttr( SwTableLine& rLine, SaveTable& rSTable ) +{ + rSTable.NewFrameFormatForLine(rLine, m_nItemSet, rLine.GetFrameFormat()); + + SaveBox* pBx = m_pBox; + for (size_t n = 0; n < rLine.GetTabBoxes().size(); ++n, pBx = pBx->m_pNext) + { + if( !pBx ) + { + OSL_ENSURE( false, "Number of boxes changed" ); + break; + } + pBx->RestoreAttr( *rLine.GetTabBoxes()[ n ], rSTable ); + } +} + +void SaveLine::SaveContentAttrs( SwDoc* pDoc ) +{ + m_pBox->SaveContentAttrs(pDoc); + if (m_pNext) + m_pNext->SaveContentAttrs(pDoc); +} + +void SaveLine::CreateNew( SwTable& rTable, SwTableBox& rParent, SaveTable& rSTable ) +{ + SwTableLineFormat* pFormat + = static_cast(rSTable.m_aFrameFormats[m_nItemSet]); + if( !pFormat ) + { + SwDoc* pDoc = rTable.GetFrameFormat()->GetDoc(); + pFormat = pDoc->MakeTableLineFormat(); + pFormat->SetFormatAttr(*rSTable.m_aSets[m_nItemSet]); + rSTable.m_aFrameFormats[m_nItemSet] = pFormat; + } + SwTableLine* pNew = new SwTableLine( pFormat, 1, &rParent ); + + rParent.GetTabLines().push_back( pNew ); + + m_pBox->CreateNew(rTable, *pNew, rSTable); + + if (m_pNext) + m_pNext->CreateNew(rTable, rParent, rSTable); +} + +SaveBox::SaveBox(SaveBox* pPrev, const SwTableBox& rBox, SaveTable& rSTable) + : m_pNext(nullptr) + , m_nStartNode(NODE_OFFSET_MAX) + , m_nRowSpan(0) +{ + m_Ptrs.pLine = nullptr; + + if( pPrev ) + pPrev->m_pNext = this; + + m_nItemSet = rSTable.AddFormat(rBox.GetFrameFormat(), false); + + if( rBox.GetSttNd() ) + { + m_nStartNode = rBox.GetSttIdx(); + m_nRowSpan = rBox.getRowSpan(); + } + else + { + m_Ptrs.pLine = new SaveLine(nullptr, *rBox.GetTabLines()[0], rSTable); + + SaveLine* pLn = m_Ptrs.pLine; + for( size_t n = 1; n < rBox.GetTabLines().size(); ++n ) + pLn = new SaveLine( pLn, *rBox.GetTabLines()[ n ], rSTable ); + } +} + +SaveBox::~SaveBox() +{ + if (NODE_OFFSET_MAX == m_nStartNode) // no EndBox + delete m_Ptrs.pLine; + else + delete m_Ptrs.pContentAttrs; + delete m_pNext; +} + +void SaveBox::RestoreAttr( SwTableBox& rBox, SaveTable& rSTable ) +{ + rSTable.NewFrameFormatForBox(rBox, m_nItemSet, rBox.GetFrameFormat()); + + if (NODE_OFFSET_MAX == m_nStartNode) // no EndBox + { + if( rBox.GetTabLines().empty() ) + { + OSL_ENSURE( false, "Number of lines changed" ); + } + else + { + SaveLine* pLn = m_Ptrs.pLine; + for (size_t n = 0; n < rBox.GetTabLines().size(); ++n, pLn = pLn->m_pNext) + { + if( !pLn ) + { + OSL_ENSURE( false, "Number of lines changed" ); + break; + } + + pLn->RestoreAttr( *rBox.GetTabLines()[ n ], rSTable ); + } + } + } + else if (rBox.GetSttNd() && rBox.GetSttIdx() == m_nStartNode) + { + if (m_Ptrs.pContentAttrs) + { + SwNodes& rNds = rBox.GetFrameFormat()->GetDoc()->GetNodes(); + sal_uInt16 nSet = 0; + SwNodeOffset nEnd = rBox.GetSttNd()->EndOfSectionIndex(); + for (SwNodeOffset n = m_nStartNode + 1; n < nEnd; ++n) + { + SwContentNode* pCNd = rNds[ n ]->GetContentNode(); + if( pCNd ) + { + std::shared_ptr pSet((*m_Ptrs.pContentAttrs)[nSet++]); + if( pSet ) + { + for( const WhichPair& rPair : aSave_BoxContentSet ) + pCNd->ResetAttr( rPair.first, rPair.second ); + pCNd->SetAttr( *pSet ); + } + else + pCNd->ResetAllAttr(); + } + } + } + } + else + { + OSL_ENSURE( false, "Box not anymore at the same node" ); + } +} + +void SaveBox::SaveContentAttrs( SwDoc* pDoc ) +{ + if (NODE_OFFSET_MAX == m_nStartNode) // no EndBox + { + // continue in current line + m_Ptrs.pLine->SaveContentAttrs(pDoc); + } + else + { + SwNodeOffset nEnd = pDoc->GetNodes()[m_nStartNode]->EndOfSectionIndex(); + m_Ptrs.pContentAttrs = new SfxItemSets; + for (SwNodeOffset n = m_nStartNode + 1; n < nEnd; ++n) + { + SwContentNode* pCNd = pDoc->GetNodes()[ n ]->GetContentNode(); + if( pCNd ) + { + std::shared_ptr pSet; + if( pCNd->HasSwAttrSet() ) + { + pSet = std::make_shared( pDoc->GetAttrPool(), + aSave_BoxContentSet ); + pSet->Put( *pCNd->GetpSwAttrSet() ); + } + + m_Ptrs.pContentAttrs->push_back(pSet); + } + } + } + if (m_pNext) + m_pNext->SaveContentAttrs(pDoc); +} + +void SaveBox::CreateNew( SwTable& rTable, SwTableLine& rParent, SaveTable& rSTable ) +{ + SwTableBoxFormat* pFormat = static_cast(rSTable.m_aFrameFormats[m_nItemSet]); + if( !pFormat ) + { + SwDoc* pDoc = rTable.GetFrameFormat()->GetDoc(); + pFormat = pDoc->MakeTableBoxFormat(); + pFormat->SetFormatAttr(*rSTable.m_aSets[m_nItemSet]); + rSTable.m_aFrameFormats[m_nItemSet] = pFormat; + } + + if (NODE_OFFSET_MAX == m_nStartNode) // no EndBox + { + SwTableBox* pNew = new SwTableBox( pFormat, 1, &rParent ); + rParent.GetTabBoxes().push_back( pNew ); + + m_Ptrs.pLine->CreateNew(rTable, *pNew, rSTable); + } + else + { + // search box for StartNode in old table + SwTableBox* pBox = rTable.GetTableBox(m_nStartNode); + if (pBox) + { + SwFrameFormat* pOld = pBox->GetFrameFormat(); + pBox->RegisterToFormat( *pFormat ); + if( !pOld->HasWriterListeners() ) + delete pOld; + + pBox->setRowSpan(m_nRowSpan); + + SwTableBoxes* pTBoxes = &pBox->GetUpper()->GetTabBoxes(); + pTBoxes->erase( std::find( pTBoxes->begin(), pTBoxes->end(), pBox ) ); + + pBox->SetUpper( &rParent ); + pTBoxes = &rParent.GetTabBoxes(); + pTBoxes->push_back( pBox ); + } + } + + if (m_pNext) + m_pNext->CreateNew(rTable, rParent, rSTable); +} + +// UndoObject for attribute changes on table +SwUndoAttrTable::SwUndoAttrTable( const SwTableNode& rTableNd, bool bClearTabCols ) + : SwUndo( SwUndoId::TABLE_ATTR, &rTableNd.GetDoc() ), + m_nStartNode( rTableNd.GetIndex() ) +{ + m_bClearTableCol = bClearTabCols; + m_pSaveTable.reset( new SaveTable( rTableNd.GetTable() ) ); +} + +SwUndoAttrTable::~SwUndoAttrTable() +{ +} + +void SwUndoAttrTable::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwTableNode* pTableNd = rDoc.GetNodes()[ m_nStartNode ]->GetTableNode(); + OSL_ENSURE( pTableNd, "no TableNode" ); + + if (pTableNd) + { + SaveTable* pOrig = new SaveTable( pTableNd->GetTable() ); + m_pSaveTable->RestoreAttr( pTableNd->GetTable() ); + m_pSaveTable.reset( pOrig ); + } + + if( m_bClearTableCol ) + { + ClearFEShellTabCols(rDoc, nullptr); + } +} + +void SwUndoAttrTable::RedoImpl(::sw::UndoRedoContext & rContext) +{ + UndoImpl(rContext); +} + +// UndoObject for AutoFormat on Table +SwUndoTableAutoFormat::SwUndoTableAutoFormat( const SwTableNode& rTableNd, + const SwTableAutoFormat& rAFormat ) + : SwUndo( SwUndoId::TABLE_AUTOFMT, &rTableNd.GetDoc() ) + , m_TableStyleName(rTableNd.GetTable().GetTableStyleName()) + , m_nStartNode( rTableNd.GetIndex() ) + , m_bSaveContentAttr( false ) + , m_nRepeatHeading(rTableNd.GetTable().GetRowsToRepeat()) +{ + m_pSaveTable.reset( new SaveTable( rTableNd.GetTable() ) ); + + if( rAFormat.IsFont() || rAFormat.IsJustify() ) + { + // then also go over the ContentNodes of the EndBoxes and collect + // all paragraph attributes + m_pSaveTable->SaveContentAttrs( &const_cast(rTableNd.GetDoc()) ); + m_bSaveContentAttr = true; + } +} + +SwUndoTableAutoFormat::~SwUndoTableAutoFormat() +{ +} + +void SwUndoTableAutoFormat::SaveBoxContent( const SwTableBox& rBox ) +{ + m_Undos.push_back(std::make_shared(rBox)); +} + +void +SwUndoTableAutoFormat::UndoRedo(bool const bUndo, ::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwTableNode* pTableNd = rDoc.GetNodes()[ m_nStartNode ]->GetTableNode(); + OSL_ENSURE( pTableNd, "no TableNode" ); + + SwTable& table = pTableNd->GetTable(); + if (table.GetTableStyleName() != m_TableStyleName) + { + OUString const temp(table.GetTableStyleName()); + table.SetTableStyleName(m_TableStyleName); + m_TableStyleName = temp; + } + SaveTable* pOrig = new SaveTable( table ); + // then go also over the ContentNodes of the EndBoxes and collect + // all paragraph attributes + if( m_bSaveContentAttr ) + pOrig->SaveContentAttrs( &rDoc ); + + if (bUndo) + { + for (size_t n = m_Undos.size(); 0 < n; --n) + { + m_Undos.at(n-1)->UndoImpl(rContext); + } + + table.SetRowsToRepeat(m_nRepeatHeading); + } + + m_pSaveTable->RestoreAttr( pTableNd->GetTable(), !bUndo ); + m_pSaveTable.reset( pOrig ); +} + +void SwUndoTableAutoFormat::UndoImpl(::sw::UndoRedoContext & rContext) +{ + UndoRedo(true, rContext); +} + +void SwUndoTableAutoFormat::RedoImpl(::sw::UndoRedoContext & rContext) +{ + UndoRedo(false, rContext); +} + +SwUndoTableNdsChg::SwUndoTableNdsChg( SwUndoId nAction, + const SwSelBoxes& rBoxes, + const SwTableNode& rTableNd, + tools::Long nMn, tools::Long nMx, + sal_uInt16 nCnt, bool bFlg, bool bSmHght ) + : SwUndo( nAction, &rTableNd.GetDoc() ), + m_nMin( nMn ), m_nMax( nMx ), + m_nSttNode( rTableNd.GetIndex() ), + m_nCount( nCnt ), + m_bFlag( bFlg ), + m_bSameHeight( bSmHght ) +{ + const SwTable& rTable = rTableNd.GetTable(); + m_pSaveTable.reset( new SaveTable( rTable ) ); + + // and remember selection + ReNewBoxes( rBoxes ); +} + +void SwUndoTableNdsChg::ReNewBoxes( const SwSelBoxes& rBoxes ) +{ + if (rBoxes.size() != m_Boxes.size()) + { + m_Boxes.clear(); + for (size_t n = 0; n < rBoxes.size(); ++n) + { + m_Boxes.insert( rBoxes[n]->GetSttIdx() ); + } + } +} + +SwUndoTableNdsChg::~SwUndoTableNdsChg() +{ +} + +void SwUndoTableNdsChg::SaveNewBoxes( const SwTableNode& rTableNd, + const SwTableSortBoxes& rOld ) +{ + const SwTable& rTable = rTableNd.GetTable(); + const SwTableSortBoxes& rTableBoxes = rTable.GetTabSortBoxes(); + + OSL_ENSURE( ! IsDelBox(), "wrong Action" ); + m_xNewSttNds.emplace(); + + size_t i = 0; + for (size_t n = 0; n < rOld.size(); ++i) + { + if( rOld[ n ] == rTableBoxes[ i ] ) + ++n; + else + // new box: insert sorted + m_xNewSttNds->insert( BoxMove(rTableBoxes[ i ]->GetSttIdx()) ); + } + + for( ; i < rTableBoxes.size(); ++i ) + // new box: insert sorted + m_xNewSttNds->insert( BoxMove(rTableBoxes[ i ]->GetSttIdx()) ); +} + +static SwTableLine* lcl_FindTableLine( const SwTable& rTable, + const SwTableBox& rBox ) +{ + SwTableLine* pRet = nullptr; + // i63949: For nested cells we have to take nLineNo - 1, too, not 0! + const SwTableLines &rTableLines = ( rBox.GetUpper()->GetUpper() != nullptr ) ? + rBox.GetUpper()->GetUpper()->GetTabLines() + : rTable.GetTabLines(); + const SwTableLine* pLine = rBox.GetUpper(); + sal_uInt16 nLineNo = rTableLines.GetPos( pLine ); + pRet = rTableLines[nLineNo - 1]; + + return pRet; +} + +static const SwTableLines& lcl_FindParentLines( const SwTable& rTable, + const SwTableBox& rBox ) +{ + const SwTableLines& rRet = + ( rBox.GetUpper()->GetUpper() != nullptr ) ? + rBox.GetUpper()->GetUpper()->GetTabLines() : + rTable.GetTabLines(); + + return rRet; +} + +void SwUndoTableNdsChg::SaveNewBoxes( const SwTableNode& rTableNd, + const SwTableSortBoxes& rOld, + const SwSelBoxes& rBoxes, + const std::vector &rNodeCnts ) +{ + const SwTable& rTable = rTableNd.GetTable(); + const SwTableSortBoxes& rTableBoxes = rTable.GetTabSortBoxes(); + + OSL_ENSURE( ! IsDelBox(), "wrong Action" ); + m_xNewSttNds.emplace(); + + OSL_ENSURE( rTable.IsNewModel() || rOld.size() + m_nCount * rBoxes.size() == rTableBoxes.size(), + "unexpected boxes" ); + OSL_ENSURE( rOld.size() <= rTableBoxes.size(), "more unexpected boxes" ); + for (size_t n = 0, i = 0; i < rTableBoxes.size(); ++i) + { + if( ( n < rOld.size() ) && + ( rOld[ n ] == rTableBoxes[ i ] ) ) + { + // box already known? Then nothing to be done. + ++n; + } + else + { + // new box found: insert (obey sort order) + const SwTableBox* pBox = rTableBoxes[ i ]; + + // find the source box. It must be one in rBoxes. + // We found the right one if it's in the same column as pBox. + // No, if more than one selected cell in the same column has been split, + // we have to look for the nearest one (i65201)! + const SwTableBox* pSourceBox = nullptr; + const SwTableBox* pCheckBox = nullptr; + const SwTableLine* pBoxLine = pBox->GetUpper(); + sal_uInt16 nLineDiff = lcl_FindParentLines(rTable,*pBox).GetPos(pBoxLine); + sal_uInt16 nLineNo = 0; + for (size_t j = 0; j < rBoxes.size(); ++j) + { + pCheckBox = rBoxes[j]; + if( pCheckBox->GetUpper()->GetUpper() == pBox->GetUpper()->GetUpper() ) + { + const SwTableLine* pCheckLine = pCheckBox->GetUpper(); + sal_uInt16 nCheckLine = lcl_FindParentLines( rTable, *pCheckBox ). + GetPos( pCheckLine ); + if( ( !pSourceBox || nCheckLine > nLineNo ) && nCheckLine < nLineDiff ) + { + nLineNo = nCheckLine; + pSourceBox = pCheckBox; + } + } + } + + // find the line number difference + // (to help determine bNodesMoved flag below) + nLineDiff = nLineDiff - nLineNo; + OSL_ENSURE( pSourceBox, "Split source box not found!" ); + // find out how many nodes the source box used to have + // (to help determine bNodesMoved flag below) + size_t nNdsPos = 0; + while( rBoxes[ nNdsPos ] != pSourceBox ) + ++nNdsPos; + SwNodeOffset nNodes = rNodeCnts[ nNdsPos ]; + + // When a new table cell is created, it either gets a new + // node, or it gets node(s) from elsewhere. The undo must + // know, of course, and thus we must determine here just + // where pBox's nodes are from: + // If 1) the source box has lost nodes, and + // 2) we're in the node range that got nodes + // then pBox received nodes from elsewhere. + // If bNodesMoved is set for pBox the undo must move the + // boxes back, otherwise it must delete them. + bool bNodesMoved = pSourceBox && + ( nNodes != ( pSourceBox->GetSttNd()->EndOfSectionIndex() - + pSourceBox->GetSttIdx() ) ) + && ( nNodes - 1 > SwNodeOffset(nLineDiff) ); + m_xNewSttNds->insert( BoxMove(pBox->GetSttIdx(), bNodesMoved) ); + } + } +} + +void SwUndoTableNdsChg::SaveSection( SwStartNode* pSttNd ) +{ + OSL_ENSURE( IsDelBox(), "wrong Action" ); + if (m_pDelSects == nullptr) + m_pDelSects.reset(new SwUndoSaveSections); + + SwTableNode* pTableNd = pSttNd->FindTableNode(); + std::unique_ptr> pSave(new SwUndoSaveSection); + pSave->SaveSection( SwNodeIndex( *pSttNd )); + + m_pDelSects->push_back(std::move(pSave)); + m_nSttNode = pTableNd->GetIndex(); +} + +void SwUndoTableNdsChg::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwNodeIndex aIdx( rDoc.GetNodes(), m_nSttNode ); + + SwTableNode *const pTableNd = aIdx.GetNode().GetTableNode(); + OSL_ENSURE( pTableNd, "no TableNode" ); + pTableNd->GetTable().SwitchFormulasToInternalRepresentation(); + + CHECK_TABLE( pTableNd->GetTable() ) + + FndBox_ aTmpBox( nullptr, nullptr ); + // ? TL_CHART2: notification or locking of controller required ? + + SwChartDataProvider *pPCD = rDoc.getIDocumentChartDataProviderAccess().GetChartDataProvider(); + SwSelBoxes aDelBoxes; + std::vector< std::pair > aDelNodes; + if( IsDelBox() ) + { + // Trick: add missing boxes in any line, they will be connected + // correctly when calling CreateNew + SwTableBox* pCpyBox = pTableNd->GetTable().GetTabSortBoxes()[0]; + SwTableBoxes& rLnBoxes = pCpyBox->GetUpper()->GetTabBoxes(); + + // restore sections + for (size_t n = m_pDelSects->size(); n; ) + { + SwUndoSaveSection *const pSave = (*m_pDelSects)[ --n ].get(); + pSave->RestoreSection( &rDoc, &aIdx, SwTableBoxStartNode ); + if( pSave->GetHistory() ) + pSave->GetHistory()->Rollback( &rDoc ); + SwTableBox* pBox = new SwTableBox( static_cast(pCpyBox->GetFrameFormat()), aIdx, + pCpyBox->GetUpper() ); + rLnBoxes.push_back( pBox ); + } + m_pDelSects->clear(); + } + else if( !m_xNewSttNds->empty() ) + { + // Then the nodes have be moved and not deleted! + // But for that we need a temp array. + std::vector aTmp( m_xNewSttNds->begin(), m_xNewSttNds->end() ); + + // backwards + for (size_t n = aTmp.size(); n > 0 ; ) + { + --n; + // delete box from table structure + SwNodeOffset nIdx = aTmp[n].index; + SwTableBox* pBox = pTableNd->GetTable().GetTableBox( nIdx ); + OSL_ENSURE( pBox, "Where is my TableBox?" ); + + // TL_CHART2: notify chart about box to be removed + if (pPCD) + pPCD->DeleteBox( &pTableNd->GetTable(), *pBox ); + + // insert _before_ deleting the section - otherwise the box + // has no start node so all boxes sort equal in SwSelBoxes + aDelBoxes.insert(pBox); + + if( aTmp[n].hasMoved ) + { + SwNodeRange aRg( *pBox->GetSttNd(), SwNodeOffset(1), + *pBox->GetSttNd()->EndOfSectionNode() ); + + SwTableLine* pLine = lcl_FindTableLine( pTableNd->GetTable(), *pBox ); + SwNodeIndex aInsPos( *(pLine->GetTabBoxes()[0]->GetSttNd()), 2 ); + + // adjust all StartNode indices + size_t i = n; + SwNodeOffset nSttIdx = aInsPos.GetIndex() - 2, + nNdCnt = aRg.aEnd.GetIndex() - aRg.aStart.GetIndex(); + while( i && aTmp[ --i ].index > nSttIdx ) + aTmp[ i ].index += nNdCnt; + + // first delete box + delete pBox; + // than move nodes + rDoc.GetNodes().MoveNodes( aRg, rDoc.GetNodes(), aInsPos.GetNode(), false ); + } + else + { + aDelNodes.emplace_back(pBox, nIdx); + } + } + } + else + { + // Remove nodes from nodes array (backwards!) + std::set::reverse_iterator it; + for( it = m_xNewSttNds->rbegin(); it != m_xNewSttNds->rend(); ++it ) + { + SwNodeOffset nIdx = (*it).index; + SwTableBox* pBox = pTableNd->GetTable().GetTableBox( nIdx ); + OSL_ENSURE( pBox, "Where's my table box?" ); + // TL_CHART2: notify chart about box to be removed + if (pPCD) + pPCD->DeleteBox( &pTableNd->GetTable(), *pBox ); + aDelBoxes.insert(pBox); + aDelNodes.emplace_back(pBox, nIdx); + } + } + + // fdo#57197: before deleting the SwTableBoxes, delete the SwTabFrames + aTmpBox.SetTableLines(aDelBoxes, pTableNd->GetTable()); + aTmpBox.DelFrames(pTableNd->GetTable()); + + // do this _after_ deleting Frames because disposing SwAccessible requires + // connection to the nodes, see SwAccessibleChild::IsAccessible() + for (const std::pair & rDelNode : aDelNodes) + { + // first disconnect box from node, otherwise ~SwTableBox would + // access pBox->pSttNd, deleted by DeleteSection + rDelNode.first->RemoveFromTable(); + rDoc.getIDocumentContentOperations().DeleteSection(rDoc.GetNodes()[ rDelNode.second ]); + } + + // Remove boxes from table structure + for( size_t n = 0; n < aDelBoxes.size(); ++n ) + { + SwTableBox* pCurrBox = aDelBoxes[n]; + SwTableBoxes* pTBoxes = &pCurrBox->GetUpper()->GetTabBoxes(); + pTBoxes->erase( std::find( pTBoxes->begin(), pTBoxes->end(), pCurrBox ) ); + delete pCurrBox; + } + + m_pSaveTable->CreateNew( pTableNd->GetTable(), true, false ); + + // TL_CHART2: need to inform chart of probably changed cell names + rDoc.UpdateCharts( pTableNd->GetTable().GetFrameFormat()->GetName() ); + if (SwFEShell* pFEShell = rDoc.GetDocShell()->GetFEShell()) + pFEShell->UpdateTableStyleFormatting(pTableNd); + if( IsDelBox() ) + m_nSttNode = pTableNd->GetIndex(); + ClearFEShellTabCols(rDoc, nullptr); + CHECK_TABLE( pTableNd->GetTable() ) +} + +void SwUndoTableNdsChg::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + + SwTableNode* pTableNd = rDoc.GetNodes()[ m_nSttNode ]->GetTableNode(); + OSL_ENSURE( pTableNd, "no TableNode" ); + CHECK_TABLE( pTableNd->GetTable() ) + + SwSelBoxes aSelBoxes; + for (const auto& rBox : m_Boxes) + { + SwTableBox* pBox = pTableNd->GetTable().GetTableBox( rBox ); + aSelBoxes.insert( pBox ); + } + + // create SelBoxes and call InsertCell/-Row/SplitTable + switch( GetId() ) + { + case SwUndoId::TABLE_INSCOL: + rDoc.InsertCol( aSelBoxes, m_nCount, m_bFlag ); + break; + + case SwUndoId::TABLE_INSROW: + rDoc.InsertRow( aSelBoxes, m_nCount, m_bFlag ); + break; + + case SwUndoId::TABLE_SPLIT: + rDoc.SplitTable( aSelBoxes, m_bFlag, m_nCount, m_bSameHeight ); + break; + case SwUndoId::TABLE_DELBOX: + case SwUndoId::ROW_DELETE: + case SwUndoId::COL_DELETE: + { + SwTable &rTable = pTableNd->GetTable(); + rTable.SwitchFormulasToInternalRepresentation(); + if( m_nMax > m_nMin && rTable.IsNewModel() ) + rTable.PrepareDeleteCol( m_nMin, m_nMax ); + rTable.DeleteSel( &rDoc, aSelBoxes, nullptr, this, true, true ); + m_nSttNode = pTableNd->GetIndex(); + } + break; + default: + ; + } + ClearFEShellTabCols(rDoc, nullptr); + CHECK_TABLE( pTableNd->GetTable() ) +} + +SwUndoTableMerge::SwUndoTableMerge( const SwPaM& rTableSel ) + : SwUndo( SwUndoId::TABLE_MERGE, &rTableSel.GetDoc() ), SwUndRng( rTableSel ) +{ + const SwTableNode* pTableNd = rTableSel.GetPointNode().FindTableNode(); + OSL_ENSURE( pTableNd, "Where is the TableNode?" ); + m_pSaveTable.reset( new SaveTable( pTableNd->GetTable() ) ); + m_nTableNode = pTableNd->GetIndex(); +} + +SwUndoTableMerge::~SwUndoTableMerge() +{ + m_pSaveTable.reset(); + m_vMoves.clear(); + m_pHistory.reset(); +} + +void SwUndoTableMerge::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwNodeIndex aIdx( rDoc.GetNodes(), m_nTableNode ); + + SwTableNode *const pTableNd = aIdx.GetNode().GetTableNode(); + OSL_ENSURE( pTableNd, "no TableNode" ); + + pTableNd->GetTable().SwitchFormulasToInternalRepresentation(); + + // ? TL_CHART2: notification or locking of controller required ? + + // 1. restore deleted boxes: + // Trick: add missing boxes in any line, they will be connected + // correctly when calling CreateNew + SwTableBox *pBox, *pCpyBox = pTableNd->GetTable().GetTabSortBoxes()[0]; + SwTableBoxes& rLnBoxes = pCpyBox->GetUpper()->GetTabBoxes(); + + CHECKTABLE(pTableNd->GetTable()) + + SwSelBoxes aSelBoxes; + SwTextFormatColl* pColl = rDoc.getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ); + + for (const auto& rBox : m_Boxes) + { + aIdx = rBox; + SwStartNode* pSttNd = rDoc.GetNodes().MakeTextSection( aIdx.GetNode(), + SwTableBoxStartNode, pColl ); + pBox = new SwTableBox( static_cast(pCpyBox->GetFrameFormat()), *pSttNd, + pCpyBox->GetUpper() ); + rLnBoxes.push_back( pBox ); + + aSelBoxes.insert( pBox ); + } + + CHECKTABLE(pTableNd->GetTable()) + + SwChartDataProvider *pPCD = rDoc.getIDocumentChartDataProviderAccess().GetChartDataProvider(); + // 2. deleted the inserted boxes + // delete nodes (from last to first) + for( size_t n = m_aNewStartNodes.size(); n; ) + { + // remove box from table structure + SwNodeOffset nIdx = m_aNewStartNodes[ --n ]; + + if( !nIdx && n ) + { + nIdx = m_aNewStartNodes[ --n ]; + pBox = pTableNd->GetTable().GetTableBox( nIdx ); + OSL_ENSURE( pBox, "Where is my TableBox?" ); + + if( !m_pSaveTable->IsNewModel() ) + rDoc.GetNodes().MakeTextNode( + const_cast(*pBox->GetSttNd()->EndOfSectionNode()), pColl ); + + // this was the separator -> restore moved ones + for (size_t i = m_vMoves.size(); i; ) + { + SwTextNode* pTextNd = nullptr; + sal_Int32 nDelPos = 0; + SwUndoMove *const pUndo = m_vMoves[ --i ].get(); + if( !pUndo->IsMoveRange() ) + { + pTextNd = rDoc.GetNodes()[ pUndo->GetDestSttNode() ]->GetTextNode(); + nDelPos = pUndo->GetDestSttContent() - 1; + } + pUndo->UndoImpl(rContext); + if( pUndo->IsMoveRange() ) + { + // delete the unnecessary node + aIdx = pUndo->GetEndNode(); + SwContentNode *pCNd = aIdx.GetNode().GetContentNode(); + if( pCNd ) + { + SwNodeIndex aTmp( aIdx, -1 ); + SwContentNode *pMove = aTmp.GetNode().GetContentNode(); + if( pMove ) + pCNd->MoveTo( *pMove ); + } + rDoc.GetNodes().Delete( aIdx ); + } + else if( pTextNd ) + { + // also delete not needed attributes + SwContentIndex aTmpIdx( pTextNd, nDelPos ); + if( pTextNd->GetpSwpHints() && pTextNd->GetpSwpHints()->Count() ) + pTextNd->RstTextAttr( nDelPos, pTextNd->GetText().getLength() - nDelPos + 1 ); + // delete separator + pTextNd->EraseText( aTmpIdx, 1 ); + } + } + nIdx = pBox->GetSttIdx(); + } + else + pBox = pTableNd->GetTable().GetTableBox( nIdx ); + + if( !m_pSaveTable->IsNewModel() ) + { + // TL_CHART2: notify chart about box to be removed + if (pPCD) + pPCD->DeleteBox( &pTableNd->GetTable(), *pBox ); + + SwTableBoxes* pTBoxes = &pBox->GetUpper()->GetTabBoxes(); + pTBoxes->erase( std::find(pTBoxes->begin(), pTBoxes->end(), pBox ) ); + + // delete indices from section + { + SwNodeIndex aTmpIdx( *pBox->GetSttNd() ); + SwDoc::CorrAbs( SwNodeIndex( aTmpIdx, 1 ), + SwNodeIndex( *aTmpIdx.GetNode().EndOfSectionNode() ), + SwPosition( aTmpIdx, nullptr, 0 ), true ); + } + + delete pBox; + rDoc.getIDocumentContentOperations().DeleteSection( rDoc.GetNodes()[ nIdx ] ); + } + } + CHECKTABLE(pTableNd->GetTable()) + + m_pSaveTable->CreateNew( pTableNd->GetTable(), true, false ); + + // TL_CHART2: need to inform chart of probably changed cell names + rDoc.UpdateCharts( pTableNd->GetTable().GetFrameFormat()->GetName() ); + + if( m_pHistory ) + { + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + } + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + pPam->DeleteMark(); + pPam->GetPoint()->Assign(m_nSttNode, m_nSttContent ); + pPam->SetMark(); + pPam->DeleteMark(); + + CHECKTABLE(pTableNd->GetTable()) + ClearFEShellTabCols(rDoc, nullptr); +} + +void SwUndoTableMerge::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwPaM & rPam( AddUndoRedoPaM(rContext) ); + rDoc.MergeTable(rPam); +} + +void SwUndoTableMerge::MoveBoxContent( SwDoc& rDoc, SwNodeRange& rRg, SwNode& rPos ) +{ + SwNodeIndex aTmp( rRg.aStart, -1 ), aTmp2( rPos, -1 ); + std::unique_ptr pUndo(new SwUndoMove( rDoc, rRg, rPos )); + ::sw::UndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo()); + rDoc.getIDocumentContentOperations().MoveNodeRange( rRg, rPos, m_pSaveTable->IsNewModel() ? + SwMoveFlags::NO_DELFRMS : + SwMoveFlags::DEFAULT ); + ++aTmp; + ++aTmp2; + pUndo->SetDestRange( aTmp2.GetNode(), rPos, aTmp ); + + m_vMoves.push_back(std::move(pUndo)); +} + +void SwUndoTableMerge::SetSelBoxes( const SwSelBoxes& rBoxes ) +{ + // memorize selection + for (size_t n = 0; n < rBoxes.size(); ++n) + { + m_Boxes.insert(rBoxes[n]->GetSttIdx()); + } + + // as separator for inserts of new boxes after shifting + m_aNewStartNodes.push_back( SwNodeOffset(0) ); + + // The new table model does not delete overlapped cells (by row span), + // so the rBoxes array might be empty even some cells have been merged. + if( !rBoxes.empty() ) + m_nTableNode = rBoxes[ 0 ]->GetSttNd()->FindTableNode()->GetIndex(); +} + +void SwUndoTableMerge::SaveCollection( const SwTableBox& rBox ) +{ + if( !m_pHistory ) + m_pHistory.reset(new SwHistory); + + SwNodeIndex aIdx( *rBox.GetSttNd(), 1 ); + SwContentNode* pCNd = aIdx.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = aIdx.GetNodes().GoNext( &aIdx ); + + m_pHistory->AddColl(pCNd->GetFormatColl(), aIdx.GetIndex(), pCNd->GetNodeType()); + if( pCNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pCNd->GetpSwAttrSet(), aIdx.GetIndex() ); +} + +SwUndoTableNumFormat::SwUndoTableNumFormat( const SwTableBox& rBox, + const SfxItemSet* pNewSet ) + : SwUndo(SwUndoId::TBLNUMFMT, rBox.GetFrameFormat()->GetDoc()) + , m_nFormatIdx(getSwDefaultTextFormat()) + , m_nNewFormatIdx(0) + , m_fNum(0.0) + , m_fNewNum(0.0) + , m_bNewFormat(false) + , m_bNewFormula(false) + , m_bNewValue(false) +{ + m_nNode = rBox.GetSttIdx(); + + m_nNodePos = rBox.IsValidNumTextNd( nullptr == pNewSet ); + SwDoc* pDoc = rBox.GetFrameFormat()->GetDoc(); + + if( NODE_OFFSET_MAX != m_nNodePos ) + { + SwTextNode* pTNd = pDoc->GetNodes()[ m_nNodePos ]->GetTextNode(); + + m_pHistory.reset(new SwHistory); + SwRegHistory aRHst( *rBox.GetSttNd(), m_pHistory.get() ); + // always save all text attributes because of possibly overlapping + // areas of on/off + m_pHistory->CopyAttr( pTNd->GetpSwpHints(), m_nNodePos, 0, + pTNd->GetText().getLength(), true ); + + if( pTNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pTNd->GetpSwAttrSet(), m_nNodePos ); + + m_aStr = pTNd->GetText(); + if( pTNd->GetpSwpHints() ) + pTNd->GetpSwpHints()->DeRegister(); + } + + m_pBoxSet.reset( new SfxItemSet( pDoc->GetAttrPool(), aTableBoxSetRange ) ); + m_pBoxSet->Put( rBox.GetFrameFormat()->GetAttrSet() ); + + if( pNewSet ) + { + if( const SwTableBoxNumFormat* pItem = pNewSet->GetItemIfSet( RES_BOXATR_FORMAT, + false )) + { + m_bNewFormat = true; + m_nNewFormatIdx = pItem->GetValue(); + } + if( const SwTableBoxFormula* pItem = pNewSet->GetItemIfSet( RES_BOXATR_FORMULA, + false )) + { + m_bNewFormula = true; + m_aNewFormula = pItem->GetFormula(); + } + if( const SwTableBoxValue* pItem = pNewSet->GetItemIfSet( RES_BOXATR_VALUE, + false )) + { + m_bNewValue = true; + m_fNewNum = pItem->GetValue(); + } + } + + // is a history needed at all? + if (m_pHistory && !m_pHistory->Count()) + { + m_pHistory.reset(); + } +} + +SwUndoTableNumFormat::~SwUndoTableNumFormat() +{ + m_pHistory.reset(); + m_pBoxSet.reset(); +} + +void SwUndoTableNumFormat::UndoImpl(::sw::UndoRedoContext & rContext) +{ + OSL_ENSURE( m_pBoxSet, "Where's the stored item set?" ); + + SwDoc & rDoc = rContext.GetDoc(); + SwStartNode* pSttNd = rDoc.GetNodes()[ m_nNode ]-> + FindSttNodeByType( SwTableBoxStartNode ); + OSL_ENSURE( pSttNd, "without StartNode no TableBox" ); + SwTableBox* pBox = pSttNd->FindTableNode()->GetTable().GetTableBox( + pSttNd->GetIndex() ); + OSL_ENSURE( pBox, "found no TableBox" ); + + SwTableBoxFormat* pFormat = rDoc.MakeTableBoxFormat(); + pFormat->SetFormatAttr( *m_pBoxSet ); + pBox->ChgFrameFormat( pFormat ); + + if( NODE_OFFSET_MAX == m_nNodePos ) + return; + + SwTextNode* pTextNd = rDoc.GetNodes()[ m_nNodePos ]->GetTextNode(); + // If more than one node was deleted then all "node" attributes were also + // saved + if( pTextNd->HasSwAttrSet() ) + pTextNd->ResetAllAttr(); + + if( pTextNd->GetpSwpHints() && !m_aStr.isEmpty() ) + pTextNd->ClearSwpHintsArr( true ); + + // ChgTextToNum(..) only acts when the strings are different. We need to do + // the same here. + if( pTextNd->GetText() != m_aStr ) + { + rDoc.getIDocumentRedlineAccess().DeleteRedline( *( pBox->GetSttNd() ), false, RedlineType::Any ); + + SwContentIndex aIdx( pTextNd, 0 ); + if( !m_aStr.isEmpty() ) + { + pTextNd->EraseText( aIdx ); + pTextNd->InsertText( m_aStr, aIdx, + SwInsertFlags::NOHINTEXPAND ); + } + } + + if( m_pHistory ) + { + sal_uInt16 nTmpEnd = m_pHistory->GetTmpEnd(); + m_pHistory->TmpRollback( &rDoc, 0 ); + m_pHistory->SetTmpEnd( nTmpEnd ); + } + + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + pPam->DeleteMark(); + pPam->GetPoint()->Assign( m_nNode + 1 ); +} + +namespace { + +/** switch the RedlineFlags on the given document, using + * SetRedlineFlags_intern. This class set the mode in the constructor, + * and changes it back in the destructor, i.e. it uses the + * initialization-is-resource-acquisition idiom. + */ +class RedlineFlagsInternGuard +{ + SwDoc& mrDoc; + RedlineFlags meOldRedlineFlags; + +public: + RedlineFlagsInternGuard( + SwDoc& rDoc, // change mode of this document + RedlineFlags eNewRedlineFlags, // new redline mode + RedlineFlags eRedlineFlagsMask /*change only bits set in this mask*/); + + ~RedlineFlagsInternGuard(); +}; + +} + +RedlineFlagsInternGuard::RedlineFlagsInternGuard( + SwDoc& rDoc, + RedlineFlags eNewRedlineFlags, + RedlineFlags eRedlineFlagsMask ) + : mrDoc( rDoc ), + meOldRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ) +{ + mrDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( ( meOldRedlineFlags & ~eRedlineFlagsMask ) | + ( eNewRedlineFlags & eRedlineFlagsMask ) ); +} + +RedlineFlagsInternGuard::~RedlineFlagsInternGuard() +{ + mrDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( meOldRedlineFlags ); +} + +void SwUndoTableNumFormat::RedoImpl(::sw::UndoRedoContext & rContext) +{ + // Could the box be changed? + if( !m_pBoxSet ) + return ; + + SwDoc & rDoc = rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + pPam->DeleteMark(); + pPam->GetPoint()->Assign( m_nNode ); + + SwNode * pNd = & pPam->GetPoint()->GetNode(); + SwStartNode* pSttNd = pNd->FindSttNodeByType( SwTableBoxStartNode ); + assert(pSttNd && "without StartNode no TableBox"); + SwTableBox* pBox = pSttNd->FindTableNode()->GetTable().GetTableBox( + pSttNd->GetIndex() ); + OSL_ENSURE( pBox, "found no TableBox" ); + + SwFrameFormat* pBoxFormat = pBox->ClaimFrameFormat(); + if( m_bNewFormat || m_bNewFormula || m_bNewValue ) + { + SfxItemSetFixed aBoxSet( rDoc.GetAttrPool() ); + + // Resetting attributes is not enough. In addition, take care that the + // text will be also formatted correctly. + pBoxFormat->LockModify(); + + if( m_bNewFormula ) + aBoxSet.Put( SwTableBoxFormula( m_aNewFormula )); + else + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMULA ); + if( m_bNewFormat ) + aBoxSet.Put( SwTableBoxNumFormat( m_nNewFormatIdx )); + else + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT ); + if( m_bNewValue ) + aBoxSet.Put( SwTableBoxValue( m_fNewNum )); + else + pBoxFormat->ResetFormatAttr( RES_BOXATR_VALUE ); + pBoxFormat->UnlockModify(); + + // dvo: When redlining is (was) enabled, setting the attribute + // will also change the cell content. To allow this, the + // RedlineFlags::Ignore flag must be removed during Redo. #108450# + RedlineFlagsInternGuard aGuard( rDoc, RedlineFlags::NONE, RedlineFlags::Ignore ); + pBoxFormat->SetFormatAttr( aBoxSet ); + } + else if( getSwDefaultTextFormat() != m_nFormatIdx ) + { + SfxItemSetFixed aBoxSet( rDoc.GetAttrPool() ); + + aBoxSet.Put( SwTableBoxNumFormat( m_nFormatIdx )); + aBoxSet.Put( SwTableBoxValue( m_fNum )); + + // Resetting attributes is not enough. In addition, take care that the + // text will be also formatted correctly. + pBoxFormat->LockModify(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMULA ); + pBoxFormat->UnlockModify(); + + // dvo: When redlining is (was) enabled, setting the attribute + // will also change the cell content. To allow this, the + // RedlineFlags::Ignore flag must be removed during Redo. #108450# + RedlineFlagsInternGuard aGuard( rDoc, RedlineFlags::NONE, RedlineFlags::Ignore ); + pBoxFormat->SetFormatAttr( aBoxSet ); + } + else + { + // it's no number + + // Resetting attributes is not enough. In addition, take care that the + // text will be also formatted correctly. + pBoxFormat->SetFormatAttr( *GetDfltAttr( RES_BOXATR_FORMAT )); + + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ); + } + + if( m_bNewFormula ) + { + // No matter what was set, an update of the table is always a good idea + rDoc.getIDocumentFieldsAccess().UpdateTableFields(&pSttNd->FindTableNode()->GetTable()); + } + + if( !pNd->IsContentNode() ) + pNd = rDoc.GetNodes().GoNext( pPam->GetPoint() ); +} + +void SwUndoTableNumFormat::SetBox( const SwTableBox& rBox ) +{ + m_nNode = rBox.GetSttIdx(); +} + +UndoTableCpyTable_Entry::UndoTableCpyTable_Entry( const SwTableBox& rBox ) + : nBoxIdx( rBox.GetSttIdx() ), nOffset( 0 ), + bJoin( false ) +{ +} + +void UndoTableCpyTable_Entry::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("UndoTableCpyTable_Entry")); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("nBoxIdx")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::number(sal_Int32(nBoxIdx)).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("nOffset")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::number(sal_Int32(nOffset)).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + if (pBoxNumAttr) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("pBoxNumAttr")); + pBoxNumAttr->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + } + + if (pUndo) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("pUndo")); + pUndo->dumpAsXml(pWriter); + (void)xmlTextWriterEndElement(pWriter); + } + + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("bJoin")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(bJoin).getStr())); + (void)xmlTextWriterEndElement(pWriter); + + (void)xmlTextWriterEndElement(pWriter); +} + +SwUndoTableCpyTable::SwUndoTableCpyTable(const SwDoc& rDoc) + : SwUndo( SwUndoId::TBLCPYTBL, &rDoc ) +{ +} + +SwUndoTableCpyTable::~SwUndoTableCpyTable() +{ + m_vArr.clear(); + m_pInsRowUndo.reset(); +} + +void SwUndoTableCpyTable::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + DEBUG_REDLINE( &rDoc ) + + SwTableNode* pTableNd = nullptr; + for (size_t n = m_vArr.size(); n; ) + { + UndoTableCpyTable_Entry *const pEntry = m_vArr[ --n ].get(); + SwNodeOffset nSttPos = pEntry->nBoxIdx + pEntry->nOffset; + SwStartNode* pSNd = rDoc.GetNodes()[ nSttPos ]->StartOfSectionNode(); + if( !pTableNd ) + pTableNd = pSNd->FindTableNode(); + + SwTableBox* pBox = pTableNd->GetTable().GetTableBox( nSttPos ); + if (!pBox) + { + SAL_WARN("sw.core", + "SwUndoTableCpyTable::UndoImpl: invalid start node index for table box"); + continue; + } + + SwTableBox& rBox = *pBox; + + SwNodeIndex aInsIdx( *rBox.GetSttNd(), 1 ); + rDoc.GetNodes().MakeTextNode( aInsIdx.GetNode(), rDoc.GetDfltTextFormatColl() ); + + // b62341295: Redline for copying tables + const SwNode *pEndNode = rBox.GetSttNd()->EndOfSectionNode(); + SwPaM aPam( aInsIdx.GetNode(), *pEndNode ); + std::unique_ptr pUndo; + + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) ) + { + bool bDeleteCompleteParagraph = false; + bool bShiftPam = false; + // There are a couple of different situations to consider during redlining + if( pEntry->pUndo ) + { + SwUndoDelete *const pUndoDelete = + dynamic_cast(pEntry->pUndo.get()); + SwUndoRedlineDelete *const pUndoRedlineDelete = + dynamic_cast(pEntry->pUndo.get()); + assert(pUndoDelete || pUndoRedlineDelete); + if (pUndoRedlineDelete) + { + // The old content was not empty or he has been merged with the new content + bDeleteCompleteParagraph = !pEntry->bJoin; // bJoin is set when merged + // Set aTmpIdx to the beginning of the old content + SwNodeIndex aTmpIdx( *pEndNode, + pUndoRedlineDelete->NodeDiff()-1 ); + SwTextNode *pText = aTmpIdx.GetNode().GetTextNode(); + if( pText ) + { + aPam.GetPoint()->Assign(*pText, + pUndoRedlineDelete->ContentStart() ); + } + else + aPam.GetPoint()->Assign( aTmpIdx ); + } + else if (pUndoDelete && pUndoDelete->IsDelFullPara()) + { + // When the old content was an empty paragraph, but could not be joined + // with the new content (e.g. because of a section or table) + // We "save" the aPam.Point, we go one step backwards (because later on the + // empty paragraph will be inserted by the undo) and set the "ShiftPam-flag + // for step forward later on. + bDeleteCompleteParagraph = true; + bShiftPam = true; + aPam.GetPoint()->Assign(*pEndNode, SwNodeOffset(-1)); + } + } + rDoc.getIDocumentRedlineAccess().DeleteRedline( aPam, true, RedlineType::Any ); + + if( pEntry->pUndo ) + { + pEntry->pUndo->UndoImpl(rContext); + pEntry->pUndo.reset(); + } + if( bShiftPam ) + { + // The aPam.Point is at the moment at the last position of the new content and has to be + // moved to the first position of the old content for the SwUndoDelete operation + aPam.GetPoint()->Assign(aPam.GetPoint()->GetNode(), SwNodeOffset(1)); + } + pUndo = std::make_unique(aPam, SwDeleteFlags::Default, bDeleteCompleteParagraph, true); + } + else + { + pUndo = std::make_unique(aPam, SwDeleteFlags::Default, true); + if( pEntry->pUndo ) + { + pEntry->pUndo->UndoImpl(rContext); + pEntry->pUndo.reset(); + } + } + pEntry->pUndo = std::move(pUndo); + + aInsIdx = rBox.GetSttIdx() + 1; + rDoc.GetNodes().Delete( aInsIdx ); + + SfxItemSetFixed< + RES_VERT_ORIENT, RES_VERT_ORIENT, + RES_BOXATR_FORMAT, RES_BOXATR_VALUE> + aTmpSet(rDoc.GetAttrPool()); + aTmpSet.Put( rBox.GetFrameFormat()->GetAttrSet() ); + if( aTmpSet.Count() ) + { + SwFrameFormat* pBoxFormat = rBox.ClaimFrameFormat(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ); + pBoxFormat->ResetFormatAttr( RES_VERT_ORIENT ); + } + + if( pEntry->pBoxNumAttr ) + { + rBox.ClaimFrameFormat()->SetFormatAttr( *pEntry->pBoxNumAttr ); + pEntry->pBoxNumAttr.reset(); + } + + if( aTmpSet.Count() ) + { + pEntry->pBoxNumAttr = std::make_unique>(rDoc.GetAttrPool()); + pEntry->pBoxNumAttr->Put( aTmpSet ); + } + + pEntry->nOffset = rBox.GetSttIdx() - pEntry->nBoxIdx; + } + + if( m_pInsRowUndo ) + { + m_pInsRowUndo->UndoImpl(rContext); + } + DEBUG_REDLINE( &rDoc ) +} + +void SwUndoTableCpyTable::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + DEBUG_REDLINE( &rDoc ) + + if( m_pInsRowUndo ) + { + m_pInsRowUndo->RedoImpl(rContext); + } + + SwTableNode* pTableNd = nullptr; + for (size_t n = 0; n < m_vArr.size(); ++n) + { + UndoTableCpyTable_Entry *const pEntry = m_vArr[ n ].get(); + SwNodeOffset nSttPos = pEntry->nBoxIdx + pEntry->nOffset; + SwStartNode* pSNd = rDoc.GetNodes()[ nSttPos ]->StartOfSectionNode(); + if( !pTableNd ) + pTableNd = pSNd->FindTableNode(); + + SwTableBox& rBox = *pTableNd->GetTable().GetTableBox( nSttPos ); + + SwNodeIndex aInsIdx( *rBox.GetSttNd(), 1 ); + + // b62341295: Redline for copying tables - Start. + rDoc.GetNodes().MakeTextNode( aInsIdx.GetNode(), rDoc.GetDfltTextFormatColl() ); + SwPaM aPam( aInsIdx.GetNode(), *rBox.GetSttNd()->EndOfSectionNode()); + std::unique_ptr pUndo(IDocumentRedlineAccess::IsRedlineOn(GetRedlineFlags()) + ? nullptr + : std::make_unique(aPam, SwDeleteFlags::Default, true)); + if( pEntry->pUndo ) + { + pEntry->pUndo->UndoImpl(rContext); + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ) ) + { + // PrepareRedline has to be called with the beginning of the old content + // When new and old content has been joined, the rIter.pAktPam has been set + // by the Undo operation to this point. + // Otherwise aInsIdx has been moved during the Undo operation + if( pEntry->bJoin ) + { + SwPaM& rLastPam = + rContext.GetCursorSupplier().GetCurrentShellCursor(); + pUndo = PrepareRedline( &rDoc, rBox, *rLastPam.GetPoint(), + pEntry->bJoin, true ); + } + else + { + SwPosition aTmpPos( aInsIdx ); + pUndo = PrepareRedline( &rDoc, rBox, aTmpPos, pEntry->bJoin, true ); + } + } + pEntry->pUndo.reset(); + } + pEntry->pUndo = std::move(pUndo); + // b62341295: Redline for copying tables - End. + + aInsIdx = rBox.GetSttIdx() + 1; + rDoc.GetNodes().Delete( aInsIdx ); + + SfxItemSetFixed< + RES_VERT_ORIENT, RES_VERT_ORIENT, + RES_BOXATR_FORMAT, RES_BOXATR_VALUE> aTmpSet(rDoc.GetAttrPool()); + aTmpSet.Put( rBox.GetFrameFormat()->GetAttrSet() ); + if( aTmpSet.Count() ) + { + SwFrameFormat* pBoxFormat = rBox.ClaimFrameFormat(); + pBoxFormat->ResetFormatAttr( RES_BOXATR_FORMAT, RES_BOXATR_VALUE ); + pBoxFormat->ResetFormatAttr( RES_VERT_ORIENT ); + } + if( pEntry->pBoxNumAttr ) + { + rBox.ClaimFrameFormat()->SetFormatAttr( *pEntry->pBoxNumAttr ); + pEntry->pBoxNumAttr.reset(); + } + + if( aTmpSet.Count() ) + { + pEntry->pBoxNumAttr = std::make_unique>(rDoc.GetAttrPool()); + pEntry->pBoxNumAttr->Put( aTmpSet ); + } + + pEntry->nOffset = rBox.GetSttIdx() - pEntry->nBoxIdx; + } + DEBUG_REDLINE( &rDoc ) +} + +void SwUndoTableCpyTable::AddBoxBefore( const SwTableBox& rBox, bool bDelContent ) +{ + if (!m_vArr.empty() && !bDelContent) + return; + + UndoTableCpyTable_Entry* pEntry = new UndoTableCpyTable_Entry( rBox ); + m_vArr.push_back(std::unique_ptr(pEntry)); + + SwDoc* pDoc = rBox.GetFrameFormat()->GetDoc(); + DEBUG_REDLINE( pDoc ) + if( bDelContent ) + { + SwNodeIndex aInsIdx( *rBox.GetSttNd(), 1 ); + pDoc->GetNodes().MakeTextNode( aInsIdx.GetNode(), pDoc->GetDfltTextFormatColl() ); + SwPaM aPam( aInsIdx.GetNode(), *rBox.GetSttNd()->EndOfSectionNode() ); + + if( !pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + pEntry->pUndo = std::make_unique(aPam, SwDeleteFlags::Default, true); + } + + pEntry->pBoxNumAttr = std::make_unique>(pDoc->GetAttrPool()); + pEntry->pBoxNumAttr->Put( rBox.GetFrameFormat()->GetAttrSet() ); + if( !pEntry->pBoxNumAttr->Count() ) + { + pEntry->pBoxNumAttr.reset(); + } + DEBUG_REDLINE( pDoc ) +} + +void SwUndoTableCpyTable::AddBoxAfter( const SwTableBox& rBox, const SwNodeIndex& rIdx, bool bDelContent ) +{ + UndoTableCpyTable_Entry *const pEntry = m_vArr.back().get(); + + // If the content was deleted then remove also the temporarily created node + if( bDelContent ) + { + SwDoc* pDoc = rBox.GetFrameFormat()->GetDoc(); + DEBUG_REDLINE( pDoc ) + + if( pDoc->getIDocumentRedlineAccess().IsRedlineOn() ) + { + SwPosition aTmpPos( rIdx ); + pEntry->pUndo = PrepareRedline( pDoc, rBox, aTmpPos, pEntry->bJoin, false ); + } + SwNodeIndex aDelIdx( *rBox.GetSttNd(), 1 ); + rBox.GetFrameFormat()->GetDoc()->GetNodes().Delete( aDelIdx ); + DEBUG_REDLINE( pDoc ) + } + + pEntry->nOffset = rBox.GetSttIdx() - pEntry->nBoxIdx; +} + +// PrepareRedline is called from AddBoxAfter() and from Redo() in slightly different situations. +// bRedo is set by calling from Redo() +// rJoin is false by calling from AddBoxAfter() and will be set if the old and new content has +// been merged. +// rJoin is true if Redo() is calling and the content has already been merged + +std::unique_ptr SwUndoTableCpyTable::PrepareRedline( SwDoc* pDoc, const SwTableBox& rBox, + SwPosition& rPos, bool& rJoin, bool bRedo ) +{ + std::unique_ptr pUndo; + // b62341295: Redline for copying tables + // What's to do? + // Mark the cell content before rIdx as insertion, + // mark the cell content behind rIdx as deletion + // merge text nodes at rIdx if possible + RedlineFlags eOld = pDoc->getIDocumentRedlineAccess().GetRedlineFlags(); + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( ( eOld | RedlineFlags::DontCombineRedlines ) & ~RedlineFlags::Ignore ); + SwPosition aInsertEnd( rPos ); + SwTextNode* pText; + if( !rJoin ) + { + // If the content is not merged, the end of the insertion is at the end of the node + // _before_ the given position rPos + aInsertEnd.Adjust(SwNodeOffset(-1)); + pText = aInsertEnd.GetNode().GetTextNode(); + if( pText ) + { + aInsertEnd.SetContent(pText->GetText().getLength()); + if( !bRedo && rPos.GetNode().GetTextNode() ) + { // Try to merge, if not called by Redo() + rJoin = true; + + // Park this somewhere else so nothing points to the to-be-deleted node. + rPos.nContent.Assign(pText, 0); + + pText->JoinNext(); + } + } + else + aInsertEnd.nContent.Assign(nullptr, 0); + } + // For joined (merged) contents the start of deletion and end of insertion are identical + // otherwise adjacent nodes. + SwPosition aDeleteStart( rJoin ? aInsertEnd : rPos ); + if( !rJoin ) + { + pText = aDeleteStart.GetNode().GetTextNode(); + if( pText ) + aDeleteStart.SetContent( 0 ); + } + SwPosition aCellEnd( *rBox.GetSttNd()->EndOfSectionNode(), SwNodeOffset(-1) ); + pText = aCellEnd.GetNode().GetTextNode(); + if( pText ) + aCellEnd.SetContent(pText->GetText().getLength()); + if( aDeleteStart != aCellEnd ) + { // If the old (deleted) part is not empty, here we are... + SwPaM aDeletePam( aDeleteStart, aCellEnd ); + pUndo = std::make_unique( aDeletePam, SwUndoId::DELETE ); + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Delete, aDeletePam ), true ); + } + else if( !rJoin ) // If the old part is empty and joined, we are finished + { // if it is not joined, we have to delete this empty paragraph + aCellEnd.Assign(*rBox.GetSttNd()->EndOfSectionNode()); + SwPaM aTmpPam( aDeleteStart, aCellEnd ); + pUndo = std::make_unique(aTmpPam, SwDeleteFlags::Default, true); + } + SwPosition aCellStart( *rBox.GetSttNd(), SwNodeOffset(2) ); + pText = aCellStart.GetNode().GetTextNode(); + if( pText ) + aCellStart.SetContent( 0 ); + if( aCellStart != aInsertEnd ) // An empty insertion will not been marked + { + SwPaM aTmpPam( aCellStart, aInsertEnd ); + pDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Insert, aTmpPam ), true ); + } + + pDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + return pUndo; +} + +bool SwUndoTableCpyTable::InsertRow( SwTable& rTable, const SwSelBoxes& rBoxes, + sal_uInt16 nCnt ) +{ + SwTableNode* pTableNd = const_cast(rTable.GetTabSortBoxes()[0]-> + GetSttNd()->FindTableNode()); + + m_pInsRowUndo.reset( new SwUndoTableNdsChg( SwUndoId::TABLE_INSROW, rBoxes, *pTableNd, + 0, 0, nCnt, true, false ) ); + SwTableSortBoxes aTmpLst( rTable.GetTabSortBoxes() ); + + bool bRet = rTable.InsertRow( rTable.GetFrameFormat()->GetDoc(), rBoxes, nCnt, /*bBehind*/true ); + if( bRet ) + m_pInsRowUndo->SaveNewBoxes( *pTableNd, aTmpLst ); + else + { + m_pInsRowUndo.reset(); + } + return bRet; +} + +void SwUndoTableCpyTable::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwUndoTableCpyTable")); + + for (const auto& pEntry : m_vArr) + { + pEntry->dumpAsXml(pWriter); + } + + if (m_pInsRowUndo) + { + m_pInsRowUndo->dumpAsXml(pWriter); + } + + (void)xmlTextWriterEndElement(pWriter); +} + +bool SwUndoTableCpyTable::IsEmpty() const +{ + return !m_pInsRowUndo && m_vArr.empty(); +} + +SwUndoCpyTable::SwUndoCpyTable(const SwDoc& rDoc) + : SwUndo( SwUndoId::CPYTBL, &rDoc ), m_nTableNode( 0 ) +{ +} + +SwUndoCpyTable::~SwUndoCpyTable() +{ +} + +void SwUndoCpyTable::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc & rDoc = rContext.GetDoc(); + SwTableNode* pTNd = rDoc.GetNodes()[ m_nTableNode ]->GetTableNode(); + + // move hard page breaks into next node + SwContentNode* pNextNd = rDoc.GetNodes()[ pTNd->EndOfSectionIndex()+1 ]->GetContentNode(); + if( pNextNd ) + { + SwFrameFormat* pTableFormat = pTNd->GetTable().GetFrameFormat(); + + if( const SwFormatPageDesc* pItem = pTableFormat->GetItemIfSet( RES_PAGEDESC, + false ) ) + pNextNd->SetAttr( *pItem ); + + if( const SvxFormatBreakItem* pItem = pTableFormat->GetItemIfSet( RES_BREAK, + false ) ) + pNextNd->SetAttr( *pItem ); + } + + SwPaM aPam( *pTNd, *pTNd->EndOfSectionNode(), SwNodeOffset(0) , SwNodeOffset(1) ); + m_pDelete.reset(new SwUndoDelete(aPam, SwDeleteFlags::Default, true)); +} + +void SwUndoCpyTable::RedoImpl(::sw::UndoRedoContext & rContext) +{ + m_pDelete->UndoImpl(rContext); + m_pDelete.reset(); +} + +SwUndoSplitTable::SwUndoSplitTable( const SwTableNode& rTableNd, + std::unique_ptr pRowSp, SplitTable_HeadlineOption eMode, bool bNewSize ) + : SwUndo( SwUndoId::SPLIT_TABLE, &rTableNd.GetDoc() ), + m_nTableNode( rTableNd.GetIndex() ), m_nOffset( 0 ), mpSaveRowSpan( std::move(pRowSp) ), + m_nMode( eMode ), m_nFormulaEnd( 0 ), m_bCalcNewSize( bNewSize ) +{ + switch( m_nMode ) + { + case SplitTable_HeadlineOption::BoxAttrAllCopy: + m_pHistory.reset(new SwHistory); + [[fallthrough]]; + case SplitTable_HeadlineOption::BorderCopy: + case SplitTable_HeadlineOption::BoxAttrCopy: + m_pSavedTable.reset(new SaveTable( rTableNd.GetTable(), 1, false )); + break; + default: break; + } +} + +SwUndoSplitTable::~SwUndoSplitTable() +{ + m_pSavedTable.reset(); + m_pHistory.reset(); + mpSaveRowSpan.reset(); +} + +void SwUndoSplitTable::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + SwPosition& rPtPos = *pPam->GetPoint(); + rPtPos.Assign( m_nTableNode + m_nOffset ); + assert(rPtPos.GetNode().GetContentNode()->Len() == 0); // empty para inserted + + { + // avoid asserts from ~SwContentIndexReg + SwNodeIndex const idx(pDoc->GetNodes(), m_nTableNode + m_nOffset); + { + SwPaM pam(idx); + pam.Move(fnMoveBackward, GoInContent); + ::PaMCorrAbs(*pPam, *pam.GetPoint()); + } + + // remove implicitly created paragraph again + pDoc->GetNodes().Delete( idx ); + } + + rPtPos.Assign( m_nTableNode + m_nOffset ); + SwTableNode* pTableNd = rPtPos.GetNode().GetTableNode(); + SwTable& rTable = pTableNd->GetTable(); + rTable.SwitchFormulasToInternalRepresentation(); + + switch( m_nMode ) + { + case SplitTable_HeadlineOption::BoxAttrAllCopy: + if( m_pHistory ) + m_pHistory->TmpRollback( pDoc, m_nFormulaEnd ); + [[fallthrough]]; + case SplitTable_HeadlineOption::BoxAttrCopy: + case SplitTable_HeadlineOption::BorderCopy: + { + m_pSavedTable->CreateNew( rTable, false ); + m_pSavedTable->RestoreAttr( rTable ); + } + break; + + case SplitTable_HeadlineOption::ContentCopy: + // the created first line has to be removed again + { + SwSelBoxes aSelBoxes; + SwTableBox* pBox = rTable.GetTableBox( m_nTableNode + m_nOffset + 1 ); + SwTable::SelLineFromBox( pBox, aSelBoxes ); + FndBox_ aTmpBox( nullptr, nullptr ); + aTmpBox.SetTableLines( aSelBoxes, rTable ); + aTmpBox.DelFrames( rTable ); + rTable.DeleteSel( pDoc, aSelBoxes, nullptr, nullptr, false, false ); + } + break; + default: break; + } + + pDoc->GetNodes().MergeTable( rPtPos.GetNode() ); + + if( m_pHistory ) + { + m_pHistory->TmpRollback( pDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + } + if( mpSaveRowSpan ) + { + pTableNd = rPtPos.GetNode().FindTableNode(); + if( pTableNd ) + pTableNd->GetTable().RestoreRowSpan( *mpSaveRowSpan ); + } + ClearFEShellTabCols(*pDoc, nullptr); +} + +void SwUndoSplitTable::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + pPam->DeleteMark(); + pPam->GetPoint()->Assign( m_nTableNode ); + pDoc->SplitTable( *pPam->GetPoint(), m_nMode, m_bCalcNewSize ); + + ClearFEShellTabCols(*pDoc, nullptr); +} + +void SwUndoSplitTable::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwPaM *const pPam = & rContext.GetRepeatPaM(); + SwDoc *const pDoc = & rContext.GetDoc(); + + pDoc->SplitTable( *pPam->GetPoint(), m_nMode, m_bCalcNewSize ); + ClearFEShellTabCols(*pDoc, nullptr); +} + +void SwUndoSplitTable::SaveFormula( SwHistory& rHistory ) +{ + if( !m_pHistory ) + m_pHistory.reset(new SwHistory); + + m_nFormulaEnd = rHistory.Count(); + m_pHistory->Move( 0, &rHistory ); +} + +SwUndoMergeTable::SwUndoMergeTable( const SwTableNode& rTableNd, + const SwTableNode& rDelTableNd, + bool bWithPrv ) + : SwUndo( SwUndoId::MERGE_TABLE, &rTableNd.GetDoc() ), + m_bWithPrev( bWithPrv ) +{ + // memorize end node of the last table cell that'll stay in position + if( m_bWithPrev ) + m_nTableNode = rDelTableNd.EndOfSectionIndex() - 1; + else + m_nTableNode = rTableNd.EndOfSectionIndex() - 1; + + m_aName = rDelTableNd.GetTable().GetFrameFormat()->GetName(); + m_pSaveTable.reset(new SaveTable( rDelTableNd.GetTable() )); + + if (m_bWithPrev) + m_pSaveHdl.reset( new SaveTable( rTableNd.GetTable(), 1 ) ); +} + +SwUndoMergeTable::~SwUndoMergeTable() +{ + m_pSaveTable.reset(); + m_pSaveHdl.reset(); + m_pHistory.reset(); +} + +void SwUndoMergeTable::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + pPam->DeleteMark(); + SwPosition& rPtPos = *pPam->GetPoint(); + rPtPos.Assign( m_nTableNode); + + SwTableNode* pTableNd = rPtPos.GetNode().FindTableNode(); + SwTable* pTable = &pTableNd->GetTable(); + pTable->SwitchFormulasToInternalRepresentation(); + + // get lines for layout update + FndBox_ aFndBox( nullptr, nullptr ); + aFndBox.SetTableLines( *pTable ); + aFndBox.DelFrames( *pTable ); + // ? TL_CHART2: notification or locking of controller required ? + + SwTableNode* pNew = pDoc->GetNodes().SplitTable( rPtPos.GetNode() ); + + // update layout + aFndBox.MakeFrames( *pTable ); + // ? TL_CHART2: notification or locking of controller required ? + + if( m_bWithPrev ) + { + // move name + pNew->GetTable().GetFrameFormat()->SetFormatName( pTable->GetFrameFormat()->GetName() ); + m_pSaveHdl->RestoreAttr( pNew->GetTable() ); + } + else + pTable = &pNew->GetTable(); + + pTable->GetFrameFormat()->SetFormatName( m_aName ); + m_pSaveTable->RestoreAttr( *pTable ); + + if( m_pHistory ) + { + m_pHistory->TmpRollback( pDoc, 0 ); + m_pHistory->SetTmpEnd( m_pHistory->Count() ); + } + + // create frames for the new table + pNew->MakeOwnFrames(); + + // position cursor somewhere in content + pDoc->GetNodes().GoNext( &rPtPos ); + + ClearFEShellTabCols(*pDoc, nullptr); + + // TL_CHART2: need to inform chart of probably changed cell names + SwChartDataProvider *pPCD = pDoc->getIDocumentChartDataProviderAccess().GetChartDataProvider(); + if (pPCD) + { + pDoc->UpdateCharts( pTable->GetFrameFormat()->GetName() ); + pDoc->UpdateCharts( pNew->GetTable().GetFrameFormat()->GetName() ); + } +} + +void SwUndoMergeTable::RedoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwPaM *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor()); + + pPam->DeleteMark(); + if( m_bWithPrev ) + pPam->GetPoint()->Assign( m_nTableNode + 3 ); + else + pPam->GetPoint()->Assign( m_nTableNode ); + + pDoc->MergeTable( *pPam->GetPoint(), m_bWithPrev ); + + ClearFEShellTabCols(*pDoc, nullptr); +} + +void SwUndoMergeTable::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwDoc *const pDoc = & rContext.GetDoc(); + SwPaM *const pPam = & rContext.GetRepeatPaM(); + + pDoc->MergeTable( *pPam->GetPoint(), m_bWithPrev ); + ClearFEShellTabCols(*pDoc, nullptr); +} + +void SwUndoMergeTable::SaveFormula( SwHistory& rHistory ) +{ + if( !m_pHistory ) + m_pHistory.reset( new SwHistory ); + m_pHistory->Move( 0, &rHistory ); +} + +void InsertSort( std::vector& rArr, sal_uInt16 nIdx ) +{ + size_t nO = rArr.size(); + size_t nU = 0; + if( nO > 0 ) + { + nO--; + while( nU <= nO ) + { + const size_t nM = nU + ( nO - nU ) / 2; + if ( rArr[nM] == nIdx ) + { + OSL_FAIL( "Index already exists. This should never happen." ); + return; + } + if( rArr[nM] < nIdx ) + nU = nM + 1; + else if( nM == 0 ) + break; + else + nO = nM - 1; + } + } + rArr.insert( rArr.begin() + nU, nIdx ); +} + +#if OSL_DEBUG_LEVEL > 0 +void CheckTable( const SwTable& rTable ) +{ + const SwNodes& rNds = rTable.GetFrameFormat()->GetDoc()->GetNodes(); + const SwTableSortBoxes& rSrtArr = rTable.GetTabSortBoxes(); + for (size_t n = 0; n < rSrtArr.size(); ++n) + { + const SwTableBox* pBox = rSrtArr[ n ]; + const SwNode* pNd = pBox->GetSttNd(); + OSL_ENSURE( rNds[ pBox->GetSttIdx() ] == pNd, "Box with wrong StartNode" ); + } +} +#endif + +SwUndoTableStyleMake::SwUndoTableStyleMake(OUString aName, const SwDoc& rDoc) + : SwUndo(SwUndoId::TBLSTYLE_CREATE, &rDoc), + m_sName(std::move(aName)) +{ } + +SwUndoTableStyleMake::~SwUndoTableStyleMake() +{ } + +void SwUndoTableStyleMake::UndoImpl(::sw::UndoRedoContext & rContext) +{ + m_pAutoFormat = rContext.GetDoc().DelTableStyle(m_sName, true); +} + +void SwUndoTableStyleMake::RedoImpl(::sw::UndoRedoContext & rContext) +{ + if (m_pAutoFormat) + { + SwTableAutoFormat* pFormat = rContext.GetDoc().MakeTableStyle(m_sName, true); + if (pFormat) + { + *pFormat = *m_pAutoFormat; + m_pAutoFormat.reset(); + } + } +} + +SwRewriter SwUndoTableStyleMake::GetRewriter() const +{ + SwRewriter aResult; + aResult.AddRule(UndoArg1, m_sName); + return aResult; +} + +SwUndoTableStyleDelete::SwUndoTableStyleDelete(std::unique_ptr pAutoFormat, std::vector&& rAffectedTables, const SwDoc& rDoc) + : SwUndo(SwUndoId::TBLSTYLE_DELETE, &rDoc), + m_pAutoFormat(std::move(pAutoFormat)), + m_rAffectedTables(std::move(rAffectedTables)) +{ } + +SwUndoTableStyleDelete::~SwUndoTableStyleDelete() +{ } + +void SwUndoTableStyleDelete::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwTableAutoFormat* pNewFormat = rContext.GetDoc().MakeTableStyle(m_pAutoFormat->GetName(), true); + *pNewFormat = *m_pAutoFormat; + for (size_t i=0; i < m_rAffectedTables.size(); i++) + m_rAffectedTables[i]->SetTableStyleName(m_pAutoFormat->GetName()); +} + +void SwUndoTableStyleDelete::RedoImpl(::sw::UndoRedoContext & rContext) +{ + // Don't need to remember deleted table style nor affected tables, because they must be the same as these already known. + rContext.GetDoc().DelTableStyle(m_pAutoFormat->GetName()); +} + +SwRewriter SwUndoTableStyleDelete::GetRewriter() const +{ + SwRewriter aResult; + aResult.AddRule(UndoArg1, m_pAutoFormat->GetName()); + return aResult; +} + +SwUndoTableStyleUpdate::SwUndoTableStyleUpdate(const SwTableAutoFormat& rNewFormat, const SwTableAutoFormat& rOldFormat, const SwDoc& rDoc) + : SwUndo(SwUndoId::TBLSTYLE_UPDATE, &rDoc) + , m_pOldFormat(new SwTableAutoFormat(rOldFormat)) + , m_pNewFormat(new SwTableAutoFormat(rNewFormat)) +{ } + +SwUndoTableStyleUpdate::~SwUndoTableStyleUpdate() +{ } + +void SwUndoTableStyleUpdate::UndoImpl(::sw::UndoRedoContext & rContext) +{ + rContext.GetDoc().ChgTableStyle(m_pNewFormat->GetName(), *m_pOldFormat); +} + +void SwUndoTableStyleUpdate::RedoImpl(::sw::UndoRedoContext & rContext) +{ + rContext.GetDoc().ChgTableStyle(m_pNewFormat->GetName(), *m_pNewFormat); +} + +SwRewriter SwUndoTableStyleUpdate::GetRewriter() const +{ + SwRewriter aResult; + aResult.AddRule(UndoArg1, m_pNewFormat->GetName()); + return aResult; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/undo/untblk.cxx b/sw/source/core/undo/untblk.cxx new file mode 100644 index 0000000000..fd5c3239c0 --- /dev/null +++ b/sw/source/core/undo/untblk.cxx @@ -0,0 +1,484 @@ +/* -*- 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 +#include + +namespace sw { + +std::optional> +GetFlysAnchoredAt(SwDoc & rDoc, SwNodeOffset const nSttNode) +{ + std::optional> pFrameFormats; + const size_t nArrLen = rDoc.GetSpzFrameFormats()->size(); + for (size_t n = 0; n < nArrLen; ++n) + { + SwFrameFormat *const pFormat = (*rDoc.GetSpzFrameFormats())[n]; + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + SwNode const*const pAnchorNode = pAnchor->GetAnchorNode(); + if (pAnchorNode + && nSttNode == pAnchorNode->GetIndex() + && ((pAnchor->GetAnchorId() == RndStdIds::FLY_AT_PARA) + || (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR))) + { + if (!pFrameFormats) + pFrameFormats.emplace(); + pFrameFormats->push_back( pFormat ); + } + } + return pFrameFormats; +} + +} // namespace sw + +//note: parameter is SwPam just so we can init SwUndRng, the End is ignored! +SwUndoInserts::SwUndoInserts( SwUndoId nUndoId, const SwPaM& rPam ) + : SwUndo( nUndoId, &rPam.GetDoc() ) + , SwUndRng( rPam ) + , m_pTextFormatColl(nullptr) + , m_pLastNodeColl(nullptr) + , m_nDeleteTextNodes(1) + , m_nNodeDiff(0) + , m_nSetPos(0) +{ + m_pHistory.reset( new SwHistory ); + SwDoc& rDoc = rPam.GetDoc(); + + SwTextNode* pTextNd = rPam.GetPoint()->GetNode().GetTextNode(); + if( pTextNd ) + { + m_pTextFormatColl = pTextNd->GetTextColl(); + assert(m_pTextFormatColl); + m_pHistory->CopyAttr( pTextNd->GetpSwpHints(), m_nSttNode, + 0, pTextNd->GetText().getLength(), false ); + if( pTextNd->HasSwAttrSet() ) + m_pHistory->CopyFormatAttr( *pTextNd->GetpSwAttrSet(), m_nSttNode ); + + // We may have some flys anchored to paragraph where we inserting. + // These flys will be saved in pFrameFormats array (only flys which exist BEFORE insertion!) + // Then in SwUndoInserts::SetInsertRange the flys saved in pFrameFormats will NOT create Undos. + // m_FlyUndos will only be filled with newly inserted flys. + m_pFrameFormats = sw::GetFlysAnchoredAt(rDoc, m_nSttNode); + } + // consider Redline + if( rDoc.getIDocumentRedlineAccess().IsRedlineOn() ) + { + m_pRedlineData.reset( new SwRedlineData( RedlineType::Insert, rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ) ); + SetRedlineFlags( rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ); + } +} + +// This method does two things: +// 1. Adjusts SwUndoRng members, required for Undo. +// Members are: +// SwUndoRng::nSttNode - all nodes starting from this node will be deleted during Undo (in SwUndoInserts::UndoImpl) +// SwUndoRng::nSttContent - corresponding content index in SwUndoRng::nSttNode +// SwUndoRng::nEndNode - end node for deletion +// SwUndoRng::nEndContent - end content index +// All these members are filled in during construction of SwUndoInserts instance, and can be adjusted using this method +// +// 2. Fills in m_FlyUndos array with flys anchored ONLY to first and last paragraphs (first == rPam.Start(), last == rPam.End()) +// Flys, anchored to any paragraph, but not first and last, are handled by DelContentIndex (see SwUndoInserts::UndoImpl) and are not stored in m_FlyUndos. + +void SwUndoInserts::SetInsertRange( const SwPaM& rPam, bool bScanFlys, + SwNodeOffset const nDeleteTextNodes) +{ + const SwPosition* pTmpPos = rPam.End(); + m_nEndNode = pTmpPos->GetNodeIndex(); + m_nEndContent = pTmpPos->GetContentIndex(); + if( rPam.HasMark() ) + { + if( pTmpPos == rPam.GetPoint() ) + pTmpPos = rPam.GetMark(); + else + pTmpPos = rPam.GetPoint(); + + m_nSttNode = pTmpPos->GetNodeIndex(); + m_nSttContent = pTmpPos->GetContentIndex(); + + m_nDeleteTextNodes = nDeleteTextNodes; + if (m_nDeleteTextNodes == SwNodeOffset(0)) // if a table selection is added... + { + ++m_nSttNode; // ... then the CopyPam is not fully correct + } + } + + // Fill m_FlyUndos with flys anchored to first and last paragraphs + + if( !bScanFlys) + return; + + // than collect all new Flys + SwDoc& rDoc = rPam.GetDoc(); + const size_t nArrLen = rDoc.GetSpzFrameFormats()->size(); + for( size_t n = 0; n < nArrLen; ++n ) + { + SwFrameFormat* pFormat = (*rDoc.GetSpzFrameFormats())[n]; + SwFormatAnchor const*const pAnchor = &pFormat->GetAnchor(); + if (IsCreateUndoForNewFly(*pAnchor, m_nSttNode, m_nEndNode)) + { + std::vector::iterator it; + if( !m_pFrameFormats || + m_pFrameFormats->end() == ( it = std::find( m_pFrameFormats->begin(), m_pFrameFormats->end(), pFormat ) ) ) + { + std::shared_ptr const pFlyUndo = + std::make_shared(pFormat, SwNodeOffset(0), 0); + m_FlyUndos.push_back(pFlyUndo); + } + else + m_pFrameFormats->erase( it ); + } + } + m_pFrameFormats.reset(); +} + +/** This is not the same as IsDestroyFrameAnchoredAtChar() + and intentionally so: because the SwUndoInserts::UndoImpl() must remove + the flys at the start/end position that were inserted but not the ones + at the start/insert position that were already there; + handle all at-char flys at start/end node like this, even if they're + not *on* the start/end position, because it makes it easier to ensure + that the Undo/Redo run in inverse order. + */ +bool SwUndoInserts::IsCreateUndoForNewFly(SwFormatAnchor const& rAnchor, + SwNodeOffset const nStartNode, SwNodeOffset const nEndNode) +{ + assert(nStartNode <= nEndNode); + + // check all at-char flys at the start/end nodes: + // ExcludeFlyAtStartEnd will exclude them! + SwNode const*const pAnchorNode = rAnchor.GetAnchorNode(); + return pAnchorNode != nullptr + && ( rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA + || rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR) + && ( nStartNode == pAnchorNode->GetIndex() + || nEndNode == pAnchorNode->GetIndex()); +} + +void SwUndoInserts::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwUndoInserts")); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("symbol"), "%s", + BAD_CAST(typeid(*this).name())); + + SwUndo::dumpAsXml(pWriter); + SwUndoSaveContent::dumpAsXml(pWriter); + + if (m_pFrameFormats) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_pFrameFormats")); + for (const auto& pFormat : *m_pFrameFormats) + { + pFormat->dumpAsXml(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); + } + + if (!m_FlyUndos.empty()) + { + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("m_FlyUndos")); + for (const auto& pFly : m_FlyUndos) + { + pFly->dumpAsXml(pWriter); + } + (void)xmlTextWriterEndElement(pWriter); + } + + (void)xmlTextWriterEndElement(pWriter); +} + +SwUndoInserts::~SwUndoInserts() +{ + if (m_oUndoNodeIndex) // delete also the section from UndoNodes array + { + // Insert saves content in IconSection + SwNodes& rUNds = m_oUndoNodeIndex->GetNodes(); + rUNds.Delete(*m_oUndoNodeIndex, + rUNds.GetEndOfExtras().GetIndex() - m_oUndoNodeIndex->GetIndex()); + m_oUndoNodeIndex.reset(); + } + m_pFrameFormats.reset(); + m_pRedlineData.reset(); +} + +// Undo Insert operation +// It's important to note that Undo stores absolute node indexes. I.e. if during insertion, you insert nodes 31 to 33, +// during Undo nodes with indices from 31 to 33 will be deleted. Undo doesn't check that nodes 31 to 33 are the same nodes which were inserted. +// It just deletes them. +// This may seem as bad programming practice, but Undo actions are strongly ordered. If you change your document in some way, a new Undo action is added. +// During Undo most recent actions will be executed first. So during execution of particular Undo action indices will be correct. +// But storing absolute indices leads to crashes if some action in Undo fails to roll back some modifications. + +// Has following main steps: +// 1. m_FlyUndos removes flys anchored to first and last paragraph in Undo range. +// This array may be empty. +// 2. DelContentIndex to delete footnotes, flys, bookmarks (see comment for this function) +// Deleted flys are stored in pHistory array. +// First and last paragraphs flys are not deleted by DelContentIndex! +// For flys anchored to last paragraph, DelContentIndex re-anchors them to +// the last paragraph that will remain after Undo. +// 3. MoveToUndoNds moves nodes to Undo nodes array and removes them from document. +// 4. Lastly (starting from if(pTextNode)), text from last paragraph is joined to last remaining paragraph and FormatColl for last paragraph is restored. +// Format coll for last paragraph is removed during execution of UndoImpl + +void SwUndoInserts::UndoImpl(::sw::UndoRedoContext & rContext) +{ + SwDoc& rDoc = rContext.GetDoc(); + SwPaM& rPam = AddUndoRedoPaM(rContext); + + m_nNodeDiff = SwNodeOffset(0); + + if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any); + + // if Point and Mark are different text nodes so a JoinNext has to be done + bool bJoinNext = m_nSttNode != m_nEndNode && + rPam.GetMark()->GetNode().GetTextNode() && + rPam.GetPoint()->GetNode().GetTextNode(); + + // Is there any content? (loading from template does not have content) + if( m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent ) + { + if( m_nSttNode != m_nEndNode ) + { + SwTextNode* pTextNd = rDoc.GetNodes()[ m_nEndNode ]->GetTextNode(); + if (pTextNd && pTextNd->GetText().getLength() == m_nEndContent) + m_pLastNodeColl = pTextNd->GetTextColl(); + } + + // tdf#128739 correct cursors but do not delete bookmarks yet + ::PaMCorrAbs(rPam, *rPam.End()); + + SetPaM(rPam); + } + + // ... for consistency with the Insert File code in shellio.cxx, which + // creates separate SwUndoInsLayFormat for mysterious reasons, do this + // *before* anything else: + // after SetPaM but before MoveToUndoNds and DelContentIndex. + // note: there isn't an order dep wrt. initial Copy action because Undo + // overwrites the indexes but there is wrt. Redo because that uses the + // indexes + if (!m_FlyUndos.empty()) + { + SwNodeOffset nTmp = rPam.GetPoint()->GetNodeIndex(); + for (size_t n = m_FlyUndos.size(); 0 < n; --n) + { + m_FlyUndos[ n-1 ]->UndoImpl(rContext); + } + m_nNodeDiff += nTmp - rPam.GetPoint()->GetNodeIndex(); + } + + if (m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent) + { + // are there Footnotes or ContentFlyFrames in text? + m_nSetPos = m_pHistory->Count(); + SwNodeOffset nTmp = rPam.GetMark()->GetNodeIndex(); + DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(), + DelContentType::AllMask|DelContentType::ExcludeFlyAtStartEnd); + m_nNodeDiff += nTmp - rPam.GetMark()->GetNodeIndex(); + if( *rPam.GetPoint() != *rPam.GetMark() ) + { + m_oUndoNodeIndex.emplace(rDoc.GetNodes().GetEndOfContent()); + MoveToUndoNds(rPam, &*m_oUndoNodeIndex); + + if (m_nDeleteTextNodes == SwNodeOffset(0)) + { + rPam.Move( fnMoveBackward, GoInContent ); + } + } + } + + SwTextNode* pTextNode = rPam.GetPoint()->GetNode().GetTextNode(); + if( !pTextNode ) + return; + + if( !m_pTextFormatColl ) // if 0 than it's no TextNode -> delete + { + SwNodeIndex aDelIdx( *pTextNode ); + assert(SwNodeOffset(0) < m_nDeleteTextNodes && m_nDeleteTextNodes < SwNodeOffset(3)); + for (SwNodeOffset i(0); i < m_nDeleteTextNodes; ++i) + { + rPam.Move(fnMoveForward, GoInNode); + } + rPam.DeleteMark(); + + for (SwNodeOffset i(0); i < m_nDeleteTextNodes; ++i) + { + RemoveIdxRel(aDelIdx.GetIndex() + i, *rPam.GetPoint()); + } + + rDoc.GetNodes().Delete( aDelIdx, m_nDeleteTextNodes ); + } + else + { + if( bJoinNext && pTextNode->CanJoinNext()) + { + { + RemoveIdxRel( pTextNode->GetIndex()+1, + SwPosition( *pTextNode, pTextNode, pTextNode->GetText().getLength() ) ); + } + pTextNode->JoinNext(); + } + // reset all text attributes in the paragraph! + pTextNode->RstTextAttr( 0, pTextNode->Len(), 0, nullptr, true ); + + pTextNode->ResetAllAttr(); + + if (rDoc.GetTextFormatColls()->IsAlive(m_pTextFormatColl)) + m_pTextFormatColl = static_cast(pTextNode->ChgFormatColl( m_pTextFormatColl )) ; + + m_pHistory->SetTmpEnd( m_nSetPos ); + m_pHistory->TmpRollback(&rDoc, 0, false); + } +} + +// See SwUndoInserts::UndoImpl comments +// All actions here should be done in reverse order to what is done in SwUndoInserts::UndoImpl! + +void SwUndoInserts::RedoImpl(::sw::UndoRedoContext & rContext) +{ + // position cursor onto REDO section + SwCursor& rPam(rContext.GetCursorSupplier().CreateNewShellCursor()); + SwDoc& rDoc = rPam.GetDoc(); + rPam.DeleteMark(); + rPam.GetPoint()->Assign( m_nSttNode - m_nNodeDiff, m_nSttContent ); + SwContentNode* pCNd = rPam.GetPointContentNode(); + + SwTextFormatColl* pSavTextFormatColl = m_pTextFormatColl; + if( m_pTextFormatColl && pCNd && pCNd->IsTextNode() ) + pSavTextFormatColl = static_cast(pCNd)->GetTextColl(); + + m_pHistory->SetTmpEnd( m_nSetPos ); + + // retrieve start position for rollback + if( ( m_nSttNode != m_nEndNode || m_nSttContent != m_nEndContent ) && m_oUndoNodeIndex) + { + auto const pFlysAtInsPos(sw::GetFlysAnchoredAt(rDoc, + rPam.GetPoint()->GetNodeIndex())); + + ::std::optional oMvBkwrd = MovePtBackward(rPam); + + // re-insert content again (first detach m_oUndoNodeIndex!) + SwNodeOffset const nMvNd = m_oUndoNodeIndex->GetIndex(); + m_oUndoNodeIndex.reset(); + MoveFromUndoNds(rDoc, nMvNd, *rPam.GetMark()); + if (m_nDeleteTextNodes != SwNodeOffset(0) || oMvBkwrd) + { + MovePtForward(rPam, ::std::move(oMvBkwrd)); + } + rPam.Exchange(); + + // at-char anchors post SplitNode are on index 0 of 2nd node and will + // remain there - move them back to the start (end would also work?) + if (pFlysAtInsPos) + { + for (SwFrameFormat * pFly : *pFlysAtInsPos) + { + SwFormatAnchor const*const pAnchor = &pFly->GetAnchor(); + if (pAnchor->GetAnchorId() == RndStdIds::FLY_AT_CHAR) + { + SwFormatAnchor anchor(*pAnchor); + anchor.SetAnchor( rPam.GetMark() ); + pFly->SetFormatAttr(anchor); + } + } + } + } + + if (m_pTextFormatColl && rDoc.GetTextFormatColls()->IsAlive(m_pTextFormatColl)) + { + SwTextNode* pTextNd = rPam.GetMark()->GetNode().GetTextNode(); + if( pTextNd ) + pTextNd->ChgFormatColl( m_pTextFormatColl ); + } + m_pTextFormatColl = pSavTextFormatColl; + + if (m_pLastNodeColl && rDoc.GetTextFormatColls()->IsAlive(m_pLastNodeColl) + && rPam.GetPoint()->GetNode() != rPam.GetMark()->GetNode()) + { + SwTextNode* pTextNd = rPam.GetPoint()->GetNode().GetTextNode(); + if( pTextNd ) + pTextNd->ChgFormatColl( m_pLastNodeColl ); + } + + // tdf#108124 the SwHistoryChangeFlyAnchor/SwHistoryFlyCnt must run before + // m_FlyUndos as they were created by DelContentIndex() + m_pHistory->Rollback( &rDoc, m_nSetPos ); + + // tdf#108124 (10/25/2017) + // During UNDO we call SwUndoInsLayFormat::UndoImpl in reverse order, + // firstly for m_FlyUndos[ m_FlyUndos.size()-1 ], etc. + // As absolute node index of fly stored in SwUndoFlyBase::nNdPgPos we + // should recover from Undo in direct order (last should be recovered first) + // During REDO we should recover Flys (Images) in direct order, + // firstly m_FlyUndos[0], then with m_FlyUndos[1] index, etc. + + for (size_t n = 0; m_FlyUndos.size() > n; ++n) + { + m_FlyUndos[n]->RedoImpl(rContext); + } + + if( m_pRedlineData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() )) + { + RedlineFlags eOld = rDoc.getIDocumentRedlineAccess().GetRedlineFlags(); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld & ~RedlineFlags::Ignore ); + rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlineData, rPam ), true); + rDoc.getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld ); + } + else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) && + !rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() ) + rDoc.getIDocumentRedlineAccess().SplitRedline(rPam); +} + +void SwUndoInserts::RepeatImpl(::sw::RepeatContext & rContext) +{ + SwPaM aPam( rContext.GetDoc().GetNodes().GetEndOfContent() ); + SetPaM( aPam ); + SwPaM & rRepeatPaM( rContext.GetRepeatPaM() ); + aPam.GetDoc().getIDocumentContentOperations().CopyRange( aPam, *rRepeatPaM.GetPoint(), SwCopyFlags::CheckPosInFly); +} + +SwUndoInsDoc::SwUndoInsDoc( const SwPaM& rPam ) + : SwUndoInserts( SwUndoId::INSDOKUMENT, rPam ) +{ +} + +SwUndoCpyDoc::SwUndoCpyDoc( const SwPaM& rPam ) + : SwUndoInserts( SwUndoId::COPY, rPam ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3