summaryrefslogtreecommitdiffstats
path: root/sw/source/core/edit
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sw/source/core/edit
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/core/edit')
-rw-r--r--sw/source/core/edit/acorrect.cxx715
-rw-r--r--sw/source/core/edit/autofmt.cxx2916
-rw-r--r--sw/source/core/edit/edatmisc.cxx160
-rw-r--r--sw/source/core/edit/edattr.cxx846
-rw-r--r--sw/source/core/edit/eddel.cxx427
-rw-r--r--sw/source/core/edit/edfcol.cxx2326
-rw-r--r--sw/source/core/edit/edfld.cxx411
-rw-r--r--sw/source/core/edit/edfldexp.cxx58
-rw-r--r--sw/source/core/edit/edfmt.cxx157
-rw-r--r--sw/source/core/edit/edglbldc.cxx374
-rw-r--r--sw/source/core/edit/edglss.cxx324
-rw-r--r--sw/source/core/edit/editsh.cxx1098
-rw-r--r--sw/source/core/edit/edlingu.cxx1731
-rw-r--r--sw/source/core/edit/ednumber.cxx880
-rw-r--r--sw/source/core/edit/edredln.cxx196
-rw-r--r--sw/source/core/edit/edsect.cxx439
-rw-r--r--sw/source/core/edit/edtab.cxx527
-rw-r--r--sw/source/core/edit/edtox.cxx394
-rw-r--r--sw/source/core/edit/edundo.cxx257
-rw-r--r--sw/source/core/edit/edws.cxx331
20 files changed, 14567 insertions, 0 deletions
diff --git a/sw/source/core/edit/acorrect.cxx b/sw/source/core/edit/acorrect.cxx
new file mode 100644
index 0000000000..06ab1231ff
--- /dev/null
+++ b/sw/source/core/edit/acorrect.cxx
@@ -0,0 +1,715 @@
+/* -*- 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 <hintids.hxx>
+
+#include <fmtinfmt.hxx>
+#include <editsh.hxx>
+#include <doc.hxx>
+#include <pam.hxx>
+#include <unocrsr.hxx>
+#include <txatbase.hxx>
+#include <txtfrm.hxx>
+#include <ndtxt.hxx>
+#include <acorrect.hxx>
+#include <shellio.hxx>
+#include <swundo.hxx>
+#include <viscrs.hxx>
+#include <com/sun/star/linguistic2/XHyphenator.hpp>
+#include <com/sun/star/linguistic2/XHyphenatedWord.hpp>
+#include <osl/diagnose.h>
+#include <svl/numformat.hxx>
+
+#include <editeng/acorrcfg.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <rootfrm.hxx>
+
+using namespace ::com::sun::star;
+
+namespace {
+
+class PaMIntoCursorShellRing
+{
+ SwPaM &m_rDelPam, &m_rCursor;
+ SwPaM* m_pPrevDelPam;
+ SwPaM* m_pPrevCursor;
+
+ static void RemoveFromRing( SwPaM& rPam, SwPaM const * pPrev );
+public:
+ PaMIntoCursorShellRing( SwCursorShell& rSh, SwPaM& rCursor, SwPaM& rPam );
+ ~PaMIntoCursorShellRing();
+};
+
+}
+
+PaMIntoCursorShellRing::PaMIntoCursorShellRing(SwCursorShell& rCSh, SwPaM& rShCursor, SwPaM& rPam)
+ : m_rDelPam(rPam)
+ , m_rCursor(rShCursor)
+{
+ SwPaM* pShCursor = rCSh.GetCursor_();
+
+ m_pPrevDelPam = m_rDelPam.GetPrev();
+ m_pPrevCursor = m_rCursor.GetPrev();
+
+ m_rDelPam.GetRingContainer().merge(pShCursor->GetRingContainer());
+ m_rCursor.GetRingContainer().merge(pShCursor->GetRingContainer());
+}
+
+PaMIntoCursorShellRing::~PaMIntoCursorShellRing()
+{
+ // and take out the Pam again:
+ RemoveFromRing(m_rDelPam, m_pPrevDelPam);
+ RemoveFromRing(m_rCursor, m_pPrevCursor);
+}
+
+void PaMIntoCursorShellRing::RemoveFromRing( SwPaM& rPam, SwPaM const * pPrev )
+{
+ SwPaM* p;
+ SwPaM* pNext = &rPam;
+ do {
+ p = pNext;
+ pNext = p->GetNext();
+ p->MoveTo( &rPam );
+ } while( p != pPrev );
+}
+
+SwAutoCorrDoc::SwAutoCorrDoc( SwEditShell& rEditShell, SwPaM& rPam,
+ sal_Unicode cIns )
+ : m_rEditSh( rEditShell ), m_rCursor( rPam )
+ , m_nEndUndoCounter(0)
+ , m_bUndoIdInitialized( cIns == 0 )
+{
+}
+
+SwAutoCorrDoc::~SwAutoCorrDoc()
+{
+ for (int i = 0; i < m_nEndUndoCounter; ++i)
+ {
+ m_rEditSh.EndUndo();
+ }
+}
+
+void SwAutoCorrDoc::DeleteSel( SwPaM& rDelPam )
+{
+ // this should work with plain SwPaM as well because start and end
+ // are always in same node, but since there is GetRanges already...
+ std::vector<std::shared_ptr<SwUnoCursor>> ranges;
+ if (sw::GetRanges(ranges, *m_rEditSh.GetDoc(), rDelPam))
+ {
+ DeleteSelImpl(rDelPam);
+ }
+ else
+ {
+ for (auto const& pCursor : ranges)
+ {
+ DeleteSelImpl(*pCursor);
+ }
+ }
+}
+
+void SwAutoCorrDoc::DeleteSelImpl(SwPaM & rDelPam)
+{
+ SwDoc* pDoc = m_rEditSh.GetDoc();
+ if( pDoc->IsAutoFormatRedline() )
+ {
+ // so that also the DelPam be moved, include it in the
+ // Shell-Cursr-Ring !!
+ // ??? is that really necessary - this should never join nodes, so Update should be enough?
+// PaMIntoCursorShellRing aTmp( rEditSh, rCursor, rDelPam );
+ assert(rDelPam.GetPoint()->GetNode() == rDelPam.GetMark()->GetNode());
+ pDoc->getIDocumentContentOperations().DeleteAndJoin( rDelPam );
+ }
+ else
+ {
+ pDoc->getIDocumentContentOperations().DeleteRange( rDelPam );
+ }
+}
+
+bool SwAutoCorrDoc::Delete( sal_Int32 nStt, sal_Int32 nEnd )
+{
+ SwTextNode const*const pTextNd = m_rCursor.GetPointNode().GetTextNode();
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(
+ pTextNd->getLayoutFrame(m_rEditSh.GetLayout())));
+ assert(pFrame);
+ SwPaM aSel(pFrame->MapViewToModelPos(TextFrameIndex(nStt)),
+ pFrame->MapViewToModelPos(TextFrameIndex(nEnd)));
+ DeleteSel( aSel );
+
+ if( m_bUndoIdInitialized )
+ m_bUndoIdInitialized = true;
+ return true;
+}
+
+bool SwAutoCorrDoc::Insert( sal_Int32 nPos, const OUString& rText )
+{
+ SwTextNode const*const pTextNd = m_rCursor.GetPointNode().GetTextNode();
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(
+ pTextNd->getLayoutFrame(m_rEditSh.GetLayout())));
+ assert(pFrame);
+ SwPaM aPam(pFrame->MapViewToModelPos(TextFrameIndex(nPos)));
+ m_rEditSh.GetDoc()->getIDocumentContentOperations().InsertString( aPam, rText );
+ if( !m_bUndoIdInitialized )
+ {
+ m_bUndoIdInitialized = true;
+ if( 1 == rText.getLength() )
+ {
+ m_rEditSh.StartUndo( SwUndoId::AUTOCORRECT );
+ ++m_nEndUndoCounter;
+ }
+ }
+ return true;
+}
+
+bool SwAutoCorrDoc::Replace( sal_Int32 nPos, const OUString& rText )
+{
+ return ReplaceRange( nPos, rText.getLength(), rText );
+}
+
+bool SwAutoCorrDoc::ReplaceRange( sal_Int32 nPos, sal_Int32 nSourceLength, const OUString& rText )
+{
+ assert(nSourceLength == 1); // sw_redlinehide: this is currently the case,
+ // and ensures that the replace range cannot *contain* delete redlines,
+ // so we don't need something along the lines of:
+ // if (sw::GetRanges(ranges, *rEditSh.GetDoc(), aPam))
+ // ReplaceImpl(...)
+ // else
+ // ReplaceImpl(ranges.begin())
+ // for (ranges.begin() + 1; ranges.end(); )
+ // DeleteImpl(*it)
+
+ SwTextNode * const pNd = m_rCursor.GetPointNode().GetTextNode();
+ if ( !pNd )
+ {
+ return false;
+ }
+
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(
+ pNd->getLayoutFrame(m_rEditSh.GetLayout())));
+ assert(pFrame);
+ std::pair<SwTextNode *, sal_Int32> const pos(pFrame->MapViewToModel(TextFrameIndex(nPos)));
+
+ SwPaM* pPam = &m_rCursor;
+ if (pPam->GetPoint()->GetNode() != *pos.first
+ || pPam->GetPoint()->GetContentIndex() != pos.second)
+ {
+ pPam = new SwPaM(*pos.first, pos.second);
+ }
+
+ // text attributes with dummy characters must not be replaced!
+ bool bDoReplace = true;
+ sal_Int32 const nLen = rText.getLength();
+ for (sal_Int32 n = 0; n < nLen && n + nPos < pFrame->GetText().getLength(); ++n)
+ {
+ sal_Unicode const Char = pFrame->GetText()[n + nPos];
+ if (CH_TXTATR_BREAKWORD == Char || CH_TXTATR_INWORD == Char)
+ {
+ assert(pFrame->MapViewToModel(TextFrameIndex(n+nPos)).first->GetTextAttrForCharAt(pFrame->MapViewToModel(TextFrameIndex(n+nPos)).second));
+ bDoReplace = false;
+ break;
+ }
+ }
+
+ // tdf#83419 avoid bad autocorrect with visible redlines
+ // e.g. replacing the first letter of the tracked deletion
+ // with its capitalized (and not deleted) version.
+ if ( bDoReplace && !pFrame->getRootFrame()->IsHideRedlines() &&
+ m_rEditSh.GetDoc()->getIDocumentRedlineAccess().HasRedline( *pPam, RedlineType::Delete, /*bStartOrEndInRange=*/false ) )
+ {
+ bDoReplace = false;
+ }
+
+ if ( bDoReplace )
+ {
+ SwDoc* pDoc = m_rEditSh.GetDoc();
+
+ if( pDoc->IsAutoFormatRedline() )
+ {
+ if (nPos == pFrame->GetText().getLength()) // at the End do an Insert
+ {
+ pDoc->getIDocumentContentOperations().InsertString( *pPam, rText );
+ }
+ else
+ {
+ assert(pos.second != pos.first->Len()); // must be _before_ char
+ PaMIntoCursorShellRing aTmp( m_rEditSh, m_rCursor, *pPam );
+
+ pPam->SetMark();
+ pPam->GetPoint()->SetContent( std::min<sal_Int32>(
+ pos.first->GetText().getLength(), pos.second + nSourceLength) );
+ pDoc->getIDocumentContentOperations().ReplaceRange( *pPam, rText, false );
+ pPam->Exchange();
+ pPam->DeleteMark();
+ }
+ }
+ else
+ {
+ pPam->SetMark();
+ pPam->GetPoint()->SetContent( std::min<sal_Int32>(
+ pos.first->GetText().getLength(), pos.second + nSourceLength) );
+ pDoc->getIDocumentContentOperations().ReplaceRange( *pPam, rText, false );
+ pPam->Exchange();
+ pPam->DeleteMark();
+ }
+
+ if( m_bUndoIdInitialized )
+ {
+ m_bUndoIdInitialized = true;
+ if( 1 == rText.getLength() )
+ {
+ m_rEditSh.StartUndo( SwUndoId::AUTOCORRECT );
+ ++m_nEndUndoCounter;
+ }
+ }
+ }
+
+ if( pPam != &m_rCursor )
+ delete pPam;
+
+ return true;
+}
+
+void SwAutoCorrDoc::SetAttr( sal_Int32 nStt, sal_Int32 nEnd, sal_uInt16 nSlotId,
+ SfxPoolItem& rItem )
+{
+ SwTextNode const*const pTextNd = m_rCursor.GetPointNode().GetTextNode();
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(
+ pTextNd->getLayoutFrame(m_rEditSh.GetLayout())));
+ assert(pFrame);
+ SwPaM aPam(pFrame->MapViewToModelPos(TextFrameIndex(nStt)),
+ pFrame->MapViewToModelPos(TextFrameIndex(nEnd)));
+
+ SfxItemPool& rPool = m_rEditSh.GetDoc()->GetAttrPool();
+ sal_uInt16 nWhich = rPool.GetWhich( nSlotId, false );
+ if( nWhich )
+ {
+ rItem.SetWhich( nWhich );
+
+ SfxItemSet aSet( rPool, aCharFormatSetRange );
+ SetAllScriptItem( aSet, rItem );
+
+ m_rEditSh.GetDoc()->SetFormatItemByAutoFormat( aPam, aSet );
+
+ if( m_bUndoIdInitialized )
+ m_bUndoIdInitialized = true;
+ }
+}
+
+bool SwAutoCorrDoc::SetINetAttr( sal_Int32 nStt, sal_Int32 nEnd, const OUString& rURL )
+{
+ SwTextNode const*const pTextNd = m_rCursor.GetPointNode().GetTextNode();
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(
+ pTextNd->getLayoutFrame(m_rEditSh.GetLayout())));
+ assert(pFrame);
+ SwPaM aPam(pFrame->MapViewToModelPos(TextFrameIndex(nStt)),
+ pFrame->MapViewToModelPos(TextFrameIndex(nEnd)));
+
+ SfxItemSetFixed<RES_TXTATR_INETFMT, RES_TXTATR_INETFMT>
+ aSet( m_rEditSh.GetDoc()->GetAttrPool() );
+ aSet.Put( SwFormatINetFormat( rURL, OUString() ));
+ m_rEditSh.GetDoc()->SetFormatItemByAutoFormat( aPam, aSet );
+ if( m_bUndoIdInitialized )
+ m_bUndoIdInitialized = true;
+ return true;
+}
+
+/** Return the text of a previous paragraph
+ *
+ * @param bAtNormalPos If <true> before the normal insert position; if <false> in which the
+ * corrected word was inserted. (Doesn't need to be the same paragraph!)
+ * @return text or 0, if previous paragraph does not exists or there are only blankness
+ */
+OUString const* SwAutoCorrDoc::GetPrevPara(bool const bAtNormalPos)
+{
+ OUString const* pStr(nullptr);
+
+ if( bAtNormalPos || !m_oIndex )
+ {
+ m_oIndex.emplace(m_rCursor.GetPoint()->GetNode());
+ }
+ sw::GotoPrevLayoutTextFrame(*m_oIndex, m_rEditSh.GetLayout());
+
+ SwTextFrame const* pFrame(nullptr);
+ for (SwTextNode * pTextNd = m_oIndex->GetNode().GetTextNode();
+ pTextNd; pTextNd = m_oIndex->GetNode().GetTextNode())
+ {
+ pFrame = static_cast<SwTextFrame const*>(
+ pTextNd->getLayoutFrame(m_rEditSh.GetLayout()));
+ if (pFrame && !pFrame->GetText().isEmpty())
+ {
+ break;
+ }
+ sw::GotoPrevLayoutTextFrame(*m_oIndex, m_rEditSh.GetLayout());
+ }
+ if (pFrame && !pFrame->GetText().isEmpty() &&
+ 0 == pFrame->GetTextNodeForParaProps()->GetAttrOutlineLevel())
+ {
+ pStr = & pFrame->GetText();
+ }
+
+ if( m_bUndoIdInitialized )
+ m_bUndoIdInitialized = true;
+
+ return pStr;
+}
+
+bool SwAutoCorrDoc::ChgAutoCorrWord( sal_Int32& rSttPos, sal_Int32 nEndPos,
+ SvxAutoCorrect& rACorrect,
+ OUString* pPara )
+{
+ if( m_bUndoIdInitialized )
+ m_bUndoIdInitialized = true;
+
+ // Found a beginning of a paragraph or a Blank,
+ // search for the word Kuerzel (Shortcut) in the Auto
+ SwTextNode* pTextNd = m_rCursor.GetPointNode().GetTextNode();
+ OSL_ENSURE( pTextNd, "where is the TextNode?" );
+
+ bool bRet = false;
+ if( nEndPos == rSttPos )
+ return bRet;
+
+ LanguageType eLang = GetLanguage(nEndPos);
+ if(LANGUAGE_SYSTEM == eLang)
+ eLang = GetAppLanguage();
+ LanguageTag aLanguageTag( eLang);
+
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(
+ pTextNd->getLayoutFrame(m_rEditSh.GetLayout())));
+ assert(pFrame);
+
+ const OUString sFrameText = pFrame->GetText();
+ const SvxAutocorrWord* pFnd = rACorrect.SearchWordsInList(
+ sFrameText, rSttPos, nEndPos, *this, aLanguageTag);
+ SwDoc* pDoc = m_rEditSh.GetDoc();
+ if( pFnd )
+ {
+ // replace also last colon of keywords surrounded by colons (for example, ":name:")
+ const bool replaceLastChar = sFrameText.getLength() > nEndPos && pFnd->GetShort()[0] == ':'
+ && pFnd->GetShort().endsWith(":");
+
+ SwPosition aStartPos( pFrame->MapViewToModelPos(TextFrameIndex(rSttPos) ));
+ SwPosition aEndPos( pFrame->MapViewToModelPos(TextFrameIndex(nEndPos + (replaceLastChar ? 1 : 0))) );
+ SwPaM aPam(aStartPos, aEndPos);
+
+ // don't replace, if a redline starts or ends within the original text
+ if ( pDoc->getIDocumentRedlineAccess().HasRedline( aPam, RedlineType::Any, /*bStartOrEndInRange=*/true ) )
+ {
+ return bRet;
+ }
+
+ if( pFnd->IsTextOnly() )
+ {
+ //JP 22.04.99: Bug 63883 - Special treatment for dots.
+ const bool bLastCharIsPoint
+ = nEndPos < sFrameText.getLength() && ('.' == sFrameText[nEndPos]);
+ if( !bLastCharIsPoint || pFnd->GetLong().isEmpty() ||
+ '.' != pFnd->GetLong()[ pFnd->GetLong().getLength() - 1 ] )
+ {
+ // replace the selection
+ std::vector<std::shared_ptr<SwUnoCursor>> ranges;
+ if (sw::GetRanges(ranges, *m_rEditSh.GetDoc(), aPam))
+ {
+ pDoc->getIDocumentContentOperations().ReplaceRange(aPam, pFnd->GetLong(), false);
+ bRet = true;
+ }
+ else if (!ranges.empty())
+ {
+ assert(ranges.front()->GetPoint()->GetNode() == ranges.front()->GetMark()->GetNode());
+ pDoc->getIDocumentContentOperations().ReplaceRange(
+ *ranges.front(), pFnd->GetLong(), false);
+ for (auto it = ranges.begin() + 1; it != ranges.end(); ++it)
+ {
+ DeleteSelImpl(**it);
+ }
+ bRet = true;
+ }
+
+ // tdf#83260 After calling sw::DocumentContentOperationsManager::ReplaceRange
+ // pTextNd may become invalid when change tracking is on and Edit -> Track Changes -> Show == OFF.
+ // ReplaceRange shows changes, this moves deleted nodes from special section to document.
+ // Then Show mode is disabled again. As a result pTextNd may be invalidated.
+ pTextNd = m_rCursor.GetPointNode().GetTextNode();
+ }
+ }
+ else
+ {
+ SwTextBlocks aTBlks( rACorrect.GetAutoCorrFileName( aLanguageTag, false, true ));
+ sal_uInt16 nPos = aTBlks.GetIndex( pFnd->GetShort() );
+ if( USHRT_MAX != nPos && aTBlks.BeginGetDoc( nPos ) )
+ {
+ DeleteSel( aPam );
+ pDoc->DontExpandFormat( *aPam.GetPoint() );
+
+ if( pPara )
+ {
+ OSL_ENSURE( !m_oIndex, "who has not deleted his Index?" );
+ m_oIndex.emplace(m_rCursor.GetPoint()->GetNode());
+ sw::GotoPrevLayoutTextFrame(*m_oIndex, m_rEditSh.GetLayout());
+ }
+
+ SwDoc* pAutoDoc = aTBlks.GetDoc();
+ SwNodeIndex aSttIdx( pAutoDoc->GetNodes().GetEndOfExtras(), 1 );
+ SwContentNode* pContentNd = pAutoDoc->GetNodes().GoNext( &aSttIdx );
+ SwPaM aCpyPam( aSttIdx );
+
+ const SwTableNode* pTableNd = pContentNd->FindTableNode();
+ if( pTableNd )
+ {
+ aCpyPam.GetPoint()->Assign( *pTableNd );
+ }
+ aCpyPam.SetMark();
+
+ // then until the end of the Nodes Array
+ aCpyPam.GetPoint()->Assign( pAutoDoc->GetNodes().GetEndOfContent(), SwNodeOffset(-1) );
+ pContentNd = aCpyPam.GetPointContentNode();
+ if (pContentNd)
+ aCpyPam.GetPoint()->SetContent( pContentNd->Len() );
+
+ SwDontExpandItem aExpItem;
+ aExpItem.SaveDontExpandItems( *aPam.GetPoint() );
+
+ pAutoDoc->getIDocumentContentOperations().CopyRange(aCpyPam, *aPam.GetPoint(), SwCopyFlags::CheckPosInFly);
+
+ aExpItem.RestoreDontExpandItems( *aPam.GetPoint() );
+
+ if( pPara )
+ {
+ sw::GotoNextLayoutTextFrame(*m_oIndex, m_rEditSh.GetLayout());
+ pTextNd = m_oIndex->GetNode().GetTextNode();
+ }
+ bRet = true;
+ }
+ aTBlks.EndGetDoc();
+ }
+ }
+
+ if( bRet && pPara && pTextNd )
+ {
+ SwTextFrame const*const pNewFrame(static_cast<SwTextFrame const*>(
+ pTextNd->getLayoutFrame(m_rEditSh.GetLayout())));
+ *pPara = pNewFrame->GetText();
+ }
+
+ return bRet;
+}
+
+bool SwAutoCorrDoc::TransliterateRTLWord( sal_Int32& rSttPos, sal_Int32 nEndPos, bool bApply )
+{
+ if( m_bUndoIdInitialized )
+ m_bUndoIdInitialized = true;
+
+ SwTextNode* pTextNd = m_rCursor.GetPointNode().GetTextNode();
+ OSL_ENSURE( pTextNd, "where is the TextNode?" );
+
+ bool bRet = false;
+ if( nEndPos == rSttPos )
+ return bRet;
+
+ LanguageType eLang = GetLanguage(nEndPos);
+ if(LANGUAGE_SYSTEM == eLang)
+ eLang = GetAppLanguage();
+ LanguageTag aLanguageTag(eLang);
+
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(
+ pTextNd->getLayoutFrame(m_rEditSh.GetLayout())));
+ assert(pFrame);
+
+ const OUString sFrameText = pFrame->GetText();
+ SwDoc* pDoc = m_rEditSh.GetDoc();
+ if ( pFrame->IsRightToLeft() || bApply )
+ {
+ // transliterate to Old Hungarian using Numbertext via NatNum12 number format modifier
+ OUString sWord(sFrameText.copy(rSttPos, nEndPos - rSttPos));
+ // Consonant disambiguation using hyphenation
+ uno::Reference< linguistic2::XHyphenator > xHyph;
+ xHyph = ::GetHyphenator();
+ OUStringBuffer sDisambiguatedWord;
+
+ const ::css::uno::Sequence< ::css::beans::PropertyValue > aProperties;
+ css::uno::Reference< css::linguistic2::XHyphenatedWord > xHyphWord;
+ for (int i = 0; i+1 < sWord.getLength(); i++ )
+ {
+ xHyphWord = xHyph->hyphenate( sWord,
+ aLanguageTag.getLocale(),
+ i,
+ aProperties );
+ // insert ZWSP at a hyphenation point, if it's not an alternative one (i.e. ssz->sz-sz)
+ if (xHyphWord.is() && xHyphWord->getHyphenationPos()+1 == i && !xHyphWord->isAlternativeSpelling())
+ {
+ sDisambiguatedWord.append(CHAR_ZWSP);
+ }
+ sDisambiguatedWord.append(sWord[i]);
+ }
+ sDisambiguatedWord.append(sWord[sWord.getLength()-1]);
+
+ SvNumberFormatter* pFormatter = pDoc->GetNumberFormatter();
+ OUString sConverted;
+ if (pFormatter && !sWord.isEmpty())
+ {
+ const Color* pColor = nullptr;
+
+ // Send text as NatNum12 prefix: "word" -> "[NatNum12 word]0"
+
+ // Closing bracket doesn't allowed in NatNum parameters, remove it from transliteration:
+ // "[word]" -> "[NatNum12 [word]0"
+ bool bHasBracket = sWord.endsWith("]");
+ if ( !bHasBracket )
+ sDisambiguatedWord.append("]");
+ OUString sPrefix("[NatNum12 " + sDisambiguatedWord + "0");
+ if (pFormatter->GetPreviewString(sPrefix, 0, sConverted, &pColor, LANGUAGE_USER_HUNGARIAN_ROVAS))
+ {
+ if ( bHasBracket )
+ sConverted = sConverted + "]";
+ bRet = true;
+ }
+ }
+
+ SwPaM aPam(pFrame->MapViewToModelPos(TextFrameIndex(rSttPos)),
+ pFrame->MapViewToModelPos(TextFrameIndex(nEndPos)));
+ if (bRet && nEndPos <= sFrameText.getLength())
+ pDoc->getIDocumentContentOperations().ReplaceRange(aPam, sConverted, false);
+ }
+
+ return bRet;
+}
+
+// Called by the functions:
+// - FnCapitalStartWord
+// - FnCapitalStartSentence
+// after the exchange of characters. Then the words, if necessary, can be inserted
+// into the exception list.
+void SwAutoCorrDoc::SaveCpltSttWord( ACFlags nFlag, sal_Int32 nPos,
+ const OUString& rExceptWord,
+ sal_Unicode cChar )
+{
+ SwNodeOffset nNode = m_oIndex ? m_oIndex->GetIndex() : m_rCursor.GetPoint()->GetNodeIndex();
+ LanguageType eLang = GetLanguage(nPos);
+ m_rEditSh.GetDoc()->SetAutoCorrExceptWord( std::make_unique<SwAutoCorrExceptWord>( nFlag,
+ nNode, nPos, rExceptWord, cChar, eLang ));
+}
+
+LanguageType SwAutoCorrDoc::GetLanguage( sal_Int32 nPos ) const
+{
+ LanguageType eRet = LANGUAGE_SYSTEM;
+
+ SwTextNode* pNd = m_rCursor.GetPoint()->GetNode().GetTextNode();
+
+ if( pNd )
+ {
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(
+ pNd->getLayoutFrame(m_rEditSh.GetLayout())));
+ assert(pFrame);
+ eRet = pFrame->GetLangOfChar(TextFrameIndex(nPos), 0, true);
+ }
+ if(LANGUAGE_SYSTEM == eRet)
+ eRet = GetAppLanguage();
+ return eRet;
+}
+
+void SwAutoCorrExceptWord::CheckChar( const SwPosition& rPos, sal_Unicode cChr )
+{
+ // test only if this is an improvement.
+ // If yes, then add the word to the list.
+ if (m_cChar == cChr && rPos.GetNodeIndex() == m_nNode && rPos.GetContentIndex() == m_nContent)
+ {
+ // get the current autocorrection:
+ SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect();
+
+ // then add to the list:
+ if (ACFlags::CapitalStartWord & m_nFlags)
+ pACorr->AddWordStartException(m_sWord, m_eLanguage);
+ else if (ACFlags::CapitalStartSentence & m_nFlags)
+ pACorr->AddCplSttException(m_sWord, m_eLanguage);
+ }
+}
+
+bool SwAutoCorrExceptWord::CheckDelChar( const SwPosition& rPos )
+{
+ bool bRet = false;
+ if (!m_bDeleted && rPos.GetNodeIndex() == m_nNode && rPos.GetContentIndex() == m_nContent)
+ m_bDeleted = bRet = true;
+ return bRet;
+}
+
+SwDontExpandItem::~SwDontExpandItem()
+{
+}
+
+void SwDontExpandItem::SaveDontExpandItems( const SwPosition& rPos )
+{
+ const SwTextNode* pTextNd = rPos.GetNode().GetTextNode();
+ if( pTextNd )
+ {
+ m_pDontExpandItems.reset( new SfxItemSet( const_cast<SwDoc&>(pTextNd->GetDoc()).GetAttrPool(),
+ aCharFormatSetRange ) );
+ const sal_Int32 n = rPos.GetContentIndex();
+ if (!pTextNd->GetParaAttr( *m_pDontExpandItems, n, n,
+ n != pTextNd->GetText().getLength() ))
+ {
+ m_pDontExpandItems.reset();
+ }
+ }
+}
+
+void SwDontExpandItem::RestoreDontExpandItems( const SwPosition& rPos )
+{
+ SwTextNode* pTextNd = rPos.GetNode().GetTextNode();
+ if( !pTextNd )
+ return;
+
+ const sal_Int32 nStart = rPos.GetContentIndex();
+ if( nStart == pTextNd->GetText().getLength() )
+ pTextNd->FormatToTextAttr( pTextNd );
+
+ if( !(pTextNd->GetpSwpHints() && pTextNd->GetpSwpHints()->Count()) )
+ return;
+
+ const size_t nSize = pTextNd->GetpSwpHints()->Count();
+ sal_Int32 nAttrStart;
+
+ for( size_t n = 0; n < nSize; ++n )
+ {
+ SwTextAttr* pHt = pTextNd->GetpSwpHints()->Get( n );
+ nAttrStart = pHt->GetStart();
+ if( nAttrStart > nStart ) // beyond the area
+ break;
+
+ const sal_Int32* pAttrEnd;
+ if( nullptr != ( pAttrEnd = pHt->End() ) &&
+ ( ( nAttrStart < nStart &&
+ ( pHt->DontExpand() ? nStart < *pAttrEnd
+ : nStart <= *pAttrEnd )) ||
+ ( nStart == nAttrStart &&
+ ( nAttrStart == *pAttrEnd || !nStart ))) )
+ {
+ const SfxPoolItem* pItem;
+ if( !m_pDontExpandItems || SfxItemState::SET != m_pDontExpandItems->
+ GetItemState( pHt->Which(), false, &pItem ) ||
+ *pItem != pHt->GetAttr() )
+ {
+ // The attribute was not previously set in this form in the
+ // paragraph, so it can only be created through insert/copy
+ // Because of that it is a candidate for DontExpand
+ pHt->SetDontExpand( true );
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/autofmt.cxx b/sw/source/core/edit/autofmt.cxx
new file mode 100644
index 0000000000..6fafe1310d
--- /dev/null
+++ b/sw/source/core/edit/autofmt.cxx
@@ -0,0 +1,2916 @@
+/* -*- 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 <hintids.hxx>
+
+#include <osl/diagnose.h>
+#include <unotools/charclass.hxx>
+
+#include <editeng/boxitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/formatbreakitem.hxx>
+#include <editeng/adjustitem.hxx>
+#include <editeng/tstpitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/acorrcfg.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <swwait.hxx>
+#include <fmtpdsc.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <DocumentRedlineManager.hxx>
+#include <IDocumentStylePoolAccess.hxx>
+#include <redline.hxx>
+#include <unocrsr.hxx>
+#include <docary.hxx>
+#include <editsh.hxx>
+#include <contentindex.hxx>
+#include <pam.hxx>
+#include <swundo.hxx>
+#include <poolfmt.hxx>
+#include <ndtxt.hxx>
+#include <rootfrm.hxx>
+#include <txtfrm.hxx>
+#include <frminf.hxx>
+#include <pagedesc.hxx>
+#include <paratr.hxx>
+#include <acorrect.hxx>
+#include <shellres.hxx>
+#include <section.hxx>
+#include <frmatr.hxx>
+#include <charatr.hxx>
+#include <mdiexp.hxx>
+#include <strings.hrc>
+#include <comcore.hxx>
+#include <numrule.hxx>
+#include <itabenum.hxx>
+
+#include <memory>
+#include <utility>
+
+using namespace ::com::sun::star;
+
+//JP 16.12.99: definition:
+// from pos cPosEnDash to cPosEmDash all chars changed to em dashes,
+// from pos cPosEmDash to cPosEnd all chars changed to em dashes
+// all other chars are changed to the user configuration
+
+const sal_Unicode pBulletChar[6] = { '+', '*', '-', 0x2013, 0x2014, 0 };
+const int cnPosEnDash = 2, cnPosEmDash = 4;
+
+const sal_Unicode cStarSymbolEnDash = 0x2013;
+const sal_Unicode cStarSymbolEmDash = 0x2014;
+
+SvxSwAutoFormatFlags* SwEditShell::s_pAutoFormatFlags = nullptr;
+
+// Number of num-/bullet-paragraph templates. MAXLEVEL will soon be raised
+// to x, but not the number of templates. (Artifact from <= 4.0)
+const sal_uInt16 cnNumBullColls = 4;
+
+class SwAutoFormat
+{
+ SvxSwAutoFormatFlags m_aFlags;
+ SwPaM m_aDelPam; // a Pam that can be used
+ SwNodeIndex m_aNdIdx; // the index on the current TextNode
+ SwNodeIndex m_aEndNdIdx; // index on the end of the area
+
+ SwEditShell* m_pEditShell;
+ SwDoc* m_pDoc;
+ SwTextNode* m_pCurTextNd; // the current TextNode
+ SwTextFrame* m_pCurTextFrame; // frame of the current TextNode
+ bool m_bIsRightToLeft; // text direction of the current frame
+ SwNodeOffset m_nEndNdIdx; // for the percentage-display
+ mutable std::optional<CharClass> m_oCharClass; // Character classification
+ mutable LanguageType m_eCharClassLang;
+
+ sal_uInt16 m_nRedlAutoFormatSeqId;
+
+ enum
+ {
+ NONE = 0,
+ DELIM = 1,
+ DIGIT = 2,
+ CHG = 4,
+ LOWER_ALPHA = 8,
+ UPPER_ALPHA = 16,
+ LOWER_ROMAN = 32,
+ UPPER_ROMAN = 64,
+ NO_DELIM = (DIGIT|LOWER_ALPHA|UPPER_ALPHA|LOWER_ROMAN|UPPER_ROMAN)
+ };
+
+ bool m_bEnd : 1;
+ bool m_bMoreLines : 1;
+
+ CharClass& GetCharClass( LanguageType eLang ) const
+ {
+ if( !m_oCharClass || eLang != m_eCharClassLang )
+ {
+ m_oCharClass.emplace( LanguageTag( eLang ) );
+ m_eCharClassLang = eLang;
+ }
+ return *m_oCharClass;
+ }
+
+ static bool IsSpace( const sal_Unicode c )
+ { return (' ' == c || '\t' == c || 0x0a == c|| 0x3000 == c /* Jap. space */); }
+
+ void SetColl( sal_uInt16 nId, bool bHdLineOrText = false );
+ void GoNextPara();
+ static bool HasObjects(const SwTextFrame &);
+
+ // TextNode methods
+ const SwTextFrame * GetNextNode(bool isCheckEnd = true) const;
+ static bool IsEmptyLine(const SwTextFrame & rFrame)
+ {
+ return rFrame.GetText().isEmpty()
+ || rFrame.GetText().getLength() == GetLeadingBlanks(rFrame.GetText());
+ }
+
+ bool IsOneLine(const SwTextFrame &) const;
+ bool IsFastFullLine(const SwTextFrame &) const;
+ bool IsNoAlphaLine(const SwTextFrame &) const;
+ bool IsEnumericChar(const SwTextFrame &) const;
+ static bool IsBlanksInString(const SwTextFrame&);
+ sal_uInt16 CalcLevel(const SwTextFrame&, sal_uInt16 *pDigitLvl = nullptr) const;
+ sal_Int32 GetBigIndent(TextFrameIndex & rCurrentSpacePos) const;
+
+ static OUString DelLeadingBlanks(const OUString& rStr);
+ static OUString DelTrailingBlanks( const OUString& rStr );
+ static sal_Int32 GetLeadingBlanks( std::u16string_view aStr );
+ static sal_Int32 GetTrailingBlanks( std::u16string_view aStr );
+
+ bool IsFirstCharCapital(const SwTextFrame & rNd) const;
+ sal_uInt16 GetDigitLevel(const SwTextFrame& rFrame, TextFrameIndex& rPos,
+ OUString* pPrefix = nullptr, OUString* pPostfix = nullptr,
+ OUString* pNumTypes = nullptr ) const;
+ /// get the FORMATTED TextFrame
+ SwTextFrame* GetFrame( const SwTextNode& rTextNd ) const;
+ SwTextFrame * EnsureFormatted(SwTextFrame const&) const;
+
+ void BuildIndent();
+ void BuildText();
+ void BuildTextIndent();
+ void BuildEnum( sal_uInt16 nLvl, sal_uInt16 nDigitLevel );
+ void BuildNegIndent( SwTwips nSpaces );
+ void BuildHeadLine( sal_uInt16 nLvl );
+
+ static bool HasBreakAttr(const SwTextFrame &);
+ void DeleteSel( SwPaM& rPam );
+ void DeleteSelImpl(SwPaM & rDelPam, SwPaM & rPamToCorrect);
+ bool DeleteJoinCurNextPara(SwTextFrame const* pNextFrame, bool bIgnoreLeadingBlanks = false);
+ /// delete in the node start and/or end
+ void DeleteLeadingTrailingBlanks( bool bStart = true, bool bEnd = true );
+ void DelEmptyLine( bool bTstNextPara = true );
+ /// when using multiline paragraphs delete the "left" and/or
+ /// "right" margins
+ void DelMoreLinesBlanks( bool bWithLineBreaks = false );
+ /// join with the previous paragraph
+ void JoinPrevPara();
+ /// execute AutoCorrect on current TextNode
+ void AutoCorrect(TextFrameIndex nSttPos = TextFrameIndex(0));
+
+ bool CanJoin(const SwTextFrame * pNextFrame) const
+ {
+ return !m_bEnd && pNextFrame
+ && !IsEmptyLine(*pNextFrame)
+ && !IsNoAlphaLine(*pNextFrame)
+ && !IsEnumericChar(*pNextFrame)
+ // check the last / first nodes here...
+ && ((COMPLETE_STRING - 50 - pNextFrame->GetTextNodeFirst()->GetText().getLength())
+ > (m_pCurTextFrame->GetMergedPara()
+ ? m_pCurTextFrame->GetMergedPara()->pLastNode
+ : m_pCurTextNd)->GetText().getLength())
+ && !HasBreakAttr(*pNextFrame);
+ }
+
+ /// is a dot at the end ??
+ static bool IsSentenceAtEnd(const SwTextFrame & rTextFrame);
+
+ bool DoUnderline();
+ bool DoTable();
+
+ void SetRedlineText_( sal_uInt16 nId );
+ bool SetRedlineText( sal_uInt16 nId ) {
+ if( m_aFlags.bWithRedlining )
+ SetRedlineText_( nId );
+ return true;
+ }
+ void ClearRedlineText() {
+ if( m_aFlags.bWithRedlining )
+ m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment(nullptr);
+ }
+
+public:
+ SwAutoFormat( SwEditShell* pEdShell, SvxSwAutoFormatFlags aFlags,
+ SwNode const * pSttNd = nullptr, SwNode const * pEndNd = nullptr );
+};
+
+static const sal_Unicode* StrChr( const sal_Unicode* pSrc, sal_Unicode c )
+{
+ while( *pSrc && *pSrc != c )
+ ++pSrc;
+ return *pSrc ? pSrc : nullptr;
+}
+
+SwTextFrame* SwAutoFormat::GetFrame( const SwTextNode& rTextNd ) const
+{
+ // get the Frame
+ const SwContentFrame *pFrame = rTextNd.getLayoutFrame( m_pEditShell->GetLayout() );
+ assert(pFrame && "For Autoformat a Layout is needed");
+ return EnsureFormatted(*static_cast<SwTextFrame const*>(pFrame));
+}
+
+SwTextFrame * SwAutoFormat::EnsureFormatted(SwTextFrame const& rFrame) const
+{
+ SwTextFrame *const pFrame(const_cast<SwTextFrame*>(&rFrame));
+ if( m_aFlags.bAFormatByInput && !pFrame->isFrameAreaDefinitionValid() )
+ {
+ DisableCallbackAction a(*pFrame->getRootFrame());
+ SwRect aTmpFrame( pFrame->getFrameArea() );
+ SwRect aTmpPrt( pFrame->getFramePrintArea() );
+ pFrame->Calc(pFrame->getRootFrame()->GetCurrShell()->GetOut());
+
+ if( pFrame->getFrameArea() != aTmpFrame || pFrame->getFramePrintArea() != aTmpPrt ||
+ !pFrame->GetPaintSwRect().IsEmpty())
+ {
+ pFrame->SetCompletePaint();
+ }
+ }
+
+ return pFrame->GetFormatted();
+}
+
+void SwAutoFormat::SetRedlineText_( sal_uInt16 nActionId )
+{
+ OUString sText;
+ sal_uInt16 nSeqNo = 0;
+ if( STR_AUTOFMTREDL_END > nActionId )
+ {
+ sText = SwViewShell::GetShellRes()->GetAutoFormatNameLst()[ nActionId ];
+ switch( nActionId )
+ {
+ case STR_AUTOFMTREDL_SET_NUMBER_BULLET:
+ case STR_AUTOFMTREDL_DEL_MORELINES:
+
+ // AutoCorrect actions
+ case STR_AUTOFMTREDL_USE_REPLACE:
+ case STR_AUTOFMTREDL_CPTL_STT_WORD:
+ case STR_AUTOFMTREDL_CPTL_STT_SENT:
+ case STR_AUTOFMTREDL_TYPO:
+ case STR_AUTOFMTREDL_UNDER:
+ case STR_AUTOFMTREDL_BOLD:
+ case STR_AUTOFMTREDL_FRACTION:
+ case STR_AUTOFMTREDL_DASH:
+ case STR_AUTOFMTREDL_ORDINAL:
+ case STR_AUTOFMTREDL_NON_BREAK_SPACE:
+ case STR_AUTOFMTREDL_TRANSLITERATE_RTL:
+ case STR_AUTOFMTREDL_ITALIC:
+ case STR_AUTOFMTREDL_STRIKETHROUGH:
+ nSeqNo = ++m_nRedlAutoFormatSeqId;
+ break;
+ }
+ }
+#if OSL_DEBUG_LEVEL > 0
+ else
+ sText = "Action text is missing";
+#endif
+
+ m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment( &sText, nSeqNo );
+}
+
+void SwAutoFormat::GoNextPara()
+{
+ SwNode* pNewNd = nullptr;
+ do {
+ // has to be checked twice before and after incrementation
+ if( m_aNdIdx.GetIndex() >= m_aEndNdIdx.GetIndex() )
+ {
+ m_bEnd = true;
+ return;
+ }
+
+ sw::GotoNextLayoutTextFrame(m_aNdIdx, m_pEditShell->GetLayout());
+ if( m_aNdIdx.GetIndex() >= m_aEndNdIdx.GetIndex() )
+ {
+ m_bEnd = true;
+ return;
+ }
+ else
+ pNewNd = &m_aNdIdx.GetNode();
+
+ // not a TextNode ->
+ // TableNode : skip table
+ // NoTextNode : skip nodes
+ // EndNode : at the end, terminate
+ if( pNewNd->IsEndNode() )
+ {
+ m_bEnd = true;
+ return;
+ }
+ else if( pNewNd->IsTableNode() )
+ m_aNdIdx = *pNewNd->EndOfSectionNode();
+ else if( pNewNd->IsSectionNode() )
+ {
+ const SwSection& rSect = pNewNd->GetSectionNode()->GetSection();
+ if( rSect.IsHiddenFlag() || rSect.IsProtectFlag() )
+ m_aNdIdx = *pNewNd->EndOfSectionNode();
+ }
+ } while( !pNewNd->IsTextNode() );
+
+ if( !m_aFlags.bAFormatByInput )
+ ::SetProgressState( sal_Int32(m_aNdIdx.GetIndex() + m_nEndNdIdx - m_aEndNdIdx.GetIndex()),
+ m_pDoc->GetDocShell() );
+
+ m_pCurTextNd = static_cast<SwTextNode*>(pNewNd);
+ m_pCurTextFrame = GetFrame( *m_pCurTextNd );
+ m_bIsRightToLeft = m_pCurTextFrame->IsRightToLeft();
+}
+
+bool SwAutoFormat::HasObjects(const SwTextFrame & rFrame)
+{
+ // Is there something bound to the paragraph in the paragraph
+ // like Frames, DrawObjects, ...
+ SwNodeIndex node(*rFrame.GetTextNodeFirst());
+ do
+ {
+ if (!node.GetNode().GetAnchoredFlys().empty())
+ return true;
+ ++node;
+ }
+ while (sw::FrameContainsNode(rFrame, node.GetIndex()));
+ return false;
+}
+
+const SwTextFrame* SwAutoFormat::GetNextNode(bool const isCheckEnd) const
+{
+ SwNodeIndex tmp(m_aNdIdx);
+ sw::GotoNextLayoutTextFrame(tmp, m_pEditShell->GetLayout());
+ if ((isCheckEnd && m_aEndNdIdx <= tmp) || !tmp.GetNode().IsTextNode())
+ return nullptr;
+ // note: the returned frame is not necessarily formatted, have to call
+ // EnsureFormatted for that
+ return static_cast<SwTextFrame*>(tmp.GetNode().GetTextNode()->getLayoutFrame(m_pEditShell->GetLayout()));
+}
+
+bool SwAutoFormat::IsOneLine(const SwTextFrame & rFrame) const
+{
+ SwTextFrameInfo aFInfo( EnsureFormatted(rFrame) );
+ return aFInfo.IsOneLine();
+}
+
+bool SwAutoFormat::IsFastFullLine(const SwTextFrame & rFrame) const
+{
+ bool bRet = m_aFlags.bRightMargin;
+ if( bRet )
+ {
+ SwTextFrameInfo aFInfo( EnsureFormatted(rFrame) );
+ bRet = aFInfo.IsFilled( m_aFlags.nRightMargin );
+ }
+ return bRet;
+}
+
+bool SwAutoFormat::IsEnumericChar(const SwTextFrame& rFrame) const
+{
+ const OUString& rText = rFrame.GetText();
+ bool bIsShortBullet = rText == "* " || rText == "- ";
+ sal_uInt16 nMinLen = bIsShortBullet ? 1 : 2;
+ TextFrameIndex nBlanks(GetLeadingBlanks(rText));
+ const TextFrameIndex nLen = TextFrameIndex(rText.getLength()) - nBlanks;
+ if( !nLen )
+ return false;
+
+ // -, +, * separated by blank ??
+ if (TextFrameIndex(nMinLen) < nLen && IsSpace(rText[sal_Int32(nBlanks) + 1]))
+ {
+ if (StrChr(pBulletChar, rText[sal_Int32(nBlanks)]))
+ return true;
+ // Should there be a symbol font at the position?
+ SwTextFrameInfo aFInfo( EnsureFormatted(rFrame) );
+ if (aFInfo.IsBullet(nBlanks))
+ return true;
+ }
+
+ // 1.) / 1. / 1.1.1 / (1). / (1) / ...
+ return USHRT_MAX != GetDigitLevel(rFrame, nBlanks);
+}
+
+bool SwAutoFormat::IsBlanksInString(const SwTextFrame& rFrame)
+{
+ // Search more than 5 consecutive blanks/tabs in the string.
+ OUString sTmp( DelLeadingBlanks(rFrame.GetText()) );
+ const sal_Int32 nLen = sTmp.getLength();
+ sal_Int32 nIdx = 0;
+ while (nIdx < nLen)
+ {
+ // Skip non-blanks
+ while (nIdx < nLen && !IsSpace(sTmp[nIdx])) ++nIdx;
+ if (nIdx == nLen)
+ return false;
+ // Then count consecutive blanks
+ const sal_Int32 nFirst = nIdx;
+ while (nIdx < nLen && IsSpace(sTmp[nIdx])) ++nIdx;
+ // And exit if enough consecutive blanks were found
+ if (nIdx-nFirst > 5)
+ return true;
+ }
+ return false;
+}
+
+sal_uInt16 SwAutoFormat::CalcLevel(const SwTextFrame & rFrame,
+ sal_uInt16 *const pDigitLvl) const
+{
+ sal_uInt16 nLvl = 0, nBlnk = 0;
+ const OUString& rText = rFrame.GetText();
+ if( pDigitLvl )
+ *pDigitLvl = USHRT_MAX;
+
+ if (RES_POOLCOLL_TEXT_MOVE == rFrame.GetTextNodeForParaProps()->GetTextColl()->GetPoolFormatId())
+ {
+ if( m_aFlags.bAFormatByInput )
+ {
+ // this is very non-obvious: on the *first* invocation of
+ // AutoFormat, the node will have the tabs (any number) converted
+ // to a fixed indent in BuildTextIndent(), and the number of tabs
+ // is stored in the node;
+ // on the *second* invocation of AutoFormat, CalcLevel() will
+ // retrieve the stored number, and it will be used by
+ // BuildHeadLine() to select the corresponding heading style.
+ nLvl = rFrame.GetTextNodeForParaProps()->GetAutoFormatLvl();
+ const_cast<SwTextNode *>(rFrame.GetTextNodeForParaProps())->SetAutoFormatLvl(0);
+ if( nLvl )
+ return nLvl;
+ }
+ ++nLvl;
+ }
+
+ for (TextFrameIndex n(0),
+ nEnd(rText.getLength()); n < nEnd; ++n)
+ {
+ switch (rText[sal_Int32(n)])
+ {
+ case ' ': if( 3 == ++nBlnk )
+ {
+ ++nLvl;
+ nBlnk = 0;
+ }
+ break;
+ case '\t': ++nLvl;
+ nBlnk = 0;
+ break;
+ default:
+ if( pDigitLvl )
+ // test 1.) / 1. / 1.1.1 / (1). / (1) / ...
+ *pDigitLvl = GetDigitLevel(rFrame, n);
+ return nLvl;
+ }
+ }
+ return nLvl;
+}
+
+sal_Int32 SwAutoFormat::GetBigIndent(TextFrameIndex & rCurrentSpacePos) const
+{
+ SwTextFrameInfo aFInfo( m_pCurTextFrame );
+ const SwTextFrame* pNextFrame = nullptr;
+
+ if( !m_bMoreLines )
+ {
+ pNextFrame = GetNextNode();
+ if (!CanJoin(pNextFrame) || !IsOneLine(*pNextFrame))
+ return 0;
+
+ pNextFrame = EnsureFormatted(*pNextFrame);
+ }
+
+ return aFInfo.GetBigIndent( rCurrentSpacePos, pNextFrame );
+}
+
+bool SwAutoFormat::IsNoAlphaLine(const SwTextFrame & rFrame) const
+{
+ const OUString& rStr = rFrame.GetText();
+ if( rStr.isEmpty() )
+ return false;
+ // or better: determine via number of AlphaNum and !AlphaNum characters
+ sal_Int32 nANChar = 0, nBlnk = 0;
+
+ for (TextFrameIndex n(0),
+ nEnd(rStr.getLength()); n < nEnd; ++n)
+ if (IsSpace(rStr[sal_Int32(n)]))
+ ++nBlnk;
+ else
+ {
+ auto const pair = rFrame.MapViewToModel(n);
+ CharClass& rCC = GetCharClass(pair.first->GetSwAttrSet().GetLanguage().GetLanguage());
+ if (rCC.isLetterNumeric(rStr, sal_Int32(n)))
+ ++nANChar;
+ }
+
+ // If there are 75% of non-alphanumeric characters, then true
+ sal_uLong nLen = rStr.getLength() - nBlnk;
+ nLen = ( nLen * 3 ) / 4; // long overflow, if the strlen > sal_uInt16
+ return sal_Int32(nLen) < (rStr.getLength() - nANChar - nBlnk);
+}
+
+bool SwAutoFormat::DoUnderline()
+{
+ if( !m_aFlags.bSetBorder )
+ return false;
+
+ OUString const& rText(m_pCurTextFrame->GetText());
+ int eState = 0;
+ sal_Int32 nCnt = 0;
+ while (nCnt < rText.getLength())
+ {
+ int eTmp = 0;
+ switch (rText[nCnt])
+ {
+ case '-': eTmp = 1; break;
+ case '_': eTmp = 2; break;
+ case '=': eTmp = 3; break;
+ case '*': eTmp = 4; break;
+ case '~': eTmp = 5; break;
+ case '#': eTmp = 6; break;
+ default:
+ return false;
+ }
+ if( 0 == eState )
+ eState = eTmp;
+ else if( eState != eTmp )
+ return false;
+ ++nCnt;
+ }
+
+ if( 2 < nCnt )
+ {
+ // then underline the previous paragraph if one exists
+ DelEmptyLine( false ); // -> point will be on end of current paragraph
+ // WARNING: rText may be deleted now, m_pCurTextFrame may be nullptr
+ m_aDelPam.SetMark();
+ // apply to last node & rely on InsertItemSet to apply it to props-node
+
+ editeng::SvxBorderLine aLine;
+ switch( eState )
+ {
+ case 1: // single, hairline
+ aLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID);
+ aLine.SetWidth( SvxBorderLineWidth::Hairline );
+ break;
+ case 2: // single, thin
+ aLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID);
+ aLine.SetWidth( SvxBorderLineWidth::Thin );
+ break;
+ case 3: // double, thin
+ aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
+ aLine.SetWidth( SvxBorderLineWidth::Thin );
+ break;
+ case 4: // double, thick/thin
+ aLine.SetBorderLineStyle(SvxBorderLineStyle::THICKTHIN_SMALLGAP);
+ aLine.SetWidth( SvxBorderLineWidth::Thick );
+ break;
+ case 5: // double, thin/thick
+ aLine.SetBorderLineStyle(SvxBorderLineStyle::THINTHICK_SMALLGAP);
+ aLine.SetWidth( SvxBorderLineWidth::Thick );
+ break;
+ case 6: // double, medium
+ aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
+ aLine.SetWidth( SvxBorderLineWidth::Medium );
+ break;
+ }
+ SfxItemSetFixed<RES_PARATR_CONNECT_BORDER, RES_PARATR_CONNECT_BORDER,
+ RES_BOX, RES_BOX> aSet(m_pDoc->GetAttrPool());
+ aSet.Put( SwParaConnectBorderItem( false ) );
+ SvxBoxItem aBox( RES_BOX );
+ aBox.SetLine( &aLine, SvxBoxItemLine::BOTTOM );
+ aBox.SetDistance(42, SvxBoxItemLine::BOTTOM ); // ~0,75 mm
+ aSet.Put(aBox);
+ m_pDoc->getIDocumentContentOperations().InsertItemSet(m_aDelPam, aSet,
+ SetAttrMode::DEFAULT, m_pEditShell->GetLayout());
+
+ m_aDelPam.DeleteMark();
+ }
+ return 2 < nCnt;
+}
+
+bool SwAutoFormat::DoTable()
+{
+ if( !m_aFlags.bCreateTable || !m_aFlags.bAFormatByInput ||
+ m_pCurTextNd->FindTableNode() )
+ return false;
+
+ const OUString& rTmp = m_pCurTextFrame->GetText();
+ TextFrameIndex nSttPlus(GetLeadingBlanks(rTmp));
+ TextFrameIndex nEndPlus(GetTrailingBlanks(rTmp));
+ sal_Unicode cChar;
+
+ if (TextFrameIndex(2) > nEndPlus - nSttPlus
+ || ('+' != (cChar = rTmp[sal_Int32(nSttPlus)]) && '|' != cChar)
+ || ('+' != (cChar = rTmp[sal_Int32(nEndPlus) - 1]) && '|' != cChar))
+ return false;
+
+ SwTextFrameInfo aInfo( m_pCurTextFrame );
+
+ TextFrameIndex n = nSttPlus;
+ std::vector<sal_uInt16> aPosArr;
+
+ while (n < TextFrameIndex(rTmp.getLength()))
+ {
+ switch (rTmp[sal_Int32(n)])
+ {
+ case '-':
+ case '_':
+ case '=':
+ case ' ':
+ case '\t':
+ break;
+
+ case '+':
+ case '|':
+ aPosArr.push_back( o3tl::narrowing<sal_uInt16>(aInfo.GetCharPos(n)) );
+ break;
+
+ default:
+ return false;
+ }
+ if( ++n == nEndPlus )
+ break;
+ }
+
+ if( 1 < aPosArr.size() )
+ {
+ // get the text node's alignment
+ sal_uInt16 nColCnt = aPosArr.size() - 1;
+ SwTwips nSttPos = aPosArr[ 0 ];
+ sal_Int16 eHori;
+ switch (m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust().GetAdjust())
+ {
+ case SvxAdjust::Center: eHori = text::HoriOrientation::CENTER; break;
+ case SvxAdjust::Right: eHori = text::HoriOrientation::RIGHT; break;
+
+ default:
+ if( nSttPos )
+ {
+ eHori = text::HoriOrientation::NONE;
+ // then - as last - we need to add the current frame width into the array
+ aPosArr.push_back( o3tl::narrowing<sal_uInt16>(m_pCurTextFrame->getFrameArea().Width()) );
+ }
+ else
+ eHori = text::HoriOrientation::LEFT;
+ break;
+ }
+
+ // then create a table that matches the character
+ DelEmptyLine();
+ // WARNING: rTmp may be deleted now, m_pCurTextFrame may be nullptr
+ SwNodeIndex aIdx( m_aDelPam.GetPoint()->GetNode() );
+ m_aDelPam.Move( fnMoveForward );
+ m_pDoc->InsertTable( SwInsertTableOptions( SwInsertTableFlags::All , 1 ),
+ *m_aDelPam.GetPoint(), 1, nColCnt, eHori,
+ nullptr, &aPosArr );
+ m_aDelPam.GetPoint()->Assign(aIdx);
+ }
+ return 1 < aPosArr.size();
+}
+
+OUString SwAutoFormat::DelLeadingBlanks( const OUString& rStr )
+{
+ sal_Int32 nL, n;
+ for( nL = rStr.getLength(), n = 0; n < nL && IsSpace( rStr[n] ); ++n )
+ ;
+ if( n ) // no Spaces
+ return rStr.copy(n);
+ return rStr;
+}
+
+OUString SwAutoFormat::DelTrailingBlanks( const OUString& rStr )
+{
+ sal_Int32 nL = rStr.getLength(), n = nL;
+ if( !nL )
+ return rStr;
+
+ while( --n && IsSpace( rStr[ n ] ) )
+ ;
+ if( n+1 != nL ) // no Spaces
+ return rStr.copy( 0, n+1 );
+ return rStr;
+}
+
+sal_Int32 SwAutoFormat::GetLeadingBlanks( std::u16string_view aStr )
+{
+ size_t nL;
+ size_t n;
+
+ for( nL = aStr.size(), n = 0; n < nL && IsSpace( aStr[ n ] ); ++n )
+ ;
+ return n;
+}
+
+sal_Int32 SwAutoFormat::GetTrailingBlanks( std::u16string_view aStr )
+{
+ size_t nL = aStr.size(), n = nL;
+ if( !nL )
+ return 0;
+
+ while( --n && IsSpace( aStr[ n ] ) )
+ ;
+ return ++n;
+}
+
+bool SwAutoFormat::IsFirstCharCapital(const SwTextFrame& rFrame) const
+{
+ const OUString& rText = rFrame.GetText();
+ for (TextFrameIndex n(0),
+ nEnd(rText.getLength()); n < nEnd; ++n)
+ if (!IsSpace(rText[sal_Int32(n)]))
+ {
+ auto const pair = rFrame.MapViewToModel(n);
+ CharClass& rCC = GetCharClass( pair.first->GetSwAttrSet().
+ GetLanguage().GetLanguage() );
+ sal_Int32 nCharType = rCC.getCharacterType(rText, sal_Int32(n));
+ return CharClass::isLetterType( nCharType ) &&
+ 0 != ( i18n::KCharacterType::UPPER &
+ nCharType );
+ }
+ return false;
+}
+
+sal_uInt16
+SwAutoFormat::GetDigitLevel(const SwTextFrame& rFrame, TextFrameIndex& rPos,
+ OUString* pPrefix, OUString* pPostfix, OUString* pNumTypes ) const
+{
+
+ // check for 1.) / 1. / 1.1.1 / (1). / (1) / ...
+ const OUString& rText = rFrame.GetText();
+ sal_Int32 nPos(rPos);
+ int eScan = NONE;
+
+ sal_uInt16 nStart = 0;
+ sal_uInt8 nDigitLvl = 0, nDigitCnt = 0;
+ // count number of parenthesis to assure a sensible order is found
+ sal_uInt16 nOpeningParentheses = 0;
+ sal_uInt16 nClosingParentheses = 0;
+
+ while (nPos < rText.getLength() && nDigitLvl < MAXLEVEL - 1)
+ {
+ auto const pair = rFrame.MapViewToModel(TextFrameIndex(nPos));
+ CharClass& rCC = GetCharClass(pair.first->GetSwAttrSet().GetLanguage().GetLanguage());
+ const sal_Unicode cCurrentChar = rText[nPos];
+ if( ('0' <= cCurrentChar && '9' >= cCurrentChar) ||
+ (0xff10 <= cCurrentChar && 0xff19 >= cCurrentChar) )
+ {
+ if( eScan & DELIM )
+ {
+ if( eScan & CHG ) // not if it starts with a number
+ {
+ ++nDigitLvl;
+ if( pPostfix )
+ *pPostfix += "\x01";
+ }
+
+ if( pNumTypes )
+ *pNumTypes += OUStringChar(sal_Unicode('0' + SVX_NUM_ARABIC));
+
+ eScan = eScan | CHG;
+ }
+ else if( pNumTypes && !(eScan & DIGIT) )
+ *pNumTypes += OUStringChar(sal_Unicode('0' + SVX_NUM_ARABIC));
+
+ eScan &= ~DELIM; // remove Delim
+ if( 0 != (eScan & ~CHG) && DIGIT != (eScan & ~CHG))
+ return USHRT_MAX;
+
+ eScan |= DIGIT; // add Digit
+ if( 3 == ++nDigitCnt ) // more than 2 numbers are not an enum anymore
+ return USHRT_MAX;
+
+ nStart *= 10;
+ nStart += cCurrentChar <= '9' ? cCurrentChar - '0' : cCurrentChar - 0xff10;
+ }
+ else if( rCC.isAlpha( rText, nPos ) )
+ {
+ bool bIsUpper =
+ 0 != ( i18n::KCharacterType::UPPER &
+ rCC.getCharacterType( rText, nPos ));
+ sal_Unicode cLow = rCC.lowercase(rText, nPos, 1)[0], cNumTyp;
+ int eTmpScan;
+
+ // Roman numbers are "mdclxvi". Since we want to start numbering with c or d more often,
+ // convert first to characters and later to roman numbers if needed.
+ if( 256 > cLow && strchr( "mdclxvi", cLow ) )
+ {
+ if( bIsUpper )
+ {
+ cNumTyp = '0' + SVX_NUM_ROMAN_UPPER;
+ eTmpScan = UPPER_ROMAN;
+ }
+ else
+ {
+ cNumTyp = '0' + SVX_NUM_ROMAN_LOWER;
+ eTmpScan = LOWER_ROMAN;
+ }
+ }
+ else if( bIsUpper )
+ {
+ cNumTyp = '0' + SVX_NUM_CHARS_UPPER_LETTER;
+ eTmpScan = UPPER_ALPHA;
+ }
+ else
+ {
+ cNumTyp = '0' + SVX_NUM_CHARS_LOWER_LETTER;
+ eTmpScan = LOWER_ALPHA;
+ }
+
+ // Switch to roman numbers (only for c/d!)
+ if( 1 == nDigitCnt && ( eScan & (UPPER_ALPHA|LOWER_ALPHA) ) &&
+ ( 3 == nStart || 4 == nStart) && 256 > cLow &&
+ strchr( "mdclxvi", cLow ) &&
+ (( eScan & UPPER_ALPHA ) ? (eTmpScan & (UPPER_ALPHA|UPPER_ROMAN))
+ : (eTmpScan & (LOWER_ALPHA|LOWER_ROMAN))) )
+ {
+ sal_Unicode c = '0';
+ nStart = 3 == nStart ? 100 : 500;
+ if( UPPER_ALPHA == eTmpScan )
+ {
+ eTmpScan = UPPER_ROMAN;
+ c += SVX_NUM_ROMAN_UPPER;
+ }
+ else
+ {
+ eTmpScan = LOWER_ROMAN;
+ c += SVX_NUM_ROMAN_LOWER;
+ }
+
+ eScan = (eScan & ~(UPPER_ALPHA|LOWER_ALPHA)) | eTmpScan;
+ if( pNumTypes )
+ (*pNumTypes) = pNumTypes->replaceAt( pNumTypes->getLength() - 1, 1, rtl::OUStringChar(c) );
+ }
+
+ if( eScan & DELIM )
+ {
+ if( eScan & CHG ) // not if it starts with a number
+ {
+ ++nDigitLvl;
+ if( pPostfix )
+ *pPostfix += "\x01";
+ }
+
+ if( pNumTypes )
+ *pNumTypes += OUStringChar(cNumTyp);
+ eScan = eScan | CHG;
+ }
+ else if( pNumTypes && !(eScan & eTmpScan) )
+ *pNumTypes += OUStringChar(cNumTyp);
+
+ eScan &= ~DELIM; // remove Delim
+
+ // if another type is set, stop here
+ if( 0 != ( eScan & ~CHG ) && eTmpScan != ( eScan & ~CHG ))
+ return USHRT_MAX;
+
+ if( eTmpScan & (UPPER_ALPHA | LOWER_ALPHA) )
+ {
+ // allow characters only if they appear once
+ return USHRT_MAX;
+ }
+ else
+ {
+ // roman numbers, check if valid characters
+ sal_uInt16 nVal;
+ bool bError = false;
+ switch( cLow )
+ {
+ case 'm': nVal = 1000; goto CHECK_ROMAN_1;
+ case 'd': nVal = 500; goto CHECK_ROMAN_5;
+ case 'c': nVal = 100; goto CHECK_ROMAN_1;
+ case 'l': nVal = 50; goto CHECK_ROMAN_5;
+ case 'x': nVal = 10; goto CHECK_ROMAN_1;
+ case 'v': nVal = 5; goto CHECK_ROMAN_5;
+
+CHECK_ROMAN_1:
+ {
+ int nMod5 = nStart % (nVal * 5);
+ int nLast = nStart % nVal;
+ int n10 = nVal / 10;
+
+ if( nMod5 == ((3 * nVal) + n10 ) ||
+ nMod5 == ((4 * nVal) + n10 ) ||
+ nLast == n10 )
+ nStart = o3tl::narrowing<sal_uInt16>(nStart + (n10 * 8));
+ else if( nMod5 == 0 ||
+ nMod5 == (1 * nVal) ||
+ nMod5 == (2 * nVal) )
+ nStart = nStart + nVal;
+ else
+ bError = true;
+ }
+ break;
+
+CHECK_ROMAN_5:
+ {
+ if( ( nStart / nVal ) & 1 )
+ bError = true;
+ else
+ {
+ int nMod = nStart % nVal;
+ int n10 = nVal / 5;
+ if( n10 == nMod )
+ nStart = o3tl::narrowing<sal_uInt16>(nStart + (3 * n10));
+ else if( 0 == nMod )
+ nStart = nStart + nVal;
+ else
+ bError = true;
+ }
+ }
+ break;
+
+ case 'i':
+ if( nStart % 5 >= 3 )
+ bError = true;
+ else
+ nStart += 1;
+ break;
+
+ default:
+ bError = true;
+ }
+
+ if( bError )
+ return USHRT_MAX;
+ }
+ eScan |= eTmpScan; // add Digit
+ ++nDigitCnt;
+ }
+ else if( (256 > cCurrentChar &&
+ strchr( ".)(", cCurrentChar )) ||
+ 0x3002 == cCurrentChar /* Chinese trad. dot */||
+ 0xff0e == cCurrentChar /* Japanese dot */||
+ 0xFF08 == cCurrentChar /* opening bracket Chin./Jap.*/||
+ 0xFF09 == cCurrentChar )/* closing bracket Chin./Jap. */
+ {
+ if(cCurrentChar == '(' || cCurrentChar == 0xFF09)
+ nOpeningParentheses++;
+ else if(cCurrentChar == ')'|| cCurrentChar == 0xFF08)
+ nClosingParentheses++;
+ // only if no numbers were read until here
+ if( pPrefix && !( eScan & ( NO_DELIM | CHG )) )
+ *pPrefix += OUStringChar(rText[nPos]);
+ else if( pPostfix )
+ *pPostfix += OUStringChar(rText[nPos]);
+
+ if( NO_DELIM & eScan )
+ {
+ eScan |= CHG;
+ if( pPrefix )
+ *pPrefix += "\x01" + OUString::number( nStart );
+ }
+ eScan &= ~NO_DELIM; // remove Delim
+ eScan |= DELIM; // add Digit
+ nDigitCnt = 0;
+ nStart = 0;
+ }
+ else
+ break;
+ ++nPos;
+ }
+ if (!( CHG & eScan ) || rPos == TextFrameIndex(nPos) ||
+ nPos == rText.getLength() || !IsSpace(rText[nPos]) ||
+ (nOpeningParentheses > nClosingParentheses))
+ return USHRT_MAX;
+
+ if( (NO_DELIM & eScan) && pPrefix ) // do not forget the last one
+ *pPrefix += "\x01" + OUString::number( nStart );
+
+ rPos = TextFrameIndex(nPos);
+ return nDigitLvl; // 0 .. 9 (MAXLEVEL - 1)
+}
+
+void SwAutoFormat::SetColl( sal_uInt16 nId, bool bHdLineOrText )
+{
+ m_aDelPam.DeleteMark();
+ m_aDelPam.GetPoint()->Assign( *m_pCurTextFrame->GetTextNodeForParaProps() );
+
+ // keep hard tabs, alignment, language, hyphenation, DropCaps and nearly all frame attributes
+ SfxItemSetFixed<
+ RES_CHRATR_LANGUAGE, RES_CHRATR_LANGUAGE,
+ RES_PARATR_ADJUST, RES_PARATR_ADJUST,
+ RES_PARATR_TABSTOP, RES_PARATR_DROP,
+ RES_BACKGROUND, RES_SHADOW> aSet(m_pDoc->GetAttrPool());
+
+ if (m_aDelPam.GetPoint()->GetNode().GetTextNode()->HasSwAttrSet())
+ {
+ aSet.Put(*m_aDelPam.GetPoint()->GetNode().GetTextNode()->GetpSwAttrSet());
+ // take HeaderLine/TextBody only if centered or right aligned, otherwise only justification
+ if( SvxAdjustItem const * pAdj = aSet.GetItemIfSet( RES_PARATR_ADJUST, false) )
+ {
+ SvxAdjust eAdj = pAdj->GetAdjust();
+ if( bHdLineOrText ? (SvxAdjust::Right != eAdj &&
+ SvxAdjust::Center != eAdj)
+ : SvxAdjust::Block != eAdj )
+ aSet.ClearItem( RES_PARATR_ADJUST );
+ }
+ }
+
+ m_pDoc->SetTextFormatCollByAutoFormat( *m_aDelPam.GetPoint(), nId, &aSet );
+}
+
+static bool HasSelBlanks(
+ SwTextFrame const*const pStartFrame, TextFrameIndex & rStartIndex,
+ SwTextFrame const*const pEndFrame, TextFrameIndex & rEndIndex)
+{
+ if (TextFrameIndex(0) < rEndIndex
+ && rEndIndex < TextFrameIndex(pEndFrame->GetText().getLength())
+ && ' ' == pEndFrame->GetText()[sal_Int32(rEndIndex) - 1])
+ {
+ --rEndIndex;
+ return true;
+ }
+ if (rStartIndex < TextFrameIndex(pStartFrame->GetText().getLength())
+ && ' ' == pStartFrame->GetText()[sal_Int32(rStartIndex)])
+ {
+ ++rStartIndex;
+ return true;
+ }
+ return false;
+}
+
+bool SwAutoFormat::HasBreakAttr(const SwTextFrame& rTextFrame)
+{
+ const SfxItemSet *const pSet = rTextFrame.GetTextNodeFirst()->GetpSwAttrSet();
+ if( !pSet )
+ return false;
+
+ const SvxFormatBreakItem* pBreakItem = pSet->GetItemIfSet( RES_BREAK, false );
+ if( pBreakItem && SvxBreak::NONE != pBreakItem->GetBreak() )
+ return true;
+
+ const SwFormatPageDesc* pItem = pSet->GetItemIfSet( RES_PAGEDESC, false );
+ if( pItem && pItem->GetPageDesc()
+ && UseOnPage::NONE != pItem->GetPageDesc()->GetUseOn() )
+ return true;
+ return false;
+}
+
+/// Is there a dot at the end?
+bool SwAutoFormat::IsSentenceAtEnd(const SwTextFrame & rTextFrame)
+{
+ const OUString& rStr = rTextFrame.GetText();
+ sal_Int32 n = rStr.getLength();
+ if( !n )
+ return true;
+
+ while( --n && IsSpace( rStr[ n ] ) )
+ ;
+ return '.' == rStr[ n ];
+}
+
+/// Delete beginning and/or end in a node
+void SwAutoFormat::DeleteLeadingTrailingBlanks(bool bStart, bool bEnd)
+{
+ if( !(m_aFlags.bAFormatByInput
+ ? m_aFlags.bAFormatByInpDelSpacesAtSttEnd
+ : m_aFlags.bAFormatDelSpacesAtSttEnd) )
+ return;
+
+ // delete blanks at the end of the current and at the beginning of the next one
+ m_aDelPam.DeleteMark();
+ TextFrameIndex nPos(GetLeadingBlanks(m_pCurTextFrame->GetText()));
+ if (bStart && TextFrameIndex(0) != nPos)
+ {
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0));
+ m_aDelPam.SetMark();
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
+ DeleteSel( m_aDelPam );
+ m_aDelPam.DeleteMark();
+ }
+ nPos = TextFrameIndex(GetTrailingBlanks(m_pCurTextFrame->GetText()));
+ if (bEnd && TextFrameIndex(m_pCurTextFrame->GetText().getLength()) != nPos)
+ {
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(
+ TextFrameIndex(m_pCurTextFrame->GetText().getLength()));
+ m_aDelPam.SetMark();
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
+ DeleteSel( m_aDelPam );
+ m_aDelPam.DeleteMark();
+ }
+}
+
+namespace sw {
+
+bool GetRanges(std::vector<std::shared_ptr<SwUnoCursor>> & rRanges,
+ SwDoc & rDoc, SwPaM const& rDelPam)
+{
+ bool isNoRedline(true);
+ SwRedlineTable::size_type tmp;
+ IDocumentRedlineAccess const& rIDRA(rDoc.getIDocumentRedlineAccess());
+ if (!(rIDRA.GetRedlineFlags() & RedlineFlags::ShowDelete))
+ {
+ return isNoRedline;
+ }
+ rIDRA.GetRedline(*rDelPam.Start(), &tmp);
+ SwPosition const* pCurrent(rDelPam.Start());
+ for ( ; tmp < rIDRA.GetRedlineTable().size(); ++tmp)
+ {
+ SwRangeRedline const*const pRedline(rIDRA.GetRedlineTable()[tmp]);
+ if (*rDelPam.End() <= *pRedline->Start())
+ {
+ break;
+ }
+ if (*pRedline->End() <= *rDelPam.Start())
+ {
+ continue;
+ }
+ if (pRedline->GetType() == RedlineType::Delete)
+ {
+ assert(*pRedline->Start() != *pRedline->End());
+ isNoRedline = false;
+ if (*pCurrent < *pRedline->Start())
+ {
+ rRanges.push_back(rDoc.CreateUnoCursor(*pCurrent));
+ rRanges.back()->SetMark();
+ *rRanges.back()->GetPoint() = *pRedline->Start();
+ }
+ pCurrent = pRedline->End();
+ }
+ }
+ if (!isNoRedline && *pCurrent < *rDelPam.End())
+ {
+ rRanges.push_back(rDoc.CreateUnoCursor(*pCurrent));
+ rRanges.back()->SetMark();
+ *rRanges.back()->GetPoint() = *rDelPam.End();
+ }
+ return isNoRedline;
+}
+
+} // namespace sw
+
+void SwAutoFormat::DeleteSel(SwPaM & rDelPam)
+{
+ std::vector<std::shared_ptr<SwUnoCursor>> ranges; // need correcting cursor
+ if (GetRanges(ranges, *m_pDoc, rDelPam))
+ {
+ DeleteSelImpl(rDelPam, rDelPam);
+ }
+ else
+ {
+ for (auto const& pCursor : ranges)
+ {
+ DeleteSelImpl(*pCursor, rDelPam);
+ }
+ }
+}
+
+void SwAutoFormat::DeleteSelImpl(SwPaM & rDelPam, SwPaM & rPamToCorrect)
+{
+ if (m_aFlags.bWithRedlining || &rDelPam != &rPamToCorrect)
+ {
+ // Add to Shell-Cursor-Ring so that DelPam will be moved as well!
+ SwPaM* pShCursor = m_pEditShell->GetCursor_();
+ SwPaM aTmp( *m_pCurTextNd, 0, pShCursor );
+
+ SwPaM* pPrev = rPamToCorrect.GetPrev();
+ rPamToCorrect.GetRingContainer().merge( pShCursor->GetRingContainer() );
+
+ m_pEditShell->DeleteSel(rDelPam, true);
+
+ // and remove Pam again:
+ SwPaM* p;
+ SwPaM* pNext = &rPamToCorrect;
+ do {
+ p = pNext;
+ pNext = p->GetNext();
+ p->MoveTo( &rPamToCorrect );
+ } while( p != pPrev );
+
+ m_aNdIdx = aTmp.GetPoint()->GetNode();
+ m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
+ m_pCurTextFrame = GetFrame(*m_pCurTextNd); // keep it up to date
+ }
+ else
+ m_pEditShell->DeleteSel(rDelPam, true);
+}
+
+bool SwAutoFormat::DeleteJoinCurNextPara(SwTextFrame const*const pNextFrame,
+ bool const bIgnoreLeadingBlanks)
+{
+ // delete blanks at the end of the current and at the beginning of the next one
+ m_aDelPam.DeleteMark();
+ TextFrameIndex nTrailingPos(GetTrailingBlanks(m_pCurTextFrame->GetText()));
+
+ SwTextFrame const*const pEndFrame(pNextFrame ? pNextFrame : m_pCurTextFrame);
+ TextFrameIndex nLeadingPos(0);
+ if (pNextFrame)
+ {
+ nLeadingPos = TextFrameIndex(
+ bIgnoreLeadingBlanks ? 0 : GetLeadingBlanks(pNextFrame->GetText()));
+ }
+ else
+ {
+ nLeadingPos = TextFrameIndex(m_pCurTextFrame->GetText().getLength());
+ }
+
+ // Is there a Blank at the beginning or end?
+ // Do not delete it, it will be inserted again.
+ bool bHasBlnks = HasSelBlanks(m_pCurTextFrame, nTrailingPos, pEndFrame, nLeadingPos);
+
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nTrailingPos);
+ m_aDelPam.SetMark();
+ *m_aDelPam.GetPoint() = pEndFrame->MapViewToModelPos(nLeadingPos);
+
+ if( *m_aDelPam.GetPoint() != *m_aDelPam.GetMark() )
+ DeleteSel( m_aDelPam );
+ m_aDelPam.DeleteMark();
+ // note: keep m_aDelPam point at insert pos. for clients
+
+ return !bHasBlnks;
+}
+
+void SwAutoFormat::DelEmptyLine( bool bTstNextPara )
+{
+ SetRedlineText( STR_AUTOFMTREDL_DEL_EMPTY_PARA );
+ // delete blanks in empty paragraph
+ m_aDelPam.DeleteMark();
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(
+ TextFrameIndex(0));
+ m_aDelPam.SetMark();
+
+ m_aDelPam.GetMark()->Assign( m_pCurTextFrame->GetTextNodeFirst()->GetIndex() - 1 );
+ SwTextNode* pTNd = m_aDelPam.GetMarkNode().GetTextNode();
+ if( pTNd )
+ // first use the previous text node
+ m_aDelPam.GetMark()->SetContent(pTNd->GetText().getLength());
+ else if( bTstNextPara )
+ {
+ // then try the next (at the beginning of a Doc, table cells, frames, ...)
+ const SwTextNode* pNext = m_pCurTextFrame->GetMergedPara()
+ ? m_pCurTextFrame->GetMergedPara()->pLastNode
+ : m_pCurTextNd;
+ m_aDelPam.GetMark()->Assign(pNext->GetIndex() + 1);
+ pTNd = m_aDelPam.GetMarkNode().GetTextNode();
+ if( pTNd )
+ {
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(
+ TextFrameIndex(m_pCurTextFrame->GetText().getLength()));
+ }
+ }
+ if( pTNd )
+ { // join with previous or next paragraph
+ DeleteSel(m_aDelPam);
+ }
+ assert(m_aDelPam.GetPointNode().IsTextNode());
+ assert(!m_aDelPam.HasMark());
+ m_aDelPam.SetMark(); // mark remains at join position
+ m_pCurTextFrame = GetFrame(*m_aDelPam.GetPointNode().GetTextNode());
+ // replace until the end of the merged paragraph
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(
+ TextFrameIndex(m_pCurTextFrame->GetText().getLength()));
+ if (*m_aDelPam.GetPoint() != *m_aDelPam.GetMark())
+ { // tdf#137245 replace (not delete) to preserve any flys
+ m_pDoc->getIDocumentContentOperations().ReplaceRange(m_aDelPam, "", false);
+ }
+
+ m_aDelPam.DeleteMark();
+ ClearRedlineText();
+ // note: this likely has deleted m_pCurTextFrame - update it...
+ m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
+ m_pCurTextFrame = m_pCurTextNd ? GetFrame( *m_pCurTextNd ) : nullptr;
+}
+
+void SwAutoFormat::DelMoreLinesBlanks( bool bWithLineBreaks )
+{
+ if( !(m_aFlags.bAFormatByInput
+ ? m_aFlags.bAFormatByInpDelSpacesBetweenLines
+ : m_aFlags.bAFormatDelSpacesBetweenLines) )
+ return;
+
+ // delete all blanks on the left and right of the indentation
+ m_aDelPam.DeleteMark();
+
+ SwTextFrameInfo aFInfo( m_pCurTextFrame );
+ std::vector<std::pair<TextFrameIndex, TextFrameIndex>> spaces;
+ aFInfo.GetSpaces(spaces, !m_aFlags.bAFormatByInput || bWithLineBreaks);
+
+ // tdf#123285 iterate backwards - delete invalidates following indexes
+ for (auto iter = spaces.rbegin(); iter != spaces.rend(); ++iter)
+ {
+ auto & rSpaceRange(*iter);
+ assert(rSpaceRange.first != rSpaceRange.second);
+ bool const bHasBlanks = HasSelBlanks(
+ m_pCurTextFrame, rSpaceRange.first,
+ m_pCurTextFrame, rSpaceRange.second);
+ if (rSpaceRange.first != rSpaceRange.second)
+ {
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(rSpaceRange.first);
+ m_aDelPam.SetMark();
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(rSpaceRange.second);
+ DeleteSel(m_aDelPam);
+ if (!bHasBlanks)
+ {
+ m_pDoc->getIDocumentContentOperations().InsertString(m_aDelPam, OUString(' '));
+ }
+ m_aDelPam.DeleteMark();
+ }
+ }
+}
+
+void SwAutoFormat::JoinPrevPara()
+{
+ m_aDelPam.DeleteMark();
+ m_aDelPam.GetPoint()->Assign( *m_pCurTextFrame->GetTextNodeFirst() );
+ m_aDelPam.SetMark();
+
+ m_aDelPam.GetPoint()->Adjust(SwNodeOffset(-1));
+ SwTextNode* pTNd = m_aDelPam.GetPointNode().GetTextNode();
+ if( pTNd )
+ {
+ // use the previous text node first
+ m_aDelPam.GetPoint()->SetContent(pTNd->GetText().getLength());
+ DeleteSel( m_aDelPam );
+ }
+ m_aDelPam.DeleteMark();
+}
+
+void SwAutoFormat::BuildIndent()
+{
+ SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_INDENT );
+
+ // read all succeeding paragraphs that belong to this indentation
+ bool bBreak = true;
+ if( m_bMoreLines )
+ DelMoreLinesBlanks( true );
+ else
+ bBreak = !IsFastFullLine(*m_pCurTextFrame)
+ || IsBlanksInString(*m_pCurTextFrame)
+ || IsSentenceAtEnd(*m_pCurTextFrame);
+ SetColl( RES_POOLCOLL_TEXT_IDENT );
+ if( !bBreak )
+ {
+ SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
+ const SwTextFrame * pNextFrame = GetNextNode();
+ if (pNextFrame && !m_bEnd)
+ {
+ do {
+ bBreak = !IsFastFullLine(*pNextFrame)
+ || IsBlanksInString(*pNextFrame)
+ || IsSentenceAtEnd(*pNextFrame);
+ if (DeleteJoinCurNextPara(pNextFrame))
+ {
+ m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
+ }
+ if( bBreak )
+ break;
+ pNextFrame = GetNextNode();
+ }
+ while (CanJoin(pNextFrame)
+ && !CalcLevel(*pNextFrame));
+ }
+ }
+ DeleteLeadingTrailingBlanks();
+ AutoCorrect();
+}
+
+void SwAutoFormat::BuildTextIndent()
+{
+ SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_TEXT_INDENT);
+ // read all succeeding paragraphs that belong to this indentation
+ bool bBreak = true;
+ if( m_bMoreLines )
+ DelMoreLinesBlanks( true );
+ else
+ bBreak = !IsFastFullLine(*m_pCurTextFrame)
+ || IsBlanksInString(*m_pCurTextFrame)
+ || IsSentenceAtEnd(*m_pCurTextFrame);
+
+ if( m_aFlags.bAFormatByInput )
+ {
+ const_cast<SwTextNode*>(m_pCurTextFrame->GetTextNodeForParaProps())->SetAutoFormatLvl(
+ static_cast<sal_uInt8>(CalcLevel(*m_pCurTextFrame)));
+ }
+
+ SetColl( RES_POOLCOLL_TEXT_MOVE );
+ if( !bBreak )
+ {
+ SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
+ const SwTextFrame * pNextFrame = GetNextNode();
+ while (CanJoin(pNextFrame) &&
+ CalcLevel(*pNextFrame))
+ {
+ bBreak = !IsFastFullLine(*pNextFrame)
+ || IsBlanksInString(*pNextFrame)
+ || IsSentenceAtEnd(*pNextFrame);
+ if (DeleteJoinCurNextPara(pNextFrame))
+ {
+ m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
+ }
+ if( bBreak )
+ break;
+ pNextFrame = GetNextNode();
+ }
+ }
+ DeleteLeadingTrailingBlanks();
+ AutoCorrect();
+}
+
+void SwAutoFormat::BuildText()
+{
+ SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_TEXT );
+ // read all succeeding paragraphs that belong to this text without indentation
+ bool bBreak = true;
+ if( m_bMoreLines )
+ DelMoreLinesBlanks();
+ else
+ bBreak = !IsFastFullLine(*m_pCurTextFrame)
+ || IsBlanksInString(*m_pCurTextFrame)
+ || IsSentenceAtEnd(*m_pCurTextFrame);
+ if( !bBreak )
+ {
+ SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
+ const SwTextFrame * pNextFrame = GetNextNode();
+ while (CanJoin(pNextFrame) &&
+ !CalcLevel(*pNextFrame))
+ {
+ bBreak = !IsFastFullLine(*pNextFrame)
+ || IsBlanksInString(*pNextFrame)
+ || IsSentenceAtEnd(*pNextFrame);
+ if (DeleteJoinCurNextPara(pNextFrame))
+ {
+ m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
+ }
+ if( bBreak )
+ break;
+ const SwTextFrame *const pCurrNode = pNextFrame;
+ pNextFrame = GetNextNode();
+ if (!pNextFrame || pCurrNode == pNextFrame)
+ break;
+ }
+ }
+ DeleteLeadingTrailingBlanks();
+ AutoCorrect();
+}
+
+void SwAutoFormat::BuildEnum( sal_uInt16 nLvl, sal_uInt16 nDigitLevel )
+{
+ SetRedlineText( STR_AUTOFMTREDL_SET_NUMBER_BULLET );
+
+ bool bBreak = true;
+
+ // first, determine current indentation and frame width
+ SwTwips nFrameWidth = m_pCurTextFrame->getFramePrintArea().Width();
+ SwTwips nLeftTextPos;
+ {
+ TextFrameIndex nPos(0);
+ while (nPos < TextFrameIndex(m_pCurTextFrame->GetText().getLength())
+ && IsSpace(m_pCurTextFrame->GetText()[sal_Int32(nPos)]))
+ {
+ ++nPos;
+ }
+
+ SwTextFrameInfo aInfo( m_pCurTextFrame );
+ nLeftTextPos = aInfo.GetCharPos(nPos);
+ nLeftTextPos -= m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetTextLeftMargin().GetLeft(m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent());
+ }
+
+ if( m_bMoreLines )
+ DelMoreLinesBlanks();
+ else
+ bBreak = !IsFastFullLine(*m_pCurTextFrame)
+ || IsBlanksInString(*m_pCurTextFrame)
+ || IsSentenceAtEnd(*m_pCurTextFrame);
+ bool bRTL = m_pEditShell->IsInRightToLeftText();
+
+ const OUString sStrWithTrailingBlanks = DelLeadingBlanks(m_pCurTextFrame->GetText());
+ bool bIsShortBullet = sStrWithTrailingBlanks == "* " || sStrWithTrailingBlanks == "- ";
+ sal_uInt16 nMinLen = bIsShortBullet ? 1 : 2;
+
+ DeleteLeadingTrailingBlanks();
+
+ bool bChgBullet = false, bChgEnum = false;
+ TextFrameIndex nAutoCorrPos(0);
+
+ // if numbering is set, get the current one
+ SwNumRule aRule( m_pDoc->GetUniqueNumRuleName(),
+ // #i89178#
+ numfunc::GetDefaultPositionAndSpaceMode() );
+
+ const SwNumRule* pCur = nullptr;
+ if (m_aFlags.bSetNumRule)
+ {
+ pCur = m_pCurTextFrame->GetTextNodeForParaProps()->GetNumRule();
+ if (pCur)
+ {
+ aRule = *pCur;
+ }
+ }
+
+ // replace bullet character with defined one
+ const OUString& rStr = bIsShortBullet ? sStrWithTrailingBlanks : m_pCurTextFrame->GetText();
+ TextFrameIndex nTextStt(0);
+ const sal_Unicode* pFndBulletChr = nullptr;
+ if (m_aFlags.bChgEnumNum && nMinLen < rStr.getLength())
+ pFndBulletChr = StrChr(pBulletChar, rStr[sal_Int32(nTextStt)]);
+ if (nullptr != pFndBulletChr && IsSpace(rStr[sal_Int32(nTextStt) + 1]))
+ {
+ if( m_aFlags.bAFormatByInput )
+ {
+ if( m_aFlags.bSetNumRule)
+ {
+ SwCharFormat* pCFormat = m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool(
+ RES_POOLCHR_BULLET_LEVEL );
+ bChgBullet = true;
+ // Was the format already somewhere adjusted?
+ if( !aRule.GetNumFormat( nLvl ) )
+ {
+ int nBulletPos = pFndBulletChr - pBulletChar;
+ sal_UCS4 cBullChar;
+ const vcl::Font* pBullFnt( nullptr );
+ if( nBulletPos < cnPosEnDash )
+ {
+ cBullChar = m_aFlags.cBullet;
+ pBullFnt = &m_aFlags.aBulletFont;
+ }
+ else
+ {
+ cBullChar = nBulletPos < cnPosEmDash
+ ? cStarSymbolEnDash
+ : cStarSymbolEmDash;
+ // #i63395#
+ // Only apply user defined default bullet font
+ if ( numfunc::IsDefBulletFontUserDefined() )
+ {
+ pBullFnt = &numfunc::GetDefBulletFont();
+ }
+ }
+
+ sal_Int32 nAbsPos = lBulletIndent;
+ SwTwips nSpaceSteps = nLvl
+ ? nLeftTextPos / nLvl
+ : lBulletIndent;
+ for( sal_uInt8 n = 0; n < MAXLEVEL; ++n, nAbsPos = nAbsPos + nSpaceSteps )
+ {
+ SwNumFormat aFormat( aRule.Get( n ) );
+ aFormat.SetBulletFont( pBullFnt );
+ aFormat.SetBulletChar( cBullChar );
+ aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL);
+ // #i93908# clear suffix for bullet lists
+ aFormat.SetListFormat("", "", n);
+ aFormat.SetFirstLineOffset( lBulletFirstLineOffset );
+ aFormat.SetAbsLSpace( nAbsPos );
+ if( !aFormat.GetCharFormat() )
+ aFormat.SetCharFormat( pCFormat );
+ if( bRTL )
+ aFormat.SetNumAdjust( SvxAdjust::Right );
+
+ aRule.Set( n, aFormat );
+
+ if( n == nLvl &&
+ nFrameWidth < ( nSpaceSteps * MAXLEVEL ) )
+ nSpaceSteps = ( nFrameWidth - nLeftTextPos ) /
+ ( MAXLEVEL - nLvl );
+ }
+ }
+ }
+ }
+ else
+ {
+ bChgBullet = true;
+ SetColl( o3tl::narrowing<sal_uInt16>(RES_POOLCOLL_BULLET_LEVEL1 + ( std::min( nLvl, cnNumBullColls ) * 4 )) );
+ }
+ }
+ else
+ {
+ // Then it is a numbering
+
+ //JP 21.11.97: The NumLevel is either the DigitLevel or, if the latter is not existent or 0,
+ // it is determined by the indentation level.
+
+ OUString aPostfix, aPrefix, aNumTypes;
+ nDigitLevel = GetDigitLevel(*m_pCurTextFrame, nTextStt,
+ &aPrefix, &aPostfix, &aNumTypes);
+ if (USHRT_MAX != nDigitLevel)
+ {
+ bChgEnum = true;
+
+ // Level 0 and Indentation, determine level by left indentation and default NumIndent
+ if( !nDigitLevel && nLeftTextPos )
+ nLvl = std::min( sal_uInt16( nLeftTextPos / lNumberIndent ),
+ sal_uInt16( MAXLEVEL - 1 ) );
+ else
+ nLvl = nDigitLevel;
+ }
+
+ if( bChgEnum && m_aFlags.bSetNumRule )
+ {
+ if( !pCur ) // adjust NumRule if it is new
+ {
+ SwCharFormat* pCFormat = m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool(
+ RES_POOLCHR_NUM_LEVEL );
+
+ sal_Int32 nPrefixIdx{ 0 };
+ if( !nDigitLevel )
+ {
+ SwNumFormat aFormat( aRule.Get( nLvl ) );
+ const OUString sPrefix = aPrefix.getToken(0, u'\x0001', nPrefixIdx);
+ aFormat.SetStart( o3tl::narrowing<sal_uInt16>(o3tl::toInt32(o3tl::getToken(aPrefix, 0, u'\x0001', nPrefixIdx ))));
+ aFormat.SetListFormat(sPrefix, aPostfix.getToken(0, u'\x0001'), nLvl);
+ aFormat.SetIncludeUpperLevels( 0 );
+
+ if( !aFormat.GetCharFormat() )
+ aFormat.SetCharFormat( pCFormat );
+
+ if( !aNumTypes.isEmpty() )
+ aFormat.SetNumberingType(static_cast<SvxNumType>(aNumTypes[ 0 ] - '0'));
+
+ if( bRTL )
+ aFormat.SetNumAdjust( SvxAdjust::Right );
+ aRule.Set( nLvl, aFormat );
+ }
+ else
+ {
+ auto const nSpaceSteps = nLvl ? nLeftTextPos / nLvl : 0;
+ sal_uInt16 n;
+ sal_Int32 nPostfixIdx{ 0 };
+ for( n = 0; n <= nLvl; ++n )
+ {
+ SwNumFormat aFormat( aRule.Get( n ) );
+
+ const OUString sPrefix = n ? "" : aPrefix.getToken(0, u'\x0001', nPrefixIdx);
+ aFormat.SetStart( o3tl::narrowing<sal_uInt16>(o3tl::toInt32(o3tl::getToken(aPrefix, 0, u'\x0001', nPrefixIdx )) ));
+ aFormat.SetListFormat(sPrefix, aPostfix.getToken(0, u'\x0001', nPostfixIdx), n);
+ aFormat.SetIncludeUpperLevels( MAXLEVEL );
+ if( n < aNumTypes.getLength() )
+ aFormat.SetNumberingType(static_cast<SvxNumType>(aNumTypes[ n ] - '0'));
+
+ aFormat.SetAbsLSpace( nSpaceSteps * n
+ + lNumberIndent );
+
+ if( !aFormat.GetCharFormat() )
+ aFormat.SetCharFormat( pCFormat );
+ if( bRTL )
+ aFormat.SetNumAdjust( SvxAdjust::Right );
+
+ aRule.Set( n, aFormat );
+ }
+
+ // Does it fit completely into the frame?
+ bool bDefStep = nFrameWidth < (nSpaceSteps * MAXLEVEL);
+ for( ; n < MAXLEVEL; ++n )
+ {
+ SwNumFormat aFormat( aRule.Get( n ) );
+ aFormat.SetIncludeUpperLevels( MAXLEVEL );
+ if( bDefStep )
+ aFormat.SetAbsLSpace( nLeftTextPos +
+ SwNumRule::GetNumIndent(static_cast<sal_uInt8>(n-nLvl)));
+ else
+ aFormat.SetAbsLSpace( nSpaceSteps * n
+ + lNumberIndent );
+ aRule.Set( n, aFormat );
+ }
+ }
+ }
+ }
+ else if( !m_aFlags.bAFormatByInput )
+ SetColl( o3tl::narrowing<sal_uInt16>(RES_POOLCOLL_NUM_LEVEL1 + ( std::min( nLvl, cnNumBullColls ) * 4 ) ));
+ else
+ bChgEnum = false;
+ }
+
+ if ( bChgEnum || bChgBullet )
+ {
+ m_aDelPam.DeleteMark();
+ m_aDelPam.GetPoint()->Assign( *m_pCurTextFrame->GetTextNodeForParaProps() );
+
+ if( m_aFlags.bSetNumRule )
+ {
+ if( m_aFlags.bAFormatByInput )
+ {
+ m_aDelPam.SetMark();
+ SwTextFrame const*const pNextFrame = GetNextNode(false);
+ if (pNextFrame)
+ {
+ m_aDelPam.GetMark()->Assign( *pNextFrame->GetTextNodeForParaProps() );
+ m_aDelPam.GetMarkNode().GetTextNode()->SetAttrListLevel( nLvl );
+ }
+ }
+
+ const_cast<SwTextNode*>(m_pCurTextFrame->GetTextNodeForParaProps())->SetAttrListLevel(nLvl);
+
+ // start new list
+ m_pDoc->SetNumRule(m_aDelPam, aRule, true, m_pEditShell->GetLayout());
+ m_aDelPam.DeleteMark();
+
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0));
+ }
+ else
+ {
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(
+ bChgEnum ? nTextStt : TextFrameIndex(0));
+ }
+ m_aDelPam.SetMark();
+
+ if ( bChgBullet )
+ nTextStt += TextFrameIndex(bIsShortBullet ? 1 : 2);
+
+ while (!bIsShortBullet && nTextStt < TextFrameIndex(rStr.getLength()) && IsSpace(rStr[sal_Int32(nTextStt)]))
+ nTextStt++;
+
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nTextStt);
+ DeleteSel( m_aDelPam );
+
+ if( !m_aFlags.bSetNumRule )
+ {
+ OUString sChgStr('\t');
+ if( bChgBullet )
+ sChgStr = OUString(&m_aFlags.cBullet, 1) + sChgStr;
+ m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, sChgStr );
+
+ SfxItemSet aSet( m_pDoc->GetAttrPool(), aTextNodeSetRange );
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0));
+ assert(&m_aDelPam.GetPoint()->GetNode() == m_pCurTextFrame->GetTextNodeForParaProps());
+ if( bChgBullet )
+ {
+ m_aDelPam.SetMark();
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(1));
+ SetAllScriptItem( aSet,
+ SvxFontItem( m_aFlags.aBulletFont.GetFamilyType(),
+ m_aFlags.aBulletFont.GetFamilyName(),
+ m_aFlags.aBulletFont.GetStyleName(),
+ m_aFlags.aBulletFont.GetPitch(),
+ m_aFlags.aBulletFont.GetCharSet(),
+ RES_CHRATR_FONT ) );
+ m_pDoc->SetFormatItemByAutoFormat( m_aDelPam, aSet );
+ m_aDelPam.DeleteMark();
+ nAutoCorrPos = TextFrameIndex(2);
+ aSet.ClearItem();
+ }
+ SvxTabStopItem aTStops( RES_PARATR_TABSTOP );
+ aTStops.Insert( SvxTabStop( 0 ) );
+ aSet.Put( aTStops );
+ assert(&m_aDelPam.GetPoint()->GetNode() == m_pCurTextFrame->GetTextNodeForParaProps());
+ m_pDoc->SetFormatItemByAutoFormat( m_aDelPam, aSet );
+ }
+ }
+
+ if( bBreak )
+ {
+ AutoCorrect( nAutoCorrPos ); /* Offset due to Bullet + Tab */
+ return;
+ }
+
+ const SwTextFrame * pNextFrame = GetNextNode();
+ while (CanJoin(pNextFrame)
+ && nLvl == CalcLevel(*pNextFrame))
+ {
+ SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
+ bBreak = !IsFastFullLine(*pNextFrame)
+ || IsBlanksInString(*pNextFrame)
+ || IsSentenceAtEnd(*pNextFrame);
+ if (DeleteJoinCurNextPara(pNextFrame))
+ {
+ m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
+ }
+ if( bBreak )
+ break;
+ const SwTextFrame *const pCurrNode = pNextFrame;
+ pNextFrame = GetNextNode();
+ if (!pNextFrame || pCurrNode == pNextFrame)
+ break;
+ }
+ DeleteLeadingTrailingBlanks( false );
+ AutoCorrect( nAutoCorrPos );
+}
+
+void SwAutoFormat::BuildNegIndent( SwTwips nSpaces )
+{
+ SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_NEG_INDENT );
+ // Test of contraposition (n words, divided by spaces/tabs, with same indentation in 2nd line)
+
+ // read all succeeding paragraphs that belong to this enumeration
+ bool bBreak = true;
+ TextFrameIndex nSpacePos(0);
+ const sal_Int32 nTextPos = GetBigIndent( nSpacePos );
+ if( m_bMoreLines )
+ DelMoreLinesBlanks( true );
+ else
+ bBreak = !IsFastFullLine(*m_pCurTextFrame)
+ || (!nTextPos && IsBlanksInString(*m_pCurTextFrame))
+ || IsSentenceAtEnd(*m_pCurTextFrame);
+
+ SetColl( o3tl::narrowing<sal_uInt16>( nTextPos
+ ? RES_POOLCOLL_CONFRONTATION
+ : RES_POOLCOLL_TEXT_NEGIDENT ) );
+
+ if( nTextPos )
+ {
+ const OUString& rStr = m_pCurTextFrame->GetText();
+ bool bInsTab = true;
+
+ if ('\t' == rStr[sal_Int32(nSpacePos) + 1]) // leave tab alone
+ {
+ --nSpacePos;
+ bInsTab = false;
+ }
+
+ TextFrameIndex nSpaceStt = nSpacePos;
+ while (nSpaceStt && IsSpace(rStr[sal_Int32(--nSpaceStt)]))
+ ;
+ ++nSpaceStt;
+
+ if (bInsTab && '\t' == rStr[sal_Int32(nSpaceStt)]) // leave tab alone
+ {
+ ++nSpaceStt;
+ bInsTab = false;
+ }
+
+ m_aDelPam.DeleteMark();
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nSpacePos);
+
+ // delete old Spaces, etc.
+ if( nSpaceStt < nSpacePos )
+ {
+ m_aDelPam.SetMark();
+ *m_aDelPam.GetMark() = m_pCurTextFrame->MapViewToModelPos(nSpaceStt);
+ DeleteSel( m_aDelPam );
+ if( bInsTab )
+ {
+ m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString('\t') );
+ }
+ }
+ }
+
+ if( !bBreak )
+ {
+ SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
+ SwTextFrameInfo aFInfo( m_pCurTextFrame );
+ const SwTextFrame * pNextFrame = GetNextNode();
+ while (CanJoin(pNextFrame) &&
+ 20 < std::abs( static_cast<tools::Long>(nSpaces - aFInfo.SetFrame(
+ EnsureFormatted(*pNextFrame)).GetLineStart()) )
+ )
+ {
+ bBreak = !IsFastFullLine(*pNextFrame)
+ || IsBlanksInString(*pNextFrame)
+ || IsSentenceAtEnd(*pNextFrame);
+ if (DeleteJoinCurNextPara(pNextFrame))
+ {
+ m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
+ }
+ if( bBreak )
+ break;
+ pNextFrame = GetNextNode();
+ }
+ }
+ DeleteLeadingTrailingBlanks();
+ AutoCorrect();
+}
+
+void SwAutoFormat::BuildHeadLine( sal_uInt16 nLvl )
+{
+ if( m_aFlags.bWithRedlining )
+ {
+ OUString sText(SwViewShell::GetShellRes()->GetAutoFormatNameLst()[
+ STR_AUTOFMTREDL_SET_TMPL_HEADLINE ] );
+ sText = sText.replaceAll( "$(ARG1)", OUString::number( nLvl + 1 ) );
+ m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment( &sText );
+ }
+
+ SetColl( o3tl::narrowing<sal_uInt16>(RES_POOLCOLL_HEADLINE1 + nLvl ), true );
+ if( m_aFlags.bAFormatByInput )
+ {
+ SwTextFormatColl& rNxtColl = m_pCurTextFrame->GetTextNodeForParaProps()->GetTextColl()->GetNextTextFormatColl();
+
+ JoinPrevPara();
+
+ DeleteLeadingTrailingBlanks( true, false );
+ const SwTextFrame* pNextFrame = GetNextNode(false);
+ if (pNextFrame->GetNext())
+ {
+ (void)DeleteJoinCurNextPara(pNextFrame, true);
+ pNextFrame = GetNextNode(false);
+ }
+ m_aDelPam.DeleteMark();
+ m_aDelPam.GetPoint()->Assign( *pNextFrame->GetTextNodeForParaProps() );
+ m_pDoc->SetTextFormatColl( m_aDelPam, &rNxtColl );
+ }
+ else
+ {
+ DeleteLeadingTrailingBlanks();
+ AutoCorrect();
+ }
+}
+
+/// Start autocorrection for the current TextNode
+void SwAutoFormat::AutoCorrect(TextFrameIndex nPos)
+{
+ SvxAutoCorrect* pATst = SvxAutoCorrCfg::Get().GetAutoCorrect();
+ ACFlags aSvxFlags = pATst->GetFlags( );
+ bool bReplaceQuote( aSvxFlags & ACFlags::ChgQuotes );
+ bool bReplaceSglQuote( aSvxFlags & ACFlags::ChgSglQuotes );
+
+ if( m_aFlags.bAFormatByInput ||
+ (!m_aFlags.bAutoCorrect && !bReplaceQuote && !bReplaceSglQuote &&
+ !m_aFlags.bCapitalStartSentence && !m_aFlags.bCapitalStartWord &&
+ !m_aFlags.bChgOrdinalNumber && !m_aFlags.bTransliterateRTL &&
+ !m_aFlags.bChgToEnEmDash && !m_aFlags.bSetINetAttr &&
+ !m_aFlags.bChgWeightUnderl && !m_aFlags.bAddNonBrkSpace) )
+ return;
+
+ const OUString* pText = &m_pCurTextFrame->GetText();
+ if (TextFrameIndex(pText->getLength()) <= nPos)
+ return;
+
+ bool bGetLanguage = m_aFlags.bChgOrdinalNumber || m_aFlags.bTransliterateRTL ||
+ m_aFlags.bChgToEnEmDash || m_aFlags.bSetINetAttr ||
+ m_aFlags.bCapitalStartWord || m_aFlags.bCapitalStartSentence ||
+ m_aFlags.bAddNonBrkSpace;
+
+ m_aDelPam.DeleteMark();
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0));
+
+ SwAutoCorrDoc aACorrDoc( *m_pEditShell, m_aDelPam );
+
+ SwTextFrameInfo aFInfo( nullptr );
+
+ TextFrameIndex nSttPos, nLastBlank = nPos;
+ bool bFirst = m_aFlags.bCapitalStartSentence, bFirstSent = bFirst;
+ sal_Unicode cChar = 0;
+ bool bNbspRunNext = false;
+
+ CharClass& rAppCC = GetAppCharClass();
+
+ do {
+ while (nPos < TextFrameIndex(pText->getLength())
+ && IsSpace(cChar = (*pText)[sal_Int32(nPos)]))
+ ++nPos;
+ if (nPos == TextFrameIndex(pText->getLength()))
+ break; // that's it
+
+ if( ( ( bReplaceQuote && '\"' == cChar ) ||
+ ( bReplaceSglQuote && '\'' == cChar ) ) &&
+ (!nPos || ' ' == (*pText)[sal_Int32(nPos)-1]))
+ {
+
+ // note: special case symbol fonts !!!
+ if( !aFInfo.GetFrame() )
+ aFInfo.SetFrame( GetFrame( *m_pCurTextNd ) );
+ if( !aFInfo.IsBullet( nPos ))
+ {
+ SetRedlineText( STR_AUTOFMTREDL_TYPO );
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
+ bool bSetHardBlank = false;
+
+ OUString sReplace( pATst->GetQuote( aACorrDoc,
+ sal_Int32(nPos), cChar, true ));
+
+ m_aDelPam.SetMark();
+ m_aDelPam.GetPoint()->SetContent( m_aDelPam.GetMark()->GetContentIndex() + 1 );
+ if( 2 == sReplace.getLength() && ' ' == sReplace[ 1 ])
+ {
+ sReplace = sReplace.copy( 0, 1 );
+ bSetHardBlank = true;
+ }
+ m_pDoc->getIDocumentContentOperations().ReplaceRange( m_aDelPam, sReplace, false );
+
+ if( m_aFlags.bWithRedlining )
+ {
+ m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
+ m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
+ m_pCurTextFrame = GetFrame( *m_pCurTextNd );
+ pText = &m_pCurTextFrame->GetText();
+ m_aDelPam.SetMark();
+ aFInfo.SetFrame( nullptr );
+ }
+
+ nPos += TextFrameIndex(sReplace.getLength() - 1);
+ m_aDelPam.DeleteMark();
+ if( bSetHardBlank )
+ {
+ m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(CHAR_HARDBLANK) );
+ ++nPos;
+ }
+ }
+ }
+
+ bool bCallACorr = false;
+ int bBreak = 0;
+ if (nPos && IsSpace((*pText)[sal_Int32(nPos) - 1]))
+ nLastBlank = nPos;
+ for (nSttPos = nPos; !bBreak && nPos < TextFrameIndex(pText->getLength()); ++nPos)
+ {
+ cChar = (*pText)[sal_Int32(nPos)];
+ switch (cChar)
+ {
+ case '\"':
+ case '\'':
+ if( ( cChar == '\"' && bReplaceQuote ) || ( cChar == '\'' && bReplaceSglQuote ) )
+ {
+ // consider Symbolfonts!
+ if( !aFInfo.GetFrame() )
+ aFInfo.SetFrame( GetFrame( *m_pCurTextNd ) );
+ if( !aFInfo.IsBullet( nPos ))
+ {
+ SetRedlineText( STR_AUTOFMTREDL_TYPO );
+ bool bSetHardBlank = false;
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
+ OUString sReplace( pATst->GetQuote( aACorrDoc,
+ sal_Int32(nPos), cChar, false) );
+
+ if( 2 == sReplace.getLength() && ' ' == sReplace[ 0 ])
+ {
+ sReplace = sReplace.copy( 1 );
+ bSetHardBlank = true;
+ }
+
+ m_aDelPam.SetMark();
+ m_aDelPam.GetPoint()->SetContent( m_aDelPam.GetMark()->GetContentIndex() + 1 );
+ m_pDoc->getIDocumentContentOperations().ReplaceRange( m_aDelPam, sReplace, false );
+
+ if( m_aFlags.bWithRedlining )
+ {
+ m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
+ m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
+ m_pCurTextFrame = GetFrame( *m_pCurTextNd );
+ pText = &m_pCurTextFrame->GetText();
+ m_aDelPam.SetMark();
+ m_aDelPam.DeleteMark();
+ aFInfo.SetFrame( nullptr );
+ }
+
+ nPos += TextFrameIndex(sReplace.getLength() - 1);
+ m_aDelPam.DeleteMark();
+
+ if( bSetHardBlank )
+ {
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
+ m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(CHAR_HARDBLANK) );
+ ++nPos;
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
+ }
+ }
+ }
+ break;
+ case '*':
+ case '_':
+ if( m_aFlags.bChgWeightUnderl )
+ {
+ // consider Symbolfonts!
+ if( !aFInfo.GetFrame() )
+ aFInfo.SetFrame( GetFrame( *m_pCurTextNd ) );
+ if( !aFInfo.IsBullet( nPos ))
+ {
+ SetRedlineText( '*' == cChar
+ ? STR_AUTOFMTREDL_BOLD
+ : STR_AUTOFMTREDL_UNDER );
+
+ sal_Unicode cBlank = nSttPos ? (*pText)[sal_Int32(nSttPos) - 1] : 0;
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
+
+ if (pATst->FnChgWeightUnderl(aACorrDoc, *pText, sal_Int32(nPos)))
+ {
+ if( m_aFlags.bWithRedlining )
+ {
+ m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
+ m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
+ m_pCurTextFrame = GetFrame( *m_pCurTextNd );
+ pText = &m_pCurTextFrame->GetText();
+ m_aDelPam.SetMark();
+ m_aDelPam.DeleteMark();
+ aFInfo.SetFrame( nullptr );
+ }
+ //#125102# in case of the mode RedlineFlags::ShowDelete the ** are still contained in pText
+ if (m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags() & RedlineFlags::ShowDelete)
+ {
+ nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint())
+ - TextFrameIndex(1);
+ bBreak++;
+ }
+ // Was a character deleted before starting?
+ if (cBlank && cBlank != (*pText)[sal_Int32(nSttPos) - 1])
+ --nSttPos;
+ }
+ }
+ }
+ break;
+ case '/':
+ if ( m_aFlags.bAddNonBrkSpace )
+ {
+ LanguageType eLang = bGetLanguage
+ ? m_pCurTextFrame->GetLangOfChar(nSttPos, 0, true)
+ : LANGUAGE_SYSTEM;
+
+ SetRedlineText( STR_AUTOFMTREDL_NON_BREAK_SPACE );
+ if (sal_Int32 nUpdatedPos = pATst->FnAddNonBrkSpace(aACorrDoc, *pText, sal_Int32(nPos), eLang, bNbspRunNext); nUpdatedPos >= 0)
+ {
+ nPos = TextFrameIndex(nUpdatedPos);
+ break;
+ }
+ }
+ [[fallthrough]];
+ case '-':
+ if (m_aFlags.bChgWeightUnderl)
+ {
+ // consider Symbolfonts!
+ if (!aFInfo.GetFrame())
+ aFInfo.SetFrame(GetFrame(*m_pCurTextNd));
+ if (!aFInfo.IsBullet(nPos))
+ {
+ SetRedlineText('/' == cChar ? STR_AUTOFMTREDL_ITALIC : STR_AUTOFMTREDL_STRIKETHROUGH);
+
+ sal_Unicode cBlank = nSttPos ? (*pText)[sal_Int32(nSttPos) - 1] : 0;
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
+
+ if (pATst->FnChgWeightUnderl(aACorrDoc, *pText, sal_Int32(nPos)))
+ {
+ if (m_aFlags.bWithRedlining)
+ {
+ m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
+ m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
+ m_pCurTextFrame = GetFrame(*m_pCurTextNd);
+ pText = &m_pCurTextFrame->GetText();
+ m_aDelPam.SetMark();
+ m_aDelPam.DeleteMark();
+ aFInfo.SetFrame(nullptr);
+ }
+ //#125102# in case of the mode RedlineFlags::ShowDelete the // or -- are still contained in pText
+ if (m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags() & RedlineFlags::ShowDelete)
+ {
+ nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint())
+ - TextFrameIndex(1);
+ bBreak++;
+ }
+ // Was a character deleted before starting?
+ if (cBlank && cBlank != (*pText)[sal_Int32(nSttPos) - 1])
+ --nSttPos;
+ }
+ }
+ }
+ break;
+
+ case '.':
+ case '!':
+ case '?':
+ if( m_aFlags.bCapitalStartSentence )
+ bFirstSent = true;
+ [[fallthrough]];
+ default:
+ if (!(rAppCC.isBase(*pText, sal_Int32(nPos))
+ || '/' == cChar )) // '/' should not be a word separator (e.g. '1/2' needs to be handled as one word for replacement)
+ {
+ --nPos; // revert ++nPos which was decremented in for loop
+ ++bBreak;
+ }
+ break;
+ }
+ }
+
+ if( nPos == nSttPos )
+ {
+ if (++nPos == TextFrameIndex(pText->getLength()))
+ bCallACorr = true;
+ }
+ else
+ bCallACorr = true;
+
+ if( bCallACorr )
+ {
+ *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
+ SetRedlineText( STR_AUTOFMTREDL_USE_REPLACE );
+
+ LanguageType eLang = bGetLanguage
+ ? m_pCurTextFrame->GetLangOfChar(nSttPos, 0, true)
+ : LANGUAGE_SYSTEM;
+
+ if( m_bIsRightToLeft && m_aFlags.bTransliterateRTL && eLang == LANGUAGE_HUNGARIAN &&
+ SetRedlineText( STR_AUTOFMTREDL_TRANSLITERATE_RTL ) &&
+ aACorrDoc.TransliterateRTLWord(reinterpret_cast<sal_Int32&>(nSttPos), sal_Int32(nPos), /*bApply=*/true))
+ {
+ nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint());
+
+ if( m_aFlags.bWithRedlining )
+ {
+ m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
+ m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
+ m_pCurTextFrame = GetFrame( *m_pCurTextNd );
+ pText = &m_pCurTextFrame->GetText();
+ m_aDelPam.SetMark();
+ m_aDelPam.DeleteMark();
+ }
+
+ continue; // do not check further
+ }
+
+ if( m_aFlags.bAutoCorrect &&
+ aACorrDoc.ChgAutoCorrWord(reinterpret_cast<sal_Int32&>(nSttPos), sal_Int32(nPos), *pATst, nullptr) )
+ {
+ nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint());
+ if( m_aFlags.bWithRedlining )
+ {
+ m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
+ m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
+ m_pCurTextFrame = GetFrame( *m_pCurTextNd );
+ pText = &m_pCurTextFrame->GetText();
+ m_aDelPam.SetMark();
+ m_aDelPam.DeleteMark();
+ }
+
+ continue; // do not check further
+ }
+
+ if ( m_aFlags.bAddNonBrkSpace && nPos < TextFrameIndex(pText->getLength()) )
+ {
+ SetRedlineText( STR_AUTOFMTREDL_NON_BREAK_SPACE );
+ if (sal_Int32 nUpdatedPos = pATst->FnAddNonBrkSpace(aACorrDoc, *pText, sal_Int32(nPos), eLang, bNbspRunNext); nUpdatedPos >= 0)
+ nPos = TextFrameIndex(nUpdatedPos);
+ }
+
+ if( ( m_aFlags.bChgOrdinalNumber &&
+ SetRedlineText( STR_AUTOFMTREDL_ORDINAL ) &&
+ pATst->FnChgOrdinalNumber(aACorrDoc, *pText, sal_Int32(nSttPos), sal_Int32(nPos), eLang)) ||
+ ( m_aFlags.bChgToEnEmDash &&
+ SetRedlineText( STR_AUTOFMTREDL_DASH ) &&
+ pATst->FnChgToEnEmDash(aACorrDoc, *pText, sal_Int32(nSttPos), sal_Int32(nPos), eLang)) ||
+ ( m_aFlags.bSetINetAttr &&
+ (nPos == TextFrameIndex(pText->getLength()) || IsSpace((*pText)[sal_Int32(nPos)])) &&
+ SetRedlineText( STR_AUTOFMTREDL_DETECT_URL ) &&
+ pATst->FnSetINetAttr(aACorrDoc, *pText, sal_Int32(nLastBlank), sal_Int32(nPos), eLang)) ||
+ ( m_aFlags.bSetDOIAttr &&
+ (nPos == TextFrameIndex(pText->getLength()) || IsSpace((*pText)[sal_Int32(nPos)])) &&
+ SetRedlineText( STR_AUTOFMTREDL_DETECT_DOI ) &&
+ pATst->FnSetDOIAttr(aACorrDoc, *pText, sal_Int32(nLastBlank), sal_Int32(nPos), eLang)))
+ {
+ nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint());
+ }
+ else
+ {
+ // two capital letters at the beginning of a word?
+ if( m_aFlags.bCapitalStartWord )
+ {
+ SetRedlineText( STR_AUTOFMTREDL_CPTL_STT_WORD );
+ pATst->FnCapitalStartWord(aACorrDoc, *pText, sal_Int32(nSttPos), sal_Int32(nPos), eLang);
+ }
+ // capital letter at the beginning of a sentence?
+ if( m_aFlags.bCapitalStartSentence && bFirst )
+ {
+ SetRedlineText( STR_AUTOFMTREDL_CPTL_STT_SENT );
+ pATst->FnCapitalStartSentence(aACorrDoc, *pText, true, sal_Int32(nSttPos), sal_Int32(nPos), eLang);
+ }
+
+ bFirst = bFirstSent;
+ bFirstSent = false;
+
+ if( m_aFlags.bWithRedlining )
+ {
+ m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
+ m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
+ m_pCurTextFrame = GetFrame( *m_pCurTextNd );
+ pText = &m_pCurTextFrame->GetText();
+ m_aDelPam.SetMark();
+ m_aDelPam.DeleteMark();
+ }
+ }
+ }
+ }
+ while (nPos < TextFrameIndex(pText->getLength()));
+ ClearRedlineText();
+}
+
+SwAutoFormat::SwAutoFormat( SwEditShell* pEdShell, SvxSwAutoFormatFlags aFlags,
+ SwNode const * pSttNd, SwNode const * pEndNd )
+ : m_aFlags(std::move( aFlags )),
+ m_aDelPam( pEdShell->GetDoc()->GetNodes().GetEndOfExtras() ),
+ m_aNdIdx( pEdShell->GetDoc()->GetNodes().GetEndOfExtras(), SwNodeOffset(+1) ),
+ m_aEndNdIdx( pEdShell->GetDoc()->GetNodes().GetEndOfContent() ),
+ m_pEditShell( pEdShell ),
+ m_pDoc( pEdShell->GetDoc() ),
+ m_pCurTextNd( nullptr ), m_pCurTextFrame( nullptr ),
+ m_nRedlAutoFormatSeqId( 0 )
+{
+ OSL_ENSURE( (pSttNd && pEndNd) || (!pSttNd && !pEndNd),
+ "Got no area" );
+
+ if( m_aFlags.bSetNumRule && !m_aFlags.bAFormatByInput )
+ m_aFlags.bSetNumRule = false;
+
+ bool bReplaceStyles = !m_aFlags.bAFormatByInput || m_aFlags.bReplaceStyles;
+
+ const SwTextFrame * pNextFrame = nullptr;
+ bool bNxtEmpty = false;
+ bool bNxtAlpha = false;
+ sal_uInt16 nNxtLevel = 0;
+ bool bEmptyLine;
+
+ // set area for autoformatting
+ if( pSttNd )
+ {
+ m_aNdIdx = *pSttNd;
+ // for GoNextPara, one paragraph prior to that
+ sw::GotoPrevLayoutTextFrame(m_aNdIdx, m_pEditShell->GetLayout());
+ m_aEndNdIdx = *pEndNd;
+ sw::GotoNextLayoutTextFrame(m_aEndNdIdx, m_pEditShell->GetLayout());
+
+ // check the previous TextNode
+ SwTextFrame const*const pPrevFrame = m_aNdIdx.GetNode().GetTextNode()
+ ? static_cast<SwTextFrame const*>(m_aNdIdx.GetNode().GetTextNode()->getLayoutFrame(m_pEditShell->GetLayout()))
+ : nullptr;
+ bEmptyLine = !pPrevFrame
+ || IsEmptyLine(*pPrevFrame)
+ || IsNoAlphaLine(*pPrevFrame);
+ }
+ else
+ bEmptyLine = true; // at document beginning
+
+ m_bEnd = false;
+
+ // set value for percentage display
+ m_nEndNdIdx = m_aEndNdIdx.GetIndex();
+
+ if( !m_aFlags.bAFormatByInput )
+ {
+ ::StartProgress( STR_STATSTR_AUTOFORMAT, sal_Int32(m_aNdIdx.GetIndex()),
+ sal_Int32(m_nEndNdIdx),
+ m_pDoc->GetDocShell() );
+ }
+
+ RedlineFlags eRedlMode = m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags(), eOldMode = eRedlMode;
+ if( m_aFlags.bWithRedlining )
+ {
+ m_pDoc->SetAutoFormatRedline( true );
+ eRedlMode = RedlineFlags::On | (eOldMode & RedlineFlags::ShowMask);
+ }
+ else
+ eRedlMode = RedlineFlags::Ignore | (eOldMode & RedlineFlags::ShowMask);
+ m_pDoc->getIDocumentRedlineAccess().SetRedlineFlags( eRedlMode );
+
+ // save undo state (might be turned off)
+ bool const bUndoState = m_pDoc->GetIDocumentUndoRedo().DoesUndo();
+
+ // If multiple lines, then do not merge with next paragraph
+ m_bMoreLines = false;
+
+ sal_uInt16 nLastCalcHeadLvl = 0;
+ sal_uInt16 nLastHeadLvl = USHRT_MAX;
+ sal_uInt16 nLevel = 0;
+ sal_uInt16 nDigitLvl = 0;
+
+ // set defaults
+ SwTextFrameInfo aFInfo( nullptr );
+
+ enum Format_Status
+ {
+ READ_NEXT_PARA, // -> ISEND, TST_EMPTY_LINE
+ TST_EMPTY_LINE, // -> READ_NEXT_PARA, TST_ALPHA_LINE
+ TST_ALPHA_LINE, // -> READ_NEXT_PARA, GET_ALL_INFO, TST_ENUMERIC, IS_END
+ GET_ALL_INFO, // -> READ_NEXT_PARA, IS_ONE_LINE, TST_ENUMERIC, HAS_FMTCOLL
+ IS_ONE_LINE, // -> READ_NEXT_PARA, TST_ENUMERIC
+ TST_ENUMERIC, // -> READ_NEXT_PARA, TST_IDENT, TST_NEG_IDENT
+ TST_IDENT, // -> READ_NEXT_PARA, TST_TXT_BODY
+ TST_NEG_IDENT, // -> READ_NEXT_PARA, TST_TXT_BODY
+ TST_TXT_BODY, // -> READ_NEXT_PARA
+ HAS_FMTCOLL, // -> READ_NEXT_PARA
+ IS_END
+ } eStat;
+
+ // This is the automat for autoformatting
+ eStat = READ_NEXT_PARA;
+ while( !m_bEnd )
+ {
+ switch( eStat )
+ {
+ case READ_NEXT_PARA:
+ {
+ GoNextPara();
+ eStat = m_bEnd ? IS_END : TST_EMPTY_LINE;
+ }
+ break;
+
+ case TST_EMPTY_LINE:
+ if (IsEmptyLine(*m_pCurTextFrame))
+ {
+ if (m_aFlags.bDelEmptyNode && !HasObjects(*m_pCurTextFrame))
+ {
+ bEmptyLine = true;
+ SwNodeOffset nOldCnt = m_pDoc->GetNodes().Count();
+ DelEmptyLine();
+ // Was there really a deletion of a node?
+ if( nOldCnt != m_pDoc->GetNodes().Count() )
+ {
+ // do not skip the next paragraph
+ sw::GotoPrevLayoutTextFrame(m_aNdIdx, m_pEditShell->GetLayout());
+ }
+ }
+ eStat = READ_NEXT_PARA;
+ }
+ else
+ eStat = TST_ALPHA_LINE;
+ break;
+
+ case TST_ALPHA_LINE:
+ if (IsNoAlphaLine(*m_pCurTextFrame))
+ {
+ // recognize a table definition +---+---+
+ if( m_aFlags.bAFormatByInput && m_aFlags.bCreateTable && DoTable() )
+ {
+ //JP 30.09.96: DoTable() builds on PopCursor and MoveCursor after AutoFormat!
+ pEdShell->Pop(SwCursorShell::PopMode::DeleteCurrent);
+ static_cast<SwPaM&>(*pEdShell->GetCursor()) = m_aDelPam;
+ pEdShell->Push();
+
+ eStat = IS_END;
+ break;
+ }
+
+ const OUString& rStr = m_pCurTextFrame->GetText();
+ SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect();
+ SvxSwAutoFormatFlags& rFlags = pACorr->GetSwFlags();
+ if (rFlags.bChgEnumNum && (rStr == "- " || rStr == "* "))
+ {
+ eStat = TST_ENUMERIC;
+ break;
+ }
+
+ // Check for 3 "---" or "===". In this case, the previous paragraph should be
+ // underlined and the current be deleted!
+ if( !DoUnderline() && bReplaceStyles )
+ {
+ SetColl( RES_POOLCOLL_STANDARD, true );
+ bEmptyLine = true;
+ }
+ eStat = READ_NEXT_PARA;
+ }
+ else
+ eStat = GET_ALL_INFO;
+ break;
+
+ case GET_ALL_INFO:
+ {
+ if (m_pCurTextFrame->GetTextNodeForParaProps()->GetNumRule())
+ {
+ // do nothing in numbering, go to next
+ bEmptyLine = false;
+ eStat = READ_NEXT_PARA;
+ // delete all blanks at beginning/end and in between
+ //JP 29.04.98: first only "all in between"
+ DelMoreLinesBlanks();
+ // auto correct paragraphs that fail to enter state HAS_FMTCOLL
+ AutoCorrect();
+ break;
+ }
+
+ aFInfo.SetFrame( m_pCurTextFrame );
+
+ // so far: if there were templates assigned, keep these and go to next node
+ sal_uInt16 nPoolId = m_pCurTextFrame->GetTextNodeForParaProps()->GetTextColl()->GetPoolFormatId();
+ if( IsPoolUserFormat( nPoolId )
+ ? !m_aFlags.bChgUserColl
+ : ( RES_POOLCOLL_STANDARD != nPoolId &&
+ ( !m_aFlags.bAFormatByInput ||
+ (RES_POOLCOLL_TEXT_MOVE != nPoolId &&
+ RES_POOLCOLL_TEXT != nPoolId )) ))
+ {
+ eStat = HAS_FMTCOLL;
+ break;
+ }
+
+ // replace custom styles with text body
+ if ( IsPoolUserFormat( nPoolId ) && m_aFlags.bChgUserColl )
+ {
+ SetColl( RES_POOLCOLL_TEXT, true );
+ }
+
+ // check for left margin set by the style
+ if( IsPoolUserFormat( nPoolId ) ||
+ RES_POOLCOLL_STANDARD == nPoolId )
+ {
+ SvxFirstLineIndentItem const*const pFirstLineIndent(
+ m_pCurTextFrame->GetTextNodeForParaProps()
+ ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_FIRSTLINE));
+ SvxTextLeftMarginItem const*const pTextLeftMargin(
+ m_pCurTextFrame->GetTextNodeForParaProps()
+ ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_TEXTLEFT));
+ short nSz(pFirstLineIndent ? pFirstLineIndent->GetTextFirstLineOffset() : 0);
+ if (0 != nSz ||
+ (pTextLeftMargin && 0 != pTextLeftMargin->GetTextLeft()))
+ {
+ // exception: numbering/enumeration can have an indentation
+ if (IsEnumericChar(*m_pCurTextFrame))
+ {
+ nLevel = CalcLevel(*m_pCurTextFrame, &nDigitLvl);
+ if( nLevel >= MAXLEVEL )
+ nLevel = MAXLEVEL-1;
+ BuildEnum( nLevel, nDigitLvl );
+ eStat = READ_NEXT_PARA;
+ break;
+ }
+
+ // never merge (maybe only indent as exception)
+ m_bMoreLines = true;
+
+ if( bReplaceStyles )
+ {
+ // then use one of our templates
+ if( 0 < nSz ) // positive 1st line indentation
+ BuildIndent();
+ else if( 0 > nSz ) // negative 1st line indentation
+ BuildNegIndent( aFInfo.GetLineStart() );
+ else if (pTextLeftMargin && pTextLeftMargin->GetTextLeft() != 0) // is indentation
+ BuildTextIndent();
+ }
+ eStat = READ_NEXT_PARA;
+ break;
+ }
+ }
+
+ nLevel = CalcLevel( *m_pCurTextFrame, &nDigitLvl );
+ m_bMoreLines = !IsOneLine(*m_pCurTextFrame);
+ // note: every use of pNextFrame in following states, until the
+ // next READ_NEXT_PARA, relies on this update
+ pNextFrame = GetNextNode();
+ if (pNextFrame)
+ {
+ bNxtEmpty = IsEmptyLine(*pNextFrame);
+ bNxtAlpha = IsNoAlphaLine(*pNextFrame);
+ nNxtLevel = CalcLevel(*pNextFrame);
+
+ if (!bEmptyLine && HasBreakAttr(*m_pCurTextFrame))
+ bEmptyLine = true;
+ if (!bNxtEmpty && HasBreakAttr(*pNextFrame))
+ bNxtEmpty = true;
+
+ }
+ else
+ {
+ bNxtEmpty = false;
+ bNxtAlpha = false;
+ nNxtLevel = 0;
+ }
+ eStat = !m_bMoreLines ? IS_ONE_LINE : TST_ENUMERIC;
+ }
+ break;
+
+ case IS_ONE_LINE:
+ {
+ eStat = TST_ENUMERIC;
+ if( !bReplaceStyles )
+ break;
+
+ const OUString sClrStr( DelLeadingBlanks(m_pCurTextFrame->GetText()) );
+
+ if( sClrStr.isEmpty() )
+ {
+ bEmptyLine = true;
+ eStat = READ_NEXT_PARA;
+ break; // read next paragraph
+ }
+
+ // check if headline
+ if (!bEmptyLine || !IsFirstCharCapital(*m_pCurTextFrame)
+ || IsBlanksInString(*m_pCurTextFrame))
+ break;
+
+ bEmptyLine = false;
+ const OUString sEndClrStr( DelTrailingBlanks(sClrStr) );
+ const sal_Unicode cLast = sEndClrStr[sEndClrStr.getLength() - 1];
+
+ // not, then check if headline
+ if( ':' == cLast )
+ {
+ BuildHeadLine( 2 );
+ eStat = READ_NEXT_PARA;
+ break;
+ }
+ else if( 256 <= cLast || !strchr( ",.;", cLast ) )
+ {
+ if( bNxtEmpty || bNxtAlpha
+ || (pNextFrame && IsEnumericChar(*pNextFrame)))
+ {
+
+ // one level below?
+ if( nLevel >= MAXLEVEL )
+ nLevel = MAXLEVEL-1;
+
+ if( USHRT_MAX == nLastHeadLvl )
+ nLastHeadLvl = 0;
+ else if( nLastCalcHeadLvl < nLevel )
+ {
+ if( nLastHeadLvl+1 < MAXLEVEL )
+ ++nLastHeadLvl;
+ }
+ // one level above?
+ else if( nLastCalcHeadLvl > nLevel )
+ {
+ if( nLastHeadLvl )
+ --nLastHeadLvl;
+ }
+ nLastCalcHeadLvl = nLevel;
+
+ if( m_aFlags.bAFormatByInput )
+ BuildHeadLine( nLevel );
+ else
+ BuildHeadLine( nLastHeadLvl );
+ eStat = READ_NEXT_PARA;
+ break;
+ }
+ }
+ }
+ break;
+
+ case TST_ENUMERIC:
+ {
+ bEmptyLine = false;
+ if (IsEnumericChar(*m_pCurTextFrame))
+ {
+ if( nLevel >= MAXLEVEL )
+ nLevel = MAXLEVEL-1;
+ BuildEnum( nLevel, nDigitLvl );
+ eStat = READ_NEXT_PARA;
+ }
+ else if( bReplaceStyles )
+ eStat = nLevel ? TST_IDENT : TST_NEG_IDENT;
+ else
+ eStat = READ_NEXT_PARA;
+ }
+ break;
+
+ case TST_IDENT:
+ // Spaces at the beginning, check again for indentation
+ if( m_bMoreLines && nLevel )
+ {
+ SwTwips nSz = aFInfo.GetFirstIndent();
+ if( 0 < nSz ) // positive 1st line indentation
+ BuildIndent();
+ else if( 0 > nSz ) // negative 1st line indentation
+ BuildNegIndent( aFInfo.GetLineStart() );
+ else // is indentation
+ BuildTextIndent();
+ eStat = READ_NEXT_PARA;
+ }
+ else if (nLevel && pNextFrame &&
+ !bNxtEmpty && !bNxtAlpha && !nNxtLevel &&
+ !IsEnumericChar(*pNextFrame))
+ {
+ // is an indentation
+ BuildIndent();
+ eStat = READ_NEXT_PARA;
+ }
+ else
+ eStat = TST_TXT_BODY;
+ break;
+
+ case TST_NEG_IDENT:
+ // no spaces at the beginning, check again for negative indentation
+ {
+ if( m_bMoreLines && !nLevel )
+ {
+ SwTwips nSz = aFInfo.GetFirstIndent();
+ if( 0 < nSz ) // positive 1st line indentation
+ BuildIndent();
+ else if( 0 > nSz ) // negative 1st line indentation
+ BuildNegIndent( aFInfo.GetLineStart() );
+ else // is _no_ indentation
+ BuildText();
+ eStat = READ_NEXT_PARA;
+ }
+ else if (!nLevel && pNextFrame &&
+ !bNxtEmpty && !bNxtAlpha && nNxtLevel &&
+ !IsEnumericChar(*pNextFrame))
+ {
+ // is a negative indentation
+ BuildNegIndent( aFInfo.GetLineStart() );
+ eStat = READ_NEXT_PARA;
+ }
+ else
+ eStat = TST_TXT_BODY;
+ }
+ break;
+
+ case TST_TXT_BODY:
+ {
+ if( m_bMoreLines )
+ {
+ SwTwips nSz = aFInfo.GetFirstIndent();
+ if( 0 < nSz ) // positive 1st line indentation
+ BuildIndent();
+ else if( 0 > nSz ) // negative 1st line indentation
+ BuildNegIndent( aFInfo.GetLineStart() );
+ else if( nLevel ) // is indentation
+ BuildTextIndent();
+ else
+ BuildText();
+ }
+ else if( nLevel )
+ BuildTextIndent();
+ else
+ BuildText();
+ eStat = READ_NEXT_PARA;
+ }
+ break;
+
+ case HAS_FMTCOLL:
+ {
+ // so far: if there were templates assigned, keep these and go to next node
+ bEmptyLine = false;
+ eStat = READ_NEXT_PARA;
+ // delete all blanks at beginning/end and in between
+ //JP 29.04.98: first only "all in between"
+ DelMoreLinesBlanks();
+
+ // handle hard attributes
+ if (m_pCurTextFrame->GetTextNodeForParaProps()->HasSwAttrSet())
+ {
+ SvxFirstLineIndentItem const*const pFirstLineIndent(
+ m_pCurTextFrame->GetTextNodeForParaProps()
+ ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_FIRSTLINE, false));
+ SvxTextLeftMarginItem const*const pTextLeftMargin(
+ m_pCurTextFrame->GetTextNodeForParaProps()
+ ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_TEXTLEFT, false));
+ short nSz(pFirstLineIndent ? pFirstLineIndent->GetTextFirstLineOffset() : 0);
+ if( bReplaceStyles &&
+ (0 != nSz ||
+ (pTextLeftMargin && 0 != pTextLeftMargin->GetTextLeft())))
+ {
+ // then use one of our templates
+ if( 0 < nSz ) // positive 1st line indentation
+ BuildIndent();
+ else if( 0 > nSz ) // negative 1st line indentation
+ {
+ BuildNegIndent( aFInfo.GetLineStart() );
+ }
+ else if (pTextLeftMargin && pTextLeftMargin->GetTextLeft()) // is indentation
+ BuildTextIndent();
+ else
+ BuildText();
+ }
+ }
+ // force auto correct
+ AutoCorrect();
+ }
+ break;
+
+ case IS_END:
+ m_bEnd = true;
+ break;
+ }
+ }
+
+ if( m_aFlags.bWithRedlining )
+ m_pDoc->SetAutoFormatRedline( false );
+ m_pDoc->getIDocumentRedlineAccess().SetRedlineFlags( eOldMode );
+
+ // restore undo (in case it has been changed)
+ m_pDoc->GetIDocumentUndoRedo().DoUndo(bUndoState);
+
+ // disable display of percentage again
+ if( !m_aFlags.bAFormatByInput )
+ ::EndProgress( m_pDoc->GetDocShell() );
+}
+
+void SwEditShell::AutoFormat( const SvxSwAutoFormatFlags* pAFlags, bool bCurrentParagraphOnly )
+{
+ std::optional<SwWait> oWait;
+
+ CurrShell aCurr( this );
+ StartAllAction();
+ StartUndo( SwUndoId::AUTOFORMAT );
+
+ SvxSwAutoFormatFlags aAFFlags; // use default values or add params?
+ if( pAFlags )
+ {
+ aAFFlags = *pAFlags;
+ if( !aAFFlags.bAFormatByInput )
+ oWait.emplace( *GetDoc()->GetDocShell(), true );
+ }
+
+ SwPaM* pCursor = GetCursor();
+ // There are more than one or a selection is open
+ if( pCursor->GetNext() != pCursor || pCursor->HasMark() )
+ {
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ if( rPaM.HasMark() )
+ {
+ SwAutoFormat( this, aAFFlags, &rPaM.Start()->GetNode(),
+ &rPaM.End()->GetNode() );
+ }
+ }
+ }
+ else if (bCurrentParagraphOnly)
+ {
+ pCursor->SetMark();
+ SwAutoFormat aFormat( this, std::move(aAFFlags), &pCursor->GetMark()->GetNode(),
+ &pCursor->GetPoint()->GetNode() );
+ }
+ else
+ {
+ SwAutoFormat( this, std::move(aAFFlags) );
+ }
+
+ EndUndo( SwUndoId::AUTOFORMAT );
+ EndAllAction();
+}
+
+void SwEditShell::AutoFormatBySplitNode()
+{
+ CurrShell aCurr( this );
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->IsMultiSelection() || !pCursor->Move( fnMoveBackward, GoInNode ) )
+ return;
+
+ StartAllAction();
+ StartUndo( SwUndoId::AUTOFORMAT );
+
+ bool bRange = false;
+ pCursor->SetMark();
+ SwPosition* pMarkPos = pCursor->GetMark();
+ if( pMarkPos->GetContentIndex() )
+ {
+ pMarkPos->SetContent(0);
+ bRange = true;
+ }
+ else
+ {
+ // then go one node backwards
+ SwNodeIndex aNdIdx(pCursor->GetMark()->GetNode());
+ sw::GotoPrevLayoutTextFrame(aNdIdx, GetLayout());
+ SwTextNode* pTextNd = aNdIdx.GetNode().GetTextNode();
+ if (pTextNd && !pTextNd->GetText().isEmpty())
+ {
+ pCursor->GetMark()->Assign( aNdIdx );
+ bRange = true;
+ }
+ }
+
+ if( bRange )
+ {
+ Push(); // save cursor
+
+ SvxSwAutoFormatFlags aAFFlags = *GetAutoFormatFlags(); // use default values so far
+
+ SwAutoFormat aFormat( this, std::move(aAFFlags), &pCursor->GetMark()->GetNode(),
+ &pCursor->GetPoint()->GetNode() );
+ SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect();
+ if( pACorr && !pACorr->IsAutoCorrFlag( ACFlags::CapitalStartSentence | ACFlags::CapitalStartWord |
+ ACFlags::AddNonBrkSpace | ACFlags::ChgOrdinalNumber | ACFlags::TransliterateRTL |
+ ACFlags::ChgToEnEmDash | ACFlags::SetINetAttr | ACFlags::Autocorrect |
+ ACFlags::SetDOIAttr ))
+ pACorr = nullptr;
+
+ if( pACorr )
+ AutoCorrect( *pACorr,false, u'\0' );
+
+ //JP 30.09.96: DoTable() builds on PopCursor and MoveCursor!
+ Pop(PopMode::DeleteCurrent);
+ pCursor = GetCursor();
+ }
+ pCursor->DeleteMark();
+ pCursor->Move( fnMoveForward, GoInNode );
+
+ EndUndo( SwUndoId::AUTOFORMAT );
+ EndAllAction();
+
+}
+
+SvxSwAutoFormatFlags* SwEditShell::GetAutoFormatFlags()
+{
+ if (!s_pAutoFormatFlags)
+ s_pAutoFormatFlags = new SvxSwAutoFormatFlags;
+
+ return s_pAutoFormatFlags;
+}
+
+void SwEditShell::SetAutoFormatFlags(SvxSwAutoFormatFlags const * pFlags)
+{
+ SvxSwAutoFormatFlags* pEditFlags = GetAutoFormatFlags();
+
+ pEditFlags->bSetNumRule = pFlags->bSetNumRule;
+ pEditFlags->bChgEnumNum = pFlags->bChgEnumNum;
+ pEditFlags->bSetBorder = pFlags->bSetBorder;
+ pEditFlags->bCreateTable = pFlags->bCreateTable;
+ pEditFlags->bReplaceStyles = pFlags->bReplaceStyles;
+ pEditFlags->bAFormatByInpDelSpacesAtSttEnd =
+ pFlags->bAFormatByInpDelSpacesAtSttEnd;
+ pEditFlags->bAFormatByInpDelSpacesBetweenLines =
+ pFlags->bAFormatByInpDelSpacesBetweenLines;
+
+ //JP 15.12.98: copy BulletChar and Font into "normal" ones
+ // because AutoFormat can only work with the latter!
+ pEditFlags->cBullet = pFlags->cByInputBullet;
+ pEditFlags->aBulletFont = pFlags->aByInputBulletFont;
+ pEditFlags->cByInputBullet = pFlags->cByInputBullet;
+ pEditFlags->aByInputBulletFont = pFlags->aByInputBulletFont;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/edatmisc.cxx b/sw/source/core/edit/edatmisc.cxx
new file mode 100644
index 0000000000..cfc465fce8
--- /dev/null
+++ b/sw/source/core/edit/edatmisc.cxx
@@ -0,0 +1,160 @@
+/* -*- 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 <editsh.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <pam.hxx>
+#include <swundo.hxx>
+#include <ndtxt.hxx>
+
+/*
+ * hard formatting (Attribute)
+ */
+
+void SwEditShell::ResetAttr( const o3tl::sorted_vector<sal_uInt16> &attrs, SwPaM* pPaM )
+{
+ CurrShell aCurr( this );
+ SwPaM* pCursor = pPaM ? pPaM : GetCursor( );
+
+ StartAllAction();
+ bool bUndoGroup = pCursor->GetNext() != pCursor;
+ if( bUndoGroup )
+ {
+ GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::RESETATTR, nullptr);
+ }
+
+ for(const SwPaM& rCurrentCursor : pCursor->GetRingContainer())
+ GetDoc()->ResetAttrs(rCurrentCursor, true, attrs, true, GetLayout());
+
+ if( bUndoGroup )
+ {
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::RESETATTR, nullptr);
+ }
+ CallChgLnk();
+ EndAllAction();
+}
+
+void SwEditShell::GCAttr()
+{
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ if ( !rPaM.HasMark() )
+ {
+ SwTextNode *const pTextNode =
+ rPaM.GetPoint()->GetNode().GetTextNode();
+ if (pTextNode)
+ {
+ pTextNode->GCAttr();
+ }
+ }
+ else
+ {
+ const SwNode& rEnd = rPaM.End()->GetNode();
+ SwNodeIndex aIdx( rPaM.Start()->GetNode() );
+ SwNode* pNd = &aIdx.GetNode();
+ do {
+ if( pNd->IsTextNode() )
+ static_cast<SwTextNode*>(pNd)->GCAttr();
+ }
+ while( nullptr != ( pNd = GetDoc()->GetNodes().GoNext( &aIdx )) &&
+ aIdx <= rEnd );
+ }
+ }
+}
+
+/// Set the attribute as new default attribute in the document.
+void SwEditShell::SetDefault( const SfxPoolItem& rFormatHint )
+{
+ // 7502: Action-Parenthesis
+ StartAllAction();
+ GetDoc()->SetDefault( rFormatHint );
+ EndAllAction();
+}
+
+/// request the default attribute in this document.
+const SfxPoolItem& SwEditShell::GetDefault( sal_uInt16 nFormatHint ) const
+{
+ return GetDoc()->GetDefault( nFormatHint );
+}
+
+void SwEditShell::SetAttrItem( const SfxPoolItem& rHint, SetAttrMode nFlags, const bool /*bParagraphSetting*/ )
+{
+ CurrShell aCurr( this );
+ StartAllAction();
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->GetNext() != pCursor ) // Ring of Cursors
+ {
+ bool bIsTableMode = IsTableMode();
+ GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSATTR, nullptr);
+
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ if( rPaM.HasMark() && ( bIsTableMode ||
+ *rPaM.GetPoint() != *rPaM.GetMark() ))
+ {
+ GetDoc()->getIDocumentContentOperations().InsertPoolItem(rPaM, rHint, nFlags, GetLayout());
+ }
+ }
+
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSATTR, nullptr);
+ }
+ else
+ {
+ if( !HasSelection() )
+ UpdateAttr();
+
+ GetDoc()->getIDocumentContentOperations().InsertPoolItem(*pCursor, rHint, nFlags, GetLayout());
+ }
+ EndAllAction();
+}
+
+void SwEditShell::SetAttrSet( const SfxItemSet& rSet, SetAttrMode nFlags, SwPaM* pPaM, const bool /*bParagraphSetting*/ )
+{
+ CurrShell aCurr( this );
+ SwPaM* pCursor = pPaM ? pPaM : GetCursor();
+ StartAllAction();
+ if( pCursor->GetNext() != pCursor ) // Ring of Cursors
+ {
+ bool bIsTableMode = IsTableMode();
+ GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSATTR, nullptr);
+
+ for(SwPaM& rTmpCursor : pCursor->GetRingContainer())
+ {
+ if( rTmpCursor.HasMark() && ( bIsTableMode ||
+ *rTmpCursor.GetPoint() != *rTmpCursor.GetMark() ))
+ {
+ GetDoc()->getIDocumentContentOperations().InsertItemSet(rTmpCursor, rSet, nFlags, GetLayout());
+ }
+ }
+
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSATTR, nullptr);
+ }
+ else
+ {
+ if( !HasSelection() )
+ UpdateAttr();
+
+ GetDoc()->getIDocumentContentOperations().InsertItemSet(*pCursor, rSet, nFlags, GetLayout());
+ }
+ EndAllAction();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/edattr.cxx b/sw/source/core/edit/edattr.cxx
new file mode 100644
index 0000000000..8ff0026d03
--- /dev/null
+++ b/sw/source/core/edit/edattr.cxx
@@ -0,0 +1,846 @@
+/* -*- 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 <memory>
+#include <hintids.hxx>
+#include <editeng/tstpitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <osl/diagnose.h>
+#include <txatbase.hxx>
+#include <txtftn.hxx>
+#include <fmtftn.hxx>
+#include <editsh.hxx>
+#include <edimp.hxx>
+#include <doc.hxx>
+#include <swundo.hxx>
+#include <ndtxt.hxx>
+#include <ftnidx.hxx>
+#include <expfld.hxx>
+#include <rootfrm.hxx>
+#include <cntfrm.hxx>
+#include <breakit.hxx>
+#include <fmtfld.hxx>
+#include <txtfrm.hxx>
+#include <scriptinfo.hxx>
+#include <svl/itemiter.hxx>
+#include <svl/languageoptions.hxx>
+#include <charfmt.hxx>
+#include <numrule.hxx>
+
+/*
+ * hard Formatting (Attributes)
+ */
+
+// if selection is bigger as max nodes or more than max selections
+// => no attributes
+static sal_uInt16 getMaxLookup()
+{
+ return 10000;
+}
+
+bool SwEditShell::GetPaMAttr( SwPaM* pPaM, SfxItemSet& rSet,
+ const bool bMergeIndentValuesOfNumRule ) const
+{
+ // ??? pPaM can be different from the Cursor ???
+ if( GetCursorCnt() > getMaxLookup() )
+ {
+ rSet.InvalidateAllItems();
+ return false;
+ }
+
+ SfxItemSet aSet( *rSet.GetPool(), rSet.GetRanges() );
+ SfxItemSet *pSet = &rSet;
+
+ for(SwPaM& rCurrentPaM : pPaM->GetRingContainer())
+ {
+ // #i27615# if the cursor is in front of the numbering label
+ // the attributes to get are those from the numbering format.
+ if (rCurrentPaM.IsInFrontOfLabel())
+ {
+ SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(),
+ rCurrentPaM.GetPoint()->GetNode());
+
+ if (pTextNd)
+ {
+ SwNumRule * pNumRule = pTextNd->GetNumRule();
+
+ if (pNumRule)
+ {
+ int nListLevel = pTextNd->GetActualListLevel();
+
+ if (nListLevel < 0)
+ nListLevel = 0;
+
+ if (nListLevel >= MAXLEVEL)
+ nListLevel = MAXLEVEL - 1;
+
+ const OUString & aCharFormatName =
+ pNumRule->Get(o3tl::narrowing<sal_uInt16>(nListLevel)).GetCharFormatName();
+ SwCharFormat * pCharFormat =
+ GetDoc()->FindCharFormatByName(aCharFormatName);
+
+ if (pCharFormat)
+ rSet.Put(pCharFormat->GetAttrSet());
+ }
+ }
+
+ continue;
+ }
+
+ SwNodeOffset nSttNd = rCurrentPaM.Start()->GetNodeIndex(),
+ nEndNd = rCurrentPaM.End()->GetNodeIndex();
+ sal_Int32 nSttCnt = rCurrentPaM.Start()->GetContentIndex();
+ sal_Int32 nEndCnt = rCurrentPaM.End()->GetContentIndex();
+
+ if( sal_Int32(nEndNd - nSttNd) >= getMaxLookup() )
+ {
+ rSet.ClearItem();
+ rSet.InvalidateAllItems();
+ return false;
+ }
+
+ // at first node the node enter his values into the GetSet (Initial)
+ // all additional nodes are additional merged to GetSet
+ for( SwNodeOffset n = nSttNd; n <= nEndNd; ++n )
+ {
+ SwNode* pNd = GetDoc()->GetNodes()[ n ];
+ switch( pNd->GetNodeType() )
+ {
+ case SwNodeType::Text:
+ {
+ const sal_Int32 nStt = (n == nSttNd) ? nSttCnt : 0;
+ const sal_Int32 nEnd = (n == nEndNd)
+ ? nEndCnt
+ : pNd->GetTextNode()->GetText().getLength();
+
+ static_cast<SwTextNode*>(pNd)->GetParaAttr(*pSet, nStt, nEnd,
+ false, true,
+ bMergeIndentValuesOfNumRule,
+ GetLayout());
+ }
+ break;
+ case SwNodeType::Grf:
+ case SwNodeType::Ole:
+ static_cast<SwContentNode*>(pNd)->GetAttr( *pSet );
+ break;
+
+ default:
+ pNd = nullptr;
+ }
+
+ if( pNd )
+ {
+ if( pSet != &rSet )
+ {
+ if (!GetLayout()->HasMergedParas()
+ || pNd->GetRedlineMergeFlag() != SwNode::Merge::Hidden)
+ {
+ rSet.MergeValues( aSet );
+ }
+ }
+
+ if( aSet.Count() )
+ aSet.ClearItem();
+ }
+ pSet = &aSet;
+ }
+
+ }
+
+ return true;
+}
+
+bool SwEditShell::GetCurAttr( SfxItemSet& rSet,
+ const bool bMergeIndentValuesOfNumRule ) const
+{
+ return GetPaMAttr( GetCursor(), rSet, bMergeIndentValuesOfNumRule );
+}
+
+void SwEditShell::GetCurParAttr( SfxItemSet& rSet) const
+{
+ GetPaMParAttr( GetCursor(), rSet );
+}
+
+void SwEditShell::GetPaMParAttr( SwPaM* pPaM, SfxItemSet& rSet ) const
+{
+ // number of nodes the function has explored so far
+ sal_uInt16 numberOfLookup = 0;
+
+ SfxItemSet aSet( *rSet.GetPool(), rSet.GetRanges() );
+ SfxItemSet* pSet = &rSet;
+
+ for(SwPaM& rCurrentPaM : pPaM->GetRingContainer())
+ { // for all the point and mark (selections)
+
+ // get the start and the end node of the current selection
+ SwNodeOffset nSttNd = rCurrentPaM.GetMark()->GetNodeIndex(),
+ nEndNd = rCurrentPaM.GetPoint()->GetNodeIndex();
+
+ // reverse start and end if there number aren't sorted correctly
+ if( nSttNd > nEndNd )
+ std::swap(nSttNd, nEndNd);
+
+ // for all the nodes in the current selection
+ // get the node (paragraph) attributes
+ // and merge them in rSet
+ for( SwNodeOffset n = nSttNd; n <= nEndNd; ++n )
+ {
+ // get the node
+ SwNode* pNd = GetDoc()->GetNodes()[ n ];
+
+ if (GetLayout()->HasMergedParas()
+ && pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden)
+ {
+ continue;
+ }
+
+ if( pNd->IsTextNode() )
+ {
+ // get the node (paragraph) attributes
+ sw::GetAttrMerged(*pSet, *pNd->GetTextNode(), GetLayout());
+
+ if( pSet != &rSet && aSet.Count() )
+ {
+ rSet.MergeValues( aSet );
+ aSet.ClearItem();
+ }
+
+ pSet = &aSet;
+ }
+
+ ++numberOfLookup;
+
+ // if the maximum number of node that can be inspected has been reached
+ if (numberOfLookup >= getMaxLookup())
+ return;
+ }
+ }
+}
+
+SwTextFormatColl* SwEditShell::GetCurTextFormatColl( ) const
+{
+ return GetPaMTextFormatColl( GetCursor() );
+}
+
+SwTextFormatColl* SwEditShell::GetPaMTextFormatColl( SwPaM* pPaM ) const
+{
+ // number of nodes the function have explored so far
+ sal_uInt16 numberOfLookup = 0;
+
+ for(SwPaM& rCurrentPaM : pPaM->GetRingContainer())
+ { // for all the point and mark (selections)
+
+ // get the start and the end node of the current selection
+ SwNodeOffset nSttNd = rCurrentPaM.Start()->GetNodeIndex(),
+ nEndNd = rCurrentPaM.End()->GetNodeIndex();
+
+ // for all the nodes in the current Point and Mark
+ for( SwNodeOffset n = nSttNd; n <= nEndNd; ++n )
+ {
+ // get the node
+ SwNode* pNd = GetDoc()->GetNodes()[ n ];
+
+ ++numberOfLookup;
+
+ // if the maximum number of node that can be inspected has been reached
+ if (numberOfLookup >= getMaxLookup())
+ return nullptr;
+
+ if( pNd->IsTextNode() )
+ {
+ SwTextNode *const pTextNode(sw::GetParaPropsNode(*GetLayout(), *pNd));
+ // if it's a text node get its named paragraph format
+ SwTextFormatColl *const pFormat = pTextNode->GetTextColl();
+
+ // if the paragraph format exist stop here and return it
+ if( pFormat != nullptr )
+ return pFormat;
+ }
+ }
+ }
+
+ // if none of the selected node contain a named paragraph format
+ return nullptr;
+}
+
+std::vector<std::pair< const SfxPoolItem*, std::unique_ptr<SwPaM> >> SwEditShell::GetItemWithPaM( sal_uInt16 nWhich )
+{
+ assert(isCHRATR(nWhich)); // sw_redlinehide: only thing that works
+ std::vector<std::pair< const SfxPoolItem*, std::unique_ptr<SwPaM> >> vItem;
+ for(SwPaM& rCurrentPaM : GetCursor()->GetRingContainer())
+ { // for all the point and mark (selections)
+
+ // get the start and the end node of the current selection
+ SwNodeOffset nSttNd = rCurrentPaM.Start()->GetNodeIndex(),
+ nEndNd = rCurrentPaM.End()->GetNodeIndex();
+ sal_Int32 nSttCnt = rCurrentPaM.Start()->GetContentIndex();
+ sal_Int32 nEndCnt = rCurrentPaM.End()->GetContentIndex();
+
+ SwPaM* pNewPaM = nullptr;
+ const SfxPoolItem* pItem = nullptr;
+
+ // for all the nodes in the current selection
+ for( SwNodeOffset n = nSttNd; n <= nEndNd; ++n )
+ {
+ SwNode* pNd = GetDoc()->GetNodes()[ n ];
+ if( pNd->IsTextNode() )
+ {
+ SwTextNode* pTextNd = static_cast< SwTextNode* >( pNd );
+ const sal_Int32 nStt = (n == nSttNd) ? nSttCnt : 0;
+ const sal_Int32 nEnd = (n == nEndNd)
+ ? nEndCnt : pTextNd->GetText().getLength();
+ SwTextFrame const* pFrame;
+ const SwScriptInfo *const pScriptInfo =
+ SwScriptInfo::GetScriptInfo(*pTextNd, &pFrame);
+ TextFrameIndex const iStt(pScriptInfo
+ ? pFrame->MapModelToView(pTextNd, nStt)
+ : TextFrameIndex(-1/*invalid, do not use*/));
+ sal_uInt8 nScript = pScriptInfo
+ ? pScriptInfo->ScriptType(iStt)
+ : css::i18n::ScriptType::WEAK;
+ nWhich = GetWhichOfScript( nWhich, nScript );
+
+ // item from attribute set
+ if( pTextNd->HasSwAttrSet() )
+ {
+ pNewPaM = new SwPaM(*pNd, nStt, *pNd, nEnd);
+ pItem = pTextNd->GetSwAttrSet().GetItem( nWhich );
+ vItem.emplace_back( pItem, std::unique_ptr<SwPaM>(pNewPaM) );
+ }
+
+ if( !pTextNd->HasHints() )
+ continue;
+
+ // items with limited range
+ const size_t nSize = pTextNd->GetpSwpHints()->Count();
+ for( size_t m = 0; m < nSize; m++ )
+ {
+ const SwTextAttr* pHt = pTextNd->GetpSwpHints()->Get(m);
+ if( pHt->Which() == RES_TXTATR_AUTOFMT ||
+ pHt->Which() == RES_TXTATR_CHARFMT ||
+ pHt->Which() == RES_TXTATR_INETFMT )
+ {
+ const sal_Int32 nAttrStart = pHt->GetStart();
+ const sal_Int32* pAttrEnd = pHt->End();
+
+ // Ignore items not in selection
+ if( nAttrStart > nEnd )
+ break;
+ if( *pAttrEnd <= nStt )
+ continue;
+
+ nScript = pScriptInfo
+ ? pScriptInfo->ScriptType(iStt)
+ : css::i18n::ScriptType::WEAK;
+ nWhich = GetWhichOfScript( nWhich, nScript );
+ const SfxItemSet* pAutoSet = CharFormat::GetItemSet( pHt->GetAttr() );
+ if( pAutoSet )
+ {
+ SfxItemIter aItemIter( *pAutoSet );
+ pItem = aItemIter.GetCurItem();
+ while( pItem )
+ {
+ if( pItem->Which() == nWhich )
+ {
+ sal_Int32 nStart = 0, nStop = 0;
+ if( nAttrStart < nStt ) // Attribute starts before selection
+ nStart = nStt;
+ else
+ nStart = nAttrStart;
+ if( *pAttrEnd > nEnd ) // Attribute ends after selection
+ nStop = nEnd;
+ else
+ nStop = *pAttrEnd;
+ pNewPaM = new SwPaM(*pNd, nStart, *pNd, nStop);
+ vItem.emplace_back( pItem, std::unique_ptr<SwPaM>(pNewPaM) );
+ break;
+ }
+ pItem = aItemIter.NextItem();
+ }
+ // default item
+ if( !pItem && !pTextNd->HasSwAttrSet() )
+ {
+ pNewPaM = new SwPaM(*pNd, nStt, *pNd, nEnd);
+ pItem = pAutoSet->GetPool()->GetPoolDefaultItem( nWhich );
+ vItem.emplace_back( pItem, std::unique_ptr<SwPaM>(pNewPaM) );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return vItem;
+}
+
+bool SwEditShell::GetCurFootnote( SwFormatFootnote* pFillFootnote )
+{
+ // The cursor must be positioned on the current footnotes anchor:
+ SwPaM* pCursor = GetCursor();
+ SwTextNode* pTextNd = pCursor->GetPointNode().GetTextNode();
+ if( !pTextNd )
+ return false;
+
+ SwTextAttr *const pFootnote = pTextNd->GetTextAttrForCharAt(
+ pCursor->GetPoint()->GetContentIndex(), RES_TXTATR_FTN);
+ if( pFootnote && pFillFootnote )
+ {
+ // Transfer data from the attribute
+ const SwFormatFootnote &rFootnote = static_cast<SwTextFootnote*>(pFootnote)->GetFootnote();
+ pFillFootnote->SetNumber( rFootnote );
+ pFillFootnote->SetEndNote( rFootnote.IsEndNote() );
+ }
+ return nullptr != pFootnote;
+}
+
+bool SwEditShell::SetCurFootnote( const SwFormatFootnote& rFillFootnote )
+{
+ bool bChgd = false;
+ StartAllAction();
+
+ for(const SwPaM& rCursor : GetCursor()->GetRingContainer())
+ {
+ bChgd |=
+ mxDoc->SetCurFootnote(rCursor, rFillFootnote.GetNumStr(), rFillFootnote.IsEndNote());
+
+ }
+
+ EndAllAction();
+ return bChgd;
+}
+
+bool SwEditShell::HasFootnotes( bool bEndNotes ) const
+{
+ const SwFootnoteIdxs &rIdxs = mxDoc->GetFootnoteIdxs();
+ for ( auto pIdx : rIdxs )
+ {
+ const SwFormatFootnote &rFootnote = pIdx->GetFootnote();
+ if ( bEndNotes == rFootnote.IsEndNote() )
+ return true;
+ }
+ return false;
+}
+
+/// Give a List of all footnotes and their beginning texts
+size_t SwEditShell::GetSeqFootnoteList( SwSeqFieldList& rList, bool bEndNotes )
+{
+ rList.Clear();
+
+ IDocumentRedlineAccess & rIDRA(mxDoc->getIDocumentRedlineAccess());
+
+ const size_t nFootnoteCnt = mxDoc->GetFootnoteIdxs().size();
+ SwTextFootnote* pTextFootnote;
+ for( size_t n = 0; n < nFootnoteCnt; ++n )
+ {
+ pTextFootnote = mxDoc->GetFootnoteIdxs()[ n ];
+ const SwFormatFootnote& rFootnote = pTextFootnote->GetFootnote();
+ if ( rFootnote.IsEndNote() != bEndNotes )
+ continue;
+
+ const SwNodeIndex* pIdx = pTextFootnote->GetStartNode();
+ if( pIdx )
+ {
+ SwNodeIndex aIdx( *pIdx, 1 );
+ SwTextNode* pTextNd = aIdx.GetNode().GetTextNode();
+ if( !pTextNd )
+ pTextNd = static_cast<SwTextNode*>(mxDoc->GetNodes().GoNext( &aIdx ));
+
+ if( pTextNd )
+ {
+ if (GetLayout()->IsHideRedlines()
+ && sw::IsFootnoteDeleted(rIDRA, *pTextFootnote))
+ {
+ continue;
+ }
+
+ OUString sText(rFootnote.GetViewNumStr(*mxDoc, GetLayout()));
+ if( !sText.isEmpty() )
+ sText += " ";
+ sText += pTextNd->GetExpandText(GetLayout());
+
+ SeqFieldLstElem aNew( sText, pTextFootnote->GetSeqRefNo() );
+ while( rList.InsertSort( aNew ) )
+ aNew.sDlgEntry += " ";
+ }
+ }
+ }
+
+ return rList.Count();
+}
+
+/// Adjust left margin via object bar (similar to adjustment of numerations).
+bool SwEditShell::IsMoveLeftMargin( bool bRight, bool bModulus ) const
+{
+ bool bRet = true;
+
+ constexpr sal_uInt16 constTwips_2cm = o3tl::toTwips(20, o3tl::Length::mm);
+ constexpr tools::Long constTwips_5mm = o3tl::toTwips(5, o3tl::Length::mm);
+
+ const SvxTabStopItem& rTabItem = GetDoc()->GetDefault( RES_PARATR_TABSTOP );
+ sal_uInt16 nDefDist = o3tl::narrowing<sal_uInt16>(
+ rTabItem.Count() ? rTabItem[0].GetTabPos() : constTwips_2cm);
+
+ if( !nDefDist )
+ return false;
+
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ SwNodeOffset nSttNd = rPaM.Start()->GetNodeIndex(),
+ nEndNd = rPaM.End()->GetNodeIndex();
+
+ SwContentNode* pCNd;
+ for( SwNodeOffset n = nSttNd; bRet && n <= nEndNd; ++n )
+ {
+ pCNd = GetDoc()->GetNodes()[ n ]->GetTextNode();
+ if( nullptr != pCNd )
+ {
+ pCNd = sw::GetParaPropsNode(*GetLayout(), *pCNd);
+ const SvxLRSpaceItem& rLS = pCNd->GetAttr( RES_LR_SPACE );
+ if( bRight )
+ {
+ tools::Long nNext = rLS.GetTextLeft() + nDefDist;
+ if( bModulus )
+ nNext = ( nNext / nDefDist ) * nDefDist;
+ SwFrame* pFrame = pCNd->getLayoutFrame( GetLayout() );
+ if ( pFrame )
+ {
+ const SwTwips nFrameWidth = pFrame->IsVertical() ?
+ pFrame->getFrameArea().Height() :
+ pFrame->getFrameArea().Width();
+ bRet = nFrameWidth > (nNext + constTwips_5mm);
+ }
+ else
+ bRet = false;
+ }
+ }
+ }
+
+ if( !bRet )
+ break;
+
+ }
+ return bRet;
+}
+
+void SwEditShell::MoveLeftMargin( bool bRight, bool bModulus )
+{
+ StartAllAction();
+ StartUndo( SwUndoId::START );
+
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->GetNext() != pCursor ) // Multiple selection ?
+ {
+ SwPamRanges aRangeArr( *pCursor );
+ SwPaM aPam( *pCursor->GetPoint() );
+ for( size_t n = 0; n < aRangeArr.Count(); ++n )
+ GetDoc()->MoveLeftMargin( aRangeArr.SetPam( n, aPam ),
+ bRight, bModulus, GetLayout() );
+ }
+ else
+ GetDoc()->MoveLeftMargin( *pCursor, bRight, bModulus, GetLayout() );
+
+ EndUndo( SwUndoId::END );
+ EndAllAction();
+}
+
+static SvtScriptType lcl_SetScriptFlags( sal_uInt16 nType )
+{
+ switch( nType )
+ {
+ case css::i18n::ScriptType::LATIN:
+ return SvtScriptType::LATIN;
+ case css::i18n::ScriptType::ASIAN:
+ return SvtScriptType::ASIAN;
+ case css::i18n::ScriptType::COMPLEX:
+ return SvtScriptType::COMPLEX;
+ default:
+ return SvtScriptType::NONE;
+ }
+}
+
+static bool lcl_IsNoEndTextAttrAtPos(SwRootFrame const& rLayout,
+ const SwTextNode& rTNd, sal_Int32 const nPos,
+ SvtScriptType &rScrpt, bool bInSelection, bool bNum )
+{
+ bool bRet = false;
+ OUString sExp;
+
+ // consider numbering
+ if ( bNum )
+ {
+ bRet = false;
+ SwTextNode const*const pPropsNode(sw::GetParaPropsNode(rLayout, rTNd));
+ if (pPropsNode->IsInList())
+ {
+ OSL_ENSURE( pPropsNode->GetNumRule(),
+ "<lcl_IsNoEndTextAttrAtPos(..)> - no list style found at text node. Serious defect." );
+ const SwNumRule* pNumRule = pPropsNode->GetNumRule();
+ if(pNumRule)
+ {
+ int nListLevel = pPropsNode->GetActualListLevel();
+
+ if (nListLevel < 0)
+ nListLevel = 0;
+
+ if (nListLevel >= MAXLEVEL)
+ nListLevel = MAXLEVEL - 1;
+
+ const SwNumFormat &rNumFormat = pNumRule->Get( o3tl::narrowing<sal_uInt16>(nListLevel) );
+ if( SVX_NUM_BITMAP != rNumFormat.GetNumberingType() )
+ {
+ if ( SVX_NUM_CHAR_SPECIAL == rNumFormat.GetNumberingType() )
+ {
+ sal_UCS4 cBullet = rNumFormat.GetBulletChar();
+ sExp = OUString(&cBullet, 1);
+ }
+ else
+ sExp = pPropsNode->GetNumString(true, MAXLEVEL, &rLayout);
+ }
+ }
+ }
+ }
+
+ // and fields
+ if (nPos < rTNd.GetText().getLength() && CH_TXTATR_BREAKWORD == rTNd.GetText()[nPos])
+ {
+ const SwTextAttr* const pAttr = rTNd.GetTextAttrForCharAt( nPos );
+ if (pAttr)
+ {
+ bRet = true; // all other than fields can be
+ // defined as weak-script ?
+ if ( RES_TXTATR_FIELD == pAttr->Which() )
+ {
+ const SwField* const pField = pAttr->GetFormatField().GetField();
+ if (pField)
+ {
+ sExp += pField->ExpandField(true, &rLayout);
+ }
+ }
+ }
+ }
+
+ const sal_Int32 nEnd = sExp.getLength();
+ if ( nEnd )
+ {
+ if( bInSelection )
+ {
+ sal_uInt16 nScript;
+ for( sal_Int32 n = 0; n < nEnd;
+ n = g_pBreakIt->GetBreakIter()->endOfScript( sExp, n, nScript ))
+ {
+ nScript = g_pBreakIt->GetBreakIter()->getScriptType( sExp, n );
+ rScrpt |= lcl_SetScriptFlags( nScript );
+ }
+ }
+ else
+ rScrpt |= lcl_SetScriptFlags( g_pBreakIt->GetBreakIter()->
+ getScriptType( sExp, nEnd-1 ));
+ }
+
+ return bRet;
+}
+
+/// returns the script type of the selection
+SvtScriptType SwEditShell::GetScriptType() const
+{
+ SvtScriptType nRet = SvtScriptType::NONE;
+
+ {
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition*
+ if( pStt == pEnd || *pStt == *pEnd )
+ {
+ const SwTextNode* pTNd = pStt->GetNode().GetTextNode();
+ if( pTNd )
+ {
+ // try to get SwScriptInfo
+ SwTextFrame const* pFrame;
+ const SwScriptInfo *const pScriptInfo =
+ SwScriptInfo::GetScriptInfo(*pTNd, &pFrame);
+
+ sal_Int32 nPos = pStt->GetContentIndex();
+ //Task 90448: we need the scripttype of the previous
+ // position, if no selection exist!
+ if( nPos )
+ {
+ SwContentIndex aIdx( pStt->GetContentNode(), pStt->GetContentIndex() );
+ if( pTNd->GoPrevious( &aIdx, SwCursorSkipMode::Chars ) )
+ nPos = aIdx.GetIndex();
+ }
+
+ sal_uInt16 nScript;
+
+ if (!pTNd->GetText().isEmpty())
+ {
+ nScript = pScriptInfo
+ ? pScriptInfo->ScriptType(pFrame->MapModelToView(pTNd, nPos))
+ : g_pBreakIt->GetBreakIter()->getScriptType( pTNd->GetText(), nPos );
+ }
+ else
+ nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() );
+
+ if (!lcl_IsNoEndTextAttrAtPos(*GetLayout(), *pTNd, nPos, nRet, false, false))
+ nRet |= lcl_SetScriptFlags( nScript );
+ }
+ }
+ else
+ {
+ SwNodeOffset nEndIdx = pEnd->GetNodeIndex();
+ SwNodeIndex aIdx( pStt->GetNode() );
+ for( ; aIdx.GetIndex() <= nEndIdx; ++aIdx )
+ if( aIdx.GetNode().IsTextNode() )
+ {
+ const SwTextNode* pTNd = aIdx.GetNode().GetTextNode();
+ const OUString& rText = pTNd->GetText();
+
+ // try to get SwScriptInfo
+ SwTextFrame const* pFrame;
+ const SwScriptInfo *const pScriptInfo =
+ SwScriptInfo::GetScriptInfo(*pTNd, &pFrame);
+
+ sal_Int32 nChg = aIdx == pStt->GetNode()
+ ? pStt->GetContentIndex()
+ : 0;
+ sal_Int32 nEndPos = aIdx == nEndIdx
+ ? pEnd->GetContentIndex()
+ : rText.getLength();
+
+ OSL_ENSURE( nEndPos <= rText.getLength(),
+ "Index outside the range - endless loop!" );
+ if (nEndPos > rText.getLength())
+ nEndPos = rText.getLength();
+
+ bool const isUntilEnd(pScriptInfo
+ ? pFrame->MapViewToModelPos(TextFrameIndex(pFrame->GetText().getLength())) <= *pEnd
+ : rText.getLength() == nEndPos);
+ sal_uInt16 nScript;
+ while( nChg < nEndPos )
+ {
+ TextFrameIndex iChg(pScriptInfo
+ ? pFrame->MapModelToView(pTNd, nChg)
+ : TextFrameIndex(-1/*invalid, do not use*/));
+ nScript = pScriptInfo ?
+ pScriptInfo->ScriptType( iChg ) :
+ g_pBreakIt->GetBreakIter()->getScriptType(
+ rText, nChg );
+
+ if (!lcl_IsNoEndTextAttrAtPos(*GetLayout(), *pTNd, nChg, nRet, true,
+ TextFrameIndex(0) == iChg && isUntilEnd))
+ {
+ nRet |= lcl_SetScriptFlags( nScript );
+ }
+
+ if( (SvtScriptType::LATIN | SvtScriptType::ASIAN |
+ SvtScriptType::COMPLEX) == nRet )
+ break;
+
+ sal_Int32 nFieldPos = nChg+1;
+
+ if (pScriptInfo)
+ {
+ iChg = pScriptInfo->NextScriptChg(iChg);
+ if (iChg == TextFrameIndex(COMPLETE_STRING))
+ {
+ nChg = pTNd->Len();
+ }
+ else
+ {
+ std::pair<SwTextNode*, sal_Int32> const tmp(
+ pFrame->MapViewToModel(iChg));
+ nChg = (tmp.first == pTNd)
+ ? tmp.second
+ : pTNd->Len();
+ }
+ }
+ else
+ {
+ nChg = g_pBreakIt->GetBreakIter()->endOfScript(
+ rText, nChg, nScript );
+ }
+
+ nFieldPos = rText.indexOf(
+ CH_TXTATR_BREAKWORD, nFieldPos);
+ if ((-1 != nFieldPos) && (nFieldPos < nChg))
+ nChg = nFieldPos;
+ }
+ if( (SvtScriptType::LATIN | SvtScriptType::ASIAN |
+ SvtScriptType::COMPLEX) == nRet )
+ break;
+ }
+ }
+ if( (SvtScriptType::LATIN | SvtScriptType::ASIAN |
+ SvtScriptType::COMPLEX) == nRet )
+ break;
+
+ }
+ }
+ if( nRet == SvtScriptType::NONE )
+ nRet = SvtLanguageOptions::GetScriptTypeOfLanguage( LANGUAGE_SYSTEM );
+ return nRet;
+}
+
+LanguageType SwEditShell::GetCurLang() const
+{
+ const SwPaM* pCursor = GetCursor();
+ const SwPosition& rPos = *pCursor->GetPoint();
+ const SwTextNode* pTNd = rPos.GetNode().GetTextNode();
+ LanguageType nLang;
+ if( pTNd )
+ {
+ //JP 24.9.2001: if exist no selection, then get the language before
+ // the current character!
+ sal_Int32 nPos = rPos.GetContentIndex();
+ if( nPos && !pCursor->HasMark() )
+ --nPos;
+ nLang = pTNd->GetLang( nPos );
+ }
+ else
+ nLang = LANGUAGE_DONTKNOW;
+ return nLang;
+}
+
+sal_uInt16 SwEditShell::GetScalingOfSelectedText() const
+{
+ const SwPaM* pCursor = GetCursor();
+ const SwPosition* pStt = pCursor->Start();
+ const SwTextNode* pTNd = pStt->GetNode().GetTextNode();
+ OSL_ENSURE( pTNd, "no textnode available" );
+
+ sal_uInt16 nScaleWidth;
+ if( pTNd )
+ {
+ SwTextFrame *const pFrame(static_cast<SwTextFrame *>(
+ pTNd->getLayoutFrame(GetLayout(), pStt)));
+ assert(pFrame); // shell cursor must be positioned in node with frame
+ TextFrameIndex const nStart(pFrame->MapModelToViewPos(*pStt));
+ TextFrameIndex const nEnd(
+ sw::FrameContainsNode(*pFrame, pCursor->End()->GetNodeIndex())
+ ? pFrame->MapModelToViewPos(*pCursor->End())
+ : TextFrameIndex(pFrame->GetText().getLength()));
+ nScaleWidth = pFrame->GetScalingOfSelectedText(nStart, nEnd);
+ }
+ else
+ nScaleWidth = 100; // default are no scaling -> 100%
+ return nScaleWidth;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/eddel.cxx b/sw/source/core/edit/eddel.cxx
new file mode 100644
index 0000000000..9eb51da617
--- /dev/null
+++ b/sw/source/core/edit/eddel.cxx
@@ -0,0 +1,427 @@
+/* -*- 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 <memory>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentContentOperations.hxx>
+#include <editsh.hxx>
+#include <pam.hxx>
+#include <swundo.hxx>
+#include <undobj.hxx>
+#include <SwRewriter.hxx>
+#include <osl/diagnose.h>
+#include <wrtsh.hxx>
+#include <officecfg/Office/Writer.hxx>
+
+#include <strings.hrc>
+#include <vector>
+
+void SwEditShell::DeleteSel(SwPaM& rPam, bool const isArtificialSelection, bool *const pUndo)
+{
+ auto const oSelectAll(StartsWith_() != SwCursorShell::StartsWith::None
+ ? ExtendedSelectedAll()
+ : ::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode *>>>{});
+ // only for selections
+ if (!rPam.HasMark()
+ || (*rPam.GetPoint() == *rPam.GetMark()
+ && !IsFlySelectedByCursor(*GetDoc(), *rPam.Start(), *rPam.End())))
+ {
+ return;
+ }
+
+ // Is the selection in a table? Then delete only the content of the selected boxes.
+ // Here, there are two cases:
+ // 1. Point and Mark are in one box, delete selection as usual
+ // 2. Point and Mark are in different boxes, search all selected boxes and delete content
+ // 3. Point and Mark are at the document start and end, Point is in a table: delete selection as usual
+ if( rPam.GetPointNode().FindTableNode() &&
+ rPam.GetPointNode().StartOfSectionNode() !=
+ rPam.GetMarkNode().StartOfSectionNode() && !oSelectAll)
+ {
+ // group the Undo in the table
+ if( pUndo && !*pUndo )
+ {
+ GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
+ *pUndo = true;
+ }
+ SwPaM aDelPam( *rPam.Start() );
+ const SwPosition* pEndSelPos = rPam.End();
+ do {
+ aDelPam.SetMark();
+ SwNode& rNd = aDelPam.GetPointNode();
+ const SwNode& rEndNd = *rNd.EndOfSectionNode();
+ if( pEndSelPos->GetNodeIndex() <= rEndNd.GetIndex() )
+ {
+ *aDelPam.GetPoint() = *pEndSelPos;
+ pEndSelPos = nullptr; // misuse a pointer as a flag
+ }
+ else
+ {
+ // then go to the end of the selection
+ aDelPam.GetPoint()->Assign(rEndNd);
+ aDelPam.Move( fnMoveBackward, GoInContent );
+ }
+ // skip protected boxes
+ if( !rNd.IsContentNode() ||
+ !rNd.IsInProtectSect() )
+ {
+ // delete everything
+ GetDoc()->getIDocumentContentOperations().DeleteAndJoin( aDelPam );
+ SaveTableBoxContent( aDelPam.GetPoint() );
+ }
+
+ if( !pEndSelPos ) // at the end of a selection
+ break;
+ aDelPam.DeleteMark();
+ aDelPam.Move( fnMoveForward, GoInContent ); // next box
+ } while( pEndSelPos );
+ }
+ else
+ {
+ std::optional<SwPaM> pNewPam;
+ SwPaM * pPam = &rPam;
+ if (oSelectAll)
+ {
+ if (!oSelectAll->second.empty())
+ {
+ SwRewriter aRewriter;
+ aRewriter.AddRule(UndoArg1, SwResId(STR_MULTISEL));
+ GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::DELETE, &aRewriter);
+ }
+ // tdf#155685 tables at the end must be deleted separately
+ for (SwTableNode *const pTable : oSelectAll->second)
+ {
+ GetDoc()->DelTable(pTable);
+ }
+ assert(dynamic_cast<SwShellCursor*>(&rPam)); // must be corrected pam
+ pNewPam.emplace(*rPam.GetMark(), *rPam.GetPoint());
+ // Selection starts at the first para of the first cell, but we
+ // want to delete the table node before the first cell as well.
+ pNewPam->Start()->Assign(*oSelectAll->first);
+ pPam = &*pNewPam;
+ }
+ // delete everything
+ GetDoc()->getIDocumentContentOperations().DeleteAndJoin(*pPam,
+ isArtificialSelection ? SwDeleteFlags::ArtificialSelection : SwDeleteFlags::Default);
+ SaveTableBoxContent( pPam->GetPoint() );
+ if (oSelectAll && !oSelectAll->second.empty())
+ {
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr);
+ }
+ }
+
+ // Selection is not needed anymore
+ rPam.DeleteMark();
+}
+
+bool SwEditShell::Delete(bool const isArtificialSelection)
+{
+ CurrShell aCurr( this );
+ bool bRet = false;
+ if ( !HasReadonlySel() || CursorInsideInputField() )
+ {
+ if (HasHiddenSections() &&
+ officecfg::Office::Writer::Content::Display::ShowWarningHiddenSection::get())
+ {
+ if (!WarnHiddenSectionDialog())
+ {
+ bRet = RemoveParagraphMetadataFieldAtCursor();
+ return bRet;
+ }
+ }
+
+ StartAllAction();
+ bool bUndo = GetCursor()->GetNext() != GetCursor();
+ if( bUndo ) // more than one selection?
+ {
+ SwRewriter aRewriter;
+ aRewriter.AddRule(UndoArg1, SwResId(STR_MULTISEL));
+
+ GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::DELETE, &aRewriter);
+ }
+
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ DeleteSel(rPaM, isArtificialSelection, &bUndo);
+ }
+
+ // If undo container then close here
+ if( bUndo )
+ {
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr);
+ }
+ EndAllAction();
+ bRet = true;
+ }
+ else
+ {
+ bRet = RemoveParagraphMetadataFieldAtCursor();
+ if (!bRet)
+ {
+ InfoReadOnlyDialog(false);
+ }
+ }
+
+ return bRet;
+}
+
+bool SwEditShell::Copy( SwEditShell& rDestShell )
+{
+ CurrShell aCurr( &rDestShell );
+
+ // List of insert positions for smart insert of block selections
+ std::vector< std::shared_ptr<SwPosition> > aInsertList;
+
+ // Fill list of insert positions
+ {
+ SwPosition * pPos = nullptr;
+ std::shared_ptr<SwPosition> pInsertPos;
+ sal_uInt16 nMove = 0;
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ if( !pPos )
+ {
+ if( &rDestShell == this )
+ {
+ // First cursor represents the target position!!
+ rPaM.DeleteMark();
+ pPos = rPaM.GetPoint();
+ continue;
+ }
+ else
+ pPos = rDestShell.GetCursor()->GetPoint();
+ }
+ if( IsBlockMode() )
+ { // In block mode different insert positions will be calculated
+ // by simulated cursor movements from the given first insert position
+ if( nMove )
+ {
+ SwCursor aCursor( *pPos, nullptr);
+ if (aCursor.UpDown(false, nMove, nullptr, 0, *GetLayout()))
+ {
+ pInsertPos = std::make_shared<SwPosition>( *aCursor.GetPoint() );
+ aInsertList.push_back( pInsertPos );
+ }
+ }
+ else
+ pInsertPos = std::make_shared<SwPosition>( *pPos );
+ ++nMove;
+ }
+ SwPosition *pTmp = IsBlockMode() ? pInsertPos.get() : pPos;
+ // Check if a selection would be copied into itself
+ if( rDestShell.GetDoc() == GetDoc() &&
+ *rPaM.Start() <= *pTmp && *pTmp < *rPaM.End() )
+ return false;
+ }
+ }
+
+ rDestShell.StartAllAction();
+ SwPosition *pPos = nullptr;
+ bool bRet = false;
+ bool bFirstMove = true;
+ SwNodeIndex aSttNdIdx( rDestShell.GetDoc()->GetNodes() );
+ sal_Int32 nSttCntIdx = 0;
+ // For block selection this list is filled with the insert positions
+ auto pNextInsert = aInsertList.begin();
+
+ rDestShell.GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ if( !pPos )
+ {
+ if( &rDestShell == this )
+ {
+ // First cursor represents the target position!!
+ rPaM.DeleteMark();
+ pPos = rPaM.GetPoint();
+ continue;
+ }
+ else
+ pPos = rDestShell.GetCursor()->GetPoint();
+ }
+ if( !bFirstMove )
+ {
+ if( pNextInsert != aInsertList.end() )
+ {
+ pPos = pNextInsert->get();
+ ++pNextInsert;
+ }
+ else if( IsBlockMode() )
+ GetDoc()->getIDocumentContentOperations().SplitNode( *pPos, false );
+ }
+
+ // Only for a selection (non-text nodes have selection but Point/GetMark are equal)
+ if( !rPaM.HasMark() || *rPaM.GetPoint() == *rPaM.GetMark() )
+ continue;
+
+ if( bFirstMove )
+ {
+ // Store start position of the new area
+ aSttNdIdx = pPos->GetNodeIndex()-1;
+ nSttCntIdx = pPos->GetContentIndex();
+ bFirstMove = false;
+ }
+
+ const bool bSuccess( GetDoc()->getIDocumentContentOperations().CopyRange(rPaM, *pPos, SwCopyFlags::CheckPosInFly) );
+ if (!bSuccess)
+ continue;
+
+ SwPaM aInsertPaM(*pPos, SwPosition(aSttNdIdx));
+ rDestShell.GetDoc()->MakeUniqueNumRules(aInsertPaM);
+
+ bRet = true;
+ }
+
+ // Maybe nothing has been moved?
+ if( !bFirstMove )
+ {
+ SwPaM* pCursor = rDestShell.GetCursor();
+ pCursor->SetMark();
+ pCursor->GetPoint()->Assign( aSttNdIdx.GetIndex()+1, nSttCntIdx );
+ pCursor->Exchange();
+ }
+ else
+ {
+ // If the cursor moved during move process, move also its GetMark
+ rDestShell.GetCursor()->SetMark();
+ rDestShell.GetCursor()->DeleteMark();
+ }
+#if OSL_DEBUG_LEVEL > 0
+ // check if the indices are registered in the correct nodes
+ {
+ for(SwPaM& rCmp : rDestShell.GetCursor()->GetRingContainer())
+ {
+ OSL_ENSURE( rCmp.GetPoint()->GetContentNode()
+ == rCmp.GetPointContentNode(), "Point in wrong Node" );
+ OSL_ENSURE( rCmp.GetMark()->GetContentNode()
+ == rCmp.GetMarkContentNode(), "Mark in wrong Node" );
+ }
+ }
+#endif
+
+ // close Undo container here
+ rDestShell.GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
+ rDestShell.EndAllAction();
+
+ rDestShell.SaveTableBoxContent( rDestShell.GetCursor()->GetPoint() );
+
+ return bRet;
+}
+
+/** Replace a selected area in a text node with a given string.
+ *
+ * Intended for "search & replace".
+ *
+ * @param bRegExpRplc if <true> replace tabs (\\t) and replace with found string (not \&).
+ * E.g. [Fnd: "zzz", Repl: "xx\t\\t..&..\&"] --> "xx\t<Tab>..zzz..&"
+ */
+bool SwEditShell::Replace( const OUString& rNewStr, bool bRegExpRplc )
+{
+ CurrShell aCurr( this );
+
+ bool bRet = false;
+ if (!HasReadonlySel(true))
+ {
+ StartAllAction();
+ GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);
+
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ if( rPaM.HasMark() && *rPaM.GetMark() != *rPaM.GetPoint() )
+ {
+ bRet = sw::ReplaceImpl(rPaM, rNewStr, bRegExpRplc, *GetDoc(), GetLayout())
+ || bRet;
+ SaveTableBoxContent( rPaM.GetPoint() );
+ }
+ }
+
+ // close Undo container here
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
+ EndAllAction();
+ }
+ return bRet;
+}
+
+/** Replace a selected area in a text node with a given string.
+ *
+ * Method to replace a text selection with a new string while
+ * keeping possible comments (they will be moved to the end
+ * of the selection).
+ *
+ * @param rNewStr the new string which the selected area is to be replaced with
+ * @return true, if something has been replaced, false otherwise.
+ */
+bool SwEditShell::ReplaceKeepComments( const OUString& rNewStr)
+{
+ bool bRet = false;
+ SwPaM *pCursor = GetCursor();
+
+ if(pCursor != nullptr)
+ {
+ // go sure that the text selection pointed to by pCursor is valid
+ if(pCursor->HasMark())
+ {
+ // preserve comments inside of the number by deleting number portions starting from the back
+ OUString aSelectedText = pCursor->GetText();
+ sal_Int32 nCommentPos(aSelectedText.lastIndexOf(CH_TXTATR_INWORD));
+ // go sure that we have a valid selection and a comment has been found
+ while (nCommentPos > -1)
+ {
+ // select the part of the text after the last found comment
+ // selection start:
+ pCursor->GetPoint()->AdjustContent(nCommentPos + 1);
+ // selection end is left where it is -> will be adjusted later on
+ // delete the part of the word after the last found comment
+ Replace(OUString(), false);
+ // put the selection start back to the beginning of the word
+ pCursor->GetPoint()->AdjustContent(-(nCommentPos + 1));
+ // adjust the selection end, so that the last comment is no longer selected:
+ pCursor->GetMark()->AdjustContent(-1);
+ // search for the next possible comment
+ aSelectedText = pCursor->GetText();
+ nCommentPos = aSelectedText.lastIndexOf(CH_TXTATR_INWORD);
+ }
+ bRet = Replace(rNewStr, false);
+ }
+ }
+
+ return bRet;
+}
+
+/// special method for JOE's wizards
+bool SwEditShell::DelFullPara()
+{
+ bool bRet = false;
+ if( !IsTableMode() )
+ {
+ SwPaM* pCursor = GetCursor();
+ // no multi selection
+ if( !pCursor->IsMultiSelection() && !HasReadonlySel() )
+ {
+ CurrShell aCurr( this );
+ StartAllAction();
+ bRet = GetDoc()->getIDocumentContentOperations().DelFullPara( *pCursor );
+ EndAllAction();
+ }
+ }
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/edfcol.cxx b/sw/source/core/edit/edfcol.cxx
new file mode 100644
index 0000000000..f02273e073
--- /dev/null
+++ b/sw/source/core/edit/edfcol.cxx
@@ -0,0 +1,2326 @@
+/* -*- 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 <sal/config.h>
+
+#include <string_view>
+
+#include <editsh.hxx>
+
+#include <com/sun/star/container/XEnumerationAccess.hpp>
+#include <com/sun/star/container/XContentEnumerationAccess.hpp>
+#include <com/sun/star/document/XActionLockable.hpp>
+#include <com/sun/star/document/XDocumentProperties.hpp>
+#include <com/sun/star/drawing/FillStyle.hpp>
+#include <com/sun/star/drawing/HomogenMatrix3.hpp>
+#include <com/sun/star/drawing/LineStyle.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
+#include <com/sun/star/text/RelOrientation.hpp>
+#include <com/sun/star/text/TextContentAnchorType.hpp>
+#include <com/sun/star/text/VertOrientation.hpp>
+#include <com/sun/star/text/WrapTextMode.hpp>
+#include <com/sun/star/text/XTextContent.hpp>
+#include <com/sun/star/text/XTextDocument.hpp>
+#include <com/sun/star/text/XTextField.hpp>
+#include <com/sun/star/text/XTextRange.hpp>
+#include <com/sun/star/text/XParagraphAppend.hpp>
+#include <com/sun/star/text/XParagraphCursor.hpp>
+#include <com/sun/star/awt/FontWeight.hpp>
+#include <com/sun/star/rdf/XMetadatable.hpp>
+#include <com/sun/star/security/DocumentDigitalSignatures.hpp>
+#include <com/sun/star/security/XCertificate.hpp>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <comphelper/string.hxx>
+#include <editeng/unoprnms.hxx>
+#include <sfx2/classificationhelper.hxx>
+#include <svx/ClassificationCommon.hxx>
+#include <svx/ClassificationField.hxx>
+#include <svl/cryptosign.hxx>
+#include <svl/sigstruct.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/virdev.hxx>
+
+#include <redline.hxx>
+#include <poolfmt.hxx>
+#include <hintids.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <ndtxt.hxx>
+#include <paratr.hxx>
+#include <viewopt.hxx>
+#include <SwRewriter.hxx>
+#include <numrule.hxx>
+#include <swundo.hxx>
+#include <docary.hxx>
+#include <docsh.hxx>
+#include <unoprnms.hxx>
+#include <rootfrm.hxx>
+#include <pagefrm.hxx>
+#include <txtfrm.hxx>
+#include <rdfhelper.hxx>
+#include <sfx2/watermarkitem.hxx>
+
+#include <unoparagraph.hxx>
+#include <strings.hrc>
+#include <undobj.hxx>
+#include <UndoParagraphSignature.hxx>
+#include <txtatr.hxx>
+#include <fmtmeta.hxx>
+#include <unotxdoc.hxx>
+#include <unotextbodyhf.hxx>
+#include <unoport.hxx>
+
+#include <comphelper/diagnose_ex.hxx>
+#include <IDocumentRedlineAccess.hxx>
+
+constexpr OUString WATERMARK_NAME = u"PowerPlusWaterMarkObject"_ustr;
+#define WATERMARK_AUTO_SIZE sal_uInt32(1)
+
+namespace
+{
+constexpr OUString MetaFilename(u"tscp/bails.rdf"_ustr);
+constexpr OUString MetaNS(u"urn:bails"_ustr);
+constexpr OUString ParagraphSignatureRDFNamespace = u"urn:bails:loext:paragraph:signature:"_ustr;
+constexpr OUString ParagraphSignatureIdRDFName = u"urn:bails:loext:paragraph:signature:id"_ustr;
+constexpr OUString ParagraphSignatureDigestRDFName = u":digest"_ustr;
+constexpr OUString ParagraphSignatureDateRDFName = u":date"_ustr;
+constexpr OUString ParagraphSignatureUsageRDFName = u":usage"_ustr;
+constexpr OUString ParagraphSignatureLastIdRDFName = u"urn:bails:loext:paragraph:signature:lastid"_ustr;
+constexpr OUString ParagraphClassificationNameRDFName = u"urn:bails:loext:paragraph:classification:name"_ustr;
+constexpr OUString ParagraphClassificationValueRDFName = u"urn:bails:loext:paragraph:classification:value"_ustr;
+constexpr OUString ParagraphClassificationAbbrRDFName = u"urn:bails:loext:paragraph:classification:abbreviation"_ustr;
+constexpr OUString ParagraphClassificationFieldNamesRDFName = u"urn:bails:loext:paragraph:classification:fields"_ustr;
+constexpr OUString MetadataFieldServiceName = u"com.sun.star.text.textfield.MetadataField"_ustr;
+constexpr OUString DocInfoServiceName = u"com.sun.star.text.TextField.DocInfo.Custom"_ustr;
+
+/// Find all page styles which are currently used in the document.
+std::vector<OUString> lcl_getUsedPageStyles(SwViewShell const * pShell)
+{
+ std::vector<OUString> aReturn;
+
+ SwRootFrame* pLayout = pShell->GetLayout();
+ for (SwFrame* pFrame = pLayout->GetLower(); pFrame; pFrame = pFrame->GetNext())
+ {
+ SwPageFrame* pPage = static_cast<SwPageFrame*>(pFrame);
+ if (const SwPageDesc *pDesc = pPage->FindPageDesc())
+ aReturn.push_back(pDesc->GetName());
+ }
+
+ return aReturn;
+}
+
+/// Search for a field named rFieldName of type rServiceName in xText and return it.
+uno::Reference<text::XTextField> lcl_findField(const uno::Reference<text::XText>& xText, const OUString& rServiceName, std::u16string_view rFieldName)
+{
+ uno::Reference<text::XTextField> xField;
+ uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xText, uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
+ while (xParagraphs->hasMoreElements())
+ {
+ uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
+ while (xTextPortions->hasMoreElements())
+ {
+ uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
+ OUString aTextPortionType;
+ xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
+ if (aTextPortionType != UNO_NAME_TEXT_FIELD)
+ continue;
+
+ uno::Reference<lang::XServiceInfo> xTextField;
+ xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
+ if (!xTextField->supportsService(rServiceName))
+ continue;
+
+ OUString aName;
+ uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY);
+ xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName;
+ if (aName == rFieldName)
+ {
+ xField = uno::Reference<text::XTextField>(xTextField, uno::UNO_QUERY);
+ break;
+ }
+ }
+ }
+
+ return xField;
+}
+
+/// Search for a field named rFieldName of type rServiceName in xText and return true iff found.
+bool lcl_hasField(const uno::Reference<text::XText>& xText, const OUString& rServiceName, std::u16string_view rFieldName)
+{
+ return lcl_findField(xText, rServiceName, rFieldName).is();
+}
+
+/// Search for a frame with WATERMARK_NAME in name of type rServiceName in xText. Returns found name in rShapeName.
+uno::Reference<drawing::XShape> lcl_getWatermark(const uno::Reference<text::XText>& xText,
+ const OUString& rServiceName, OUString& rShapeName, bool& bSuccess)
+{
+ bSuccess = false;
+ uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xText, uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
+ while (xParagraphs->hasMoreElements())
+ {
+ uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
+ if (!xTextPortionEnumerationAccess.is())
+ continue;
+
+ bSuccess = true;
+
+ uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
+ while (xTextPortions->hasMoreElements())
+ {
+ uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
+ OUString aTextPortionType;
+ xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
+ if (aTextPortionType != "Frame")
+ continue;
+
+ uno::Reference<container::XContentEnumerationAccess> xContentEnumerationAccess(xTextPortion, uno::UNO_QUERY);
+ if (!xContentEnumerationAccess.is())
+ continue;
+
+ uno::Reference<container::XEnumeration> xEnumeration = xContentEnumerationAccess->createContentEnumeration("com.sun.star.text.TextContent");
+ if (!xEnumeration->hasMoreElements())
+ continue;
+
+ uno::Reference<lang::XServiceInfo> xWatermark(xEnumeration->nextElement(), uno::UNO_QUERY);
+ if (!xWatermark->supportsService(rServiceName))
+ continue;
+
+ uno::Reference<container::XNamed> xNamed(xWatermark, uno::UNO_QUERY);
+
+ if (!xNamed->getName().match(WATERMARK_NAME))
+ continue;
+
+ rShapeName = xNamed->getName();
+
+ uno::Reference<drawing::XShape> xShape(xWatermark, uno::UNO_QUERY);
+ return xShape;
+ }
+ }
+
+ return uno::Reference<drawing::XShape>();
+}
+
+/// Extract the text of the paragraph without any of the fields.
+/// TODO: Consider moving to SwTextNode, or extend ModelToViewHelper.
+OString lcl_getParagraphBodyText(const uno::Reference<text::XTextContent>& xText)
+{
+ OUStringBuffer strBuf;
+ uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xText, uno::UNO_QUERY);
+ if (!xTextPortionEnumerationAccess.is())
+ return OString();
+
+ uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
+ while (xTextPortions->hasMoreElements())
+ {
+ uno::Any elem = xTextPortions->nextElement();
+
+ //TODO: Consider including hidden and conditional texts/portions.
+ OUString aTextPortionType;
+ uno::Reference<beans::XPropertySet> xPropertySet(elem, uno::UNO_QUERY);
+ xPropertySet->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
+ if (aTextPortionType == "Text")
+ {
+ uno::Reference<text::XTextRange> xTextRange(elem, uno::UNO_QUERY);
+ if (xTextRange.is())
+ strBuf.append(xTextRange->getString());
+ }
+ }
+
+ // Cleanup the dummy characters added by fields (which we exclude).
+ comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDSTART);
+ comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDEND);
+ comphelper::string::remove(strBuf, CH_TXTATR_BREAKWORD);
+
+ return strBuf.makeStringAndClear().trim().toUtf8();
+}
+
+template <typename T>
+std::map<OUString, OUString> lcl_getRDFStatements(const uno::Reference<frame::XModel>& xModel,
+ const T& xRef)
+{
+ try
+ {
+ const css::uno::Reference<css::rdf::XResource> xSubject(xRef, uno::UNO_QUERY);
+ return SwRDFHelper::getStatements(xModel, MetaNS, xSubject);
+ }
+ catch (const ::css::uno::Exception&)
+ {
+ }
+
+ return std::map<OUString, OUString>();
+}
+
+/// Returns RDF (key, value) pair associated with the field, if any.
+std::pair<OUString, OUString> lcl_getFieldRDFByPrefix(const uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<css::text::XTextField>& xField,
+ std::u16string_view sPrefix)
+{
+ for (const auto& pair : lcl_getRDFStatements(xModel, xField))
+ {
+ if (pair.first.startsWith(sPrefix))
+ return pair;
+ }
+
+ return std::make_pair(OUString(), OUString());
+}
+
+/// Returns RDF (key, value) pair associated with the field, if any.
+template <typename T>
+std::pair<OUString, OUString> lcl_getRDF(const uno::Reference<frame::XModel>& xModel,
+ const T& xRef,
+ const OUString& sRDFName)
+{
+ const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xRef);
+ const auto it = aStatements.find(sRDFName);
+ return (it != aStatements.end()) ? std::make_pair(it->first, it->second) : std::make_pair(OUString(), OUString());
+}
+
+/// Returns true iff the field in question is paragraph signature.
+/// Note: must have associated RDF, since signatures are otherwise just metadata fields.
+bool lcl_IsParagraphSignatureField(const uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<css::text::XTextField>& xField)
+{
+ return (lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName).first == ParagraphSignatureIdRDFName);
+}
+
+uno::Reference<text::XTextField> lcl_findFieldByRDF(const uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<text::XTextContent>& xParagraph,
+ const OUString& sRDFName,
+ std::u16string_view sRDFValue)
+{
+ uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
+ if (!xTextPortionEnumerationAccess.is())
+ return uno::Reference<text::XTextField>();
+
+ uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
+ if (!xTextPortions.is())
+ return uno::Reference<text::XTextField>();
+
+ while (xTextPortions->hasMoreElements())
+ {
+ uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
+ OUString aTextPortionType;
+ xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
+ if (aTextPortionType != UNO_NAME_TEXT_FIELD)
+ continue;
+
+ uno::Reference<lang::XServiceInfo> xTextField;
+ xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
+ if (!xTextField->supportsService(MetadataFieldServiceName))
+ continue;
+
+ uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY);
+ const std::pair<OUString, OUString> pair = lcl_getRDF(xModel, xField, sRDFName);
+ if (pair.first == sRDFName && (sRDFValue.empty() || sRDFValue == pair.second))
+ return xField;
+ }
+
+ return uno::Reference<text::XTextField>();
+}
+
+struct SignatureDescr
+{
+ OUString msSignature;
+ OUString msUsage;
+ OUString msDate;
+
+ bool isValid() const { return !msSignature.isEmpty(); }
+};
+
+SignatureDescr lcl_getSignatureDescr(const uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<css::text::XTextContent>& xParagraph,
+ std::u16string_view sFieldId)
+{
+ SignatureDescr aDescr;
+
+ const OUString prefix = ParagraphSignatureRDFNamespace + sFieldId;
+ const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xParagraph);
+
+ const auto itSig = aStatements.find(prefix + ParagraphSignatureDigestRDFName);
+ aDescr.msSignature = (itSig != aStatements.end() ? itSig->second : OUString());
+
+ const auto itDate = aStatements.find(prefix + ParagraphSignatureDateRDFName);
+ aDescr.msDate = (itDate != aStatements.end() ? itDate->second : OUString());
+
+ const auto itUsage = aStatements.find(prefix + ParagraphSignatureUsageRDFName);
+ aDescr.msUsage = (itUsage != aStatements.end() ? itUsage->second : OUString());
+
+ return aDescr;
+}
+
+SignatureDescr lcl_getSignatureDescr(const uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<css::text::XTextContent>& xParagraph,
+ const uno::Reference<css::text::XTextField>& xField)
+{
+ const OUString sFieldId = lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName).second;
+ if (!sFieldId.isEmpty())
+ return lcl_getSignatureDescr(xModel, xParagraph, sFieldId);
+
+ return SignatureDescr();
+}
+
+/// Validate and create the signature field display text from the fields.
+std::pair<bool, OUString> lcl_MakeParagraphSignatureFieldText(const SignatureDescr& aDescr,
+ const OString& utf8Text)
+{
+ OUString msg = SwResId(STR_INVALID_SIGNATURE);
+ bool valid = false;
+
+ if (aDescr.isValid())
+ {
+ const char* pData = utf8Text.getStr();
+ const std::vector<unsigned char> data(pData, pData + utf8Text.getLength());
+
+ OString encSignature;
+ if (aDescr.msSignature.convertToString(&encSignature, RTL_TEXTENCODING_UTF8, 0))
+ {
+ const std::vector<unsigned char> sig(svl::crypto::DecodeHexString(encSignature));
+ SignatureInformation aInfo(0);
+ valid = svl::crypto::Signing::Verify(data, false, sig, aInfo);
+ valid = valid
+ && aInfo.nStatus == xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED;
+
+ assert(aInfo.GetSigningCertificate()); // it was valid
+ msg = SwResId(STR_SIGNED_BY) + ": " + aInfo.GetSigningCertificate()->X509Subject + ", " +
+ aDescr.msDate;
+ msg += (!aDescr.msUsage.isEmpty() ? (" (" + aDescr.msUsage + "): ") : OUString(": "));
+ msg += (valid ? SwResId(STR_VALID) : SwResId(STR_INVALID));
+ }
+ }
+
+ return std::make_pair(valid, msg);
+}
+
+/// Validate and return validation result and signature field display text.
+std::pair<bool, OUString>
+lcl_MakeParagraphSignatureFieldText(const uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<css::text::XTextContent>& xParagraph,
+ const uno::Reference<css::text::XTextField>& xField,
+ const OString& utf8Text)
+{
+ const SignatureDescr aDescr = lcl_getSignatureDescr(xModel, xParagraph, xField);
+ return lcl_MakeParagraphSignatureFieldText(aDescr, utf8Text);
+}
+
+/// Generate the next valid ID for the new signature on this paragraph.
+OUString lcl_getNextSignatureId(const uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<text::XTextContent>& xParagraph)
+{
+ const OUString sFieldId = lcl_getRDF(xModel, xParagraph, ParagraphSignatureLastIdRDFName).second;
+ return OUString::number(!sFieldId.isEmpty() ? sFieldId.toInt32() + 1 : 1);
+}
+
+/// Creates and inserts Paragraph Signature Metadata field and creates the RDF entry
+uno::Reference<text::XTextField> lcl_InsertParagraphSignature(const uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<text::XTextContent>& xParagraph,
+ const OUString& signature,
+ const OUString& usage)
+{
+ uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY);
+ auto xField = uno::Reference<text::XTextField>(xMultiServiceFactory->createInstance(MetadataFieldServiceName), uno::UNO_QUERY);
+
+ // Add the signature at the end.
+ xField->attach(xParagraph->getAnchor()->getEnd());
+
+ const OUString sId = lcl_getNextSignatureId(xModel, xParagraph);
+
+ const css::uno::Reference<css::rdf::XResource> xSubject(xField, uno::UNO_QUERY);
+ SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xSubject, ParagraphSignatureIdRDFName, sId);
+
+ // First convert the UTC UNIX timestamp to a tools::DateTime then to local time.
+ DateTime aDateTime = DateTime::CreateFromUnixTime(time(nullptr));
+ aDateTime.ConvertToLocalTime();
+ OUStringBuffer rBuffer;
+ rBuffer.append(static_cast<sal_Int32>(aDateTime.GetYear()));
+ rBuffer.append('-');
+ if (aDateTime.GetMonth() < 10)
+ rBuffer.append('0');
+ rBuffer.append(static_cast<sal_Int32>(aDateTime.GetMonth()));
+ rBuffer.append('-');
+ if (aDateTime.GetDay() < 10)
+ rBuffer.append('0');
+ rBuffer.append(static_cast<sal_Int32>(aDateTime.GetDay()));
+
+ // Now set the RDF on the paragraph, since that's what is preserved in .doc(x).
+ const css::uno::Reference<css::rdf::XResource> xParaSubject(xParagraph, uno::UNO_QUERY);
+ const OUString prefix = ParagraphSignatureRDFNamespace + sId;
+ SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, ParagraphSignatureLastIdRDFName, sId);
+ SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureDigestRDFName, signature);
+ SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureUsageRDFName, usage);
+ SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureDateRDFName, rBuffer.makeStringAndClear());
+
+ return xField;
+}
+
+/// Updates the signature field text if changed and returns true only iff updated.
+bool lcl_DoUpdateParagraphSignatureField(SwDoc& rDoc,
+ const uno::Reference<css::text::XTextField>& xField,
+ const OUString& sDisplayText)
+{
+ // Disable undo to avoid introducing noise when we edit the metadata field.
+ const bool isUndoEnabled = rDoc.GetIDocumentUndoRedo().DoesUndo();
+ rDoc.GetIDocumentUndoRedo().DoUndo(false);
+ comphelper::ScopeGuard const g([&rDoc, isUndoEnabled]() {
+ rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
+ });
+
+ try
+ {
+ uno::Reference<css::text::XTextRange> xText(xField, uno::UNO_QUERY);
+ const OUString curText = xText->getString();
+ if (curText != sDisplayText)
+ {
+ xText->setString(sDisplayText);
+ return true;
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ // We failed; avoid crashing.
+ DBG_UNHANDLED_EXCEPTION("sw.uno", "Failed to update paragraph signature");
+ }
+
+ return false;
+}
+
+/// Updates the signature field text if changed and returns true only iff updated.
+bool lcl_UpdateParagraphSignatureField(SwDoc& rDoc,
+ const uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<css::text::XTextContent>& xParagraph,
+ const uno::Reference<css::text::XTextField>& xField,
+ const OString& utf8Text)
+{
+ const OUString sDisplayText
+ = lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).second;
+ return lcl_DoUpdateParagraphSignatureField(rDoc, xField, sDisplayText);
+}
+
+void lcl_RemoveParagraphMetadataField(const uno::Reference<css::text::XTextField>& xField)
+{
+ uno::Reference<css::text::XTextRange> xParagraph(xField->getAnchor());
+ xParagraph->getText()->removeTextContent(xField);
+}
+
+/// Returns true iff the field in question is paragraph classification.
+/// Note: must have associated RDF, since classifications are otherwise just metadata fields.
+bool lcl_IsParagraphClassificationField(const uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<css::text::XTextField>& xField,
+ std::u16string_view sKey)
+{
+ const std::pair<OUString, OUString> rdfPair = lcl_getRDF(xModel, xField, ParagraphClassificationNameRDFName);
+ return rdfPair.first == ParagraphClassificationNameRDFName && (sKey.empty() || rdfPair.second == sKey);
+}
+
+uno::Reference<text::XTextField> lcl_FindParagraphClassificationField(const uno::Reference<frame::XModel>& xModel,
+ const rtl::Reference<SwXParagraph>& xParagraph,
+ std::u16string_view sKey = u"")
+{
+ uno::Reference<text::XTextField> xTextField;
+
+ if (!xParagraph.is())
+ return xTextField;
+
+ // Enumerate text portions to find metadata fields. This is expensive, best to enumerate fields only.
+ rtl::Reference<SwXTextPortionEnumeration> xTextPortions = xParagraph->createTextFieldsEnumeration();
+ while (xTextPortions->hasMoreElements())
+ {
+ uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
+ OUString aTextPortionType;
+ xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
+ if (aTextPortionType != UNO_NAME_TEXT_FIELD)
+ continue;
+
+ uno::Reference<lang::XServiceInfo> xServiceInfo;
+ xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xServiceInfo;
+ if (!xServiceInfo->supportsService(MetadataFieldServiceName))
+ continue;
+
+ uno::Reference<text::XTextField> xField(xServiceInfo, uno::UNO_QUERY);
+ if (lcl_IsParagraphClassificationField(xModel, xField, sKey))
+ {
+ xTextField = xField;
+ break;
+ }
+ }
+
+ return xTextField;
+}
+
+/// Creates and inserts Paragraph Classification Metadata field and creates the RDF entry
+uno::Reference<text::XTextField> lcl_InsertParagraphClassification(const uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<text::XTextContent>& xParent)
+{
+ uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY);
+ auto xField = uno::Reference<text::XTextField>(xMultiServiceFactory->createInstance(MetadataFieldServiceName), uno::UNO_QUERY);
+
+ // Add the classification at the start.
+ xField->attach(xParent->getAnchor()->getStart());
+ return xField;
+}
+
+/// Updates the paragraph classification field text if changed and returns true only iff updated.
+bool lcl_UpdateParagraphClassificationField(SwDoc* pDoc,
+ const uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<css::text::XTextContent>& xTextNode,
+ const OUString& sKey,
+ const OUString& sValue,
+ const OUString& sDisplayText)
+{
+ // Disable undo to avoid introducing noise when we edit the metadata field.
+ const bool isUndoEnabled = pDoc->GetIDocumentUndoRedo().DoesUndo();
+ pDoc->GetIDocumentUndoRedo().DoUndo(false);
+ comphelper::ScopeGuard const g([pDoc, isUndoEnabled] () {
+ pDoc->GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
+ });
+
+ uno::Reference<text::XTextField> xField = lcl_InsertParagraphClassification(xModel, xTextNode);
+
+ css::uno::Reference<css::rdf::XResource> xFieldSubject(xField, uno::UNO_QUERY);
+ SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, sKey, sValue);
+ SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphClassificationNameRDFName, sKey);
+ SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphClassificationValueRDFName, sValue);
+
+ css::uno::Reference<css::rdf::XResource> xNodeSubject(xTextNode, uno::UNO_QUERY);
+ SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, sKey, sValue);
+
+ return lcl_DoUpdateParagraphSignatureField(*pDoc, xField, sDisplayText);
+}
+
+void lcl_ValidateParagraphSignatures(SwDoc& rDoc, const uno::Reference<text::XTextContent>& xParagraph, const bool updateDontRemove)
+{
+ SwDocShell* pDocShell = rDoc.GetDocShell();
+ if (!pDocShell)
+ return;
+
+ uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
+
+ // Check if the paragraph is signed.
+ try
+ {
+ const std::pair<OUString, OUString> pair = lcl_getRDF(xModel, xParagraph, ParagraphSignatureLastIdRDFName);
+ if (pair.second.isEmpty())
+ return;
+ }
+ catch (const ::css::uno::Exception&)
+ {
+ return;
+ }
+
+ uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
+ if (!xTextPortionEnumerationAccess.is())
+ return;
+
+ uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
+ if (!xTextPortions.is())
+ return;
+
+ // Get the text (without fields).
+ const OString utf8Text = lcl_getParagraphBodyText(xParagraph);
+ if (utf8Text.isEmpty())
+ return;
+
+ while (xTextPortions->hasMoreElements())
+ {
+ uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
+ OUString aTextPortionType;
+ xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
+ if (aTextPortionType != UNO_NAME_TEXT_FIELD)
+ continue;
+
+ uno::Reference<lang::XServiceInfo> xTextField;
+ xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
+ if (!xTextField->supportsService(MetadataFieldServiceName))
+ continue;
+
+ uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY);
+ if (!lcl_IsParagraphSignatureField(xModel, xField))
+ {
+ continue;
+ }
+
+ if (updateDontRemove)
+ {
+ lcl_UpdateParagraphSignatureField(rDoc, xModel, xParagraph, xField, utf8Text);
+ }
+ else if (!lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).first)
+ {
+ rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
+ rDoc.GetIDocumentUndoRedo().AppendUndo(
+ std::make_unique<SwUndoParagraphSigning>(rDoc, xField, xParagraph, false));
+ lcl_RemoveParagraphMetadataField(xField);
+ rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
+ }
+ }
+}
+
+} // anonymous namespace
+
+SwTextFormatColl& SwEditShell::GetDfltTextFormatColl() const
+{
+ return *GetDoc()->GetDfltTextFormatColl();
+}
+
+sal_uInt16 SwEditShell::GetTextFormatCollCount() const
+{
+ return GetDoc()->GetTextFormatColls()->size();
+}
+
+SwTextFormatColl& SwEditShell::GetTextFormatColl(sal_uInt16 nFormatColl) const
+{
+ return *((*(GetDoc()->GetTextFormatColls()))[nFormatColl]);
+}
+
+static void insertFieldToDocument(uno::Reference<lang::XMultiServiceFactory> const & rxMultiServiceFactory,
+ uno::Reference<text::XText> const & rxText, uno::Reference<text::XParagraphCursor> const & rxParagraphCursor,
+ OUString const & rsKey)
+{
+ uno::Reference<beans::XPropertySet> xField(rxMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY);
+ xField->setPropertyValue(UNO_NAME_NAME, uno::Any(rsKey));
+ uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
+
+ rxText->insertTextContent(rxParagraphCursor, xTextContent, false);
+}
+
+static void removeAllClassificationFields(std::u16string_view rPolicy, uno::Reference<text::XText> const & rxText)
+{
+ uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(rxText, uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
+ while (xParagraphs->hasMoreElements())
+ {
+ uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
+ while (xTextPortions->hasMoreElements())
+ {
+ uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
+ OUString aTextPortionType;
+ xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
+ if (aTextPortionType != UNO_NAME_TEXT_FIELD)
+ continue;
+
+ uno::Reference<lang::XServiceInfo> xTextField;
+ xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
+ if (!xTextField->supportsService(DocInfoServiceName))
+ continue;
+
+ OUString aName;
+ uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY);
+ xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName;
+ if (aName.startsWith(rPolicy))
+ {
+ uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY);
+ rxText->removeTextContent(xField);
+ }
+ }
+ }
+}
+
+static sal_Int32 getNumberOfParagraphs(uno::Reference<text::XText> const & xText)
+{
+ uno::Reference<container::XEnumerationAccess> xParagraphEnumAccess(xText, uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xParagraphEnum = xParagraphEnumAccess->createEnumeration();
+ sal_Int32 nResult = 0;
+ while (xParagraphEnum->hasMoreElements())
+ {
+ xParagraphEnum->nextElement();
+ nResult++;
+ }
+ return nResult;
+}
+
+static void equaliseNumberOfParagraph(std::vector<svx::ClassificationResult> const & rResults, uno::Reference<text::XText> const & xText)
+{
+ sal_Int32 nNumberOfParagraphs = 0;
+ for (svx::ClassificationResult const & rResult : rResults)
+ {
+ if (rResult.meType == svx::ClassificationType::PARAGRAPH)
+ nNumberOfParagraphs++;
+ }
+
+ while (getNumberOfParagraphs(xText) < nNumberOfParagraphs)
+ {
+ uno::Reference<text::XParagraphAppend> xParagraphAppend(xText, uno::UNO_QUERY);
+ xParagraphAppend->finishParagraph(uno::Sequence<beans::PropertyValue>());
+ }
+}
+
+void SwEditShell::ApplyAdvancedClassification(std::vector<svx::ClassificationResult> const & rResults)
+{
+ SwDocShell* pDocShell = GetDoc()->GetDocShell();
+ if (!pDocShell)
+ return;
+
+ const SfxObjectShell* pObjSh = SfxObjectShell::Current();
+ if (!pObjSh)
+ return;
+
+ uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
+ uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY);
+ uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies();
+ uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY);
+
+ uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY);
+
+ uno::Reference<document::XDocumentProperties> xDocumentProperties = pObjSh->getDocProperties();
+
+ const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType());
+ const std::vector<OUString> aUsedPageStyles = lcl_getUsedPageStyles(this);
+ for (const OUString& rPageStyleName : aUsedPageStyles)
+ {
+ uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
+
+ // HEADER
+ bool bHeaderIsOn = false;
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
+ uno::Reference<text::XText> xHeaderText;
+ if (bHeaderIsOn)
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
+ if (xHeaderText.is())
+ removeAllClassificationFields(sPolicy, xHeaderText);
+
+ // FOOTER
+ bool bFooterIsOn = false;
+ xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn;
+ uno::Reference<text::XText> xFooterText;
+ if (bFooterIsOn)
+ xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText;
+ if (xFooterText.is())
+ removeAllClassificationFields(sPolicy, xFooterText);
+ }
+
+ // Clear properties
+ uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
+ svx::classification::removeAllProperties(xPropertyContainer);
+
+ SfxClassificationHelper aHelper(xDocumentProperties);
+
+ // Apply properties from the BA policy
+ for (svx::ClassificationResult const & rResult : rResults)
+ {
+ if (rResult.meType == svx::ClassificationType::CATEGORY)
+ {
+ aHelper.SetBACName(rResult.msName, SfxClassificationHelper::getPolicyType());
+ }
+ }
+
+ sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType());
+
+ // Insert origin document property
+ svx::classification::insertCreationOrigin(xPropertyContainer, aCreator, sfx::ClassificationCreationOrigin::MANUAL);
+
+ // Insert full text as document property
+ svx::classification::insertFullTextualRepresentationAsDocumentProperty(xPropertyContainer, aCreator, rResults);
+
+ for (const OUString& rPageStyleName : aUsedPageStyles)
+ {
+ uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
+
+ // HEADER
+ bool bHeaderIsOn = false;
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
+ if (!bHeaderIsOn)
+ xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(true));
+ uno::Reference<text::XText> xHeaderText;
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
+ equaliseNumberOfParagraph(rResults, xHeaderText);
+
+ // FOOTER
+ bool bFooterIsOn = false;
+ xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn;
+ if (!bFooterIsOn)
+ xPageStyle->setPropertyValue(UNO_NAME_FOOTER_IS_ON, uno::Any(true));
+ uno::Reference<text::XText> xFooterText;
+ xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText;
+ equaliseNumberOfParagraph(rResults, xFooterText);
+
+ // SET/DELETE WATERMARK
+ SfxWatermarkItem aWatermarkItem;
+ aWatermarkItem.SetText(aHelper.GetDocumentWatermark());
+ SetWatermark(aWatermarkItem);
+
+ uno::Reference<text::XParagraphCursor> xHeaderParagraphCursor(xHeaderText->createTextCursor(), uno::UNO_QUERY);
+ uno::Reference<text::XParagraphCursor> xFooterParagraphCursor(xFooterText->createTextCursor(), uno::UNO_QUERY);
+
+ sal_Int32 nParagraph = -1;
+
+ for (svx::ClassificationResult const & rResult : rResults)
+ {
+ switch(rResult.meType)
+ {
+ case svx::ClassificationType::TEXT:
+ {
+ OUString sKey = aCreator.makeNumberedTextKey();
+
+ svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
+ insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
+ insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
+ }
+ break;
+
+ case svx::ClassificationType::CATEGORY:
+ {
+ OUString sKey = aCreator.makeCategoryNameKey();
+ insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
+ insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
+ }
+ break;
+
+ case svx::ClassificationType::MARKING:
+ {
+ OUString sKey = aCreator.makeNumberedMarkingKey();
+ svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
+ insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
+ insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
+ }
+ break;
+
+ case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART:
+ {
+ OUString sKey = aCreator.makeNumberedIntellectualPropertyPartKey();
+ svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName);
+ insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey);
+ insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey);
+ }
+ break;
+
+ case svx::ClassificationType::PARAGRAPH:
+ {
+ nParagraph++;
+
+ if (nParagraph != 0) // only jump to next paragraph, if we aren't at the first paragraph
+ {
+ xHeaderParagraphCursor->gotoNextParagraph(false);
+ xFooterParagraphCursor->gotoNextParagraph(false);
+ }
+
+ xHeaderParagraphCursor->gotoStartOfParagraph(false);
+ xFooterParagraphCursor->gotoStartOfParagraph(false);
+
+ uno::Reference<beans::XPropertySet> xHeaderPropertySet(xHeaderParagraphCursor, uno::UNO_QUERY_THROW);
+ uno::Reference<beans::XPropertySet> xFooterPropertySet(xFooterParagraphCursor, uno::UNO_QUERY_THROW);
+ if (rResult.msName == "BOLD")
+ {
+ xHeaderPropertySet->setPropertyValue("CharWeight", uno::Any(awt::FontWeight::BOLD));
+ xFooterPropertySet->setPropertyValue("CharWeight", uno::Any(awt::FontWeight::BOLD));
+ }
+ else
+ {
+ xHeaderPropertySet->setPropertyValue("CharWeight", uno::Any(awt::FontWeight::NORMAL));
+ xFooterPropertySet->setPropertyValue("CharWeight", uno::Any(awt::FontWeight::NORMAL));
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
+
+std::vector<svx::ClassificationResult> SwEditShell::CollectAdvancedClassification()
+{
+ std::vector<svx::ClassificationResult> aResult;
+
+ SwDocShell* pDocShell = GetDoc()->GetDocShell();
+ if (!pDocShell)
+ return aResult;
+
+ const SfxObjectShell* pObjSh = SfxObjectShell::Current();
+ if (!pObjSh)
+ return aResult;
+
+ const OUString sBlank;
+
+ uno::Reference<document::XDocumentProperties> xDocumentProperties = pObjSh->getDocProperties();
+ uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
+ sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType());
+
+ uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
+ uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY);
+ uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies();
+ uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY);
+
+ std::vector<OUString> aPageStyles = lcl_getUsedPageStyles(this);
+ OUString aPageStyleString = aPageStyles.back();
+ uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(aPageStyleString), uno::UNO_QUERY);
+
+ bool bHeaderIsOn = false;
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
+ if (!bHeaderIsOn)
+ {
+ const OUString aValue = svx::classification::getProperty(xPropertyContainer, aCreator.makeCategoryNameKey());
+ if (!aValue.isEmpty())
+ aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank });
+
+ return aResult;
+ }
+
+ uno::Reference<text::XText> xHeaderText;
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
+
+ uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xHeaderText, uno::UNO_QUERY);
+ uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
+
+ // set to true if category was found in the header
+ bool bFoundClassificationCategory = false;
+
+ while (xParagraphs->hasMoreElements())
+ {
+ uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY);
+ if (!xTextPortionEnumerationAccess.is())
+ continue;
+ uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
+
+ // Check font weight
+ uno::Reference<beans::XPropertySet> xParagraphPropertySet(xTextPortionEnumerationAccess, uno::UNO_QUERY_THROW);
+ uno::Any aAny = xParagraphPropertySet->getPropertyValue("CharWeight");
+
+ OUString sWeight = (aAny.get<float>() >= awt::FontWeight::BOLD) ? OUString("BOLD") : OUString("NORMAL");
+
+ aResult.push_back({ svx::ClassificationType::PARAGRAPH, sWeight, sBlank, sBlank });
+
+ // Process portions
+ while (xTextPortions->hasMoreElements())
+ {
+ uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
+ OUString aTextPortionType;
+ xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
+ if (aTextPortionType != UNO_NAME_TEXT_FIELD)
+ continue;
+
+ uno::Reference<lang::XServiceInfo> xTextField;
+ xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField;
+ if (!xTextField->supportsService(DocInfoServiceName))
+ continue;
+
+ OUString aName;
+ uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY);
+ xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName;
+
+ if (aCreator.isMarkingTextKey(aName))
+ {
+ const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
+ if (!aValue.isEmpty())
+ aResult.push_back({ svx::ClassificationType::TEXT, aValue, sBlank, sBlank });
+ }
+ else if (aCreator.isCategoryNameKey(aName))
+ {
+ const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
+ if (!aValue.isEmpty())
+ aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank });
+ bFoundClassificationCategory = true;
+ }
+ else if (aCreator.isCategoryIdentifierKey(aName))
+ {
+ const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
+ if (!aValue.isEmpty())
+ aResult.push_back({ svx::ClassificationType::CATEGORY, sBlank, sBlank, aValue });
+ bFoundClassificationCategory = true;
+ }
+ else if (aCreator.isMarkingKey(aName))
+ {
+ const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
+ if (!aValue.isEmpty())
+ aResult.push_back({ svx::ClassificationType::MARKING, aValue, sBlank, sBlank });
+ }
+ else if (aCreator.isIntellectualPropertyPartKey(aName))
+ {
+ const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName);
+ if (!aValue.isEmpty())
+ aResult.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, aValue, sBlank, sBlank });
+ }
+ }
+ }
+
+ if (!bFoundClassificationCategory)
+ {
+ const OUString aValue = svx::classification::getProperty(xPropertyContainer, aCreator.makeCategoryNameKey());
+ if (!aValue.isEmpty())
+ aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank });
+ }
+
+ return aResult;
+}
+
+void SwEditShell::SetClassification(const OUString& rName, SfxClassificationPolicyType eType)
+{
+ SwDocShell* pDocShell = GetDoc()->GetDocShell();
+ if (!pDocShell)
+ return;
+
+ SfxClassificationHelper aHelper(pDocShell->getDocProperties());
+
+ const bool bHadWatermark = !aHelper.GetDocumentWatermark().isEmpty();
+
+ // This updates the infobar as well.
+ aHelper.SetBACName(rName, eType);
+
+ // Insert origin document property
+ uno::Reference<beans::XPropertyContainer> xPropertyContainer = pDocShell->getDocProperties()->getUserDefinedProperties();
+ sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType());
+ svx::classification::insertCreationOrigin(xPropertyContainer, aCreator, sfx::ClassificationCreationOrigin::BAF_POLICY);
+
+ bool bHeaderIsNeeded = aHelper.HasDocumentHeader();
+ bool bFooterIsNeeded = aHelper.HasDocumentFooter();
+ OUString aWatermark = aHelper.GetDocumentWatermark();
+ bool bWatermarkIsNeeded = !aWatermark.isEmpty();
+
+ if (!bHeaderIsNeeded && !bFooterIsNeeded && !bWatermarkIsNeeded && !bHadWatermark)
+ return;
+
+ uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
+ uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY);
+ uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies();
+ uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY);
+ const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames();
+
+ for (const OUString& rPageStyleName : aStyles)
+ {
+ uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
+ uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY);
+
+ if (bHeaderIsNeeded || bWatermarkIsNeeded || bHadWatermark)
+ {
+ // If the header is off, turn it on.
+ bool bHeaderIsOn = false;
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
+ if (!bHeaderIsOn)
+ xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(true));
+
+ // If the header already contains a document header field, no need to do anything.
+ uno::Reference<text::XText> xHeaderText;
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
+
+ if (bHeaderIsNeeded)
+ {
+ if (!lcl_hasField(xHeaderText, DocInfoServiceName, Concat2View(SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCHEADER())))
+ {
+ // Append a field to the end of the header text.
+ uno::Reference<beans::XPropertySet> xField(xMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY);
+ xField->setPropertyValue(UNO_NAME_NAME, uno::Any(SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCHEADER()));
+ uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
+ xHeaderText->insertTextContent(xHeaderText->getEnd(), xTextContent, /*bAbsorb=*/false);
+ }
+ }
+
+ SfxWatermarkItem aWatermarkItem;
+ aWatermarkItem.SetText(aWatermark);
+ SetWatermark(aWatermarkItem);
+ }
+
+ if (bFooterIsNeeded)
+ {
+ // If the footer is off, turn it on.
+ bool bFooterIsOn = false;
+ xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn;
+ if (!bFooterIsOn)
+ xPageStyle->setPropertyValue(UNO_NAME_FOOTER_IS_ON, uno::Any(true));
+
+ // If the footer already contains a document header field, no need to do anything.
+ uno::Reference<text::XText> xFooterText;
+ xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText;
+ static OUString sFooter = SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCFOOTER();
+ if (!lcl_hasField(xFooterText, DocInfoServiceName, sFooter))
+ {
+ // Append a field to the end of the footer text.
+ uno::Reference<beans::XPropertySet> xField(xMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY);
+ xField->setPropertyValue(UNO_NAME_NAME, uno::Any(sFooter));
+ uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
+ xFooterText->insertTextContent(xFooterText->getEnd(), xTextContent, /*bAbsorb=*/false);
+ }
+ }
+ }
+}
+
+// We pass xParent and xNodeSubject even though they point to the same thing because the UNO_QUERY is
+// on a performance-sensitive path.
+static void lcl_ApplyParagraphClassification(SwDoc* pDoc,
+ const uno::Reference<frame::XModel>& xModel,
+ const rtl::Reference<SwXParagraph>& xParent,
+ const css::uno::Reference<css::rdf::XResource>& xNodeSubject,
+ std::vector<svx::ClassificationResult> aResults)
+{
+ if (!xNodeSubject.is())
+ return;
+
+ // Remove all paragraph classification fields.
+ for (;;)
+ {
+ uno::Reference<text::XTextField> xTextField = lcl_FindParagraphClassificationField(xModel, xParent);
+ if (!xTextField.is())
+ break;
+ lcl_RemoveParagraphMetadataField(xTextField);
+ }
+
+ if (aResults.empty())
+ return;
+
+ // Since we always insert at the start of the paragraph,
+ // need to insert in reverse order.
+ std::reverse(aResults.begin(), aResults.end());
+ // Ignore "PARAGRAPH" types
+ std::erase_if(aResults,
+ [](const svx::ClassificationResult& rResult)-> bool
+ { return rResult.meType == svx::ClassificationType::PARAGRAPH; });
+
+ sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
+ std::vector<OUString> aFieldNames;
+ for (size_t nIndex = 0; nIndex < aResults.size(); ++nIndex)
+ {
+ const svx::ClassificationResult& rResult = aResults[nIndex];
+
+ const bool isLast = nIndex == 0;
+ const bool isFirst = (nIndex == aResults.size() - 1);
+ OUString sKey;
+ OUString sValue = rResult.msName;
+ switch (rResult.meType)
+ {
+ case svx::ClassificationType::TEXT:
+ {
+ sKey = aKeyCreator.makeNumberedTextKey();
+ }
+ break;
+
+ case svx::ClassificationType::CATEGORY:
+ {
+ if (rResult.msIdentifier.isEmpty())
+ {
+ sKey = aKeyCreator.makeCategoryNameKey();
+ }
+ else
+ {
+ sValue = rResult.msIdentifier;
+ sKey = aKeyCreator.makeCategoryIdentifierKey();
+ }
+ SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, ParagraphClassificationAbbrRDFName, rResult.msAbbreviatedName);
+ }
+ break;
+
+ case svx::ClassificationType::MARKING:
+ {
+ sKey = aKeyCreator.makeNumberedMarkingKey();
+ }
+ break;
+
+ case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART:
+ {
+ sKey = aKeyCreator.makeNumberedIntellectualPropertyPartKey();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ OUString sDisplayText = (isFirst ? ("(" + rResult.msAbbreviatedName) : rResult.msAbbreviatedName);
+ if (isLast)
+ sDisplayText += ")";
+ lcl_UpdateParagraphClassificationField(pDoc, xModel, xParent, sKey, sValue, sDisplayText);
+ aFieldNames.emplace_back(sKey);
+ }
+
+ // Correct the order
+ std::reverse(aFieldNames.begin(), aFieldNames.end());
+ OUStringBuffer sFieldNames;
+ bool first = true;
+ for (const OUString& rFieldName : aFieldNames)
+ {
+ if (!first)
+ sFieldNames.append("/");
+ sFieldNames.append(rFieldName);
+ first = false;
+ }
+
+ const OUString sOldFieldNames = lcl_getRDF(xModel, xNodeSubject, ParagraphClassificationFieldNamesRDFName).second;
+ SwRDFHelper::removeStatement(xModel, MetaNS, xNodeSubject, ParagraphClassificationFieldNamesRDFName, sOldFieldNames);
+ SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, ParagraphClassificationFieldNamesRDFName, sFieldNames.makeStringAndClear());
+}
+
+void SwEditShell::ApplyParagraphClassification(std::vector<svx::ClassificationResult> aResults)
+{
+ SwDocShell* pDocShell = GetDoc()->GetDocShell();
+ if (!pDocShell || !GetCursor() || !GetCursor()->Start())
+ return;
+
+ SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
+ if (pNode == nullptr)
+ return;
+
+ // Prevent recursive validation since this is triggered on node updates, which we do below.
+ const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
+ comphelper::ScopeGuard const g([this, bOldValidationFlag]() {
+ SetParagraphSignatureValidation(bOldValidationFlag);
+ });
+
+ uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
+ rtl::Reference<SwXParagraph> xParent = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr);
+ lcl_ApplyParagraphClassification(GetDoc(), xModel, xParent, css::uno::Reference<css::rdf::XResource>(xParent), std::move(aResults));
+}
+
+static std::vector<svx::ClassificationResult> lcl_CollectParagraphClassification(const uno::Reference<frame::XModel>& xModel, const uno::Reference<text::XTextContent>& xParagraph)
+{
+ std::vector<svx::ClassificationResult> aResult;
+
+ uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
+ if (!xTextPortionEnumerationAccess.is())
+ return aResult;
+
+ uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration();
+
+ const sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
+
+ while (xTextPortions->hasMoreElements())
+ {
+ uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY);
+ OUString aTextPortionType;
+ xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType;
+ if (aTextPortionType != UNO_NAME_TEXT_FIELD)
+ continue;
+
+ uno::Reference<lang::XServiceInfo> xField;
+ xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xField;
+ if (!xField->supportsService(MetadataFieldServiceName))
+ continue;
+
+ uno::Reference<text::XTextField> xTextField(xField, uno::UNO_QUERY);
+ const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType());
+ const std::pair<OUString, OUString> rdfNamePair = lcl_getFieldRDFByPrefix(xModel, xTextField, sPolicy);
+
+ uno::Reference<text::XTextRange> xTextRange(xField, uno::UNO_QUERY);
+ const OUString aName = rdfNamePair.first;
+ const OUString aValue = rdfNamePair.second;
+ static constexpr OUString sBlank(u""_ustr);
+ if (aKeyCreator.isMarkingTextKey(aName))
+ {
+ aResult.push_back({ svx::ClassificationType::TEXT, aValue, sBlank, sBlank });
+ }
+ else if (aKeyCreator.isCategoryNameKey(aName))
+ {
+ aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank });
+ }
+ else if (aKeyCreator.isCategoryIdentifierKey(aName))
+ {
+ aResult.push_back({ svx::ClassificationType::CATEGORY, sBlank, sBlank, aValue });
+ }
+ else if (aKeyCreator.isMarkingKey(aName))
+ {
+ aResult.push_back({ svx::ClassificationType::MARKING, aValue, sBlank, sBlank });
+ }
+ else if (aKeyCreator.isIntellectualPropertyPartKey(aName))
+ {
+ aResult.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, xTextRange->getString(), sBlank, sBlank });
+ }
+ }
+
+ return aResult;
+}
+
+std::vector<svx::ClassificationResult> SwEditShell::CollectParagraphClassification()
+{
+ std::vector<svx::ClassificationResult> aResult;
+
+ SwDocShell* pDocShell = GetDoc()->GetDocShell();
+ if (!pDocShell || !GetCursor() || !GetCursor()->Start())
+ return aResult;
+
+ SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
+ if (pNode == nullptr)
+ return aResult;
+
+ rtl::Reference<SwXParagraph> xParent = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr);
+ uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
+ return lcl_CollectParagraphClassification(xModel, xParent);
+}
+
+static sal_Int16 lcl_GetAngle(const drawing::HomogenMatrix3& rMatrix)
+{
+ basegfx::B2DHomMatrix aTransformation;
+ basegfx::B2DTuple aScale;
+ basegfx::B2DTuple aTranslate;
+ double fRotate = 0;
+ double fShear = 0;
+
+ aTransformation.set(0, 0, rMatrix.Line1.Column1);
+ aTransformation.set(0, 1, rMatrix.Line1.Column2);
+ aTransformation.set(0, 2, rMatrix.Line1.Column3);
+ aTransformation.set(1, 0, rMatrix.Line2.Column1);
+ aTransformation.set(1, 1, rMatrix.Line2.Column2);
+ aTransformation.set(1, 2, rMatrix.Line2.Column3);
+ // For this to be a valid 2D transform matrix, the last row must be [0,0,1]
+ assert( rMatrix.Line3.Column1 == 0 );
+ assert( rMatrix.Line3.Column2 == 0 );
+ assert( rMatrix.Line3.Column3 == 1 );
+
+ aTransformation.decompose(aScale, aTranslate, fRotate, fShear);
+ sal_Int16 nDeg = round(basegfx::rad2deg(fRotate));
+ return nDeg < 0 ? round(nDeg) * -1 : round(360.0 - nDeg);
+}
+
+SfxWatermarkItem SwEditShell::GetWatermark() const
+{
+ SwDocShell* pDocShell = GetDoc()->GetDocShell();
+ if (!pDocShell)
+ return SfxWatermarkItem();
+
+ uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
+ uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY);
+ uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies();
+ uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY);
+ std::vector<OUString> aUsedPageStyles = lcl_getUsedPageStyles(this);
+ for (const OUString& rPageStyleName : aUsedPageStyles)
+ {
+ uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
+
+ bool bHeaderIsOn = false;
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
+ if (!bHeaderIsOn)
+ return SfxWatermarkItem();
+
+ uno::Reference<text::XText> xHeaderText;
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
+
+ OUString sWatermark = "";
+ bool bSuccess = false;
+ uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, "com.sun.star.drawing.CustomShape", sWatermark, bSuccess);
+
+ if (xWatermark.is())
+ {
+ SfxWatermarkItem aItem;
+ uno::Reference<text::XTextRange> xTextRange(xWatermark, uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xPropertySet(xWatermark, uno::UNO_QUERY);
+ Color nColor;
+ sal_Int16 nTransparency;
+ OUString aFont;
+ drawing::HomogenMatrix3 aMatrix;
+
+ aItem.SetText(xTextRange->getString());
+
+ if (xPropertySet->getPropertyValue(UNO_NAME_CHAR_FONT_NAME) >>= aFont)
+ aItem.SetFont(aFont);
+ if (xPropertySet->getPropertyValue(UNO_NAME_FILLCOLOR) >>= nColor)
+ aItem.SetColor(nColor);
+ if (xPropertySet->getPropertyValue("Transformation") >>= aMatrix)
+ aItem.SetAngle(lcl_GetAngle(aMatrix));
+ if (xPropertySet->getPropertyValue(UNO_NAME_FILL_TRANSPARENCE) >>= nTransparency)
+ aItem.SetTransparency(nTransparency);
+
+ return aItem;
+ }
+ }
+ return SfxWatermarkItem();
+}
+
+static void lcl_placeWatermarkInHeader(const SfxWatermarkItem& rWatermark,
+ const uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<beans::XPropertySet>& xPageStyle,
+ const uno::Reference<text::XText>& xHeaderText)
+{
+ if (!xHeaderText.is())
+ return;
+
+ uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY);
+ OUString aShapeServiceName = "com.sun.star.drawing.CustomShape";
+ OUString sWatermark = WATERMARK_NAME;
+ bool bSuccess = false;
+ uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, aShapeServiceName, sWatermark, bSuccess);
+
+ bool bDeleteWatermark = rWatermark.GetText().isEmpty();
+ if (xWatermark.is())
+ {
+ drawing::HomogenMatrix3 aMatrix;
+ Color nColor = 0xc0c0c0;
+ sal_Int16 nTransparency = 50;
+ sal_Int16 nAngle = 45;
+ OUString aFont = "";
+
+ uno::Reference<beans::XPropertySet> xPropertySet(xWatermark, uno::UNO_QUERY);
+ xPropertySet->getPropertyValue(UNO_NAME_CHAR_FONT_NAME) >>= aFont;
+ xPropertySet->getPropertyValue(UNO_NAME_FILLCOLOR) >>= nColor;
+ xPropertySet->getPropertyValue(UNO_NAME_FILL_TRANSPARENCE) >>= nTransparency;
+ xPropertySet->getPropertyValue("Transformation") >>= aMatrix;
+ nAngle = lcl_GetAngle(aMatrix);
+
+ // If the header already contains a watermark, see if it its text is up to date.
+ uno::Reference<text::XTextRange> xTextRange(xWatermark, uno::UNO_QUERY);
+ if (xTextRange->getString() != rWatermark.GetText()
+ || aFont != rWatermark.GetFont()
+ || nColor != rWatermark.GetColor()
+ || nAngle != rWatermark.GetAngle()
+ || nTransparency != rWatermark.GetTransparency()
+ || bDeleteWatermark)
+ {
+ // No: delete it and we'll insert a replacement.
+ uno::Reference<lang::XComponent> xComponent(xWatermark, uno::UNO_QUERY);
+ xComponent->dispose();
+ xWatermark.clear();
+ }
+ }
+
+ if (!bSuccess || xWatermark.is() || bDeleteWatermark)
+ return;
+
+ const OUString& sFont = rWatermark.GetFont();
+ sal_Int16 nAngle = rWatermark.GetAngle();
+ sal_Int16 nTransparency = rWatermark.GetTransparency();
+ Color nColor = rWatermark.GetColor();
+
+ // Calc the ratio.
+ double fRatio = 0;
+
+ ScopedVclPtrInstance<VirtualDevice> pDevice;
+ vcl::Font aFont = pDevice->GetFont();
+ aFont.SetFamilyName(sFont);
+ aFont.SetFontSize(Size(0, 96));
+ pDevice->SetFont(aFont);
+
+ auto nTextWidth = pDevice->GetTextWidth(rWatermark.GetText());
+ if (nTextWidth)
+ {
+ fRatio = pDevice->GetTextHeight();
+ fRatio /= nTextWidth;
+ }
+
+ // Calc the size.
+ sal_Int32 nWidth = 0;
+ awt::Size aSize;
+ xPageStyle->getPropertyValue(UNO_NAME_SIZE) >>= aSize;
+ if (aSize.Width < aSize.Height)
+ {
+ // Portrait.
+ sal_Int32 nLeftMargin = 0;
+ xPageStyle->getPropertyValue(UNO_NAME_LEFT_MARGIN) >>= nLeftMargin;
+ sal_Int32 nRightMargin = 0;
+ xPageStyle->getPropertyValue(UNO_NAME_RIGHT_MARGIN) >>= nRightMargin;
+ nWidth = aSize.Width - nLeftMargin - nRightMargin;
+ }
+ else
+ {
+ // Landscape.
+ sal_Int32 nTopMargin = 0;
+ xPageStyle->getPropertyValue(UNO_NAME_TOP_MARGIN) >>= nTopMargin;
+ sal_Int32 nBottomMargin = 0;
+ xPageStyle->getPropertyValue(UNO_NAME_BOTTOM_MARGIN) >>= nBottomMargin;
+ nWidth = aSize.Height - nTopMargin - nBottomMargin;
+ }
+ sal_Int32 nHeight = fRatio * nWidth;
+
+ // Create and insert the shape.
+ uno::Reference<drawing::XShape> xShape(xMultiServiceFactory->createInstance(aShapeServiceName), uno::UNO_QUERY);
+
+ uno::Reference<container::XNamed> xNamed(xShape, uno::UNO_QUERY);
+ xNamed->setName(sWatermark);
+
+ basegfx::B2DHomMatrix aTransformation;
+ aTransformation.identity();
+ aTransformation.scale(nWidth, nHeight);
+ aTransformation.rotate(-basegfx::deg2rad(nAngle));
+ drawing::HomogenMatrix3 aMatrix;
+ aMatrix.Line1.Column1 = aTransformation.get(0, 0);
+ aMatrix.Line1.Column2 = aTransformation.get(0, 1);
+ aMatrix.Line1.Column3 = aTransformation.get(0, 2);
+ aMatrix.Line2.Column1 = aTransformation.get(1, 0);
+ aMatrix.Line2.Column2 = aTransformation.get(1, 1);
+ aMatrix.Line2.Column3 = aTransformation.get(1, 2);
+ aMatrix.Line3.Column1 = 0;
+ aMatrix.Line3.Column2 = 0;
+ aMatrix.Line3.Column3 = 1;
+ uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
+ xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, uno::Any(text::TextContentAnchorType_AT_CHARACTER));
+ uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY);
+ xHeaderText->insertTextContent(xHeaderText->getEnd(), xTextContent, false);
+
+ // The remaining properties have to be set after the shape is inserted: do that in one batch to avoid flickering.
+ uno::Reference<document::XActionLockable> xLockable(xShape, uno::UNO_QUERY);
+ xLockable->addActionLock();
+ xPropertySet->setPropertyValue(UNO_NAME_FILLCOLOR, uno::Any(static_cast<sal_Int32>(nColor)));
+ xPropertySet->setPropertyValue(UNO_NAME_FILLSTYLE, uno::Any(drawing::FillStyle_SOLID));
+ xPropertySet->setPropertyValue(UNO_NAME_FILL_TRANSPARENCE, uno::Any(nTransparency));
+ xPropertySet->setPropertyValue(UNO_NAME_LINESTYLE, uno::Any(drawing::LineStyle_NONE));
+ xPropertySet->setPropertyValue(UNO_NAME_OPAQUE, uno::Any(false));
+ xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT, uno::Any(false));
+ xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWWIDTH, uno::Any(false));
+ xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEHEIGHT, uno::Any(nHeight));
+ xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEWIDTH, uno::Any(nWidth));
+ xPropertySet->setPropertyValue(UNO_NAME_TEXT_WRAP, uno::Any(text::WrapTextMode_THROUGH));
+ xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION, uno::Any(text::RelOrientation::PAGE_PRINT_AREA));
+ xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT_RELATION, uno::Any(text::RelOrientation::PAGE_PRINT_AREA));
+ xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME, uno::Any(sFont));
+ xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME_ASIAN, uno::Any(sFont));
+ xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME_COMPLEX, uno::Any(sFont));
+ xPropertySet->setPropertyValue(UNO_NAME_CHAR_HEIGHT, uno::Any(WATERMARK_AUTO_SIZE));
+ xPropertySet->setPropertyValue("Transformation", uno::Any(aMatrix));
+
+ uno::Reference<text::XTextRange> xTextRange(xShape, uno::UNO_QUERY);
+ xTextRange->setString(rWatermark.GetText());
+
+ uno::Reference<drawing::XEnhancedCustomShapeDefaulter> xDefaulter(xShape, uno::UNO_QUERY);
+ xDefaulter->createCustomShapeDefaults("fontwork-plain-text");
+
+ auto aGeomPropSeq = xPropertySet->getPropertyValue("CustomShapeGeometry").get< uno::Sequence<beans::PropertyValue> >();
+ auto aGeomPropVec = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(aGeomPropSeq);
+ uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
+ {
+ {"TextPath", uno::Any(true)},
+ }));
+ auto it = std::find_if(aGeomPropVec.begin(), aGeomPropVec.end(), [](const beans::PropertyValue& rValue)
+ {
+ return rValue.Name == "TextPath";
+ });
+ if (it == aGeomPropVec.end())
+ aGeomPropVec.push_back(comphelper::makePropertyValue("TextPath", aPropertyValues));
+ else
+ it->Value <<= aPropertyValues;
+ xPropertySet->setPropertyValue("CustomShapeGeometry", uno::Any(comphelper::containerToSequence(aGeomPropVec)));
+
+ // tdf#108494, tdf#109313 the header height was switched to height of a watermark
+ // and shape was moved to the lower part of a page, force position update
+ xPropertySet->getPropertyValue("Transformation") >>= aMatrix;
+ xPropertySet->setPropertyValue("Transformation", uno::Any(aMatrix));
+
+ xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT, uno::Any(text::HoriOrientation::CENTER));
+ xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT, uno::Any(text::VertOrientation::CENTER));
+
+ xLockable->removeActionLock();
+}
+
+void SwEditShell::SetWatermark(const SfxWatermarkItem& rWatermark)
+{
+ SwDocShell* pDocShell = GetDoc()->GetDocShell();
+ if (!pDocShell)
+ return;
+ const bool bNoWatermark = rWatermark.GetText().isEmpty();
+
+ uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
+ uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY);
+ uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies();
+ uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY);
+ const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames();
+
+ for (const OUString& rPageStyleName : aStyles)
+ {
+ uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY);
+
+ // If the header is off, turn it on.
+ bool bHeaderIsOn = false;
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn;
+ if (!bHeaderIsOn)
+ {
+ if (bNoWatermark)
+ continue; // the style doesn't have any watermark - no need to do anything
+
+ xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(true));
+ }
+
+ // backup header height
+ bool bDynamicHeight = true;
+ sal_Int32 nOldValue;
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_HEIGHT) >>= nOldValue;
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT) >>= bDynamicHeight;
+ xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(false));
+
+ // If the header already contains a document header field, no need to do anything.
+ uno::Reference<text::XText> xHeaderText;
+ uno::Reference<text::XText> xHeaderTextFirst;
+ uno::Reference<text::XText> xHeaderTextLeft;
+ uno::Reference<text::XText> xHeaderTextRight;
+
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText;
+ lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderText);
+
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_FIRST) >>= xHeaderTextFirst;
+ lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextFirst);
+
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_LEFT) >>= xHeaderTextLeft;
+ lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextLeft);
+
+ xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_RIGHT) >>= xHeaderTextRight;
+ lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextRight);
+
+ // tdf#108494 the header height was switched to height of a watermark
+ // and shape was moved to the lower part of a page
+ xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::Any(sal_Int32(11)));
+ xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::Any(nOldValue));
+ xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(bDynamicHeight));
+ }
+}
+
+SwUndoParagraphSigning::SwUndoParagraphSigning(SwDoc& rDoc,
+ uno::Reference<text::XTextField> xField,
+ uno::Reference<text::XTextContent> xParent,
+ const bool bRemove)
+ : SwUndo(SwUndoId::PARA_SIGN_ADD, &rDoc),
+ m_rDoc(rDoc),
+ m_xField(std::move(xField)),
+ m_xParent(std::move(xParent)),
+ m_bRemove(bRemove)
+{
+ // Save the metadata and field content to undo/redo.
+ uno::Reference<frame::XModel> xModel = m_rDoc.GetDocShell()->GetBaseModel();
+ const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, m_xField);
+ const auto it = aStatements.find(ParagraphSignatureIdRDFName);
+ if (it != aStatements.end())
+ m_signature = it->second;
+
+ const auto it2 = aStatements.find(ParagraphSignatureUsageRDFName);
+ if (it2 != aStatements.end())
+ m_usage = it2->second;
+
+ uno::Reference<css::text::XTextRange> xText(m_xField, uno::UNO_QUERY);
+ m_display = xText->getString();
+}
+
+void SwUndoParagraphSigning::UndoImpl(::sw::UndoRedoContext&)
+{
+ if (m_bRemove)
+ Remove();
+ else
+ Insert();
+}
+
+void SwUndoParagraphSigning::RedoImpl(::sw::UndoRedoContext&)
+{
+ if (m_bRemove)
+ Insert();
+ else
+ Remove();
+}
+
+void SwUndoParagraphSigning::RepeatImpl(::sw::RepeatContext&)
+{
+}
+
+void SwUndoParagraphSigning::Insert()
+{
+ // Disable undo to avoid introducing noise when we edit the metadata field.
+ const bool isUndoEnabled = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
+ m_rDoc.GetIDocumentUndoRedo().DoUndo(false);
+
+ // Prevent validation since this will trigger a premature validation
+ // upon inserting, but before setting the metadata.
+ SwEditShell* pEditSh = m_rDoc.GetEditShell();
+ const bool bOldValidationFlag = pEditSh && pEditSh->SetParagraphSignatureValidation(false);
+ comphelper::ScopeGuard const g([&] () {
+ if (pEditSh)
+ pEditSh->SetParagraphSignatureValidation(bOldValidationFlag);
+ m_rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
+ });
+
+ m_xField = lcl_InsertParagraphSignature(m_rDoc.GetDocShell()->GetBaseModel(), m_xParent, m_signature, m_usage);
+ lcl_DoUpdateParagraphSignatureField(m_rDoc, m_xField, m_display);
+}
+
+void SwUndoParagraphSigning::Remove()
+{
+ // Disable undo to avoid introducing noise when we edit the metadata field.
+ const bool isUndoEnabled = m_rDoc.GetIDocumentUndoRedo().DoesUndo();
+ m_rDoc.GetIDocumentUndoRedo().DoUndo(false);
+
+ // Prevent validation since this will trigger a premature validation
+ // upon removing.
+ SwEditShell* pEditSh = m_rDoc.GetEditShell();
+ const bool bOldValidationFlag = pEditSh && pEditSh->SetParagraphSignatureValidation(false);
+ comphelper::ScopeGuard const g([&] () {
+ if (pEditSh)
+ pEditSh->SetParagraphSignatureValidation(bOldValidationFlag);
+ m_rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
+ });
+
+ lcl_RemoveParagraphMetadataField(m_xField);
+}
+
+void SwEditShell::SignParagraph()
+{
+ SwDoc& rDoc = *GetDoc();
+ SwDocShell* pDocShell = rDoc.GetDocShell();
+ if (!pDocShell || !GetCursor() || !GetCursor()->Start())
+ return;
+ const SwPosition* pPosStart = GetCursor()->Start();
+ if (!pPosStart)
+ return;
+ SwTextNode* pNode = pPosStart->GetNode().GetTextNode();
+ if (!pNode)
+ return;
+
+ // Table text signing is not supported.
+ if (pNode->FindTableNode() != nullptr)
+ return;
+
+ // 1. Get the text (without fields).
+ const rtl::Reference<SwXParagraph> xParagraph = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr);
+ const OString utf8Text = lcl_getParagraphBodyText(xParagraph);
+ if (utf8Text.isEmpty())
+ return;
+
+ // 2. Get certificate.
+ uno::Reference<security::XDocumentDigitalSignatures> xSigner(
+ // here none of the version-dependent methods are called
+ security::DocumentDigitalSignatures::createDefault(
+ comphelper::getProcessComponentContext()));
+
+ uno::Sequence<css::beans::PropertyValue> aProperties;
+ uno::Reference<security::XCertificate> xCertificate = xSigner->chooseCertificateWithProps(aProperties);
+ if (!xCertificate.is())
+ return;
+
+ // 3. Sign it.
+ svl::crypto::Signing signing(xCertificate);
+ signing.AddDataRange(utf8Text.getStr(), utf8Text.getLength());
+ OStringBuffer sigBuf;
+ if (!signing.Sign(sigBuf))
+ return;
+
+ const OUString signature = OStringToOUString(sigBuf, RTL_TEXTENCODING_UTF8, 0);
+
+ auto it = std::find_if(std::as_const(aProperties).begin(), std::as_const(aProperties).end(), [](const beans::PropertyValue& rValue)
+ {
+ return rValue.Name == "Usage";
+ });
+
+ OUString aUsage;
+ if (it != std::as_const(aProperties).end())
+ it->Value >>= aUsage;
+
+ // 4. Add metadata
+ // Prevent validation since this will trigger a premature validation
+ // upon inserting, but before setting the metadata.
+ const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
+ comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
+ SetParagraphSignatureValidation(bOldValidationFlag);
+ });
+
+ rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
+
+ const uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
+ uno::Reference<css::text::XTextField> xField = lcl_InsertParagraphSignature(xModel, xParagraph, signature, aUsage);
+
+ lcl_UpdateParagraphSignatureField(*GetDoc(), xModel, xParagraph, xField, utf8Text);
+
+ rDoc.GetIDocumentUndoRedo().AppendUndo(
+ std::make_unique<SwUndoParagraphSigning>(rDoc, xField, xParagraph, true));
+
+ rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
+}
+
+void SwEditShell::ValidateParagraphSignatures(SwTextNode* pNode, bool updateDontRemove)
+{
+ if (!pNode || !IsParagraphSignatureValidationEnabled())
+ return;
+
+ // Table text signing is not supported.
+ if (pNode->FindTableNode() != nullptr)
+ return;
+
+ // Prevent recursive validation since this is triggered on node updates, which we do below.
+ const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
+ comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
+ SetParagraphSignatureValidation(bOldValidationFlag);
+ });
+
+ rtl::Reference<SwXParagraph> xParentText = SwXParagraph::CreateXParagraph(*GetDoc(), pNode, nullptr);
+ lcl_ValidateParagraphSignatures(*GetDoc(), xParentText, updateDontRemove);
+}
+
+void SwEditShell::ValidateCurrentParagraphSignatures(bool updateDontRemove)
+{
+ SwDocShell* pDocShell = GetDoc()->GetDocShell();
+ if (!pDocShell || !GetCursor() || !GetCursor()->Start() || !IsParagraphSignatureValidationEnabled())
+ return;
+
+ SwPaM* pPaM = GetCursor();
+ const SwPosition* pPosStart = pPaM->Start();
+ SwTextNode* pNode = pPosStart->GetNode().GetTextNode();
+ ValidateParagraphSignatures(pNode, updateDontRemove);
+}
+
+void SwEditShell::ValidateAllParagraphSignatures(bool updateDontRemove)
+{
+ SwDocShell* pDocShell = GetDoc()->GetDocShell();
+ if (!pDocShell || !IsParagraphSignatureValidationEnabled())
+ return;
+
+ // Prevent recursive validation since this is triggered on node updates, which we do below.
+ const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
+ comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
+ SetParagraphSignatureValidation(bOldValidationFlag);
+ });
+
+ uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
+ const uno::Reference<text::XTextDocument> xDoc(xModel, uno::UNO_QUERY);
+ uno::Reference<text::XText> xParent = xDoc->getText();
+ uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xParent, uno::UNO_QUERY);
+ if (!xParagraphEnumerationAccess.is())
+ return;
+ uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
+ if (!xParagraphs.is())
+ return;
+ while (xParagraphs->hasMoreElements())
+ {
+ uno::Reference<text::XTextContent> xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY);
+ lcl_ValidateParagraphSignatures(*GetDoc(), xParagraph, updateDontRemove);
+ }
+}
+
+static uno::Reference<text::XTextField> lcl_GetParagraphMetadataFieldAtIndex(const SwDocShell* pDocSh, SwTextNode const * pNode, const sal_uLong index)
+{
+ uno::Reference<text::XTextField> xTextField;
+ if (pNode != nullptr && pDocSh != nullptr)
+ {
+ SwTextAttr* pAttr = pNode->GetTextAttrAt(index, RES_TXTATR_METAFIELD);
+ SwTextMeta* pTextMeta = static_txtattr_cast<SwTextMeta*>(pAttr);
+ if (pTextMeta != nullptr)
+ {
+ SwFormatMeta& rFormatMeta(static_cast<SwFormatMeta&>(pTextMeta->GetAttr()));
+ if (::sw::Meta* pMeta = rFormatMeta.GetMeta())
+ {
+ const css::uno::Reference<css::rdf::XResource> xSubject = pMeta->MakeUnoObject();
+ uno::Reference<frame::XModel> xModel = pDocSh->GetBaseModel();
+ const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xSubject);
+ if (aStatements.find(ParagraphSignatureIdRDFName) != aStatements.end() ||
+ aStatements.find(ParagraphClassificationNameRDFName) != aStatements.end())
+ {
+ xTextField = uno::Reference<text::XTextField>(xSubject, uno::UNO_QUERY);
+ }
+ }
+ }
+ }
+
+ return xTextField;
+}
+
+void SwEditShell::RestoreMetadataFieldsAndValidateParagraphSignatures()
+{
+ SwDocShell* pDocShell = GetDoc()->GetDocShell();
+ if (!pDocShell || !IsParagraphSignatureValidationEnabled())
+ return;
+
+ // Prevent recursive validation since this is triggered on node updates, which we do below.
+ const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
+ comphelper::ScopeGuard const g([this, bOldValidationFlag] () {
+ SetParagraphSignatureValidation(bOldValidationFlag);
+ });
+
+ uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
+ const rtl::Reference<SwXTextDocument> xDoc(dynamic_cast<SwXTextDocument*>(xModel.get()));
+ rtl::Reference<SwXBodyText> xBodyText = xDoc->getBodyText();
+ if (!xBodyText.is())
+ return;
+ rtl::Reference<SwXParagraphEnumeration> xParagraphs = xBodyText->createParagraphEnumeration();
+
+ static constexpr OUString sBlank(u""_ustr);
+ const sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
+ const css::uno::Sequence<css::uno::Reference<rdf::XURI>> aGraphNames = SwRDFHelper::getGraphNames(xModel, MetaNS);
+
+ while (xParagraphs->hasMoreElements())
+ {
+ uno::Reference<text::XTextContent> xParaOrTable(xParagraphs->nextElement(), uno::UNO_QUERY);
+ rtl::Reference<SwXParagraph> xParagraph(dynamic_cast<SwXParagraph*>(xParaOrTable.get()));
+
+ try
+ {
+ const css::uno::Reference<css::rdf::XResource> xSubject(xParagraph);
+ const std::map<OUString, OUString> aStatements = SwRDFHelper::getStatements(xModel, aGraphNames, xSubject);
+
+ const auto it = aStatements.find(ParagraphClassificationFieldNamesRDFName);
+ const OUString sFieldNames = (it != aStatements.end() ? it->second : sBlank);
+ std::vector<svx::ClassificationResult> aResults;
+ if (!sFieldNames.isEmpty())
+ {
+ assert(it != aStatements.end() && "can only be non-empty if it was valid");
+ // Order the fields
+ sal_Int32 nIndex = 0;
+ do
+ {
+ const OUString sCurFieldName = sFieldNames.getToken(0, '/', nIndex);
+ if (sCurFieldName.isEmpty())
+ break;
+
+ const auto it2 = aStatements.find(sCurFieldName);
+ bool bStatementFound = it2 != aStatements.end();
+ const OUString sName = bStatementFound ? it->first : sBlank;
+ const OUString sValue = bStatementFound ? it->second : sBlank;
+
+ if (aKeyCreator.isMarkingTextKey(sName))
+ {
+ aResults.push_back({ svx::ClassificationType::TEXT, sValue, sValue, sBlank });
+ }
+ else if (aKeyCreator.isCategoryNameKey(sName))
+ {
+ const auto it3 = aStatements.find(ParagraphClassificationAbbrRDFName);
+ const OUString sAbbreviatedName = (it3 != aStatements.end() && !it3->second.isEmpty() ? it3->second : sValue);
+ aResults.push_back({ svx::ClassificationType::CATEGORY, sValue, sAbbreviatedName, sBlank });
+ }
+ else if (aKeyCreator.isCategoryIdentifierKey(sName))
+ {
+ const auto it3 = aStatements.find(ParagraphClassificationAbbrRDFName);
+ const OUString sAbbreviatedName = (it3 != aStatements.end() && !it3->second.isEmpty() ? it3->second : sValue);
+ aResults.push_back({ svx::ClassificationType::CATEGORY, sBlank, sAbbreviatedName, sValue });
+ }
+ else if (aKeyCreator.isMarkingKey(sName))
+ {
+ aResults.push_back({ svx::ClassificationType::MARKING, sValue, sValue, sBlank });
+ }
+ else if (aKeyCreator.isIntellectualPropertyPartKey(sName))
+ {
+ aResults.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, sValue, sValue, sBlank });
+ }
+ }
+ while (nIndex >= 0);
+ }
+
+ // Update classification based on results.
+ lcl_ApplyParagraphClassification(GetDoc(), xModel, xParagraph, xSubject, aResults);
+
+ // Get Signatures
+ std::map<OUString, SignatureDescr> aSignatures;
+ for (const auto& pair : lcl_getRDFStatements(xModel, uno::Reference<css::text::XTextContent>(xParagraph)))
+ {
+ const OUString& sName = pair.first;
+ if (sName.startsWith(ParagraphSignatureRDFNamespace))
+ {
+ const OUString sSuffix = sName.copy(ParagraphSignatureRDFNamespace.getLength());
+ const sal_Int32 index = sSuffix.indexOf(":");
+ if (index >= 0)
+ {
+ const OUString id = sSuffix.copy(0, index);
+ const OUString type = sSuffix.copy(index);
+ const OUString& sValue = pair.second;
+ if (type == ParagraphSignatureDateRDFName)
+ aSignatures[id].msDate = sValue;
+ else if (type == ParagraphSignatureUsageRDFName)
+ aSignatures[id].msUsage = sValue;
+ else if (type == ParagraphSignatureDigestRDFName)
+ aSignatures[id].msSignature = sValue;
+ }
+ }
+ }
+
+ for (const auto& pair : aSignatures)
+ {
+ uno::Reference<text::XTextField> xField = lcl_findFieldByRDF(xModel, xParagraph, ParagraphSignatureIdRDFName, pair.first);
+ if (!xField.is())
+ {
+ uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY);
+ xField = uno::Reference<text::XTextField>(xMultiServiceFactory->createInstance(MetadataFieldServiceName), uno::UNO_QUERY);
+
+ // Add the signature at the end.
+ xField->attach(xParagraph->getAnchor()->getEnd());
+
+ const css::uno::Reference<css::rdf::XResource> xFieldSubject(xField, uno::UNO_QUERY);
+ SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphSignatureIdRDFName, pair.first);
+
+ const OString utf8Text = lcl_getParagraphBodyText(xParagraph);
+ lcl_UpdateParagraphSignatureField(*GetDoc(), xModel, xParagraph, xField, utf8Text);
+ }
+ }
+
+ lcl_ValidateParagraphSignatures(*GetDoc(), xParagraph, true); // Validate and Update signatures.
+ }
+ catch (const std::exception&)
+ {
+ }
+ }
+}
+
+bool SwEditShell::IsCursorInParagraphMetadataField() const
+{
+ if (GetCursor() && GetCursor()->Start())
+ {
+ SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
+ const sal_uLong index = GetCursor()->Start()->GetContentIndex();
+ uno::Reference<text::XTextField> xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index);
+ return xField.is();
+ }
+
+ return false;
+}
+
+bool SwEditShell::RemoveParagraphMetadataFieldAtCursor()
+{
+ if (GetCursor() && GetCursor()->Start())
+ {
+ SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode();
+ sal_uLong index = GetCursor()->Start()->GetContentIndex();
+ uno::Reference<text::XTextField> xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index);
+ if (!xField.is())
+ {
+ // Try moving the cursor to see if we're _facing_ a metafield or not,
+ // as opposed to being within one.
+ index--; // Backspace moves left
+
+ xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index);
+ }
+
+ if (xField.is())
+ {
+ lcl_RemoveParagraphMetadataField(xField);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static OUString lcl_GetParagraphClassification(SfxClassificationHelper & rHelper, sfx::ClassificationKeyCreator const & rKeyCreator,
+ const uno::Reference<frame::XModel>& xModel, const rtl::Reference<SwXParagraph>& xParagraph)
+{
+ uno::Reference<text::XTextField> xTextField;
+ xTextField = lcl_FindParagraphClassificationField(xModel, xParagraph, rKeyCreator.makeCategoryIdentifierKey());
+ if (xTextField.is())
+ {
+ const std::pair<OUString, OUString> rdfValuePair = lcl_getRDF(xModel, xTextField, ParagraphClassificationValueRDFName);
+ return rHelper.GetBACNameForIdentifier(rdfValuePair.second);
+ }
+
+ xTextField = lcl_FindParagraphClassificationField(xModel, xParagraph, rKeyCreator.makeCategoryNameKey());
+ if (xTextField.is())
+ {
+ return lcl_getRDF(xModel, xTextField, ParagraphClassificationNameRDFName).second;
+ }
+
+ return OUString();
+}
+
+static OUString lcl_GetHighestClassificationParagraphClass(SwPaM* pCursor)
+{
+ OUString sHighestClass;
+
+ SwTextNode* pNode = pCursor->Start()->GetNode().GetTextNode();
+ if (pNode == nullptr)
+ return sHighestClass;
+
+ SwDocShell* pDocShell = pNode->GetDoc().GetDocShell();
+ if (!pDocShell)
+ return sHighestClass;
+
+ SfxClassificationHelper aHelper(pDocShell->getDocProperties());
+ sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
+
+ uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel();
+ const rtl::Reference<SwXTextDocument> xDoc(dynamic_cast<SwXTextDocument*>(xModel.get()));
+ rtl::Reference<SwXBodyText> xBodyText = xDoc->getBodyText();
+
+ rtl::Reference<SwXParagraphEnumeration> xParagraphs = xBodyText->createParagraphEnumeration();
+ while (xParagraphs->hasMoreElements())
+ {
+ uno::Reference<text::XTextContent> xParaOrTable(xParagraphs->nextElement(), uno::UNO_QUERY);
+ rtl::Reference<SwXParagraph> xParagraph(dynamic_cast<SwXParagraph*>(xParaOrTable.get()));
+ const OUString sCurrentClass = lcl_GetParagraphClassification(aHelper, aKeyCreator, xModel, xParagraph);
+ sHighestClass = aHelper.GetHigherClass(sHighestClass, sCurrentClass);
+ }
+
+ return sHighestClass;
+}
+
+void SwEditShell::ClassifyDocPerHighestParagraphClass()
+{
+ SwDocShell* pDocShell = GetDoc()->GetDocShell();
+ if (!pDocShell)
+ return;
+
+ // Bail out as early as possible if we don't have paragraph classification.
+ if (!SwRDFHelper::hasMetadataGraph(pDocShell->GetBaseModel(), MetaNS))
+ return;
+
+ uno::Reference<document::XDocumentProperties> xDocumentProperties = pDocShell->getDocProperties();
+ uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties();
+
+ sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType());
+ SfxClassificationHelper aHelper(xDocumentProperties);
+
+ OUString sHighestClass = lcl_GetHighestClassificationParagraphClass(GetCursor());
+
+ const OUString aClassificationCategory = svx::classification::getProperty(xPropertyContainer, aKeyCreator.makeCategoryNameKey());
+
+ if (!aClassificationCategory.isEmpty())
+ {
+ sHighestClass = aHelper.GetHigherClass(sHighestClass, aClassificationCategory);
+ }
+
+ if (aClassificationCategory != sHighestClass)
+ {
+ std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Question, VclButtonsType::Ok,
+ SwResId(STR_CLASSIFICATION_LEVEL_CHANGED)));
+ xQueryBox->run();
+ }
+
+ const SfxClassificationPolicyType eHighestClassType = SfxClassificationHelper::stringToPolicyType(sHighestClass);
+
+ // Prevent paragraph signature validation since the below changes (f.e. watermarking) are benign.
+ const bool bOldValidationFlag = SetParagraphSignatureValidation(false);
+ comphelper::ScopeGuard const g([this, bOldValidationFlag]() {
+ SetParagraphSignatureValidation(bOldValidationFlag);
+ });
+
+ // Check the origin, if "manual" (created via advanced classification dialog),
+ // then we just need to set the category name.
+ if (sfx::getCreationOriginProperty(xPropertyContainer, aKeyCreator) == sfx::ClassificationCreationOrigin::MANUAL)
+ {
+ aHelper.SetBACName(sHighestClass, eHighestClassType);
+ ApplyAdvancedClassification(CollectAdvancedClassification());
+ }
+ else
+ {
+ SetClassification(sHighestClass, eHighestClassType);
+ }
+}
+
+// #i62675#
+void SwEditShell::SetTextFormatColl(SwTextFormatColl *pFormat,
+ const bool bResetListAttrs)
+{
+ SwTextFormatColl *pLocal = pFormat? pFormat: (*GetDoc()->GetTextFormatColls())[0];
+ StartAllAction();
+
+ SwRewriter aRewriter;
+
+ aRewriter.AddRule(UndoArg1, pLocal->GetName());
+
+ GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::SETFMTCOLL, &aRewriter);
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ if (!rPaM.HasReadonlySel( GetViewOptions()->IsFormView(), true))
+ {
+ // store previous paragraph style for track changes
+ OUString sParaStyleName;
+ sal_uInt16 nPoolId = USHRT_MAX;
+ SwContentNode * pCnt = rPaM.Start()->GetNode().GetContentNode();
+ if ( pCnt && pCnt->GetTextNode() && GetDoc()->getIDocumentRedlineAccess().IsRedlineOn() )
+ {
+ const SwTextFormatColl* pTextFormatColl = pCnt->GetTextNode()->GetTextColl();
+ sal_uInt16 nStylePoolId = pTextFormatColl->GetPoolFormatId();
+ // default paragraph style
+ if ( nStylePoolId == RES_POOLCOLL_STANDARD )
+ nPoolId = nStylePoolId;
+ else
+ sParaStyleName = pTextFormatColl->GetName();
+ }
+
+ // Change the paragraph style to pLocal and remove all direct paragraph formatting.
+ GetDoc()->SetTextFormatColl(rPaM, pLocal, true, bResetListAttrs, GetLayout());
+
+ // If there are hints on the nodes which cover the whole node, then remove those, too.
+ SwPaM aPaM(*rPaM.Start(), *rPaM.End());
+ if (SwTextNode* pEndTextNode = aPaM.End()->GetNode().GetTextNode())
+ {
+ aPaM.Start()->SetContent(0);
+ aPaM.End()->SetContent(pEndTextNode->GetText().getLength());
+ }
+ GetDoc()->RstTextAttrs(aPaM, /*bInclRefToxMark=*/false, /*bExactRange=*/true, GetLayout());
+
+ // add redline tracking the previous paragraph style
+ if ( GetDoc()->getIDocumentRedlineAccess().IsRedlineOn() &&
+ // multi-paragraph ParagraphFormat redline ranges
+ // haven't supported by AppendRedline(), yet
+ // TODO handle multi-paragraph selections, too,
+ // e.g. by breaking them to single paragraphs
+ aPaM.Start()->GetNode() == aPaM.End()->GetNode() )
+ {
+ SwRangeRedline * pRedline = new SwRangeRedline( RedlineType::ParagraphFormat, aPaM );
+ auto const result(GetDoc()->getIDocumentRedlineAccess().AppendRedline( pRedline, true));
+ // store original paragraph style to reject formatting change
+ if ( IDocumentRedlineAccess::AppendResult::IGNORED != result &&
+ ( nPoolId == RES_POOLCOLL_STANDARD || !sParaStyleName.isEmpty() ) )
+ {
+ std::unique_ptr<SwRedlineExtraData_FormatColl> xExtra;
+ xExtra.reset(new SwRedlineExtraData_FormatColl(sParaStyleName, nPoolId, nullptr));
+ if (xExtra)
+ pRedline->SetExtraData( xExtra.get() );
+ }
+ }
+ }
+
+ }
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::SETFMTCOLL, &aRewriter);
+ EndAllAction();
+}
+
+SwTextFormatColl* SwEditShell::MakeTextFormatColl(const OUString& rFormatCollName,
+ SwTextFormatColl* pParent)
+{
+ SwTextFormatColl *pColl;
+ if ( pParent == nullptr )
+ pParent = &GetTextFormatColl(0);
+ pColl = GetDoc()->MakeTextFormatColl(rFormatCollName, pParent);
+ if ( pColl == nullptr )
+ {
+ OSL_FAIL( "MakeTextFormatColl failed" );
+ }
+ return pColl;
+
+}
+
+void SwEditShell::FillByEx(SwTextFormatColl* pColl)
+{
+ SwPaM * pCursor = GetCursor();
+ SwContentNode * pCnt = pCursor->GetPointContentNode();
+ if (pCnt->IsTextNode()) // uhm... what nonsense would happen if not?
+ { // only need properties-node because BREAK/PAGEDESC filtered anyway!
+ pCnt = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode());
+ }
+ const SfxItemSet* pSet = pCnt->GetpSwAttrSet();
+ if( !pSet )
+ return;
+
+ // JP 05.10.98: Special treatment if one of the attributes Break/PageDesc/NumRule(auto) is
+ // in the ItemSet. Otherwise there will be too much or wrong processing (NumRules!)
+ // Bug 57568
+
+ // Do NOT copy AutoNumRules into the template
+ const SwNumRuleItem* pItem;
+ const SwNumRule* pRule = nullptr;
+ if (SfxItemState::SET == pSet->GetItemState(RES_BREAK, false)
+ || SfxItemState::SET == pSet->GetItemState(RES_PAGEDESC, false)
+ || ((pItem = pSet->GetItemIfSet(RES_PARATR_NUMRULE, false))
+ && nullptr != (pRule = GetDoc()->FindNumRulePtr(pItem->GetValue()))
+ && pRule->IsAutoRule()))
+ {
+ SfxItemSet aSet( *pSet );
+ aSet.ClearItem( RES_BREAK );
+ aSet.ClearItem( RES_PAGEDESC );
+
+ if (pRule
+ || ((pItem = pSet->GetItemIfSet(RES_PARATR_NUMRULE, false))
+ && nullptr != (pRule = GetDoc()->FindNumRulePtr(pItem->GetValue()))
+ && pRule->IsAutoRule()))
+ aSet.ClearItem( RES_PARATR_NUMRULE );
+
+ if( aSet.Count() )
+ GetDoc()->ChgFormat(*pColl, aSet );
+ }
+ else
+ GetDoc()->ChgFormat(*pColl, *pSet );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/edfld.cxx b/sw/source/core/edit/edfld.cxx
new file mode 100644
index 0000000000..f77ae8d97e
--- /dev/null
+++ b/sw/source/core/edit/edfld.cxx
@@ -0,0 +1,411 @@
+/* -*- 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 <config_features.h>
+#include <config_fuzzers.h>
+
+#include <osl/diagnose.h>
+#include <unotools/charclass.hxx>
+#include <editsh.hxx>
+#include <fldbas.hxx>
+#include <doc.hxx>
+#include <IDocumentFieldsAccess.hxx>
+#include <IDocumentState.hxx>
+#include <docary.hxx>
+#include <fmtfld.hxx>
+#include <txtfld.hxx>
+#include <pamtyp.hxx>
+#include <expfld.hxx>
+#include <swundo.hxx>
+#include <dbmgr.hxx>
+#include <hints.hxx>
+#include <fieldhint.hxx>
+#include <DocumentSettingManager.hxx>
+#include <IDocumentContentOperations.hxx>
+
+/// count field types with a ResId, if SwFieldIds::Unknown count all
+size_t SwEditShell::GetFieldTypeCount(SwFieldIds nResId ) const
+{
+ const SwFieldTypes* pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes();
+
+ if(nResId == SwFieldIds::Unknown)
+ {
+ return o3tl::narrowing<sal_uInt16>(pFieldTypes->size());
+ }
+
+ // all types with the same ResId
+ size_t nIdx = 0;
+ for(const auto & pFieldType : *pFieldTypes)
+ {
+ // same ResId -> increment index
+ if(pFieldType->Which() == nResId)
+ nIdx++;
+ }
+ return nIdx;
+}
+
+/// get field types with a ResId, if 0 get all
+SwFieldType* SwEditShell::GetFieldType(size_t nField, SwFieldIds nResId ) const
+{
+ const SwFieldTypes* pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes();
+
+ if(nResId == SwFieldIds::Unknown && nField < pFieldTypes->size())
+ {
+ return (*pFieldTypes)[nField].get();
+ }
+
+ size_t nIdx = 0;
+ for(const auto & pFieldType : *pFieldTypes)
+ {
+ // same ResId -> increment index
+ if(pFieldType->Which() == nResId)
+ {
+ if(nIdx == nField)
+ return pFieldType.get();
+ nIdx++;
+ }
+ }
+ return nullptr;
+}
+
+/// get first type with given ResId and name
+SwFieldType* SwEditShell::GetFieldType(SwFieldIds nResId, const OUString& rName) const
+{
+ return GetDoc()->getIDocumentFieldsAccess().GetFieldType( nResId, rName, false );
+}
+
+/// delete field type
+void SwEditShell::RemoveFieldType(size_t nField)
+{
+ GetDoc()->getIDocumentFieldsAccess().RemoveFieldType(nField);
+}
+
+/// delete field type based on its name
+void SwEditShell::RemoveFieldType(SwFieldIds nResId, const OUString& rStr)
+{
+ const SwFieldTypes* pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes();
+ const SwFieldTypes::size_type nSize = pFieldTypes->size();
+ const CharClass& rCC = GetAppCharClass();
+
+ OUString aTmp( rCC.lowercase( rStr ));
+
+ for(SwFieldTypes::size_type i = 0; i < nSize; ++i)
+ {
+ // same ResId -> increment index
+ SwFieldType* pFieldType = (*pFieldTypes)[i].get();
+ if( pFieldType->Which() == nResId )
+ {
+ if( aTmp == rCC.lowercase( pFieldType->GetName() ) )
+ {
+ GetDoc()->getIDocumentFieldsAccess().RemoveFieldType(i);
+ return;
+ }
+ }
+ }
+}
+
+void SwEditShell::FieldToText( SwFieldType const * pType )
+{
+ if( !pType->HasWriterListeners() )
+ return;
+
+ CurrShell aCurr( this );
+ StartAllAction();
+ StartUndo( SwUndoId::DELETE );
+ Push();
+ SwPaM* pPaM = GetCursor();
+ const SwFieldHint aHint(pPaM, GetLayout());
+ pType->CallSwClientNotify(aHint);
+
+ Pop(PopMode::DeleteCurrent);
+ EndAllAction();
+ EndUndo( SwUndoId::DELETE );
+}
+
+/// add a field at the cursor position
+bool SwEditShell::InsertField(SwField const & rField, const bool bForceExpandHints)
+{
+ CurrShell aCurr( this );
+ StartAllAction();
+ SwFormatField aField( rField );
+
+ const SetAttrMode nInsertFlags = bForceExpandHints
+ ? SetAttrMode::FORCEHINTEXPAND
+ : SetAttrMode::DEFAULT;
+
+ bool bSuccess(false);
+ for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) // for each PaM
+ {
+ bSuccess |= GetDoc()->getIDocumentContentOperations().InsertPoolItem(rPaM, aField, nInsertFlags);
+ OSL_ENSURE( bSuccess, "Doc->Insert(Field) failed");
+ }
+
+ EndAllAction();
+ return bSuccess;
+}
+
+/// Are the PaMs positioned on fields?
+static SwTextField* lcl_FindInputField( SwDoc* pDoc, const SwField& rField )
+{
+ // Search field via its address. For input fields this needs to be done in protected fields.
+ SwTextField* pTField = nullptr;
+ if (SwFieldIds::Input == rField.Which()
+ || (SwFieldIds::SetExp == rField.Which()
+ && static_cast<const SwSetExpField&>(rField).GetInputFlag()
+ && (static_cast<SwSetExpFieldType*>(rField.GetTyp())->GetType()
+ & nsSwGetSetExpType::GSE_STRING)))
+ {
+ for (const SfxPoolItem* pItem : pDoc->GetAttrPool().GetItemSurrogates(RES_TXTATR_INPUTFIELD))
+ {
+ auto pFormatField = dynamic_cast<const SwFormatField*>(pItem);
+ if( pFormatField && pFormatField->GetField() == &rField )
+ {
+ pTField = const_cast<SwFormatField*>(pFormatField)->GetTextField();
+ break;
+ }
+ }
+ }
+ else if( SwFieldIds::SetExp == rField.Which()
+ && static_cast<const SwSetExpField&>(rField).GetInputFlag() )
+ {
+ for (const SfxPoolItem* pItem : pDoc->GetAttrPool().GetItemSurrogates(RES_TXTATR_FIELD))
+ {
+ auto pFormatField = dynamic_cast<const SwFormatField*>(pItem);
+ if( pFormatField && pFormatField->GetField() == &rField )
+ {
+ pTField = const_cast<SwFormatField*>(pFormatField)->GetTextField();
+ break;
+ }
+ }
+ }
+ return pTField;
+}
+
+void SwEditShell::UpdateOneField(SwField &rField)
+{
+ CurrShell aCurr( this );
+ StartAllAction();
+ {
+ // If there are no selections so take the value of the current cursor position.
+ SwPaM* pCursor = GetCursor();
+ SwTextField *pTextField;
+ SwFormatField *pFormatField;
+
+ if ( !pCursor->IsMultiSelection() && !pCursor->HasMark())
+ {
+ pTextField = GetTextFieldAtPos(pCursor->Start(), ::sw::GetTextAttrMode::Default);
+
+ if (!pTextField) // #i30221#
+ pTextField = lcl_FindInputField( GetDoc(), rField);
+
+ if (pTextField != nullptr)
+ {
+ GetDoc()->getIDocumentFieldsAccess().UpdateField(
+ pTextField,
+ rField,
+ true);
+ }
+ }
+
+ // bOkay (instead of return because of EndAllAction) becomes false,
+ // 1) if only one PaM has more than one field or
+ // 2) if there are mixed field types
+ bool bOkay = true;
+ bool bTableSelBreak = false;
+
+ SwMsgPoolItem aFieldHint( RES_TXTATR_FIELD ); // Search-Hint
+ SwMsgPoolItem aAnnotationFieldHint( RES_TXTATR_ANNOTATION );
+ SwMsgPoolItem aInputFieldHint( RES_TXTATR_INPUTFIELD );
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer()) // for each PaM
+ {
+ if( rPaM.HasMark() && bOkay ) // ... with selection
+ {
+ // copy of the PaM
+ SwPaM aCurPam( *rPaM.GetMark(), *rPaM.GetPoint() );
+ SwPaM aPam( *rPaM.GetPoint() );
+
+ SwPosition *pCurStt = aCurPam.Start(), *pCurEnd =
+ aCurPam.End();
+ /*
+ * In case that there are two contiguous fields in a PaM, the aPam goes step by step
+ * to the end. aCurPam is reduced in each loop. If aCurPam was searched completely,
+ * the loop terminates because Start = End.
+ */
+
+ // Search for SwTextField ...
+ while( bOkay
+ && pCurStt->GetContentIndex() != pCurEnd->GetContentIndex()
+ && (sw::FindAttrImpl(aPam, aFieldHint, fnMoveForward, aCurPam, true, GetLayout())
+ || sw::FindAttrImpl(aPam, aAnnotationFieldHint, fnMoveForward, aCurPam, false, GetLayout())
+ || sw::FindAttrImpl(aPam, aInputFieldHint, fnMoveForward, aCurPam, false, GetLayout())))
+ {
+ // if only one PaM has more than one field ...
+ if( aPam.Start()->GetContentIndex() != pCurStt->GetContentIndex() )
+ bOkay = false;
+
+ pTextField = GetTextFieldAtPos(pCurStt, ::sw::GetTextAttrMode::Default);
+ if( nullptr != pTextField )
+ {
+ pFormatField = const_cast<SwFormatField*>(&pTextField->GetFormatField());
+ SwField *pCurField = pFormatField->GetField();
+
+ // if there are mixed field types
+ if( pCurField->GetTyp()->Which() !=
+ rField.GetTyp()->Which() )
+ bOkay = false;
+
+ bTableSelBreak = GetDoc()->getIDocumentFieldsAccess().UpdateField(
+ pTextField,
+ rField,
+ false);
+ }
+ // The search area is reduced by the found area:
+ pCurStt->AdjustContent(+1);
+ }
+ }
+
+ if( bTableSelBreak ) // If table section and table formula are updated -> finish
+ break;
+
+ }
+ }
+ GetDoc()->getIDocumentState().SetModified();
+ EndAllAction();
+}
+
+SwDBData const & SwEditShell::GetDBData() const
+{
+ return GetDoc()->GetDBData();
+}
+
+void SwEditShell::ChgDBData(const SwDBData& rNewData)
+{
+ GetDoc()->ChgDBData(rNewData);
+}
+
+void SwEditShell::GetAllUsedDB( std::vector<OUString>& rDBNameList,
+ std::vector<OUString> const * pAllDBNames )
+{
+ GetDoc()->GetAllUsedDB( rDBNameList, pAllDBNames );
+}
+
+void SwEditShell::ChangeDBFields( const std::vector<OUString>& rOldNames,
+ const OUString& rNewName )
+{
+ GetDoc()->ChangeDBFields( rOldNames, rNewName );
+}
+
+/// Update all expression fields
+void SwEditShell::UpdateExpFields(bool bCloseDB)
+{
+ CurrShell aCurr( this );
+ StartAllAction();
+ GetDoc()->getIDocumentFieldsAccess().UpdateExpFields(nullptr, true);
+ if (bCloseDB)
+ {
+#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS
+ GetDoc()->GetDBManager()->CloseAll(); // close all database connections
+#endif
+ }
+ EndAllAction();
+}
+
+SwDBManager* SwEditShell::GetDBManager() const
+{
+#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS
+ return GetDoc()->GetDBManager();
+#else
+ return NULL;
+#endif
+}
+
+/// insert field type
+SwFieldType* SwEditShell::InsertFieldType(const SwFieldType& rFieldType)
+{
+ return GetDoc()->getIDocumentFieldsAccess().InsertFieldType(rFieldType);
+}
+
+void SwEditShell::LockExpFields()
+{
+ GetDoc()->getIDocumentFieldsAccess().LockExpFields();
+}
+
+void SwEditShell::UnlockExpFields()
+{
+ GetDoc()->getIDocumentFieldsAccess().UnlockExpFields();
+}
+
+bool SwEditShell::IsExpFieldsLocked() const
+{
+ return GetDoc()->getIDocumentFieldsAccess().IsExpFieldsLocked();
+}
+
+void SwEditShell::SetFieldUpdateFlags( SwFieldUpdateFlags eFlags )
+{
+ getIDocumentSettingAccess().setFieldUpdateFlags( eFlags );
+}
+
+SwFieldUpdateFlags SwEditShell::GetFieldUpdateFlags() const
+{
+ return getIDocumentSettingAccess().getFieldUpdateFlags( false );
+}
+
+void SwEditShell::SetLabelDoc( bool bFlag )
+{
+ GetDoc()->GetDocumentSettingManager().set(DocumentSettingId::LABEL_DOCUMENT, bFlag );
+}
+
+bool SwEditShell::IsLabelDoc() const
+{
+ return getIDocumentSettingAccess().get(DocumentSettingId::LABEL_DOCUMENT);
+}
+
+void SwEditShell::ChangeAuthorityData(const SwAuthEntry* pNewData)
+{
+ GetDoc()->ChangeAuthorityData(pNewData);
+}
+
+bool SwEditShell::IsAnyDatabaseFieldInDoc()const
+{
+ // Similar to: SwDoc::GetDBDesc
+ const SwFieldTypes * pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes();
+ for(const auto & pFieldType : *pFieldTypes)
+ {
+ if(IsUsed(*pFieldType))
+ {
+ switch(pFieldType->Which())
+ {
+ case SwFieldIds::Database:
+ case SwFieldIds::DbNextSet:
+ case SwFieldIds::DbNumSet:
+ case SwFieldIds::DbSetNumber:
+ {
+ std::vector<SwFormatField*> vFields;
+ pFieldType->GatherFields(vFields);
+ return vFields.size();
+ }
+ break;
+ default: break;
+ }
+ }
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/edfldexp.cxx b/sw/source/core/edit/edfldexp.cxx
new file mode 100644
index 0000000000..3247d05005
--- /dev/null
+++ b/sw/source/core/edit/edfldexp.cxx
@@ -0,0 +1,58 @@
+/* -*- 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 <editsh.hxx>
+#include <dbfld.hxx>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/sdb/DatabaseContext.hpp>
+#include <doc.hxx>
+#include <IDocumentFieldsAccess.hxx>
+#include <docary.hxx>
+#include <fmtfld.hxx>
+
+using namespace com::sun::star;
+
+bool SwEditShell::IsFieldDataSourceAvailable(OUString& rUsedDataSource) const
+{
+ const SwFieldTypes* pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes();
+ uno::Reference<uno::XComponentContext> xContext(::comphelper::getProcessComponentContext());
+ uno::Reference<sdb::XDatabaseContext> xDBContext = sdb::DatabaseContext::create(xContext);
+ std::vector<SwFormatField*> vFields;
+ for (const auto& pFieldType : *pFieldTypes)
+ {
+ if (IsUsed(*pFieldType) && pFieldType->Which() == SwFieldIds::Database)
+ pFieldType->GatherFields(vFields);
+ }
+ if (!vFields.size())
+ return true;
+
+ const SwDBData& rData
+ = static_cast<SwDBFieldType*>(vFields.front()->GetField()->GetTyp())->GetDBData();
+ try
+ {
+ return xDBContext->getByName(rData.sDataSource).hasValue();
+ }
+ catch (uno::Exception const&)
+ {
+ rUsedDataSource = rData.sDataSource;
+ return false;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/edfmt.cxx b/sw/source/core/edit/edfmt.cxx
new file mode 100644
index 0000000000..ee291e82e8
--- /dev/null
+++ b/sw/source/core/edit/edfmt.cxx
@@ -0,0 +1,157 @@
+/* -*- 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 <doc.hxx>
+#include <IDocumentStylePoolAccess.hxx>
+#include <editsh.hxx>
+#include <pam.hxx>
+#include <fchrfmt.hxx>
+#include <frmfmt.hxx>
+#include <charfmt.hxx>
+#include <ndtxt.hxx>
+
+sal_uInt16 SwEditShell::GetCharFormatCount() const
+{
+ return GetDoc()->GetCharFormats()->size();
+}
+
+SwCharFormat& SwEditShell::GetCharFormat(sal_uInt16 nFormat) const
+{
+ return *((*(GetDoc()->GetCharFormats()))[nFormat]);
+}
+
+SwCharFormat* SwEditShell::GetCurCharFormat() const
+{
+ SwCharFormat *pFormat = nullptr;
+ SfxItemSetFixed<RES_TXTATR_CHARFMT, RES_TXTATR_CHARFMT> aSet( GetDoc()->GetAttrPool() );
+ const SwFormatCharFormat* pItem;
+ if( GetCurAttr( aSet ) &&
+ (pItem = aSet.GetItemIfSet( RES_TXTATR_CHARFMT, false ) ) )
+ pFormat = pItem->GetCharFormat();
+
+ return pFormat;
+}
+
+void SwEditShell::FillByEx(SwCharFormat* pCharFormat)
+{
+ SwPaM* pPam = GetCursor();
+ const SwContentNode* pCNd = pPam->GetPointContentNode();
+ if( pCNd->IsTextNode() )
+ {
+ SwTextNode const*const pTextNode(pCNd->GetTextNode());
+ sal_Int32 nStt;
+ sal_Int32 nEnd;
+ if( pPam->HasMark() )
+ {
+ const SwPosition* pPtPos = pPam->GetPoint();
+ const SwPosition* pMkPos = pPam->GetMark();
+ if( pPtPos->GetNode() == pMkPos->GetNode() ) // in the same node?
+ {
+ nStt = pPtPos->GetContentIndex();
+ if( nStt < pMkPos->GetContentIndex() )
+ nEnd = pMkPos->GetContentIndex();
+ else
+ {
+ nEnd = nStt;
+ nStt = pMkPos->GetContentIndex();
+ }
+ }
+ else
+ {
+ nStt = pMkPos->GetContentIndex();
+ if( pPtPos->GetNode() < pMkPos->GetNode() )
+ {
+ nEnd = nStt;
+ nStt = 0;
+ }
+ else
+ nEnd = pTextNode->GetText().getLength();
+ }
+ }
+ else
+ nStt = nEnd = pPam->GetPoint()->GetContentIndex();
+
+ SfxItemSet aSet( mxDoc->GetAttrPool(),
+ pCharFormat->GetAttrSet().GetRanges() );
+ pTextNode->GetParaAttr(aSet, nStt, nEnd, false, true, false, GetLayout());
+ pCharFormat->SetFormatAttr( aSet );
+ }
+ else if( pCNd->HasSwAttrSet() )
+ pCharFormat->SetFormatAttr( *pCNd->GetpSwAttrSet() );
+}
+
+size_t SwEditShell::GetTableFrameFormatCount(bool bUsed) const
+{
+ return GetDoc()->GetTableFrameFormatCount(bUsed);
+}
+
+SwFrameFormat& SwEditShell::GetTableFrameFormat(size_t nFormat, bool bUsed ) const
+{
+ return GetDoc()->GetTableFrameFormat(nFormat, bUsed );
+}
+
+OUString SwEditShell::GetUniqueTableName() const
+{
+ return GetDoc()->GetUniqueTableName();
+}
+
+SwCharFormat* SwEditShell::MakeCharFormat( const OUString& rName )
+{
+ SwCharFormat* pDerivedFrom = GetDoc()->GetDfltCharFormat();
+
+ return GetDoc()->MakeCharFormat( rName, pDerivedFrom );
+}
+
+SwTextFormatColl* SwEditShell::GetTextCollFromPool( sal_uInt16 nId )
+{
+ return GetDoc()->getIDocumentStylePoolAccess().GetTextCollFromPool( nId );
+}
+
+/// return the requested automatic format - base-class !
+SwFormat* SwEditShell::GetFormatFromPool( sal_uInt16 nId )
+{
+ return GetDoc()->getIDocumentStylePoolAccess().GetFormatFromPool( nId );
+}
+
+SwPageDesc* SwEditShell::GetPageDescFromPool( sal_uInt16 nId )
+{
+ return GetDoc()->getIDocumentStylePoolAccess().GetPageDescFromPool( nId );
+}
+
+bool SwEditShell::IsUsed( const sw::BroadcastingModify& rModify ) const
+{
+ return mxDoc->IsUsed( rModify );
+}
+
+const SwFlyFrameFormat* SwEditShell::FindFlyByName( const OUString& rName ) const
+{
+ return mxDoc->FindFlyByName(rName);
+}
+
+SwCharFormat* SwEditShell::FindCharFormatByName( const OUString& rName ) const
+{
+ return mxDoc->FindCharFormatByName( rName );
+}
+
+SwTextFormatColl* SwEditShell::FindTextFormatCollByName( const OUString& rName ) const
+{
+ return mxDoc->FindTextFormatCollByName( rName );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/edglbldc.cxx b/sw/source/core/edit/edglbldc.cxx
new file mode 100644
index 0000000000..9e71db2fd6
--- /dev/null
+++ b/sw/source/core/edit/edglbldc.cxx
@@ -0,0 +1,374 @@
+/* -*- 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 <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <IDocumentState.hxx>
+#include <editsh.hxx>
+#include <pam.hxx>
+#include <ndtxt.hxx>
+#include <docary.hxx>
+#include <swundo.hxx>
+#include <section.hxx>
+#include <doctxm.hxx>
+#include <edglbldc.hxx>
+
+bool SwEditShell::IsGlobalDoc() const
+{
+ return getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT);
+}
+
+void SwEditShell::SetGlblDocSaveLinks( bool bFlag )
+{
+ getIDocumentSettingAccess().set(DocumentSettingId::GLOBAL_DOCUMENT_SAVE_LINKS, bFlag);
+ if( !GetDoc()->getIDocumentState().IsModified() ) // Bug 57028
+ {
+ GetDoc()->GetIDocumentUndoRedo().SetUndoNoResetModified();
+ }
+ GetDoc()->getIDocumentState().SetModified();
+}
+
+bool SwEditShell::IsGlblDocSaveLinks() const
+{
+ return getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT_SAVE_LINKS);
+}
+
+void SwEditShell::GetGlobalDocContent( SwGlblDocContents& rArr ) const
+{
+ rArr.clear();
+
+ if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) )
+ return;
+
+ // then all linked areas on the topmost level
+ SwDoc* pMyDoc = GetDoc();
+ const SwSectionFormats& rSectFormats = pMyDoc->GetSections();
+
+ for( auto n = rSectFormats.size(); n; )
+ {
+ const SwSection* pSect = rSectFormats[ --n ]->GetGlobalDocSection();
+ if( pSect )
+ {
+ std::unique_ptr<SwGlblDocContent> pNew;
+ switch( pSect->GetType() )
+ {
+ case SectionType::ToxHeader:
+ break; // ignore
+ case SectionType::ToxContent:
+ assert( dynamic_cast<const SwTOXBaseSection*>( pSect) && "no TOXBaseSection!" );
+ pNew.reset(new SwGlblDocContent( static_cast<const SwTOXBaseSection*>(pSect) ));
+ break;
+
+ default:
+ pNew.reset(new SwGlblDocContent( pSect ));
+ break;
+ }
+ if (pNew)
+ rArr.insert( std::move(pNew) );
+ }
+ }
+
+ // and finally add the dummies (other text)
+ SwNode* pNd;
+ SwNodeOffset nSttIdx = pMyDoc->GetNodes().GetEndOfExtras().GetIndex() + 2;
+ for( SwGlblDocContents::size_type n = 0; n < rArr.size(); ++n )
+ {
+ const SwGlblDocContent& rNew = *rArr[ n ];
+ // Search from StartPos until rNew.DocPos for a content node.
+ // If one exists then a dummy entry is needed.
+ for( ; nSttIdx < rNew.GetDocPos(); ++nSttIdx )
+ if( ( pNd = pMyDoc->GetNodes()[ nSttIdx ])->IsContentNode()
+ || pNd->IsSectionNode() || pNd->IsTableNode() )
+ {
+ std::unique_ptr<SwGlblDocContent> pNew(new SwGlblDocContent( nSttIdx ));
+ if( rArr.insert( std::move(pNew) ).second )
+ ++n; // to the next position
+ break;
+ }
+
+ // set StartPosition to the end
+ nSttIdx = pMyDoc->GetNodes()[ rNew.GetDocPos() ]->EndOfSectionIndex();
+ ++nSttIdx;
+ }
+
+ // Should the end also be set?
+ if( !rArr.empty() )
+ {
+ SwNodeOffset nNdEnd = pMyDoc->GetNodes().GetEndOfContent().GetIndex();
+ for( ; nSttIdx < nNdEnd; ++nSttIdx )
+ if( ( pNd = pMyDoc->GetNodes()[ nSttIdx ])->IsContentNode()
+ || pNd->IsSectionNode() || pNd->IsTableNode() )
+ {
+ rArr.insert( std::make_unique<SwGlblDocContent>( nSttIdx ) );
+ break;
+ }
+ }
+ else
+ {
+ std::unique_ptr<SwGlblDocContent> pNew(new SwGlblDocContent(
+ pMyDoc->GetNodes().GetEndOfExtras().GetIndex() + 2 ));
+ rArr.insert( std::move(pNew) );
+ }
+}
+
+void SwEditShell::InsertGlobalDocContent( const SwGlblDocContent& rInsPos,
+ SwSectionData & rNew)
+{
+ if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) )
+ return;
+
+ CurrShell aCurr( this );
+ StartAllAction();
+
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->GetNext() != pCursor || IsTableMode() )
+ ClearMark();
+
+ SwPosition& rPos = *pCursor->GetPoint();
+ rPos.Assign( rInsPos.GetDocPos() );
+
+ bool bEndUndo = false;
+ SwDoc* pMyDoc = GetDoc();
+ SwTextNode *const pTextNd = rPos.GetNode().GetTextNode();
+ if( !pTextNd )
+ {
+ bEndUndo = true;
+ pMyDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
+ rPos.Adjust(SwNodeOffset(-1));
+ pMyDoc->getIDocumentContentOperations().AppendTextNode( rPos );
+ }
+
+ InsertSection( rNew );
+
+ if( bEndUndo )
+ {
+ pMyDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
+ }
+ EndAllAction();
+}
+
+bool SwEditShell::InsertGlobalDocContent( const SwGlblDocContent& rInsPos,
+ const SwTOXBase& rTOX )
+{
+ if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) )
+ return false;
+
+ CurrShell aCurr( this );
+ StartAllAction();
+
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->GetNext() != pCursor || IsTableMode() )
+ ClearMark();
+
+ SwPosition& rPos = *pCursor->GetPoint();
+ rPos.Assign(rInsPos.GetDocPos());
+
+ bool bEndUndo = false;
+ SwDoc* pMyDoc = GetDoc();
+ SwTextNode* pTextNd = rPos.GetNode().GetTextNode();
+ if (!pTextNd || !pTextNd->GetText().getLength() || rPos.GetNodeIndex() + 1 ==
+ pMyDoc->GetNodes().GetEndOfContent().GetIndex() )
+ {
+ bEndUndo = true;
+ pMyDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
+ rPos.Adjust(SwNodeOffset(-1));
+ pMyDoc->getIDocumentContentOperations().AppendTextNode( rPos );
+ }
+
+ InsertTableOf( rTOX );
+
+ if( bEndUndo )
+ {
+ pMyDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
+ }
+ EndAllAction();
+
+ return true;
+}
+
+bool SwEditShell::InsertGlobalDocContent( const SwGlblDocContent& rInsPos )
+{
+ if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) )
+ return false;
+
+ CurrShell aCurr( this );
+ StartAllAction();
+
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->GetNext() != pCursor || IsTableMode() )
+ ClearMark();
+
+ SwPosition& rPos = *pCursor->GetPoint();
+ rPos.Assign(rInsPos.GetDocPos() - 1);
+
+ SwDoc* pMyDoc = GetDoc();
+ pMyDoc->getIDocumentContentOperations().AppendTextNode( rPos );
+ EndAllAction();
+ return true;
+}
+
+void SwEditShell::DeleteGlobalDocContent( const SwGlblDocContents& rArr ,
+ size_t nDelPos )
+{
+ if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) )
+ return;
+
+ CurrShell aCurr( this );
+ StartAllAction();
+ StartUndo( SwUndoId::START );
+
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->GetNext() != pCursor || IsTableMode() )
+ ClearMark();
+
+ SwPosition& rPos = *pCursor->GetPoint();
+
+ SwDoc* pMyDoc = GetDoc();
+ const SwGlblDocContent& rDelPos = *rArr[ nDelPos ];
+ SwNodeOffset nDelIdx = rDelPos.GetDocPos();
+ if( 1 == rArr.size() )
+ {
+ // we need at least one node!
+ rPos.Assign(nDelIdx - 1);
+
+ pMyDoc->getIDocumentContentOperations().AppendTextNode( rPos );
+ ++nDelIdx;
+ }
+
+ switch( rDelPos.GetType() )
+ {
+ case GLBLDOC_UNKNOWN:
+ {
+ rPos.Assign(nDelIdx);
+ pCursor->SetMark();
+ if( ++nDelPos < rArr.size() )
+ rPos.Assign(rArr[ nDelPos ]->GetDocPos(), -1);
+ else
+ rPos.Assign(pMyDoc->GetNodes().GetEndOfContent(), -1);
+ if( !pMyDoc->getIDocumentContentOperations().DelFullPara( *pCursor ) )
+ Delete(false);
+ }
+ break;
+
+ case GLBLDOC_TOXBASE:
+ {
+ const SwTOXBaseSection* pTOX = static_cast<const SwTOXBaseSection*>(rDelPos.GetTOX());
+ pMyDoc->DeleteTOX( *pTOX, true );
+ }
+ break;
+
+ case GLBLDOC_SECTION:
+ {
+ SwSectionFormat* pSectFormat = const_cast<SwSectionFormat*>(rDelPos.GetSection()->GetFormat());
+ pMyDoc->DelSectionFormat( pSectFormat, true );
+ }
+ break;
+ }
+
+ EndUndo( SwUndoId::END );
+ EndAllAction();
+}
+
+bool SwEditShell::MoveGlobalDocContent( const SwGlblDocContents& rArr ,
+ size_t nFromPos, size_t nToPos,
+ size_t nInsPos )
+{
+ if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) ||
+ nFromPos >= rArr.size() || nToPos > rArr.size() ||
+ nInsPos > rArr.size() || nFromPos >= nToPos ||
+ ( nFromPos <= nInsPos && nInsPos <= nToPos ) )
+ return false;
+
+ CurrShell aCurr( this );
+ StartAllAction();
+
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->GetNext() != pCursor || IsTableMode() )
+ ClearMark();
+
+ SwDoc* pMyDoc = GetDoc();
+ SwNodeRange aRg( pMyDoc->GetNodes(), rArr[ nFromPos ]->GetDocPos() );
+ if( nToPos < rArr.size() )
+ aRg.aEnd = rArr[ nToPos ]->GetDocPos();
+ else
+ aRg.aEnd = pMyDoc->GetNodes().GetEndOfContent();
+
+ SwNodeIndex aInsPos( pMyDoc->GetNodes() );
+ if( nInsPos < rArr.size() )
+ aInsPos = rArr[ nInsPos ]->GetDocPos();
+ else
+ aInsPos = pMyDoc->GetNodes().GetEndOfContent();
+
+ bool bRet = pMyDoc->getIDocumentContentOperations().MoveNodeRange( aRg, aInsPos.GetNode(),
+ SwMoveFlags::CREATEUNDOOBJ );
+
+ EndAllAction();
+ return bRet;
+}
+
+void SwEditShell::GotoGlobalDocContent( const SwGlblDocContent& rPos )
+{
+ if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) )
+ return;
+
+ CurrShell aCurr( this );
+ SttCursorMove();
+
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->GetNext() != pCursor || IsTableMode() )
+ ClearMark();
+
+ SwPosition& rCursorPos = *pCursor->GetPoint();
+ rCursorPos.Assign(rPos.GetDocPos());
+
+ SwDoc* pMyDoc = GetDoc();
+ SwContentNode * pCNd = rCursorPos.GetNode().GetContentNode();
+ if( !pCNd )
+ pCNd = pMyDoc->GetNodes().GoNext( &rCursorPos );
+
+ EndCursorMove();
+}
+
+SwGlblDocContent::SwGlblDocContent( SwNodeOffset nPos )
+{
+ m_eType = GLBLDOC_UNKNOWN;
+ m_PTR.pTOX = nullptr;
+ m_nDocPos = nPos;
+}
+
+SwGlblDocContent::SwGlblDocContent( const SwTOXBaseSection* pTOX )
+{
+ m_eType = GLBLDOC_TOXBASE;
+ m_PTR.pTOX = pTOX;
+
+ const SwSectionNode* pSectNd = pTOX->GetFormat()->GetSectionNode();
+ m_nDocPos = pSectNd ? pSectNd->GetIndex() : SwNodeOffset(0);
+}
+
+SwGlblDocContent::SwGlblDocContent( const SwSection* pSect )
+{
+ m_eType = GLBLDOC_SECTION;
+ m_PTR.pSect = pSect;
+
+ const SwSectionNode* pSectNd = pSect->GetFormat()->GetSectionNode();
+ m_nDocPos = pSectNd ? pSectNd->GetIndex() : SwNodeOffset(0);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/edglss.cxx b/sw/source/core/edit/edglss.cxx
new file mode 100644
index 0000000000..80310bab4f
--- /dev/null
+++ b/sw/source/core/edit/edglss.cxx
@@ -0,0 +1,324 @@
+/* -*- 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 <sal/config.h>
+
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <osl/endian.h>
+#include <tools/urlobj.hxx>
+#include <doc.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <IDocumentFieldsAccess.hxx>
+#include <pam.hxx>
+#include <editsh.hxx>
+#include <frmfmt.hxx>
+#include <rootfrm.hxx>
+#include <ndtxt.hxx>
+#include <swtable.hxx>
+#include <shellio.hxx>
+#include <iodetect.hxx>
+#include <frameformats.hxx>
+
+void SwEditShell::InsertGlossary( SwTextBlocks& rGlossary, const OUString& rStr )
+{
+ StartAllAction();
+ GetDoc()->InsertGlossary( rGlossary, rStr, *GetCursor(), this );
+ EndAllAction();
+}
+
+/// convert current selection into text block and add to the text block document, incl. templates
+sal_uInt16 SwEditShell::MakeGlossary( SwTextBlocks& rBlks, const OUString& rName, const OUString& rShortName,
+ bool bSaveRelFile, const OUString* pOnlyText )
+{
+ SwDoc* pGDoc = rBlks.GetDoc();
+
+ OUString sBase;
+ if(bSaveRelFile)
+ {
+ INetURLObject aURL( rBlks.GetFileName() );
+ sBase = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ }
+ rBlks.SetBaseURL( sBase );
+
+ if( pOnlyText )
+ return rBlks.PutText( rShortName, rName, *pOnlyText );
+
+ rBlks.ClearDoc();
+ if( rBlks.BeginPutDoc( rShortName, rName ) )
+ {
+ rBlks.GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags_intern( RedlineFlags::DeleteRedlines );
+ CopySelToDoc(*pGDoc);
+ rBlks.GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags_intern( RedlineFlags::NONE );
+ return rBlks.PutDoc();
+ }
+
+ return USHRT_MAX;
+}
+
+sal_uInt16 SwEditShell::SaveGlossaryDoc( SwTextBlocks& rBlock,
+ const OUString& rName,
+ const OUString& rShortName,
+ bool bSaveRelFile,
+ bool bOnlyText )
+{
+ StartAllAction();
+
+ SwDoc* pGDoc = rBlock.GetDoc();
+ SwDoc* pMyDoc = GetDoc();
+
+ OUString sBase;
+ if(bSaveRelFile)
+ {
+ INetURLObject aURL( rBlock.GetFileName() );
+ sBase = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ }
+ rBlock.SetBaseURL( sBase );
+ sal_uInt16 nRet = USHRT_MAX;
+
+ if( bOnlyText )
+ {
+ KillPams();
+
+ SwPaM* pCursor = GetCursor();
+
+ SwNodeIndex aStt( pMyDoc->GetNodes().GetEndOfExtras(), 1 );
+ SwContentNode* pContentNd = pMyDoc->GetNodes().GoNext( &aStt );
+ const SwNode* pNd = pContentNd->FindTableNode();
+ if( !pNd )
+ pNd = pContentNd;
+
+ pCursor->GetPoint()->Assign(*pNd);
+ pCursor->SetMark();
+
+ // then until the end of the Node array
+ pCursor->GetPoint()->Assign(pMyDoc->GetNodes().GetEndOfContent().GetIndex()-1);
+ pContentNd = pCursor->GetPointContentNode();
+ if( pContentNd )
+ pCursor->GetPoint()->SetContent( pContentNd->Len() );
+
+ OUString sBuf;
+ GetSelectedText( sBuf, ParaBreakType::ToOnlyCR );
+ if( !sBuf.isEmpty() )
+ nRet = rBlock.PutText( rShortName, rName, sBuf );
+ }
+ else
+ {
+ rBlock.ClearDoc();
+ if( rBlock.BeginPutDoc( rShortName, rName ) )
+ {
+ SwNodeIndex aStt( pMyDoc->GetNodes().GetEndOfExtras(), 1 );
+ SwContentNode* pContentNd = pMyDoc->GetNodes().GoNext( &aStt );
+ const SwNode* pNd = pContentNd->FindTableNode();
+ if( !pNd ) pNd = pContentNd;
+ SwPaM aCpyPam( *pNd );
+ aCpyPam.SetMark();
+
+ // then until the end of the nodes array
+ aCpyPam.GetPoint()->Assign(pMyDoc->GetNodes().GetEndOfContent().GetIndex()-1);
+ pContentNd = aCpyPam.GetPointContentNode();
+ if(pContentNd)
+ aCpyPam.GetPoint()->SetContent( pContentNd->Len() );
+
+ aStt = pGDoc->GetNodes().GetEndOfExtras();
+ pContentNd = pGDoc->GetNodes().GoNext( &aStt );
+ SwPosition aInsPos( aStt );
+ pMyDoc->getIDocumentContentOperations().CopyRange(aCpyPam, aInsPos, SwCopyFlags::CheckPosInFly);
+
+ nRet = rBlock.PutDoc();
+ }
+ }
+ EndAllAction();
+ return nRet;
+}
+
+/// copy all selections to the doc
+bool SwEditShell::CopySelToDoc( SwDoc& rInsDoc )
+{
+ SwNodes& rNds = rInsDoc.GetNodes();
+
+ SwNodeIndex aIdx( rNds.GetEndOfContent(), -1 );
+ SwContentNode *const pContentNode = aIdx.GetNode().GetContentNode();
+ SwPosition aPos( aIdx, pContentNode, pContentNode ? pContentNode->Len() : 0);
+
+ bool bRet = false;
+ CurrShell aCurr( this );
+
+ rInsDoc.getIDocumentFieldsAccess().LockExpFields();
+
+ if( IsTableMode() )
+ {
+ // Copy parts of a table: create a table with the width of the original one and copy the
+ // selected boxes. The sizes are corrected on a percentage basis.
+
+ // search boxes using the layout
+ SwTableNode* pTableNd;
+ SwSelBoxes aBoxes;
+ GetTableSel( *this, aBoxes );
+ if( !aBoxes.empty() && nullptr != (pTableNd = const_cast<SwTableNode*>(aBoxes[0]
+ ->GetSttNd()->FindTableNode()) ))
+ {
+ // check if the table name can be copied
+ bool bCpyTableNm = aBoxes.size() == pTableNd->GetTable().GetTabSortBoxes().size();
+ if( bCpyTableNm )
+ {
+ const OUString rTableName = pTableNd->GetTable().GetFrameFormat()->GetName();
+ const sw::TableFrameFormats& rTableFormats = *rInsDoc.GetTableFrameFormats();
+ for( auto n = rTableFormats.size(); n; )
+ if( rTableFormats[ --n ]->GetName() == rTableName )
+ {
+ bCpyTableNm = false;
+ break;
+ }
+ }
+ bRet = rInsDoc.InsCopyOfTable( aPos, aBoxes, nullptr, bCpyTableNm, false, pTableNd->GetTable().GetTableStyleName() );
+ }
+ else
+ bRet = false;
+ }
+ else
+ {
+ bool bColSel = GetCursor_()->IsColumnSelection();
+ if( bColSel && rInsDoc.IsClipBoard() )
+ rInsDoc.SetColumnSelection( true );
+ auto const oSelectAll(StartsWith_() != SwCursorShell::StartsWith::None
+ ? ExtendedSelectedAll()
+ : ::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode*>>>{});
+ {
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ if( !rPaM.HasMark() )
+ {
+ SwContentNode *const pNd = rPaM.GetPointContentNode();
+ if (nullptr != pNd &&
+ ( bColSel || !pNd->GetTextNode() ) )
+ {
+ rPaM.SetMark();
+ rPaM.Move( fnMoveForward, GoInContent );
+ bRet = GetDoc()->getIDocumentContentOperations().CopyRange(rPaM, aPos, SwCopyFlags::CheckPosInFly)
+ || bRet;
+ rPaM.Exchange();
+ rPaM.DeleteMark();
+ }
+ }
+ else
+ {
+ // Make a copy, so that in case we need to adjust the selection
+ // for the purpose of copying, our shell cursor is not touched.
+ // (Otherwise we would have to restore it.)
+ SwPaM aPaM(*rPaM.GetMark(), *rPaM.GetPoint());
+ if (oSelectAll)
+ {
+ // Selection starts at the first para of the first cell,
+ // but we want to copy the table and the start node before
+ // the first cell as well.
+ aPaM.Start()->Assign(*oSelectAll->first);
+ }
+ bRet = GetDoc()->getIDocumentContentOperations().CopyRange( aPaM, aPos, SwCopyFlags::CheckPosInFly)
+ || bRet;
+ }
+ }
+ }
+ }
+
+ rInsDoc.getIDocumentFieldsAccess().UnlockExpFields();
+ if( !rInsDoc.getIDocumentFieldsAccess().IsExpFieldsLocked() )
+ rInsDoc.getIDocumentFieldsAccess().UpdateExpFields(nullptr, true);
+
+ return bRet;
+}
+
+/** Get text in a Selection
+ */
+void SwEditShell::GetSelectedText( OUString &rBuf, ParaBreakType nHndlParaBrk )
+{
+ GetCursor(); // creates all cursors if needed
+ if( IsSelOnePara() )
+ {
+ rBuf = GetSelText();
+ if( ParaBreakType::ToBlank == nHndlParaBrk )
+ {
+ rBuf = rBuf.replaceAll("\x0a", " ");
+ }
+ else if( IsSelFullPara() &&
+ ParaBreakType::ToOnlyCR != nHndlParaBrk )
+ {
+ rBuf += SAL_NEWLINE_STRING;
+ }
+ }
+ else if( IsSelection() )
+ {
+ SvMemoryStream aStream;
+#ifdef OSL_BIGENDIAN
+ aStream.SetEndian( SvStreamEndian::BIG );
+#else
+ aStream.SetEndian( SvStreamEndian::LITTLE );
+#endif
+ WriterRef xWrt;
+ SwReaderWriter::GetWriter( FILTER_TEXT, OUString(), xWrt );
+ if( xWrt.is() )
+ {
+ // write selected areas into an ASCII document
+ SwWriter aWriter( aStream, *this);
+ xWrt->SetShowProgress(false);
+
+ switch( nHndlParaBrk )
+ {
+ case ParaBreakType::ToBlank:
+ xWrt->m_bASCII_ParaAsBlank = true;
+ xWrt->m_bASCII_NoLastLineEnd = true;
+ break;
+
+ case ParaBreakType::ToOnlyCR:
+ xWrt->m_bASCII_ParaAsCR = true;
+ xWrt->m_bASCII_NoLastLineEnd = true;
+ break;
+ }
+
+ //JP 09.05.00: write as UNICODE ! (and not as ANSI)
+ SwAsciiOptions aAsciiOpt( xWrt->GetAsciiOptions() );
+ aAsciiOpt.SetCharSet( RTL_TEXTENCODING_UCS2 );
+ xWrt->SetAsciiOptions( aAsciiOpt );
+ xWrt->m_bUCS2_WithStartChar = false;
+ xWrt->m_bHideDeleteRedlines = GetLayout()->IsHideRedlines();
+
+ if ( ! aWriter.Write(xWrt).IsError() )
+ {
+ aStream.WriteUInt16( '\0' );
+
+ const sal_Unicode *p = static_cast<sal_Unicode const *>(aStream.GetData());
+ if (p)
+ rBuf = OUString(p);
+ else
+ {
+ const sal_uInt64 nLen = aStream.GetSize();
+ OSL_ENSURE( nLen/sizeof( sal_Unicode )<o3tl::make_unsigned(SAL_MAX_INT32), "Stream can't fit in OUString" );
+ rtl_uString *pStr = rtl_uString_alloc(static_cast<sal_Int32>(nLen / sizeof( sal_Unicode )));
+ aStream.Seek( 0 );
+ aStream.ResetError();
+ //endian specific?, yipes!
+ aStream.ReadBytes(pStr->buffer, nLen);
+ rBuf = OUString(pStr, SAL_NO_ACQUIRE);
+ }
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/editsh.cxx b/sw/source/core/edit/editsh.cxx
new file mode 100644
index 0000000000..333eecd352
--- /dev/null
+++ b/sw/source/core/edit/editsh.cxx
@@ -0,0 +1,1098 @@
+/* -*- 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 <hintids.hxx>
+#include <osl/diagnose.h>
+#include <vcl/commandevent.hxx>
+#include <unotools/charclass.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <unotools/transliterationwrapper.hxx>
+#include <fmtsrnd.hxx>
+#include <fmtinfmt.hxx>
+#include <txtinet.hxx>
+#include <frmfmt.hxx>
+#include <charfmt.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentSettingAccess.hxx>
+#include <IDocumentLinksAdministration.hxx>
+#include <IDocumentFieldsAccess.hxx>
+#include <IDocumentStatistics.hxx>
+#include <IDocumentState.hxx>
+#include <editsh.hxx>
+#include <frame.hxx>
+#include <cntfrm.hxx>
+#include <pam.hxx>
+#include <ndtxt.hxx>
+#include <flyfrm.hxx>
+#include <swundo.hxx>
+#include <calc.hxx>
+#include <ndgrf.hxx>
+#include <ndole.hxx>
+#include <txtfrm.hxx>
+#include <rootfrm.hxx>
+#include <extinput.hxx>
+#include <scriptinfo.hxx>
+#include <unocrsrhelper.hxx>
+#include <section.hxx>
+#include <numrule.hxx>
+#include <SwNodeNum.hxx>
+#include <unocrsr.hxx>
+#include <calbck.hxx>
+
+using namespace com::sun::star;
+
+void SwEditShell::Insert( sal_Unicode c, bool bOnlyCurrCursor )
+{
+ StartAllAction();
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ const bool bSuccess = GetDoc()->getIDocumentContentOperations().InsertString(rPaM, OUString(c));
+ OSL_ENSURE( bSuccess, "Doc->Insert() failed." );
+
+ SaveTableBoxContent( rPaM.GetPoint() );
+ if( bOnlyCurrCursor )
+ break;
+
+ }
+
+ EndAllAction();
+}
+
+void SwEditShell::Insert2(const OUString &rStr, const bool bForceExpandHints )
+{
+ StartAllAction();
+ {
+ const SwInsertFlags nInsertFlags =
+ bForceExpandHints
+ ? (SwInsertFlags::FORCEHINTEXPAND | SwInsertFlags::EMPTYEXPAND)
+ : SwInsertFlags::EMPTYEXPAND;
+
+ for(SwPaM& rCurrentCursor : getShellCursor( true )->GetRingContainer())
+ {
+ //OPT: GetSystemCharSet
+ GetDoc()->getIDocumentContentOperations().SetIME(false);
+ const bool bSuccess =
+ GetDoc()->getIDocumentContentOperations().InsertString(rCurrentCursor, rStr, nInsertFlags);
+ OSL_ENSURE( bSuccess, "Doc->Insert() failed." );
+
+ if (bSuccess)
+ {
+ GetDoc()->UpdateRsid( rCurrentCursor, rStr.getLength() );
+
+ // Set paragraph rsid if beginning of paragraph
+ SwTextNode *const pTextNode =
+ rCurrentCursor.GetPoint()->GetNode().GetTextNode();
+ if( pTextNode && pTextNode->Len() == 1)
+ GetDoc()->UpdateParRsid( pTextNode );
+ }
+
+ SaveTableBoxContent( rCurrentCursor.GetPoint() );
+
+ }
+ }
+
+ // calculate cursor bidi level
+ SwCursor* pTmpCursor = GetCursor_();
+ const bool bDoNotSetBidiLevel = ! pTmpCursor ||
+ ( dynamic_cast<SwUnoCursor*>(pTmpCursor) != nullptr );
+
+ if ( ! bDoNotSetBidiLevel )
+ {
+ SwNode& rNode = pTmpCursor->GetPoint()->GetNode();
+ if ( rNode.IsTextNode() )
+ {
+ sal_Int32 nPrevPos = pTmpCursor->GetPoint()->GetContentIndex();
+ if ( nPrevPos )
+ --nPrevPos;
+
+ SwTextFrame const* pFrame;
+ SwScriptInfo *const pSI = SwScriptInfo::GetScriptInfo(
+ static_cast<SwTextNode&>(rNode), &pFrame, true);
+
+ sal_uInt8 nLevel = 0;
+ if ( ! pSI )
+ {
+ // seems to be an empty paragraph.
+ Point aPt;
+ std::pair<Point, bool> const tmp(aPt, false);
+ pFrame = static_cast<SwTextFrame*>(
+ static_cast<SwTextNode&>(rNode).getLayoutFrame(
+ GetLayout(), pTmpCursor->GetPoint(), &tmp));
+
+ SwScriptInfo aScriptInfo;
+ aScriptInfo.InitScriptInfo(static_cast<SwTextNode&>(rNode),
+ pFrame->GetMergedPara(), pFrame->IsRightToLeft());
+ TextFrameIndex const iPrevPos(pFrame->MapModelToView(
+ &static_cast<SwTextNode&>(rNode), nPrevPos));
+ nLevel = aScriptInfo.DirType( iPrevPos );
+ }
+ else
+ {
+ if (TextFrameIndex(COMPLETE_STRING) != pSI->GetInvalidityA())
+ {
+ // mystery why this doesn't use the other overload?
+ pSI->InitScriptInfo(static_cast<SwTextNode&>(rNode), pFrame->GetMergedPara());
+ }
+ TextFrameIndex const iPrevPos(pFrame->MapModelToView(
+ &static_cast<SwTextNode&>(rNode), nPrevPos));
+ nLevel = pSI->DirType(iPrevPos);
+ }
+
+ pTmpCursor->SetCursorBidiLevel( nLevel );
+ }
+ }
+
+ SetInFrontOfLabel( false ); // #i27615#
+
+ EndAllAction();
+}
+
+void SwEditShell::Overwrite(const OUString &rStr)
+{
+ StartAllAction();
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ if( !GetDoc()->getIDocumentContentOperations().Overwrite(rPaM, rStr ) )
+ {
+ OSL_FAIL( "Doc->getIDocumentContentOperations().Overwrite(Str) failed." );
+ }
+ SaveTableBoxContent( rPaM.GetPoint() );
+ }
+ EndAllAction();
+}
+
+void SwEditShell::SplitNode( bool bAutoFormat, bool bCheckTableStart )
+{
+ StartAllAction();
+ GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);
+
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ // Here, a table cell becomes a normal text cell.
+ GetDoc()->ClearBoxNumAttrs( rPaM.GetPoint()->GetNode() );
+ GetDoc()->getIDocumentContentOperations().SplitNode( *rPaM.GetPoint(), bCheckTableStart );
+ }
+
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
+
+ if( bAutoFormat )
+ AutoFormatBySplitNode();
+
+ ClearTableBoxContent();
+
+ EndAllAction();
+}
+
+bool SwEditShell::AppendTextNode()
+{
+ bool bRet = false;
+ StartAllAction();
+ GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);
+
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ GetDoc()->ClearBoxNumAttrs( rPaM.GetPoint()->GetNode() );
+ bRet = GetDoc()->getIDocumentContentOperations().AppendTextNode( *rPaM.GetPoint()) || bRet;
+ }
+
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
+
+ ClearTableBoxContent();
+
+ EndAllAction();
+ return bRet;
+}
+
+// the returned SwGrfNode pointer is used in GetGraphic() and GetGraphicSize()
+SwGrfNode * SwEditShell::GetGrfNode_() const
+{
+ SwGrfNode *pGrfNode = nullptr;
+ SwPaM* pCursor = GetCursor();
+ if( !pCursor->HasMark() ||
+ pCursor->GetPoint()->GetNode() == pCursor->GetMark()->GetNode() )
+ pGrfNode = pCursor->GetPoint()->GetNode().GetGrfNode();
+
+ return pGrfNode;
+}
+
+// returns a Graphic pointer if CurrentCursor->GetPoint() points to a SwGrfNode and
+// GetMark is not set or points to the same Graphic
+const Graphic* SwEditShell::GetGraphic( bool bWait ) const
+{
+ SwGrfNode* pGrfNode = GetGrfNode_();
+ const Graphic* pGrf( nullptr );
+ if ( pGrfNode )
+ {
+ pGrf = &(pGrfNode->GetGrf(bWait && GraphicType::Default == pGrfNode->GetGrf().GetType()));
+ }
+ return pGrf;
+}
+
+bool SwEditShell::IsLinkedGrfSwapOut() const
+{
+ SwGrfNode *pGrfNode = GetGrfNode_();
+ return pGrfNode &&
+ pGrfNode->IsLinkedFile() &&
+ GraphicType::Default == pGrfNode->GetGrfObj().GetType();
+}
+
+const GraphicObject* SwEditShell::GetGraphicObj() const
+{
+ SwGrfNode* pGrfNode = GetGrfNode_();
+ return pGrfNode ? &(pGrfNode->GetGrfObj()) : nullptr;
+}
+
+const GraphicAttr* SwEditShell::GetGraphicAttr( GraphicAttr& rGA ) const
+{
+ SwGrfNode* pGrfNode = GetGrfNode_();
+ const SwFrame* pFrame = GetCurrFrame(false);
+ return pGrfNode ? &(pGrfNode->GetGraphicAttr( rGA, pFrame )) : nullptr;
+}
+
+GraphicType SwEditShell::GetGraphicType() const
+{
+ SwGrfNode *pGrfNode = GetGrfNode_();
+ return pGrfNode ? pGrfNode->GetGrfObj().GetType() : GraphicType::NONE;
+}
+
+// returns the size of a graphic in <rSz> if CurrentCursor->GetPoint() points to a SwGrfNode and
+// GetMark is not set or points to the same graphic
+bool SwEditShell::GetGrfSize(Size& rSz) const
+{
+ SwNoTextNode* pNoTextNd;
+ SwPaM* pCurrentCursor = GetCursor();
+ if( ( !pCurrentCursor->HasMark()
+ || pCurrentCursor->GetPoint()->GetNode() == pCurrentCursor->GetMark()->GetNode() )
+ && nullptr != ( pNoTextNd = pCurrentCursor->GetPointNode().GetNoTextNode() ) )
+ {
+ rSz = pNoTextNd->GetTwipSize();
+ return true;
+ }
+ return false;
+
+}
+
+/// Read again if graphic is not OK and replace old one
+void SwEditShell::ReRead( const OUString& rGrfName, const OUString& rFltName,
+ const Graphic* pGraphic )
+{
+ StartAllAction();
+ mxDoc->getIDocumentContentOperations().ReRead( *GetCursor(), rGrfName, rFltName, pGraphic );
+ EndAllAction();
+}
+
+/// Returns the name and the filter name of a graphic if the pointer is on a graphic.
+/// If a String-pointer is != 0 then return corresponding name.
+void SwEditShell::GetGrfNms( OUString* pGrfName, OUString* pFltName,
+ const SwFlyFrameFormat* pFormat ) const
+{
+ OSL_ENSURE( pGrfName || pFltName, "No parameters" );
+ if( pFormat )
+ SwDoc::GetGrfNms( *pFormat, pGrfName, pFltName );
+ else
+ {
+ SwGrfNode *pGrfNode = GetGrfNode_();
+ if( pGrfNode && pGrfNode->IsLinkedFile() )
+ pGrfNode->GetFileFilterNms( pGrfName, pFltName );
+ }
+}
+
+const tools::PolyPolygon *SwEditShell::GetGraphicPolygon() const
+{
+ SwNoTextNode *pNd = GetCursor()->GetPointNode().GetNoTextNode();
+ return pNd->HasContour();
+}
+
+void SwEditShell::SetGraphicPolygon( const tools::PolyPolygon *pPoly )
+{
+ SwNoTextNode *pNd = GetCursor()->GetPointNode().GetNoTextNode();
+ StartAllAction();
+ pNd->SetContour( pPoly );
+ SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pNd->getLayoutFrame(GetLayout())->GetUpper());
+ const SwFormatSurround &rSur = pFly->GetFormat()->GetSurround();
+ pFly->GetFormat()->CallSwClientNotify(sw::LegacyModifyHint(&rSur, &rSur));
+ GetDoc()->getIDocumentState().SetModified();
+ EndAllAction();
+}
+
+void SwEditShell::ClearAutomaticContour()
+{
+ SwNoTextNode *pNd = GetCursor()->GetPointNode().GetNoTextNode();
+ OSL_ENSURE( pNd, "is no NoTextNode!" );
+ if( pNd->HasAutomaticContour() )
+ {
+ StartAllAction();
+ pNd->SetContour( nullptr );
+ SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pNd->getLayoutFrame(GetLayout())->GetUpper());
+ const SwFormatSurround &rSur = pFly->GetFormat()->GetSurround();
+ pFly->GetFormat()->CallSwClientNotify(sw::LegacyModifyHint(&rSur, &rSur));
+ GetDoc()->getIDocumentState().SetModified();
+ EndAllAction();
+ }
+}
+
+/** Get OLE object at pointer.
+ *
+ * Returns a pointer to a SvInPlaceObjectRef if CurrentCursor->GetPoint() points to a SwOLENode and
+ * GetMark is not set or points to the same object reference. Gets this pointer from the Doc
+ * if the object should be searched by name.
+ */
+svt::EmbeddedObjectRef& SwEditShell::GetOLEObject() const
+{
+ OSL_ENSURE( CNT_OLE == GetCntType(), "GetOLEObj: no OLENode." );
+ OSL_ENSURE( !GetCursor()->HasMark() ||
+ (GetCursor()->HasMark() &&
+ GetCursor()->GetPoint()->GetNode() == GetCursor()->GetMark()->GetNode()),
+ "GetOLEObj: no OLENode." );
+
+ SwOLENode *pOLENode = GetCursor()->GetPointNode().GetOLENode();
+ OSL_ENSURE( pOLENode, "GetOLEObj: no OLENode." );
+ SwOLEObj& rOObj = pOLENode->GetOLEObj();
+ return rOObj.GetObject();
+}
+
+bool SwEditShell::HasOLEObj( std::u16string_view rName ) const
+{
+ SwStartNode *pStNd;
+ SwNodeIndex aIdx( *GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 );
+ while ( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) )
+ {
+ ++aIdx;
+ SwNode& rNd = aIdx.GetNode();
+ if( rNd.IsOLENode() &&
+ rName == static_cast<SwOLENode&>(rNd).GetChartTableName() &&
+ static_cast<SwOLENode&>(rNd).getLayoutFrame( GetLayout() ) )
+ return true;
+
+ aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 );
+ }
+ return false;
+}
+
+void SwEditShell::SetChartName( const OUString &rName )
+{
+ SwOLENode *pONd = GetCursor()->GetPointNode().GetOLENode();
+ OSL_ENSURE( pONd, "ChartNode not found" );
+ pONd->SetChartTableName( rName );
+}
+
+void SwEditShell::UpdateCharts( const OUString& rName )
+{
+ GetDoc()->UpdateCharts( rName );
+}
+
+/// change table name
+void SwEditShell::SetTableName( SwFrameFormat& rTableFormat, const OUString &rNewName )
+{
+ GetDoc()->SetTableName( rTableFormat, rNewName );
+}
+
+/// request current word
+OUString SwEditShell::GetCurWord() const
+{
+ const SwPaM& rPaM = *GetCursor();
+ const SwTextNode* pNd = rPaM.GetPointNode().GetTextNode();
+ if (!pNd)
+ {
+ return OUString();
+ }
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(pNd->getLayoutFrame(GetLayout())));
+ if (pFrame)
+ {
+ return pFrame->GetCurWord(*rPaM.GetPoint());
+ }
+ return OUString();
+}
+
+void SwEditShell::UpdateDocStat( )
+{
+ StartAllAction();
+ GetDoc()->getIDocumentStatistics().UpdateDocStat( false, true );
+ EndAllAction();
+}
+
+const SwDocStat& SwEditShell::GetUpdatedDocStat()
+{
+ StartAllAction();
+ const SwDocStat &rRet = GetDoc()->getIDocumentStatistics().GetUpdatedDocStat( false, true );
+ EndAllAction();
+ return rRet;
+}
+
+/// get the reference of a given name in the Doc
+const SwFormatRefMark* SwEditShell::GetRefMark( std::u16string_view rName ) const
+{
+ return GetDoc()->GetRefMark( rName );
+}
+
+/// get the names of all references in a Doc
+sal_uInt16 SwEditShell::GetRefMarks( std::vector<OUString>* pStrings ) const
+{
+ return GetDoc()->GetRefMarks( pStrings );
+}
+
+OUString SwEditShell::GetDropText( const sal_Int32 nChars ) const
+{
+ /*
+ * pb: made changes for #i74939#
+ *
+ * always return a string even though there is a selection
+ */
+
+ OUString aText;
+ SwPaM* pCursor = GetCursor();
+ if ( IsMultiSelection() )
+ {
+ // if a multi selection exists, search for the first line
+ // -> it is the cursor with the lowest index
+ SwNodeOffset nIndex = pCursor->GetMark()->GetNodeIndex();
+ bool bPrev = true;
+ SwPaM* pLast = pCursor;
+ SwPaM* pTemp = pCursor;
+ while ( bPrev )
+ {
+ SwPaM* pPrev2 = pTemp->GetPrev();
+ bPrev = ( pPrev2 && pPrev2 != pLast );
+ if ( bPrev )
+ {
+ pTemp = pPrev2;
+ SwNodeOffset nTemp = pPrev2->GetMark()->GetNodeIndex();
+ if ( nTemp < nIndex )
+ {
+ nIndex = nTemp;
+ pCursor = pPrev2;
+ }
+ }
+ }
+ }
+
+ SwTextNode const*const pTextNd = pCursor->GetMarkNode().GetTextNode();
+ if( pTextNd )
+ {
+ SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(
+ pTextNd->getLayoutFrame(GetLayout())));
+ SAL_WARN_IF(!pTextFrame, "sw.core", "GetDropText cursor has no frame?");
+ if (pTextFrame)
+ {
+ TextFrameIndex const nDropLen(pTextFrame->GetDropLen(TextFrameIndex(nChars)));
+ aText = pTextFrame->GetText().copy(0, sal_Int32(nDropLen));
+ }
+ }
+
+ return aText;
+}
+
+void SwEditShell::ReplaceDropText( const OUString &rStr, SwPaM* pPaM )
+{
+ SwPaM* pCursor = pPaM ? pPaM : GetCursor();
+ if( !(pCursor->GetPoint()->GetNode() == pCursor->GetMark()->GetNode() &&
+ pCursor->GetPointNode().GetTextNode()->IsTextNode()) )
+ return;
+
+ StartAllAction();
+
+ const SwNode& rNd = pCursor->GetPoint()->GetNode();
+ SwPaM aPam( rNd, rStr.getLength(), rNd, 0 );
+ SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(
+ rNd.GetTextNode()->getLayoutFrame(GetLayout())));
+ if (pTextFrame)
+ {
+ *aPam.GetPoint() = pTextFrame->MapViewToModelPos(TextFrameIndex(0));
+ *aPam.GetMark() = pTextFrame->MapViewToModelPos(TextFrameIndex(
+ std::min(rStr.getLength(), pTextFrame->GetText().getLength())));
+ }
+ if( !GetDoc()->getIDocumentContentOperations().Overwrite( aPam, rStr ) )
+ {
+ OSL_FAIL( "Doc->getIDocumentContentOperations().Overwrite(Str) failed." );
+ }
+
+ EndAllAction();
+}
+
+OUString SwEditShell::Calculate()
+{
+ OUStringBuffer aFormel; // the final formula
+ SwCalc aCalc( *GetDoc() );
+ const CharClass& rCC = GetAppCharClass();
+
+ for(SwPaM& rCurrentPaM : GetCursor()->GetNext()->GetRingContainer())
+ {
+ SwTextNode* pTextNd = rCurrentPaM.GetPointNode().GetTextNode();
+ if(pTextNd)
+ {
+ const SwPosition *pStart = rCurrentPaM.Start(), *pEnd = rCurrentPaM.End();
+ const sal_Int32 nStt = pStart->GetContentIndex();
+ OUString aStr = pTextNd->GetExpandText(GetLayout(),
+ nStt, pEnd->GetContentIndex() - nStt);
+
+ aStr = rCC.lowercase( aStr );
+
+ bool bValidFields = false;
+ sal_Int32 nPos = 0;
+
+ while( nPos < aStr.getLength() )
+ {
+ sal_Unicode ch = aStr[ nPos++ ];
+ if( rCC.isLetter( aStr, nPos-1 ) || ch == '_' )
+ {
+ sal_Int32 nTmpStt = nPos-1;
+ while( nPos < aStr.getLength() &&
+ 0 != ( ch = aStr[ nPos++ ]) &&
+ (rCC.isLetterNumeric( aStr, nPos - 1 ) ||
+ ch == '_'|| ch == '.' ))
+ ;
+
+ if( nPos < aStr.getLength() )
+ --nPos;
+
+ OUString sVar = aStr.copy( nTmpStt, nPos - nTmpStt );
+ if( !::FindOperator( sVar ) &&
+ (aCalc.GetVarTable().find(sVar) != aCalc.GetVarTable().end() ||
+ aCalc.VarLook( sVar )) )
+ {
+ if( !bValidFields )
+ {
+ GetDoc()->getIDocumentFieldsAccess().FieldsToCalc( aCalc,
+ pStart->GetNodeIndex(),
+ pStart->GetContentIndex() );
+ bValidFields = true;
+ }
+ aFormel.append("(" + aCalc.GetStrResult( aCalc.VarLook( sVar )->nValue ) + ")");
+ }
+ else
+ aFormel.append(sVar);
+ }
+ else
+ aFormel.append(ch);
+ }
+ }
+ }
+
+ return aCalc.GetStrResult( aCalc.Calculate(aFormel.makeStringAndClear()) );
+}
+
+sfx2::LinkManager& SwEditShell::GetLinkManager()
+{
+ return mxDoc->getIDocumentLinksAdministration().GetLinkManager();
+}
+
+void *SwEditShell::GetIMapInventor() const
+{
+ // The node on which the cursor points should be sufficient as a unique identifier
+ return static_cast<void*>(&(GetCursor()->GetPointNode()));
+}
+
+// #i73788#
+Graphic SwEditShell::GetIMapGraphic() const
+{
+ // returns always a graphic if the cursor is in a Fly
+ CurrShell aCurr( const_cast<SwEditShell*>(this) );
+ Graphic aRet;
+ SwPaM* pCursor = GetCursor();
+ if ( !pCursor->HasMark() )
+ {
+ SwNode& rNd =pCursor->GetPointNode();
+ if( rNd.IsGrfNode() )
+ {
+ SwGrfNode & rGrfNode(static_cast<SwGrfNode&>(rNd));
+ aRet = rGrfNode.GetGrf(GraphicType::Default == rGrfNode.GetGrf().GetType());
+ }
+ else if ( rNd.IsOLENode() )
+ {
+ if (const Graphic* pGraphic = static_cast<SwOLENode&>(rNd).GetGraphic())
+ aRet = *pGraphic;
+ }
+ else
+ {
+ SwFlyFrame* pFlyFrame = rNd.GetContentNode()->getLayoutFrame( GetLayout() )->FindFlyFrame();
+ if(pFlyFrame)
+ aRet = pFlyFrame->GetFormat()->MakeGraphic();
+ }
+ }
+ return aRet;
+}
+
+bool SwEditShell::InsertURL( const SwFormatINetFormat& rFormat, const OUString& rStr, bool bKeepSelection )
+{
+ // URL and hint text (directly or via selection) necessary
+ if( rFormat.GetValue().isEmpty() || ( rStr.isEmpty() && !HasSelection() ) )
+ return false;
+ StartAllAction();
+ GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_INSERT_URLTXT, nullptr);
+ bool bInsText = true;
+
+ if( !rStr.isEmpty() )
+ {
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->HasMark() && *pCursor->GetPoint() != *pCursor->GetMark() )
+ {
+ // Selection existent, multi selection?
+ bool bDelText = true;
+ if( !pCursor->IsMultiSelection() )
+ {
+ // simple selection -> check the text
+ const OUString sText(comphelper::string::stripEnd(GetSelText(), ' '));
+ if( sText == rStr )
+ bDelText = bInsText = false;
+ }
+ else if( rFormat.GetValue() == rStr ) // Are Name and URL equal?
+ bDelText = bInsText = false;
+
+ if( bDelText )
+ Delete(true);
+ }
+ else if( pCursor->IsMultiSelection() && rFormat.GetValue() == rStr )
+ bInsText = false;
+
+ if( bInsText )
+ {
+ Insert2( rStr );
+ SetMark();
+ ExtendSelection( false, rStr.getLength() );
+ }
+ }
+ else
+ bInsText = false;
+
+ SetAttrItem( rFormat );
+ if (bInsText && !IsCursorPtAtEnd())
+ SwapPam();
+ if(!bKeepSelection)
+ ClearMark();
+ if( bInsText )
+ DontExpandFormat();
+ GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::UI_INSERT_URLTXT, nullptr );
+ EndAllAction();
+ return true;
+}
+
+void SwEditShell::GetINetAttrs(SwGetINetAttrs& rArr, bool bIncludeInToxContent)
+{
+ rArr.clear();
+
+ const SwCharFormats* pFormats = GetDoc()->GetCharFormats();
+ for( auto n = pFormats->size(); 1 < n; )
+ {
+ SwIterator<SwTextINetFormat,SwCharFormat> aIter(*(*pFormats)[--n]);
+ for( SwTextINetFormat* pFnd = aIter.First(); pFnd; pFnd = aIter.Next() )
+ {
+ SwTextNode const*const pTextNd(pFnd->GetpTextNode());
+ SwTextFrame const*const pFrame(pTextNd
+ ? static_cast<SwTextFrame const*>(pTextNd->getLayoutFrame(GetLayout()))
+ : nullptr);
+ if (nullptr != pTextNd && nullptr != pFrame
+ && pTextNd->GetNodes().IsDocNodes()
+ // check it's not fully deleted
+ && pFrame->MapModelToView(pTextNd, pFnd->GetStart())
+ != pFrame->MapModelToView(pTextNd, *pFnd->GetEnd()))
+ {
+ // tdf#52113, tdf#148312 Don't include table of contents hyperlinks in the
+ // Navigator content tree Hyperlinks entries
+ if (!bIncludeInToxContent)
+ {
+ if(const SwSectionNode* pSectNd = pTextNd->FindSectionNode())
+ {
+ SectionType eType = pSectNd->GetSection().GetType();
+ if(SectionType::ToxContent == eType)
+ continue;
+ }
+ }
+
+ SwTextINetFormat& rAttr = *pFnd;
+ OUString sText( pTextNd->GetExpandText(GetLayout(),
+ rAttr.GetStart(), *rAttr.GetEnd() - rAttr.GetStart()) );
+
+ sText = sText.replaceAll("\x0a", "");
+ sText = comphelper::string::strip(sText, ' ');
+
+ if( !sText.isEmpty() )
+ {
+ rArr.emplace_back(sText, rAttr);
+ }
+ }
+ }
+ }
+}
+
+/// If the cursor is in an INetAttribute then it will be deleted completely (incl. hint text, the
+/// latter is needed for drag & drop)
+void SwEditShell::DelINetAttrWithText()
+{
+ bool bRet = SelectTextAttr( RES_TXTATR_INETFMT, false );
+ if( bRet )
+ DeleteSel(*GetCursor(), true);
+}
+
+/// Set the DontExpand flag at the text character attributes
+bool SwEditShell::DontExpandFormat()
+{
+ bool bRet = false;
+ if( !IsTableMode() && GetDoc()->DontExpandFormat( *GetCursor()->GetPoint() ))
+ {
+ bRet = true;
+ CallChgLnk();
+ }
+ return bRet;
+}
+
+SvNumberFormatter* SwEditShell::GetNumberFormatter()
+{
+ return GetDoc()->GetNumberFormatter();
+}
+
+bool SwEditShell::ConvertFieldsToText()
+{
+ StartAllAction();
+ bool bRet = GetDoc()->ConvertFieldsToText(*GetLayout());
+ EndAllAction();
+ return bRet;
+}
+
+void SwEditShell::SetNumberingRestart()
+{
+ StartAllAction();
+ Push();
+ // iterate over all text contents - body, frames, header, footer, footnote text
+ SwPaM* pCursor = GetCursor();
+ for(int i = 0; i < 2; i++)
+ {
+ if(!i)
+ MakeFindRange(SwDocPositions::Start, SwDocPositions::End, pCursor); // body content
+ else
+ MakeFindRange(SwDocPositions::OtherStart, SwDocPositions::OtherEnd, pCursor); // extra content
+ SwPosition* pSttPos = pCursor->Start(), *pEndPos = pCursor->End();
+ SwNodeOffset nCurrNd = pSttPos->GetNodeIndex();
+ SwNodeOffset nEndNd = pEndPos->GetNodeIndex();
+ if( nCurrNd <= nEndNd )
+ {
+ SwContentFrame* pContentFrame;
+ bool bGoOn = true;
+ // iterate over all paragraphs
+ while( bGoOn )
+ {
+ SwNode* pNd = GetDoc()->GetNodes()[ nCurrNd ];
+ switch( pNd->GetNodeType() )
+ {
+ case SwNodeType::Text:
+ pContentFrame = static_cast<SwTextNode*>(pNd)->getLayoutFrame( GetLayout() );
+ if( nullptr != pContentFrame )
+ {
+ // skip hidden frames - ignore protection!
+ if( !static_cast<SwTextFrame*>(pContentFrame)->IsHiddenNow() )
+ {
+ // if the node is numbered and the starting value of the numbering equals the
+ // start value of the numbering rule then set this value as hard starting value
+
+ // get the node num
+ // OD 2005-11-09
+ SwTextNode* pTextNd( pNd->GetTextNode() );
+ SwNumRule* pNumRule( pTextNd->GetNumRule() );
+
+ // sw_redlinehide: not sure what this should do, only called from mail-merge
+ bool bIsNodeNum =
+ ( pNumRule && pTextNd->GetNum() &&
+ ( pTextNd->HasNumber() || pTextNd->HasBullet() ) &&
+ pTextNd->IsCountedInList() &&
+ !pTextNd->IsListRestart() );
+ if (bIsNodeNum)
+ {
+ int nListLevel = pTextNd->GetActualListLevel();
+
+ if (nListLevel < 0)
+ nListLevel = 0;
+
+ if (nListLevel >= MAXLEVEL)
+ nListLevel = MAXLEVEL - 1;
+
+ bIsNodeNum = pTextNd->GetNum()->GetNumber() ==
+ pNumRule->Get( o3tl::narrowing<sal_uInt16>(nListLevel) ).GetStart();
+ }
+ if (bIsNodeNum)
+ {
+ // now set the start value as attribute
+ SwPosition aCurrentNode(*pNd);
+ GetDoc()->SetNumRuleStart( aCurrentNode );
+ }
+ }
+ }
+ break;
+ case SwNodeType::Section:
+ // skip hidden sections - ignore protection!
+ if(static_cast<SwSectionNode*>(pNd)->GetSection().IsHidden() )
+ nCurrNd = pNd->EndOfSectionIndex();
+ break;
+ default: break;
+ }
+
+ bGoOn = nCurrNd < nEndNd;
+ ++nCurrNd;
+ }
+ }
+ }
+
+ Pop(PopMode::DeleteCurrent);
+ EndAllAction();
+}
+
+sal_Int32 SwEditShell::GetLineCount()
+{
+ sal_Int32 nRet = 0;
+ CalcLayout();
+ SwPaM* pPam = GetCursor();
+ SwNodeIndex aStart( pPam->GetPoint()->GetNode() );
+ SwContentNode* pCNd;
+ SwContentFrame *pContentFrame = nullptr;
+
+ aStart = SwNodeOffset(0);
+
+ while( nullptr != ( pCNd = GetDoc()->GetNodes().GoNextSection(
+ &aStart, true, false )) )
+ {
+ if( nullptr != ( pContentFrame = pCNd->getLayoutFrame( GetLayout() ) ) && pContentFrame->IsTextFrame() )
+ {
+ SwTextFrame *const pFrame(static_cast<SwTextFrame*>(pContentFrame));
+ nRet = nRet + pFrame->GetLineCount(TextFrameIndex(COMPLETE_STRING));
+ if (GetLayout()->HasMergedParas())
+ {
+ if (auto const*const pMerged = pFrame->GetMergedPara())
+ {
+ aStart = *pMerged->pLastNode;
+ }
+ }
+ }
+ }
+ return nRet;
+}
+
+tools::Long SwEditShell::CompareDoc( const SwDoc& rDoc )
+{
+ StartAllAction();
+ tools::Long nRet = GetDoc()->CompareDoc( rDoc );
+ EndAllAction();
+ return nRet;
+}
+
+tools::Long SwEditShell::MergeDoc( const SwDoc& rDoc )
+{
+ StartAllAction();
+ tools::Long nRet = GetDoc()->MergeDoc( rDoc );
+ EndAllAction();
+ return nRet;
+}
+
+const SwFootnoteInfo& SwEditShell::GetFootnoteInfo() const
+{
+ return GetDoc()->GetFootnoteInfo();
+}
+
+void SwEditShell::SetFootnoteInfo(const SwFootnoteInfo& rInfo)
+{
+ StartAllAction();
+ CurrShell aCurr( this );
+ GetDoc()->SetFootnoteInfo(rInfo);
+ CallChgLnk();
+ EndAllAction();
+}
+
+const SwEndNoteInfo& SwEditShell::GetEndNoteInfo() const
+{
+ return GetDoc()->GetEndNoteInfo();
+}
+
+void SwEditShell::SetEndNoteInfo(const SwEndNoteInfo& rInfo)
+{
+ StartAllAction();
+ CurrShell aCurr( this );
+ GetDoc()->SetEndNoteInfo(rInfo);
+ EndAllAction();
+}
+
+const SwLineNumberInfo& SwEditShell::GetLineNumberInfo() const
+{
+ return GetDoc()->GetLineNumberInfo();
+}
+
+void SwEditShell::SetLineNumberInfo(const SwLineNumberInfo& rInfo)
+{
+ StartAllAction();
+ CurrShell aCurr( this );
+ GetDoc()->SetLineNumberInfo(rInfo);
+ AddPaintRect( GetLayout()->getFrameArea() );
+ EndAllAction();
+}
+
+sal_uInt16 SwEditShell::GetLinkUpdMode() const
+{
+ return getIDocumentSettingAccess().getLinkUpdateMode( false );
+}
+
+void SwEditShell::SetLinkUpdMode( sal_uInt16 nMode )
+{
+ getIDocumentSettingAccess().setLinkUpdateMode( nMode );
+}
+
+// Interface for TextInputData - (for text input of japanese/chinese characters)
+void SwEditShell::CreateExtTextInput(LanguageType eInputLanguage)
+{
+ SwExtTextInput* pRet = GetDoc()->CreateExtTextInput( *GetCursor() );
+ pRet->SetLanguage(eInputLanguage);
+ pRet->SetOverwriteCursor( SwCursorShell::IsOverwriteCursor() );
+}
+
+OUString SwEditShell::DeleteExtTextInput( bool bInsText )
+{
+ const SwPosition& rPos = *GetCursor()->GetPoint();
+ SwExtTextInput* pDel = GetDoc()->GetExtTextInput( rPos.GetNode(),
+ rPos.GetContentIndex() );
+ if( !pDel )
+ {
+ //JP 25.10.2001: under UNIX the cursor is moved before the Input-
+ // Engine event comes in. So take any - normally there
+ // exist only one at the time. -- Task 92016
+ pDel = GetDoc()->GetExtTextInput();
+ }
+ OUString sRet;
+ if( pDel )
+ {
+ OUString sTmp;
+ SwUnoCursorHelper::GetTextFromPam(*pDel, sTmp);
+ sRet = sTmp;
+ CurrShell aCurr( this );
+ StartAllAction();
+ pDel->SetInsText( bInsText );
+ SetOverwriteCursor( pDel->IsOverwriteCursor() );
+ const SwPosition aPos( *pDel->GetPoint() );
+ GetDoc()->DeleteExtTextInput( pDel );
+
+ // In this case, the "replace" function did not set the cursor
+ // to the original position. Therefore we have to do this manually.
+ if ( ! bInsText && IsOverwriteCursor() )
+ *GetCursor()->GetPoint() = aPos;
+
+ EndAllAction();
+ }
+ return sRet;
+}
+
+void SwEditShell::SetExtTextInputData( const CommandExtTextInputData& rData )
+{
+ SwPaM* pCurrentCursor = GetCursor();
+ const SwPosition& rPos = *pCurrentCursor->GetPoint();
+ SwExtTextInput* pInput = GetDoc()->GetExtTextInput( rPos.GetNode() );
+ if( !pInput )
+ return;
+
+ StartAllAction();
+ CurrShell aCurr( this );
+
+ if( !rData.IsOnlyCursorChanged() )
+ pInput->SetInputData( rData );
+ // position cursor
+ const SwPosition& rStt = *pInput->Start();
+ const sal_Int32 nNewCursorPos = rStt.GetContentIndex() + rData.GetCursorPos();
+
+ // ugly but works
+ ShowCursor();
+ const sal_Int32 nDiff = nNewCursorPos - rPos.GetContentIndex();
+ if( nDiff != 0)
+ {
+ bool bLeft = nDiff < 0;
+ sal_Int32 nMaxGuard = std::abs(nDiff);
+ while (true)
+ {
+ auto nOldPos = pCurrentCursor->GetPoint()->GetContentIndex();
+ if (bLeft)
+ Left(1, SwCursorSkipMode::Chars);
+ else
+ Right(1, SwCursorSkipMode::Chars);
+ auto nNewPos = pCurrentCursor->GetPoint()->GetContentIndex();
+
+ // expected success
+ if (nNewPos == nNewCursorPos)
+ break;
+
+ if (nNewPos == nOldPos)
+ {
+ // if there was no movement, we have failed for some reason
+ SAL_WARN("sw.core", "IM cursor move failed");
+ break;
+ }
+
+ if (--nMaxGuard == 0)
+ {
+ // if it takes more cursor moves than there are utf-16 chars to move past
+ // something has probably gone wrong
+ SAL_WARN("sw.core", "IM abandoning cursor positioning");
+ break;
+ }
+ }
+ }
+
+ SetOverwriteCursor( rData.IsCursorOverwrite() );
+
+ EndAllAction();
+
+ if( !rData.IsCursorVisible() ) // must be called after the EndAction
+ HideCursor();
+
+}
+
+void SwEditShell::TransliterateText( TransliterationFlags nType )
+{
+ utl::TransliterationWrapper aTrans( ::comphelper::getProcessComponentContext(), nType );
+ StartAllAction();
+ CurrShell aCurr( this );
+
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->GetNext() != pCursor )
+ {
+ GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);
+ for(const SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ if( rPaM.HasMark() )
+ GetDoc()->getIDocumentContentOperations().TransliterateText( rPaM, aTrans );
+ }
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
+ }
+ else
+ GetDoc()->getIDocumentContentOperations().TransliterateText( *pCursor, aTrans );
+
+ EndAllAction();
+}
+
+void SwEditShell::CountWords( SwDocStat& rStat ) const
+{
+ for(const SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ if( rPaM.HasMark() )
+ SwDoc::CountWords( rPaM, rStat );
+
+ }
+}
+
+void SwEditShell::ApplyViewOptions( const SwViewOption &rOpt )
+{
+ SwCursorShell::StartAction();
+ SwViewShell::ApplyViewOptions( rOpt );
+ SwEditShell::EndAction();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/edlingu.cxx b/sw/source/core/edit/edlingu.cxx
new file mode 100644
index 0000000000..5731d2b2be
--- /dev/null
+++ b/sw/source/core/edit/edlingu.cxx
@@ -0,0 +1,1731 @@
+/* -*- 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 <com/sun/star/linguistic2/ProofreadingResult.hpp>
+#include <com/sun/star/linguistic2/XProofreadingIterator.hpp>
+#include <com/sun/star/linguistic2/XHyphenatedWord.hpp>
+#include <com/sun/star/linguistic2/XLinguProperties.hpp>
+#include <com/sun/star/text/XFlatParagraph.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <o3tl/any.hxx>
+
+#include <unoflatpara.hxx>
+
+#include <strings.hrc>
+#include <hintids.hxx>
+#include <osl/diagnose.h>
+#include <unotools/linguprops.hxx>
+#include <linguistic/lngprops.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/SpellPortions.hxx>
+#include <svl/languageoptions.hxx>
+#include <editsh.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <rootfrm.hxx>
+#include <pam.hxx>
+#include <swundo.hxx>
+#include <ndtxt.hxx>
+#include <viewopt.hxx>
+#include <SwGrammarMarkUp.hxx>
+#include <mdiexp.hxx>
+#include <cntfrm.hxx>
+#include <splargs.hxx>
+#include <redline.hxx>
+#include <docary.hxx>
+#include <docsh.hxx>
+#include <txatbase.hxx>
+#include <txtfrm.hxx>
+#include <comphelper/propertyvalue.hxx>
+
+using namespace ::svx;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::linguistic2;
+
+namespace {
+
+class SwLinguIter
+{
+ SwEditShell* m_pSh;
+ std::unique_ptr<SwPosition> m_pStart;
+ std::unique_ptr<SwPosition> m_pEnd;
+ std::unique_ptr<SwPosition> m_pCurr;
+ std::unique_ptr<SwPosition> m_pCurrX;
+ sal_uInt16 m_nCursorCount;
+
+public:
+ SwLinguIter();
+
+ SwEditShell* GetSh() { return m_pSh; }
+
+ const SwPosition *GetEnd() const { return m_pEnd.get(); }
+ void SetEnd(SwPosition* pNew) { m_pEnd.reset(pNew); }
+
+ const SwPosition *GetStart() const { return m_pStart.get(); }
+ void SetStart(SwPosition* pNew) { m_pStart.reset(pNew); }
+
+ const SwPosition *GetCurr() const { return m_pCurr.get(); }
+ void SetCurr(SwPosition* pNew) { m_pCurr.reset(pNew); }
+
+ const SwPosition *GetCurrX() const { return m_pCurrX.get(); }
+ void SetCurrX(SwPosition* pNew) { m_pCurrX.reset(pNew); }
+
+ sal_uInt16& GetCursorCnt() { return m_nCursorCount; }
+
+ // for the UI:
+ void Start_( SwEditShell *pSh, SwDocPositions eStart,
+ SwDocPositions eEnd );
+ void End_(bool bRestoreSelection = true);
+};
+
+// #i18881# to be able to identify the positions of the changed words
+// the content positions of each portion need to be saved
+struct SpellContentPosition
+{
+ sal_Int32 nLeft;
+ sal_Int32 nRight;
+};
+
+}
+
+typedef std::vector<SpellContentPosition> SpellContentPositions;
+
+namespace {
+
+class SwSpellIter : public SwLinguIter
+{
+ uno::Reference<XSpellChecker1> m_xSpeller;
+ svx::SpellPortions m_aLastPortions;
+
+ SpellContentPositions m_aLastPositions;
+ bool m_bBackToStartOfSentence;
+
+ void CreatePortion(uno::Reference< XSpellAlternatives > const & xAlt,
+ const linguistic2::ProofreadingResult* pGrammarResult,
+ bool bIsField, bool bIsHidden);
+
+ void AddPortion(uno::Reference< XSpellAlternatives > const & xAlt,
+ const linguistic2::ProofreadingResult* pGrammarResult,
+ const SpellContentPositions& rDeletedRedlines);
+public:
+ SwSpellIter()
+ : m_bBackToStartOfSentence(false)
+ {
+ }
+
+ void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd );
+
+ uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt );
+
+ bool SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck);
+ void ToSentenceStart();
+ const svx::SpellPortions& GetLastPortions() const { return m_aLastPortions; }
+ const SpellContentPositions& GetLastPositions() const { return m_aLastPositions; }
+};
+
+/// used for text conversion
+class SwConvIter : public SwLinguIter
+{
+ SwConversionArgs& m_rArgs;
+
+public:
+ explicit SwConvIter(SwConversionArgs& rConvArgs)
+ : m_rArgs(rConvArgs)
+ {
+ }
+
+ void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd );
+
+ uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt );
+};
+
+class SwHyphIter : public SwLinguIter
+{
+ // With that we save a GetFrame() in Hyphenate //TODO: does it actually matter?
+ const SwTextNode *m_pLastNode;
+ SwTextFrame *m_pLastFrame;
+ friend SwTextFrame * sw::SwHyphIterCacheLastTextFrame(SwTextNode const * pNode, const sw::Creator& rCreator);
+
+ bool m_bOldIdle;
+ static void DelSoftHyph( SwPaM &rPam );
+
+public:
+ SwHyphIter()
+ : m_pLastNode(nullptr)
+ , m_pLastFrame(nullptr)
+ , m_bOldIdle(false)
+ {
+ }
+
+ void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd );
+ void End();
+
+ void Ignore();
+
+ uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt );
+
+ static bool IsAuto();
+ void InsertSoftHyph( const sal_Int32 nHyphPos );
+ void ShowSelection();
+};
+
+}
+
+static SwSpellIter* g_pSpellIter = nullptr;
+static SwConvIter* g_pConvIter = nullptr;
+static SwHyphIter* g_pHyphIter = nullptr;
+
+SwLinguIter::SwLinguIter()
+ : m_pSh(nullptr)
+ , m_nCursorCount(0)
+{
+ // TODO missing: ensurance of re-entrance, OSL_ENSURE( etc.
+}
+
+void SwLinguIter::Start_( SwEditShell *pShell, SwDocPositions eStart,
+ SwDocPositions eEnd )
+{
+ // TODO missing: ensurance of re-entrance, locking
+ if (m_pSh)
+ return;
+
+ bool bSetCurr;
+
+ m_pSh = pShell;
+
+ CurrShell aCurr(m_pSh);
+
+ OSL_ENSURE(!m_pEnd, "SwLinguIter::Start_ without End?");
+
+ SwPaM* pCursor = m_pSh->GetCursor();
+
+ if( pShell->HasSelection() || pCursor != pCursor->GetNext() )
+ {
+ bSetCurr = nullptr != GetCurr();
+ m_nCursorCount = m_pSh->GetCursorCnt();
+ if (m_pSh->IsTableMode())
+ m_pSh->TableCursorToCursor();
+
+ m_pSh->Push();
+ sal_uInt16 n;
+ for (n = 0; n < m_nCursorCount; ++n)
+ {
+ m_pSh->Push();
+ m_pSh->DestroyCursor();
+ }
+ m_pSh->Pop(SwCursorShell::PopMode::DeleteCurrent);
+ }
+ else
+ {
+ bSetCurr = false;
+ m_nCursorCount = 1;
+ m_pSh->Push();
+ m_pSh->SetLinguRange(eStart, eEnd);
+ }
+
+ pCursor = m_pSh->GetCursor();
+ if ( *pCursor->GetPoint() > *pCursor->GetMark() )
+ pCursor->Exchange();
+
+ m_pStart.reset(new SwPosition(*pCursor->GetPoint()));
+ m_pEnd.reset(new SwPosition(*pCursor->GetMark()));
+ if( bSetCurr )
+ {
+ SwPosition* pNew = new SwPosition( *GetStart() );
+ SetCurr( pNew );
+ pNew = new SwPosition( *pNew );
+ SetCurrX( pNew );
+ }
+
+ pCursor->SetMark();
+}
+
+void SwLinguIter::End_(bool bRestoreSelection)
+{
+ if (!m_pSh)
+ return;
+
+ OSL_ENSURE(m_pEnd, "SwLinguIter::End_ without end?");
+ if(bRestoreSelection)
+ {
+ while (m_nCursorCount--)
+ m_pSh->Pop(SwCursorShell::PopMode::DeleteCurrent);
+
+ m_pSh->KillPams();
+ m_pSh->ClearMark();
+ }
+ m_pStart.reset();
+ m_pEnd.reset();
+ m_pCurr.reset();
+ m_pCurrX.reset();
+
+ m_pSh = nullptr;
+}
+
+void SwSpellIter::Start( SwEditShell *pShell, SwDocPositions eStart,
+ SwDocPositions eEnd )
+{
+ if( GetSh() )
+ return;
+
+ m_xSpeller = ::GetSpellChecker();
+ if (m_xSpeller.is())
+ Start_( pShell, eStart, eEnd );
+ m_aLastPortions.clear();
+ m_aLastPositions.clear();
+}
+
+// This method is the origin of SwEditShell::SpellContinue()
+uno::Any SwSpellIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
+{
+ //!!
+ //!! Please check SwConvIter also when modifying this
+ //!!
+
+ uno::Any aSpellRet;
+ SwEditShell *pMySh = GetSh();
+ if( !pMySh )
+ return aSpellRet;
+
+ OSL_ENSURE( GetEnd(), "SwSpellIter::Continue without start?");
+
+ uno::Reference< uno::XInterface > xSpellRet;
+ bool bGoOn = true;
+ do {
+ SwPaM *pCursor = pMySh->GetCursor();
+ if ( !pCursor->HasMark() )
+ pCursor->SetMark();
+
+ *pMySh->GetCursor()->GetPoint() = *GetCurr();
+ *pMySh->GetCursor()->GetMark() = *GetEnd();
+ pMySh->GetDoc()->Spell(*pMySh->GetCursor(), m_xSpeller, pPageCnt, pPageSt, false,
+ pMySh->GetLayout())
+ >>= xSpellRet;
+ bGoOn = GetCursorCnt() > 1;
+ if( xSpellRet.is() )
+ {
+ bGoOn = false;
+ SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() );
+ SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() );
+ SetCurr( pNewPoint );
+ SetCurrX( pNewMark );
+ }
+ if( bGoOn )
+ {
+ pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent);
+ pCursor = pMySh->GetCursor();
+ if ( *pCursor->GetPoint() > *pCursor->GetMark() )
+ pCursor->Exchange();
+ SwPosition* pNew = new SwPosition( *pCursor->GetPoint() );
+ SetStart( pNew );
+ pNew = new SwPosition( *pCursor->GetMark() );
+ SetEnd( pNew );
+ pNew = new SwPosition( *GetStart() );
+ SetCurr( pNew );
+ pNew = new SwPosition( *pNew );
+ SetCurrX( pNew );
+ pCursor->SetMark();
+ --GetCursorCnt();
+ }
+ }while ( bGoOn );
+ aSpellRet <<= xSpellRet;
+ return aSpellRet;
+}
+
+void SwConvIter::Start( SwEditShell *pShell, SwDocPositions eStart,
+ SwDocPositions eEnd )
+{
+ if( GetSh() )
+ return;
+ Start_( pShell, eStart, eEnd );
+}
+
+uno::Any SwConvIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
+{
+ //!!
+ //!! Please check SwSpellIter also when modifying this
+ //!!
+
+ uno::Any aConvRet{ OUString() };
+ SwEditShell *pMySh = GetSh();
+ if( !pMySh )
+ return aConvRet;
+
+ OSL_ENSURE( GetEnd(), "SwConvIter::Continue() without Start?");
+
+ OUString aConvText;
+ bool bGoOn = true;
+ do {
+ SwPaM *pCursor = pMySh->GetCursor();
+ if ( !pCursor->HasMark() )
+ pCursor->SetMark();
+
+ *pMySh->GetCursor()->GetPoint() = *GetCurr();
+ *pMySh->GetCursor()->GetMark() = *GetEnd();
+
+ // call function to find next text portion to be converted
+ uno::Reference< linguistic2::XSpellChecker1 > xEmpty;
+ pMySh->GetDoc()->Spell(*pMySh->GetCursor(), xEmpty, pPageCnt, pPageSt, false,
+ pMySh->GetLayout(), &m_rArgs)
+ >>= aConvText;
+
+ bGoOn = GetCursorCnt() > 1;
+ if( !aConvText.isEmpty() )
+ {
+ bGoOn = false;
+ SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() );
+ SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() );
+
+ SetCurr( pNewPoint );
+ SetCurrX( pNewMark );
+ }
+ if( bGoOn )
+ {
+ pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent);
+ pCursor = pMySh->GetCursor();
+ if ( *pCursor->GetPoint() > *pCursor->GetMark() )
+ pCursor->Exchange();
+ SwPosition* pNew = new SwPosition( *pCursor->GetPoint() );
+ SetStart( pNew );
+ pNew = new SwPosition( *pCursor->GetMark() );
+ SetEnd( pNew );
+ pNew = new SwPosition( *GetStart() );
+ SetCurr( pNew );
+ pNew = new SwPosition( *pNew );
+ SetCurrX( pNew );
+ pCursor->SetMark();
+ --GetCursorCnt();
+ }
+ }while ( bGoOn );
+ return Any( aConvText );
+}
+
+bool SwHyphIter::IsAuto()
+{
+ uno::Reference< beans::XPropertySet > xProp( ::GetLinguPropertySet() );
+ return xProp.is() && *o3tl::doAccess<bool>(xProp->getPropertyValue(
+ UPN_IS_HYPH_AUTO ));
+}
+
+void SwHyphIter::ShowSelection()
+{
+ SwEditShell *pMySh = GetSh();
+ if( pMySh )
+ {
+ pMySh->StartAction();
+ // Caution! Due to EndAction() formatting is started which can lead to the fact that new
+ // words are added to/set in the Hyphenator. Thus: save!
+ pMySh->EndAction();
+ }
+}
+
+void SwHyphIter::Start( SwEditShell *pShell, SwDocPositions eStart, SwDocPositions eEnd )
+{
+ // robust
+ if( GetSh() || GetEnd() )
+ {
+ OSL_ENSURE( !GetSh(), "SwHyphIter::Start: missing HyphEnd()" );
+ return;
+ }
+
+ // nothing to do (at least not in the way as in the "else" part)
+ m_bOldIdle = pShell->GetViewOptions()->IsIdle();
+ pShell->GetViewOptions()->SetIdle( false );
+ Start_( pShell, eStart, eEnd );
+}
+
+// restore selections
+void SwHyphIter::End()
+{
+ if( !GetSh() )
+ return;
+ GetSh()->GetViewOptions()->SetIdle(m_bOldIdle);
+ End_();
+}
+
+uno::Any SwHyphIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
+{
+ uno::Any aHyphRet;
+ SwEditShell *pMySh = GetSh();
+ if( !pMySh )
+ return aHyphRet;
+
+ const bool bAuto = IsAuto();
+ uno::Reference< XHyphenatedWord > xHyphWord;
+ bool bGoOn = false;
+ do {
+ SwPaM *pCursor;
+ do {
+ OSL_ENSURE( GetEnd(), "SwHyphIter::Continue without Start?" );
+ pCursor = pMySh->GetCursor();
+ if ( !pCursor->HasMark() )
+ pCursor->SetMark();
+ if ( *pCursor->GetPoint() < *pCursor->GetMark() )
+ {
+ pCursor->Exchange();
+ pCursor->SetMark();
+ }
+
+ if ( *pCursor->End() <= *GetEnd() )
+ {
+ *pCursor->GetMark() = *GetEnd();
+
+ // Do we need to break the word at the current cursor position?
+ const Point aCursorPos( pMySh->GetCharRect().Pos() );
+ xHyphWord = pMySh->GetDoc()->Hyphenate( pCursor, aCursorPos,
+ pPageCnt, pPageSt );
+ }
+
+ if( bAuto && xHyphWord.is() )
+ {
+ SwEditShell::InsertSoftHyph( xHyphWord->getHyphenationPos() + 1);
+ }
+ } while( bAuto && xHyphWord.is() ); //end of do-while
+ bGoOn = !xHyphWord.is() && GetCursorCnt() > 1;
+
+ if( bGoOn )
+ {
+ pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent);
+ pCursor = pMySh->GetCursor();
+ if ( *pCursor->GetPoint() > *pCursor->GetMark() )
+ pCursor->Exchange();
+ SwPosition* pNew = new SwPosition(*pCursor->End());
+ SetEnd( pNew );
+ pCursor->SetMark();
+ --GetCursorCnt();
+ }
+ } while ( bGoOn );
+ aHyphRet <<= xHyphWord;
+ return aHyphRet;
+}
+
+/// ignore hyphenation
+void SwHyphIter::Ignore()
+{
+ SwEditShell *pMySh = GetSh();
+ SwPaM *pCursor = pMySh->GetCursor();
+
+ // delete old SoftHyphen
+ DelSoftHyph( *pCursor );
+
+ // and continue
+ pCursor->Start()->SetContent( pCursor->End()->GetContentIndex() );
+ pCursor->SetMark();
+}
+
+void SwHyphIter::DelSoftHyph( SwPaM &rPam )
+{
+ const SwPosition* pStt = rPam.Start();
+ const sal_Int32 nStart = pStt->GetContentIndex();
+ const sal_Int32 nEnd = rPam.End()->GetContentIndex();
+ SwTextNode *pNode = pStt->GetNode().GetTextNode();
+ pNode->DelSoftHyph( nStart, nEnd );
+}
+
+void SwHyphIter::InsertSoftHyph( const sal_Int32 nHyphPos )
+{
+ SwEditShell *pMySh = GetSh();
+ OSL_ENSURE( pMySh, "SwHyphIter::InsertSoftHyph: missing HyphStart()");
+ if( !pMySh )
+ return;
+
+ SwPaM *pCursor = pMySh->GetCursor();
+ auto [pSttPos, pEndPos] = pCursor->StartEnd(); // SwPosition*
+
+ const sal_Int32 nLastHyphLen = GetEnd()->GetContentIndex() -
+ pSttPos->GetContentIndex();
+
+ if( pSttPos->GetNode() != pEndPos->GetNode() || !nLastHyphLen )
+ {
+ OSL_ENSURE( pSttPos->GetNode() == pEndPos->GetNode(),
+ "SwHyphIter::InsertSoftHyph: node warp during hyphenation" );
+ OSL_ENSURE(nLastHyphLen, "SwHyphIter::InsertSoftHyph: missing HyphContinue()");
+ *pSttPos = *pEndPos;
+ return;
+ }
+
+ pMySh->StartAction();
+ {
+ SwDoc *pDoc = pMySh->GetDoc();
+ DelSoftHyph( *pCursor );
+ pSttPos->AdjustContent( +nHyphPos );
+ SwPaM aRg( *pSttPos );
+ pDoc->getIDocumentContentOperations().InsertString( aRg, OUString(CHAR_SOFTHYPHEN) );
+ }
+ // revoke selection
+ pCursor->DeleteMark();
+ pMySh->EndAction();
+ pCursor->SetMark();
+}
+
+namespace sw {
+
+SwTextFrame *
+SwHyphIterCacheLastTextFrame(SwTextNode const * pNode, const sw::Creator& create)
+{
+ assert(g_pHyphIter);
+ if (pNode != g_pHyphIter->m_pLastNode || !g_pHyphIter->m_pLastFrame)
+ {
+ g_pHyphIter->m_pLastNode = pNode;
+ g_pHyphIter->m_pLastFrame = create();
+ }
+ return g_pHyphIter->m_pLastFrame;
+}
+
+}
+
+bool SwEditShell::HasLastSentenceGotGrammarChecked()
+{
+ bool bTextWasGrammarChecked = false;
+ if (g_pSpellIter)
+ {
+ svx::SpellPortions aLastPortions( g_pSpellIter->GetLastPortions() );
+ for (size_t i = 0; i < aLastPortions.size() && !bTextWasGrammarChecked; ++i)
+ {
+ // bIsGrammarError is also true if the text was only checked but no
+ // grammar error was found. (That is if a ProofreadingResult was obtained in
+ // SwDoc::Spell and in turn bIsGrammarError was set in SwSpellIter::CreatePortion)
+ if (aLastPortions[i].bIsGrammarError)
+ bTextWasGrammarChecked = true;
+ }
+ }
+ return bTextWasGrammarChecked;
+}
+
+bool SwEditShell::HasConvIter()
+{
+ return nullptr != g_pConvIter;
+}
+
+bool SwEditShell::HasHyphIter()
+{
+ return nullptr != g_pHyphIter;
+}
+
+void SwEditShell::SetLinguRange( SwDocPositions eStart, SwDocPositions eEnd )
+{
+ SwPaM *pCursor = GetCursor();
+ MakeFindRange( eStart, eEnd, pCursor );
+ if( *pCursor->GetPoint() > *pCursor->GetMark() )
+ pCursor->Exchange();
+}
+
+void SwEditShell::SpellStart(
+ SwDocPositions eStart, SwDocPositions eEnd, SwDocPositions eCurr,
+ SwConversionArgs *pConvArgs )
+{
+ SwLinguIter *pLinguIter = nullptr;
+
+ // do not spell if interactive spelling is active elsewhere
+ if (!pConvArgs && !g_pSpellIter)
+ {
+ g_pSpellIter = new SwSpellIter;
+ pLinguIter = g_pSpellIter;
+ }
+ // do not do text conversion if it is active elsewhere
+ if (pConvArgs && !g_pConvIter)
+ {
+ g_pConvIter = new SwConvIter( *pConvArgs );
+ pLinguIter = g_pConvIter;
+ }
+
+ if (pLinguIter)
+ {
+ SwCursor* pSwCursor = GetCursor();
+
+ SwPosition *pTmp = new SwPosition( *pSwCursor->GetPoint() );
+ pSwCursor->FillFindPos( eCurr, *pTmp );
+ pLinguIter->SetCurr( pTmp );
+
+ pTmp = new SwPosition( *pTmp );
+ pLinguIter->SetCurrX( pTmp );
+ }
+
+ if (!pConvArgs && g_pSpellIter)
+ g_pSpellIter->Start( this, eStart, eEnd );
+ if (pConvArgs && g_pConvIter)
+ g_pConvIter->Start( this, eStart, eEnd );
+}
+
+void SwEditShell::SpellEnd( SwConversionArgs const *pConvArgs, bool bRestoreSelection )
+{
+ if (!pConvArgs && g_pSpellIter && g_pSpellIter->GetSh() == this)
+ {
+ g_pSpellIter->End_(bRestoreSelection);
+ delete g_pSpellIter;
+ g_pSpellIter = nullptr;
+ }
+ if (pConvArgs && g_pConvIter && g_pConvIter->GetSh() == this)
+ {
+ g_pConvIter->End_();
+ delete g_pConvIter;
+ g_pConvIter = nullptr;
+ }
+}
+
+/// @returns SPL_ return values as in splchk.hxx
+uno::Any SwEditShell::SpellContinue(
+ sal_uInt16* pPageCnt, sal_uInt16* pPageSt,
+ SwConversionArgs const *pConvArgs )
+{
+ uno::Any aRes;
+
+ if ((!pConvArgs && g_pSpellIter->GetSh() != this) ||
+ ( pConvArgs && g_pConvIter->GetSh() != this))
+ return aRes;
+
+ if( pPageCnt && !*pPageCnt )
+ {
+ sal_uInt16 nEndPage = GetLayout()->GetPageNum();
+ nEndPage += nEndPage * 10 / 100;
+ *pPageCnt = nEndPage;
+ if( nEndPage )
+ ::StartProgress( STR_STATSTR_SPELL, 0, nEndPage, GetDoc()->GetDocShell() );
+ }
+
+ OSL_ENSURE( pConvArgs || g_pSpellIter, "SpellIter missing" );
+ OSL_ENSURE( !pConvArgs || g_pConvIter, "ConvIter missing" );
+ //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all
+ // Paints are also disabled.
+ ++mnStartAction;
+ OUString aRet;
+ uno::Reference< uno::XInterface > xRet;
+ if (pConvArgs)
+ {
+ g_pConvIter->Continue( pPageCnt, pPageSt ) >>= aRet;
+ aRes <<= aRet;
+ }
+ else
+ {
+ g_pSpellIter->Continue( pPageCnt, pPageSt ) >>= xRet;
+ aRes <<= xRet;
+ }
+ --mnStartAction;
+
+ if( !aRet.isEmpty() || xRet.is() )
+ {
+ // then make awt::Selection again visible
+ StartAction();
+ EndAction();
+ }
+ return aRes;
+}
+
+/* Interactive Hyphenation (BP 10.03.93)
+ *
+ * 1) HyphStart
+ * - Revoke all Selections
+ * - Save current Cursor
+ * - if no selections existent:
+ * - create new selection reaching until document end
+ * 2) HyphContinue
+ * - add nLastHyphLen onto SelectionStart
+ * - iterate over all selected areas
+ * - pDoc->Hyphenate() iterates over all Nodes of a selection
+ * - pTextNode->Hyphenate() calls SwTextFrame::Hyphenate of the EditShell
+ * - SwTextFrame:Hyphenate() iterates over all rows of the Pam
+ * - LineIter::Hyphenate() sets the Hyphenator and the Pam based on
+ * the to be separated word.
+ * - Returns true if there is a hyphenation and false if the Pam is processed.
+ * - If true, show the selected word and set nLastHyphLen.
+ * - If false, delete current selection and select next one. Returns HYPH_OK if no more.
+ * 3) InsertSoftHyph (might be called by UI if needed)
+ * - Place current cursor and add attribute.
+ * 4) HyphEnd
+ * - Restore old cursor, EndAction
+ */
+void SwEditShell::HyphStart( SwDocPositions eStart, SwDocPositions eEnd )
+{
+ // do not hyphenate if interactive hyphenation is active elsewhere
+ if (!g_pHyphIter)
+ {
+ g_pHyphIter = new SwHyphIter;
+ g_pHyphIter->Start( this, eStart, eEnd );
+ }
+}
+
+/// restore selections
+void SwEditShell::HyphEnd()
+{
+ assert(g_pHyphIter);
+ if (g_pHyphIter->GetSh() == this)
+ {
+ g_pHyphIter->End();
+ delete g_pHyphIter;
+ g_pHyphIter = nullptr;
+ }
+}
+
+/// @returns HYPH_CONTINUE if hyphenation, HYPH_OK if selected area was processed.
+uno::Reference< uno::XInterface >
+ SwEditShell::HyphContinue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt )
+{
+ assert(g_pHyphIter);
+ if (g_pHyphIter->GetSh() != this)
+ return nullptr;
+
+ if( pPageCnt && !*pPageCnt && !*pPageSt )
+ {
+ sal_uInt16 nEndPage = GetLayout()->GetPageNum();
+ nEndPage += nEndPage * 10 / 100;
+ if( nEndPage > 14 )
+ {
+ *pPageCnt = nEndPage;
+ ::StartProgress( STR_STATSTR_HYPHEN, 0, nEndPage, GetDoc()->GetDocShell());
+ }
+ else // here we once and for all suppress StatLineStartPercent
+ *pPageSt = 1;
+ }
+
+ //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all
+ // Paints are also disabled.
+ ++mnStartAction;
+ uno::Reference< uno::XInterface > xRet;
+ g_pHyphIter->Continue( pPageCnt, pPageSt ) >>= xRet;
+ --mnStartAction;
+
+ if( xRet.is() )
+ g_pHyphIter->ShowSelection();
+
+ return xRet;
+}
+
+/** Insert soft hyphen
+ *
+ * @param nHyphPos Offset in the to be separated word
+ */
+void SwEditShell::InsertSoftHyph( const sal_Int32 nHyphPos )
+{
+ assert(g_pHyphIter);
+ g_pHyphIter->InsertSoftHyph( nHyphPos );
+}
+
+/// ignore hyphenation
+void SwEditShell::HyphIgnore()
+{
+ assert(g_pHyphIter);
+ //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all
+ // Paints are also disabled.
+ ++mnStartAction;
+ g_pHyphIter->Ignore();
+ --mnStartAction;
+
+ g_pHyphIter->ShowSelection();
+}
+
+void SwEditShell::HandleCorrectionError(const OUString& aText, SwPosition aPos, sal_Int32 nBegin,
+ sal_Int32 nLen, const Point* pPt,
+ SwRect& rSelectRect)
+{
+ // save the start and end positions of the line and the starting point
+ SwNode const& rNode(GetCursor()->GetPoint()->GetNode());
+ Push();
+ LeftMargin();
+ const sal_Int32 nLineStart = &rNode == &GetCursor()->GetPoint()->GetNode()
+ ? GetCursor()->GetPoint()->GetContentIndex()
+ : 0;
+ RightMargin();
+ const sal_Int32 nLineEnd = &rNode == &GetCursor()->GetPoint()->GetNode()
+ ? GetCursor()->GetPoint()->GetContentIndex()
+ : rNode.GetTextNode()->Len();
+ Pop(PopMode::DeleteCurrent);
+
+ // make sure the selection build later from the data below does
+ // not "in word" character to the left and right in order to
+ // preserve those. Therefore count those "in words" in order to
+ // modify the selection accordingly.
+ const sal_Unicode* pChar = aText.getStr();
+ sal_Int32 nLeft = 0;
+ while (*pChar++ == CH_TXTATR_INWORD)
+ ++nLeft;
+ pChar = aText.getLength() ? aText.getStr() + aText.getLength() - 1 : nullptr;
+ sal_Int32 nRight = 0;
+ while (pChar && *pChar-- == CH_TXTATR_INWORD)
+ ++nRight;
+
+ aPos.SetContent( nBegin + nLeft );
+ SwPaM* pCursor = GetCursor();
+ *pCursor->GetPoint() = aPos;
+ pCursor->SetMark();
+ ExtendSelection( true, nLen - nLeft - nRight );
+ // don't determine the rectangle in the current line
+ const sal_Int32 nWordStart = (nBegin + nLeft) < nLineStart ? nLineStart : nBegin + nLeft;
+ // take one less than the line end - otherwise the next line would be calculated
+ const sal_Int32 nWordEnd = (nBegin + nLen - nLeft - nRight) > nLineEnd
+ ? nLineEnd : (nBegin + nLen - nLeft - nRight);
+ Push();
+ pCursor->DeleteMark();
+ SwPosition& rPtPos = *GetCursor()->GetPoint();
+ rPtPos.SetContent(nWordStart);
+ SwRect aStartRect;
+ SwCursorMoveState aState;
+ aState.m_bRealWidth = true;
+ SwContentNode* pContentNode = pCursor->GetPointContentNode();
+ std::pair<Point, bool> tmp;
+ if (pPt)
+ {
+ tmp.first = *pPt;
+ tmp.second = false;
+ }
+ SwContentFrame *const pContentFrame = pContentNode->getLayoutFrame(GetLayout(), pCursor->GetPoint(), pPt ? &tmp : nullptr);
+
+ pContentFrame->GetCharRect( aStartRect, *pCursor->GetPoint(), &aState );
+ rPtPos.SetContent(nWordEnd - 1);
+ SwRect aEndRect;
+ pContentFrame->GetCharRect( aEndRect, *pCursor->GetPoint(),&aState );
+ rSelectRect = aStartRect.Union( aEndRect );
+ Pop(PopMode::DeleteCurrent);
+}
+
+/** Get a list of potential corrections for misspelled word.
+ *
+ * If empty, word is unknown but there are no corrections available.
+ * If NULL then the word is not misspelled but correct.
+ *
+ * @brief SwEditShell::GetCorrection
+ * @return list or NULL pointer
+ */
+uno::Reference< XSpellAlternatives >
+ SwEditShell::GetCorrection( const Point* pPt, SwRect& rSelectRect )
+{
+ uno::Reference< XSpellAlternatives > xSpellAlt;
+
+ if( IsTableMode() )
+ return nullptr;
+ SwPaM* pCursor = GetCursor();
+ SwPosition aPos( *pCursor->GetPoint() );
+ SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText );
+ SwTextNode *pNode = nullptr;
+ SwWrongList *pWrong = nullptr;
+ if (pPt && GetLayout()->GetModelPositionForViewPoint( &aPos, *const_cast<Point*>(pPt), &eTmpState ))
+ pNode = aPos.GetNode().GetTextNode();
+ if (nullptr == pNode)
+ pNode = pCursor->GetPointNode().GetTextNode();
+ if (nullptr != pNode)
+ pWrong = pNode->GetWrong();
+ if (nullptr != pWrong && !pNode->IsInProtectSect())
+ {
+ sal_Int32 nBegin = aPos.GetContentIndex();
+ sal_Int32 nLen = 1;
+ if (pWrong->InWrongWord(nBegin, nLen) && !pNode->IsSymbolAt(nBegin))
+ {
+ const OUString aText(pNode->GetText().copy(nBegin, nLen));
+ // TODO: this doesn't handle fieldmarks properly
+ ModelToViewHelper const aConversionMap(*pNode, GetLayout(),
+ ExpandMode::ExpandFields | ExpandMode::ExpandFootnote | ExpandMode::ReplaceMode
+ | ExpandMode::HideFieldmarkCommands
+ | (GetLayout()->IsHideRedlines() ? ExpandMode::HideDeletions : ExpandMode(0))
+ | (GetViewOptions()->IsShowHiddenChar() ? ExpandMode(0) : ExpandMode::HideInvisible));
+ auto const nBeginView(aConversionMap.ConvertToViewPosition(nBegin));
+ OUString const aWord(aConversionMap.getViewText().copy(nBeginView,
+ aConversionMap.ConvertToViewPosition(nBegin+nLen) - nBeginView));
+
+ uno::Reference< XSpellChecker1 > xSpell( ::GetSpellChecker() );
+ if( xSpell.is() )
+ {
+ LanguageType eActLang = pNode->GetLang( nBegin, nLen );
+ if( xSpell->hasLanguage( static_cast<sal_uInt16>(eActLang) ))
+ {
+ // restrict the maximal number of suggestions displayed
+ // in the context menu.
+ // Note: That could of course be done by clipping the
+ // resulting sequence but the current third party
+ // implementations result differs greatly if the number of
+ // suggestions to be returned gets changed. Statistically
+ // it gets much better if told to return e.g. only 7 strings
+ // than returning e.g. 16 suggestions and using only the
+ // first 7. Thus we hand down the value to use to that
+ // implementation here by providing an additional parameter.
+ Sequence< PropertyValue > aPropVals ( { comphelper::makePropertyValue( UPN_MAX_NUMBER_OF_SUGGESTIONS, sal_Int16(7)) } );
+
+ xSpellAlt = xSpell->spell( aWord, static_cast<sal_uInt16>(eActLang), aPropVals );
+ }
+ }
+
+ if ( xSpellAlt.is() ) // error found?
+ {
+ HandleCorrectionError( aText, std::move(aPos), nBegin, nLen, pPt, rSelectRect );
+ }
+ }
+ }
+ return xSpellAlt;
+}
+
+bool SwEditShell::GetGrammarCorrection(
+ linguistic2::ProofreadingResult /*out*/ &rResult, // the complete result
+ sal_Int32 /*out*/ &rErrorPosInText, // offset of error position in string that was grammar checked...
+ sal_Int32 /*out*/ &rErrorIndexInResult, // index of error in rResult.aGrammarErrors
+ uno::Sequence< OUString > /*out*/ &rSuggestions, // suggestions to be used for the error found
+ const Point *pPt, SwRect &rSelectRect )
+{
+ bool bRes = false;
+
+ if( IsTableMode() )
+ return bRes;
+
+ SwPaM* pCursor = GetCursor();
+ SwPosition aPos( *pCursor->GetPoint() );
+ SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText );
+ SwTextNode *pNode = nullptr;
+ SwGrammarMarkUp *pWrong = nullptr;
+ if (pPt && GetLayout()->GetModelPositionForViewPoint( &aPos, *const_cast<Point*>(pPt), &eTmpState ))
+ pNode = aPos.GetNode().GetTextNode();
+ if (nullptr == pNode)
+ pNode = pCursor->GetPointNode().GetTextNode();
+ if (nullptr != pNode)
+ pWrong = pNode->GetGrammarCheck();
+ if (nullptr != pWrong && !pNode->IsInProtectSect())
+ {
+ sal_Int32 nBegin = aPos.GetContentIndex();
+ sal_Int32 nLen = 1;
+ if (pWrong->InWrongWord(nBegin, nLen))
+ {
+ const OUString aText(pNode->GetText().copy(nBegin, nLen));
+
+ uno::Reference< linguistic2::XProofreadingIterator > xGCIterator( mxDoc->GetGCIterator() );
+ if (xGCIterator.is())
+ {
+ uno::Reference< lang::XComponent > xDoc = mxDoc->GetDocShell()->GetBaseModel();
+
+ // Expand the string:
+ const ModelToViewHelper aConversionMap(*pNode, GetLayout());
+ const OUString& aExpandText = aConversionMap.getViewText();
+ // get XFlatParagraph to use...
+ uno::Reference< text::XFlatParagraph > xFlatPara = new SwXFlatParagraph( *pNode, aExpandText, aConversionMap );
+
+ // get error position of cursor in XFlatParagraph
+ rErrorPosInText = aConversionMap.ConvertToViewPosition( nBegin );
+
+ const sal_Int32 nStartOfSentence = aConversionMap.ConvertToViewPosition( pWrong->getSentenceStart( nBegin ) );
+ const sal_Int32 nEndOfSentence = aConversionMap.ConvertToViewPosition( pWrong->getSentenceEnd( nBegin ) );
+
+ rResult = xGCIterator->checkSentenceAtPosition(
+ xDoc, xFlatPara, aExpandText, lang::Locale(), nStartOfSentence,
+ nEndOfSentence == COMPLETE_STRING ? aExpandText.getLength() : nEndOfSentence,
+ rErrorPosInText );
+ bRes = true;
+
+ // get suggestions to use for the specific error position
+ rSuggestions.realloc( 0 );
+ // return suggestions for first error that includes the given error position
+ auto pError = std::find_if(std::cbegin(rResult.aErrors), std::cend(rResult.aErrors),
+ [rErrorPosInText, nLen](const linguistic2::SingleProofreadingError &rError) {
+ return rError.nErrorStart <= rErrorPosInText
+ && rErrorPosInText + nLen <= rError.nErrorStart + rError.nErrorLength
+ && rError.aSuggestions.size() > 0; });
+ if (pError != std::cend(rResult.aErrors))
+ {
+ rSuggestions = pError->aSuggestions;
+ rErrorIndexInResult = static_cast<sal_Int32>(std::distance(std::cbegin(rResult.aErrors), pError));
+ }
+ }
+
+ if (rResult.aErrors.hasElements()) // error found?
+ {
+ HandleCorrectionError( aText, std::move(aPos), nBegin, nLen, pPt, rSelectRect );
+ }
+ }
+ }
+
+ return bRes;
+}
+
+bool SwEditShell::SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck)
+{
+ OSL_ENSURE( g_pSpellIter, "SpellIter missing" );
+ if (!g_pSpellIter)
+ return false;
+ bool bRet = g_pSpellIter->SpellSentence(rPortions, bIsGrammarCheck);
+
+ // make Selection visible - this should simply move the
+ // cursor to the end of the sentence
+ StartAction();
+ EndAction();
+ return bRet;
+}
+
+///make SpellIter start with the current sentence when called next time
+void SwEditShell::PutSpellingToSentenceStart()
+{
+ OSL_ENSURE( g_pSpellIter, "SpellIter missing" );
+ if (!g_pSpellIter)
+ return;
+ g_pSpellIter->ToSentenceStart();
+}
+
+static sal_uInt32 lcl_CountRedlines(const svx::SpellPortions& rLastPortions)
+{
+ return static_cast<sal_uInt32>(std::count_if(rLastPortions.begin(), rLastPortions.end(),
+ [](const svx::SpellPortion& rPortion) { return rPortion.bIsHidden; }));
+}
+
+void SwEditShell::MoveContinuationPosToEndOfCheckedSentence()
+{
+ // give hint that continuation position for spell/grammar checking is
+ // at the end of this sentence
+ if (g_pSpellIter)
+ {
+ g_pSpellIter->SetCurr( new SwPosition( *g_pSpellIter->GetCurrX() ) );
+ }
+}
+
+void SwEditShell::ApplyChangedSentence(const svx::SpellPortions& rNewPortions, bool bRecheck)
+{
+ // Note: rNewPortions.size() == 0 is valid and happens when the whole
+ // sentence got removed in the dialog
+
+ OSL_ENSURE( g_pSpellIter, "SpellIter missing" );
+ if (!g_pSpellIter ||
+ g_pSpellIter->GetLastPortions().empty()) // no portions -> no text to be changed
+ return;
+
+ const SpellPortions& rLastPortions = g_pSpellIter->GetLastPortions();
+ const SpellContentPositions rLastPositions = g_pSpellIter->GetLastPositions();
+ OSL_ENSURE(!rLastPortions.empty() &&
+ rLastPortions.size() == rLastPositions.size(),
+ "last vectors of spelling results are not set or not equal");
+
+ // iterate over the new portions, beginning at the end to take advantage of the previously
+ // saved content positions
+
+ mxDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_TEXT_CORRECTION, nullptr );
+ StartAction();
+
+ SwPaM *pCursor = GetCursor();
+ // save cursor position (which should be at the end of the current sentence)
+ // for later restoration
+ Push();
+
+ sal_uInt32 nRedlinePortions = lcl_CountRedlines(rLastPortions);
+ if((rLastPortions.size() - nRedlinePortions) == rNewPortions.size())
+ {
+ OSL_ENSURE( !rNewPortions.empty(), "rNewPortions should not be empty here" );
+ OSL_ENSURE( !rLastPortions.empty(), "rLastPortions should not be empty here" );
+ OSL_ENSURE( !rLastPositions.empty(), "rLastPositions should not be empty here" );
+
+ // the simple case: the same number of elements on both sides
+ // each changed element has to be applied to the corresponding source element
+ svx::SpellPortions::const_iterator aCurrentNewPortion = rNewPortions.end();
+ SpellPortions::const_iterator aCurrentOldPortion = rLastPortions.end();
+ SpellContentPositions::const_iterator aCurrentOldPosition = rLastPositions.end();
+ do
+ {
+ --aCurrentNewPortion;
+ --aCurrentOldPortion;
+ --aCurrentOldPosition;
+ //jump over redline portions
+ while(aCurrentOldPortion->bIsHidden)
+ {
+ if (aCurrentOldPortion != rLastPortions.begin() &&
+ aCurrentOldPosition != rLastPositions.begin())
+ {
+ --aCurrentOldPortion;
+ --aCurrentOldPosition;
+ }
+ else
+ {
+ OSL_FAIL("ApplyChangedSentence: iterator positions broken" );
+ break;
+ }
+ }
+ if ( !pCursor->HasMark() )
+ pCursor->SetMark();
+ pCursor->GetPoint()->SetContent( aCurrentOldPosition->nLeft );
+ pCursor->GetMark()->SetContent( aCurrentOldPosition->nRight );
+ sal_uInt16 nScriptType = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( aCurrentNewPortion->eLanguage );
+ sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE;
+ switch(nScriptType)
+ {
+ case css::i18n::ScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break;
+ case css::i18n::ScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break;
+ }
+ if(aCurrentNewPortion->sText != aCurrentOldPortion->sText)
+ {
+ // change text ...
+ // ... and apply language if necessary
+ if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage)
+ SetAttrItem( SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId) );
+
+ // if there is a comment inside the original word, don't delete it:
+ // but keep it at the end of the replacement
+ ReplaceKeepComments(aCurrentNewPortion->sText);
+ }
+ else if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage)
+ {
+ // apply language
+ SetAttrItem( SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId) );
+ }
+ else if( aCurrentNewPortion->bIgnoreThisError )
+ {
+ // add the 'ignore' markup to the TextNode's grammar ignore markup list
+ IgnoreGrammarErrorAt( *pCursor );
+ OSL_FAIL("TODO: add ignore mark to text node");
+ }
+ }
+ while(aCurrentNewPortion != rNewPortions.begin());
+ }
+ else
+ {
+ OSL_ENSURE( !rLastPositions.empty(), "rLastPositions should not be empty here" );
+
+ // select the complete sentence
+ SpellContentPositions::const_iterator aCurrentEndPosition = rLastPositions.end();
+ --aCurrentEndPosition;
+ SpellContentPositions::const_iterator aCurrentStartPosition = rLastPositions.begin();
+ pCursor->GetPoint()->SetContent( aCurrentStartPosition->nLeft );
+ pCursor->GetMark()->SetContent( aCurrentEndPosition->nRight );
+
+ // delete the sentence completely
+ mxDoc->getIDocumentContentOperations().DeleteAndJoin(*pCursor);
+ for(const auto& rCurrentNewPortion : rNewPortions)
+ {
+ // set the language attribute
+ SvtScriptType nScriptType = GetScriptType();
+ sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE;
+ switch(nScriptType)
+ {
+ case SvtScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break;
+ case SvtScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break;
+ default: break;
+ }
+ SfxItemSet aSet(GetAttrPool(), nLangWhichId, nLangWhichId);
+ GetCurAttr( aSet );
+ const SvxLanguageItem& rLang = static_cast<const SvxLanguageItem& >(aSet.Get(nLangWhichId));
+ if(rLang.GetLanguage() != rCurrentNewPortion.eLanguage)
+ SetAttrItem( SvxLanguageItem(rCurrentNewPortion.eLanguage, nLangWhichId) );
+ // insert the new string
+ mxDoc->getIDocumentContentOperations().InsertString(*pCursor, rCurrentNewPortion.sText);
+
+ // set the cursor to the end of the inserted string
+ *pCursor->Start() = *pCursor->End();
+ }
+ }
+
+ // restore cursor to the end of the sentence
+ // (will work also if the sentence length has changed,
+ // since cursors get updated automatically!)
+ Pop(PopMode::DeleteCurrent);
+
+ // collapse cursor to the end of the modified sentence
+ *pCursor->Start() = *pCursor->End();
+ if (bRecheck)
+ {
+ // in grammar check the current sentence has to be checked again
+ GoStartSentence();
+ }
+ // set continuation position for spell/grammar checking to the end of this sentence
+ g_pSpellIter->SetCurr( new SwPosition(*pCursor->Start()) );
+
+ mxDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::UI_TEXT_CORRECTION, nullptr );
+ EndAction();
+
+}
+/** Collect all deleted redlines of the current text node
+ * beginning at the start of the cursor position
+ */
+static SpellContentPositions lcl_CollectDeletedRedlines(SwEditShell const * pSh)
+{
+ SpellContentPositions aRedlines;
+ SwDoc* pDoc = pSh->GetDoc();
+ const bool bShowChg = IDocumentRedlineAccess::IsShowChanges( pDoc->getIDocumentRedlineAccess().GetRedlineFlags() );
+ if ( bShowChg )
+ {
+ SwPaM *pCursor = pSh->GetCursor();
+ const SwPosition* pStartPos = pCursor->Start();
+ const SwTextNode* pTextNode = pCursor->GetPointNode().GetTextNode();
+
+ SwRedlineTable::size_type nAct = pDoc->getIDocumentRedlineAccess().GetRedlinePos( *pTextNode, RedlineType::Any );
+ const sal_Int32 nStartIndex = pStartPos->GetContentIndex();
+ for ( ; nAct < pDoc->getIDocumentRedlineAccess().GetRedlineTable().size(); nAct++ )
+ {
+ const SwRangeRedline* pRed = pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nAct ];
+
+ if ( pRed->Start()->GetNode() > *pTextNode )
+ break;
+
+ if( RedlineType::Delete == pRed->GetType() )
+ {
+ sal_Int32 nStart_, nEnd_;
+ pRed->CalcStartEnd( pTextNode->GetIndex(), nStart_, nEnd_ );
+ sal_Int32 nStart = nStart_;
+ sal_Int32 nEnd = nEnd_;
+ if(nStart >= nStartIndex || nEnd >= nStartIndex)
+ {
+ SpellContentPosition aAdd;
+ aAdd.nLeft = nStart;
+ aAdd.nRight = nEnd;
+ aRedlines.push_back(aAdd);
+ }
+ }
+ }
+ }
+ return aRedlines;
+}
+
+/// remove the redline positions after the current selection
+static void lcl_CutRedlines( SpellContentPositions& aDeletedRedlines, SwEditShell const * pSh )
+{
+ if(!aDeletedRedlines.empty())
+ {
+ SwPaM *pCursor = pSh->GetCursor();
+ const SwPosition* pEndPos = pCursor->End();
+ const sal_Int32 nEnd = pEndPos->GetContentIndex();
+ while(!aDeletedRedlines.empty() &&
+ aDeletedRedlines.back().nLeft > nEnd)
+ {
+ aDeletedRedlines.pop_back();
+ }
+ }
+}
+
+static SpellContentPosition lcl_FindNextDeletedRedline(
+ const SpellContentPositions& rDeletedRedlines,
+ sal_Int32 nSearchFrom )
+{
+ SpellContentPosition aRet;
+ aRet.nLeft = aRet.nRight = SAL_MAX_INT32;
+ if(!rDeletedRedlines.empty())
+ {
+ auto aIter = std::find_if_not(rDeletedRedlines.begin(), rDeletedRedlines.end(),
+ [nSearchFrom](const SpellContentPosition& rPos) { return rPos.nLeft < nSearchFrom; });
+ if (aIter != rDeletedRedlines.end())
+ aRet = *aIter;
+ }
+ return aRet;
+}
+
+bool SwSpellIter::SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck)
+{
+ bool bRet = false;
+ m_aLastPortions.clear();
+ m_aLastPositions.clear();
+
+ SwEditShell *pMySh = GetSh();
+ if( !pMySh )
+ return false;
+
+ OSL_ENSURE( GetEnd(), "SwSpellIter::SpellSentence without Start?");
+
+ uno::Reference< XSpellAlternatives > xSpellRet;
+ linguistic2::ProofreadingResult aGrammarResult;
+ bool bGoOn = true;
+ bool bGrammarErrorFound = false;
+ do {
+ SwPaM *pCursor = pMySh->GetCursor();
+ if ( !pCursor->HasMark() )
+ pCursor->SetMark();
+
+ *pCursor->GetPoint() = *GetCurr();
+ *pCursor->GetMark() = *GetEnd();
+
+ if (m_bBackToStartOfSentence)
+ {
+ pMySh->GoStartSentence();
+ m_bBackToStartOfSentence = false;
+ }
+ uno::Any aSpellRet = pMySh->GetDoc()->Spell(*pCursor, m_xSpeller, nullptr, nullptr,
+ bIsGrammarCheck, pMySh->GetLayout());
+ aSpellRet >>= xSpellRet;
+ aSpellRet >>= aGrammarResult;
+ bGoOn = GetCursorCnt() > 1;
+ bGrammarErrorFound = aGrammarResult.aErrors.hasElements();
+ if( xSpellRet.is() || bGrammarErrorFound )
+ {
+ bGoOn = false;
+ SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() );
+ SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() );
+
+ SetCurr( pNewPoint );
+ SetCurrX( pNewMark );
+ }
+ if( bGoOn )
+ {
+ pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent);
+ pCursor = pMySh->GetCursor();
+ if ( *pCursor->GetPoint() > *pCursor->GetMark() )
+ pCursor->Exchange();
+ SwPosition* pNew = new SwPosition( *pCursor->GetPoint() );
+ SetStart( pNew );
+ pNew = new SwPosition( *pCursor->GetMark() );
+ SetEnd( pNew );
+ pNew = new SwPosition( *GetStart() );
+ SetCurr( pNew );
+ pNew = new SwPosition( *pNew );
+ SetCurrX( pNew );
+ pCursor->SetMark();
+ --GetCursorCnt();
+ }
+ } while ( bGoOn );
+
+ if(xSpellRet.is() || bGrammarErrorFound)
+ {
+ // an error has been found
+ // To fill the spell portions the beginning of the sentence has to be found
+ SwPaM *pCursor = pMySh->GetCursor();
+ // set the mark to the right if necessary
+ if ( *pCursor->GetPoint() > *pCursor->GetMark() )
+ pCursor->Exchange();
+ // the cursor has to be collapsed on the left to go to the start of the sentence - if sentence ends inside of the error
+ pCursor->DeleteMark();
+ pCursor->SetMark();
+ bool bStartSent = pMySh->GoStartSentence();
+ SpellContentPositions aDeletedRedlines = lcl_CollectDeletedRedlines(pMySh);
+ if(bStartSent)
+ {
+ // create a portion from the start part
+ AddPortion(nullptr, nullptr, aDeletedRedlines);
+ }
+ // Set the cursor to the error already found
+ *pCursor->GetPoint() = *GetCurrX();
+ *pCursor->GetMark() = *GetCurr();
+ AddPortion(xSpellRet, &aGrammarResult, aDeletedRedlines);
+
+ // save the end position of the error to continue from here
+ SwPosition aSaveStartPos = *pCursor->End();
+ // determine the end of the current sentence
+ if ( *pCursor->GetPoint() < *pCursor->GetMark() )
+ pCursor->Exchange();
+ // again collapse to start marking after the end of the error
+ pCursor->DeleteMark();
+ pCursor->SetMark();
+
+ pMySh->GoEndSentence();
+ if( bGrammarErrorFound )
+ {
+ const ModelToViewHelper aConversionMap(static_cast<SwTextNode&>(pCursor->GetPointNode()), pMySh->GetLayout());
+ const OUString& aExpandText = aConversionMap.getViewText();
+ sal_Int32 nSentenceEnd =
+ aConversionMap.ConvertToViewPosition( aGrammarResult.nBehindEndOfSentencePosition );
+ // remove trailing space
+ if( aExpandText[nSentenceEnd - 1] == ' ' )
+ --nSentenceEnd;
+ if( pCursor->End()->GetContentIndex() < nSentenceEnd )
+ {
+ pCursor->End()->SetContent(nSentenceEnd);
+ }
+ }
+
+ lcl_CutRedlines( aDeletedRedlines, pMySh );
+ // save the 'global' end of the spellchecking
+ const SwPosition aSaveEndPos = *GetEnd();
+ // set the sentence end as 'local' end
+ SetEnd( new SwPosition( *pCursor->End() ));
+
+ *pCursor->GetPoint() = aSaveStartPos;
+ *pCursor->GetMark() = *GetEnd();
+ // now the rest of the sentence has to be searched for errors
+ // for each error the non-error text between the current and the last error has
+ // to be added to the portions - if necessary broken into same-language-portions
+ if( !bGrammarErrorFound ) //in grammar check there's only one error returned
+ {
+ do
+ {
+ xSpellRet = nullptr;
+ // don't search for grammar errors here anymore!
+ pMySh->GetDoc()->Spell(*pCursor, m_xSpeller, nullptr, nullptr, false,
+ pMySh->GetLayout())
+ >>= xSpellRet;
+ if ( *pCursor->GetPoint() > *pCursor->GetMark() )
+ pCursor->Exchange();
+ SetCurr( new SwPosition( *pCursor->GetPoint() ));
+ SetCurrX( new SwPosition( *pCursor->GetMark() ));
+
+ // if an error has been found go back to the text preceding the error
+ if(xSpellRet.is())
+ {
+ *pCursor->GetPoint() = aSaveStartPos;
+ *pCursor->GetMark() = *GetCurr();
+ }
+ // add the portion
+ AddPortion(nullptr, nullptr, aDeletedRedlines);
+
+ if(xSpellRet.is())
+ {
+ *pCursor->GetPoint() = *GetCurr();
+ *pCursor->GetMark() = *GetCurrX();
+ AddPortion(xSpellRet, nullptr, aDeletedRedlines);
+ // move the cursor to the end of the error string
+ *pCursor->GetPoint() = *GetCurrX();
+ // and save the end of the error as new start position
+ aSaveStartPos = *GetCurrX();
+ // and the end of the sentence
+ *pCursor->GetMark() = *GetEnd();
+ }
+ // if the end of the sentence has already been reached then break here
+ if(*GetCurrX() >= *GetEnd())
+ break;
+ }
+ while(xSpellRet.is());
+ }
+ else
+ {
+ // go to the end of sentence as the grammar check returned it
+ // at this time the Point is behind the grammar error
+ // and the mark points to the sentence end as
+ if ( *pCursor->GetPoint() < *pCursor->GetMark() )
+ pCursor->Exchange();
+ }
+
+ // the part between the last error and the end of the sentence has to be added
+ *pMySh->GetCursor()->GetPoint() = *GetEnd();
+ if(*GetCurrX() < *GetEnd())
+ {
+ AddPortion(nullptr, nullptr, aDeletedRedlines);
+ }
+ // set the shell cursor to the end of the sentence to prevent a visible selection
+ *pCursor->GetMark() = *GetEnd();
+ if( !bIsGrammarCheck )
+ {
+ // set the current position to the end of the sentence
+ SetCurr( new SwPosition(*GetEnd()) );
+ }
+ // restore the 'global' end
+ SetEnd( new SwPosition(aSaveEndPos) );
+ rPortions = m_aLastPortions;
+ bRet = true;
+ }
+ else
+ {
+ // if no error could be found the selection has to be corrected - at least if it's not in the body
+ *pMySh->GetCursor()->GetPoint() = *GetEnd();
+ pMySh->GetCursor()->DeleteMark();
+ }
+
+ return bRet;
+}
+
+void SwSpellIter::ToSentenceStart() { m_bBackToStartOfSentence = true; }
+
+static LanguageType lcl_GetLanguage(SwEditShell& rSh)
+{
+ SvtScriptType nScriptType = rSh.GetScriptType();
+ TypedWhichId<SvxLanguageItem> nLangWhichId = RES_CHRATR_LANGUAGE;
+
+ switch(nScriptType)
+ {
+ case SvtScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break;
+ case SvtScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break;
+ default: break;
+ }
+ SfxItemSet aSet(rSh.GetAttrPool(), nLangWhichId, nLangWhichId);
+ rSh.GetCurAttr( aSet );
+ const SvxLanguageItem& rLang = aSet.Get(nLangWhichId);
+ return rLang.GetLanguage();
+}
+
+/// create a text portion at the given position
+void SwSpellIter::CreatePortion(uno::Reference< XSpellAlternatives > const & xAlt,
+ const linguistic2::ProofreadingResult* pGrammarResult,
+ bool bIsField, bool bIsHidden)
+{
+ svx::SpellPortion aPortion;
+ OUString sText;
+ GetSh()->GetSelectedText( sText );
+ if(sText.isEmpty())
+ return;
+
+ // in case of redlined deletions the selection of an error is not the same as the _real_ word
+ if(xAlt.is())
+ aPortion.sText = xAlt->getWord();
+ else if(pGrammarResult)
+ {
+ aPortion.bIsGrammarError = true;
+ if(pGrammarResult->aErrors.hasElements())
+ {
+ aPortion.aGrammarError = pGrammarResult->aErrors[0];
+ aPortion.sText = pGrammarResult->aText.copy( aPortion.aGrammarError.nErrorStart, aPortion.aGrammarError.nErrorLength );
+ aPortion.xGrammarChecker = pGrammarResult->xProofreader;
+ auto pProperty = std::find_if(std::cbegin(pGrammarResult->aProperties), std::cend(pGrammarResult->aProperties),
+ [](const beans::PropertyValue& rProperty) { return rProperty.Name == "DialogTitle"; });
+ if (pProperty != std::cend(pGrammarResult->aProperties))
+ pProperty->Value >>= aPortion.sDialogTitle;
+ }
+ }
+ else
+ aPortion.sText = sText;
+ aPortion.eLanguage = lcl_GetLanguage(*GetSh());
+ aPortion.bIsField = bIsField;
+ aPortion.bIsHidden = bIsHidden;
+ aPortion.xAlternatives = xAlt;
+ SpellContentPosition aPosition;
+ SwPaM *pCursor = GetSh()->GetCursor();
+ aPosition.nLeft = pCursor->Start()->GetContentIndex();
+ aPosition.nRight = pCursor->End()->GetContentIndex();
+ m_aLastPortions.push_back(aPortion);
+ m_aLastPositions.push_back(aPosition);
+}
+
+void SwSpellIter::AddPortion(uno::Reference< XSpellAlternatives > const & xAlt,
+ const linguistic2::ProofreadingResult* pGrammarResult,
+ const SpellContentPositions& rDeletedRedlines)
+{
+ SwEditShell *pMySh = GetSh();
+ OUString sText;
+ pMySh->GetSelectedText( sText );
+ if(sText.isEmpty())
+ return;
+
+ if(xAlt.is() || pGrammarResult != nullptr)
+ {
+ CreatePortion(xAlt, pGrammarResult, false, false);
+ }
+ else
+ {
+ SwPaM *pCursor = GetSh()->GetCursor();
+ if ( *pCursor->GetPoint() > *pCursor->GetMark() )
+ pCursor->Exchange();
+ // save the start and end positions
+ SwPosition aStart(*pCursor->GetPoint());
+ SwPosition aEnd(*pCursor->GetMark());
+ // iterate over the text to find changes in language
+ // set the mark equal to the point
+ *pCursor->GetMark() = aStart;
+ SwTextNode* pTextNode = pCursor->GetPointNode().GetTextNode();
+ LanguageType eStartLanguage = lcl_GetLanguage(*GetSh());
+ SpellContentPosition aNextRedline = lcl_FindNextDeletedRedline(
+ rDeletedRedlines, aStart.GetContentIndex() );
+ if( aNextRedline.nLeft == aStart.GetContentIndex() )
+ {
+ // select until the end of the current redline
+ const sal_Int32 nEnd = aEnd.GetContentIndex() < aNextRedline.nRight ?
+ aEnd.GetContentIndex() : aNextRedline.nRight;
+ pCursor->GetPoint()->SetContent( nEnd );
+ CreatePortion(xAlt, pGrammarResult, false, true);
+ aStart = *pCursor->End();
+ // search for next redline
+ aNextRedline = lcl_FindNextDeletedRedline(
+ rDeletedRedlines, aStart.GetContentIndex() );
+ }
+ while(*pCursor->GetPoint() < aEnd)
+ {
+ // #125786 in table cell with fixed row height the cursor might not move forward
+ if(!GetSh()->Right(1, SwCursorSkipMode::Cells))
+ break;
+
+ bool bField = false;
+ // read the character at the current position to check if it's a field
+ sal_Unicode const cChar =
+ pTextNode->GetText()[pCursor->GetMark()->GetContentIndex()];
+ if( CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar)
+ {
+ const SwTextAttr* pTextAttr = pTextNode->GetTextAttrForCharAt(
+ pCursor->GetMark()->GetContentIndex() );
+ const sal_uInt16 nWhich = pTextAttr
+ ? pTextAttr->Which()
+ : RES_TXTATR_END;
+ switch (nWhich)
+ {
+ case RES_TXTATR_FIELD:
+ case RES_TXTATR_ANNOTATION:
+ case RES_TXTATR_FTN:
+ case RES_TXTATR_FLYCNT:
+ bField = true;
+ break;
+ }
+ }
+ else if (cChar == CH_TXT_ATR_FORMELEMENT)
+ {
+ SwPosition aPos(*pCursor->GetMark());
+ bField = pMySh->GetDoc()->getIDocumentMarkAccess()->getDropDownFor(aPos);
+ }
+
+ LanguageType eCurLanguage = lcl_GetLanguage(*GetSh());
+ bool bRedline = aNextRedline.nLeft == pCursor->GetPoint()->GetContentIndex();
+ // create a portion if the next character
+ // - is a field,
+ // - is at the beginning of a deleted redline
+ // - has a different language
+ if(bField || bRedline || eCurLanguage != eStartLanguage)
+ {
+ eStartLanguage = eCurLanguage;
+ // go one step back - the cursor currently selects the first character
+ // with a different language
+ // in the case of redlining it's different
+ if(eCurLanguage != eStartLanguage || bField)
+ *pCursor->GetPoint() = *pCursor->GetMark();
+ // set to the last start
+ *pCursor->GetMark() = aStart;
+ // create portion should only be called if a selection exists
+ // there's no selection if there's a field at the beginning
+ if(*pCursor->Start() != *pCursor->End())
+ CreatePortion(xAlt, pGrammarResult, false, false);
+ aStart = *pCursor->End();
+ // now export the field - if there is any
+ if(bField)
+ {
+ *pCursor->GetMark() = *pCursor->GetPoint();
+ GetSh()->Right(1, SwCursorSkipMode::Cells);
+ CreatePortion(xAlt, pGrammarResult, true, false);
+ aStart = *pCursor->End();
+ }
+ }
+ // if a redline start then create a portion for it
+ if(bRedline)
+ {
+ *pCursor->GetMark() = *pCursor->GetPoint();
+ // select until the end of the current redline
+ const sal_Int32 nEnd = aEnd.GetContentIndex() < aNextRedline.nRight ?
+ aEnd.GetContentIndex() : aNextRedline.nRight;
+ pCursor->GetPoint()->SetContent( nEnd );
+ CreatePortion(xAlt, pGrammarResult, false, true);
+ aStart = *pCursor->End();
+ // search for next redline
+ aNextRedline = lcl_FindNextDeletedRedline(
+ rDeletedRedlines, aStart.GetContentIndex() );
+ }
+ *pCursor->GetMark() = *pCursor->GetPoint();
+ }
+ pCursor->SetMark();
+ *pCursor->GetMark() = aStart;
+ CreatePortion(xAlt, pGrammarResult, false, false);
+ }
+}
+
+void SwEditShell::IgnoreGrammarErrorAt( SwPaM& rErrorPosition )
+{
+ SwTextNode *pNode;
+ SwWrongList *pWrong;
+ SwNodeIndex aIdx(rErrorPosition.Start()->GetNode());
+ SwNodeIndex aEndIdx(rErrorPosition.Start()->GetNode());
+ sal_Int32 nStart = rErrorPosition.Start()->GetContentIndex();
+ sal_Int32 nEnd = COMPLETE_STRING;
+ while( aIdx <= aEndIdx )
+ {
+ pNode = aIdx.GetNode().GetTextNode();
+ if( pNode ) {
+ if( aIdx == aEndIdx )
+ nEnd = rErrorPosition.End()->GetContentIndex();
+ pWrong = pNode->GetGrammarCheck();
+ if( pWrong )
+ pWrong->RemoveEntry( nStart, nEnd );
+ pWrong = pNode->GetWrong();
+ if( pWrong )
+ pWrong->RemoveEntry( nStart, nEnd );
+ SwTextFrame::repaintTextFrames( *pNode );
+ }
+ ++aIdx;
+ nStart = 0;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/ednumber.cxx b/sw/source/core/edit/ednumber.cxx
new file mode 100644
index 0000000000..c272aa5f8e
--- /dev/null
+++ b/sw/source/core/edit/ednumber.cxx
@@ -0,0 +1,880 @@
+/* -*- 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 <editsh.hxx>
+#include <edimp.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentState.hxx>
+#include <ndtxt.hxx>
+#include <txtfrm.hxx>
+#include <swundo.hxx>
+#include <numrule.hxx>
+#include <osl/diagnose.h>
+
+SwPamRanges::SwPamRanges( const SwPaM& rRing )
+{
+ for(SwPaM& rTmp : const_cast<SwPaM*>(&rRing)->GetRingContainer())
+ Insert( rTmp.GetMark()->GetNode(), rTmp.GetPoint()->GetNode() );
+}
+
+void SwPamRanges::Insert( const SwNode& rIdx1, const SwNode& rIdx2 )
+{
+ SwPamRange aRg( rIdx1.GetIndex(), rIdx2.GetIndex() );
+ if( aRg.nEnd < aRg.nStart )
+ { aRg.nStart = aRg.nEnd; aRg.nEnd = rIdx1.GetIndex(); }
+
+ o3tl::sorted_vector<SwPamRange>::const_iterator it = maVector.lower_bound(aRg); //search Insert Position
+ size_t nPos = it - maVector.begin();
+ if (!maVector.empty() && (it != maVector.end()) && (*it) == aRg)
+ {
+ // is the one in the Array smaller?
+ SwPamRange const& rTmp = maVector[nPos];
+ if( rTmp.nEnd < aRg.nEnd )
+ {
+ aRg.nEnd = rTmp.nEnd;
+ maVector.erase(maVector.begin() + nPos); // combine
+ }
+ else
+ return; // done, because by precondition everything is combined
+ }
+
+ bool bEnd;
+ do {
+ bEnd = true;
+
+ // combine with predecessor?
+ if( nPos > 0 )
+ {
+ SwPamRange const& rTmp = maVector[nPos-1];
+ if( rTmp.nEnd == aRg.nStart
+ || rTmp.nEnd+1 == aRg.nStart )
+ {
+ aRg.nStart = rTmp.nStart;
+ bEnd = false;
+ maVector.erase( maVector.begin() + --nPos ); // combine
+ }
+ // range contained in rTmp?
+ else if( rTmp.nStart <= aRg.nStart && aRg.nEnd <= rTmp.nEnd )
+ return;
+ }
+ // combine with successor?
+ if( nPos < maVector.size() )
+ {
+ SwPamRange const& rTmp = maVector[nPos];
+ if( rTmp.nStart == aRg.nEnd ||
+ rTmp.nStart == aRg.nEnd+1 )
+ {
+ aRg.nEnd = rTmp.nEnd;
+ bEnd = false;
+ maVector.erase( maVector.begin() + nPos ); // combine
+ }
+
+ // range contained in rTmp?
+ else if( rTmp.nStart <= aRg.nStart && aRg.nEnd <= rTmp.nEnd )
+ return;
+ }
+ } while( !bEnd );
+
+ maVector.insert( aRg );
+}
+
+SwPaM& SwPamRanges::SetPam( size_t nArrPos, SwPaM& rPam )
+{
+ assert( nArrPos < Count() );
+ const SwPamRange& rTmp = maVector[ nArrPos ];
+ rPam.GetPoint()->Assign(rTmp.nStart);
+ rPam.SetMark();
+ rPam.GetPoint()->Assign(rTmp.nEnd);
+ return rPam;
+}
+
+// Rule book for outline numbering
+
+void SwEditShell::SetOutlineNumRule(const SwNumRule& rRule)
+{
+ StartAllAction(); // bracketing for updating!
+ GetDoc()->SetOutlineNumRule(rRule);
+ EndAllAction();
+}
+
+const SwNumRule* SwEditShell::GetOutlineNumRule() const
+{
+ return GetDoc()->GetOutlineNumRule();
+}
+
+// Set if there is no numbering yet, else update.
+// Works with old and new rules. Update only differences.
+
+// paragraphs without numbering, with indentations
+void SwEditShell::NoNum()
+{
+ StartAllAction();
+
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->GetNext() != pCursor ) // Multiple selection?
+ {
+ GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
+ SwPamRanges aRangeArr( *pCursor );
+ SwPaM aPam( *pCursor->GetPoint() );
+ for( size_t n = 0; n < aRangeArr.Count(); ++n )
+ GetDoc()->NoNum( aRangeArr.SetPam( n, aPam ));
+ GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
+ }
+ else
+ // sw_redlinehide: leave cursor as is, will be split at Point & apply to new node
+ GetDoc()->NoNum( *pCursor );
+
+ EndAllAction();
+}
+
+// The entire selection is numbered (ignoring unnumbered empty lines)
+bool SwEditShell::SelectionHasNumber() const
+{
+ bool bResult = false;
+ for (SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ SwNodeOffset nStt = rPaM.Start()->GetNodeIndex();
+ SwNodeOffset nEnd = rPaM.End()->GetNodeIndex();
+ for (SwNodeOffset nPos = nStt; nPos<=nEnd; nPos++)
+ {
+ SwTextNode* pTextNd = mxDoc->GetNodes()[nPos]->GetTextNode();
+ if (pTextNd)
+ {
+ pTextNd = sw::GetParaPropsNode(*GetLayout(), *pTextNd);
+ }
+ if (pTextNd && (!bResult || pTextNd->Len()!=0))
+ {
+ bResult = pTextNd->HasNumber();
+
+ // #b6340308# special case: outline numbered, not counted paragraph
+ if (bResult &&
+ pTextNd->GetNumRule() == GetDoc()->GetOutlineNumRule() &&
+ !pTextNd->IsCountedInList())
+ {
+ bResult = false;
+ }
+ if (!bResult && pTextNd->Len())
+ break;
+ }
+ }
+ }
+
+ return bResult;
+}
+
+// add a new function to determine number on/off status
+bool SwEditShell::SelectionHasBullet() const
+{
+ bool bResult = false;
+ for (SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ SwNodeOffset nStt = rPaM.Start()->GetNodeIndex();
+ SwNodeOffset nEnd = rPaM.End()->GetNodeIndex();
+ for (SwNodeOffset nPos = nStt; nPos<=nEnd; nPos++)
+ {
+ SwTextNode* pTextNd = mxDoc->GetNodes()[nPos]->GetTextNode();
+ if (pTextNd)
+ {
+ pTextNd = sw::GetParaPropsNode(*GetLayout(), *pTextNd);
+ }
+ if (pTextNd && (!bResult || pTextNd->Len()!=0))
+ {
+ bResult = pTextNd->HasBullet();
+
+ if (!bResult && pTextNd->Len())
+ break;
+ }
+ }
+ }
+
+ return bResult;
+}
+
+// -> #i29560#
+bool SwEditShell::HasNumber() const
+{
+ bool bResult = false;
+
+ const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), GetCursor()->GetPoint()->GetNode());
+
+ if (pTextNd)
+ {
+ bResult = pTextNd->HasNumber();
+
+ // special case: outline numbered, not counted paragraph
+ if ( bResult &&
+ pTextNd->GetNumRule() == GetDoc()->GetOutlineNumRule() &&
+ !pTextNd->IsCountedInList() )
+ {
+ bResult = false;
+ }
+ }
+
+ return bResult;
+}
+
+bool SwEditShell::HasBullet() const
+{
+ bool bResult = false;
+
+ const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), GetCursor()->GetPoint()->GetNode());
+
+ if (pTextNd)
+ {
+ bResult = pTextNd->HasBullet();
+ }
+
+ return bResult;
+}
+// <- #i29560#
+
+// delete, split list
+void SwEditShell::DelNumRules()
+{
+ StartAllAction();
+
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->IsMultiSelection() )
+ {
+ GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
+ for (SwPaM& rPaM : pCursor->GetRingContainer())
+ {
+ GetDoc()->DelNumRules(rPaM, GetLayout());
+ }
+ GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
+ }
+ else
+ GetDoc()->DelNumRules(*pCursor, GetLayout());
+
+ // Call AttrChangeNotify on the UI-side. Should actually be redundant but there was a bug once.
+ CallChgLnk();
+
+ // Cursor cannot be in front of a label anymore, because numbering/bullet is deleted.
+ SetInFrontOfLabel( false );
+
+ GetDoc()->getIDocumentState().SetModified();
+ EndAllAction();
+}
+
+// up- & downgrading
+void SwEditShell::NumUpDown( bool bDown )
+{
+ StartAllAction();
+
+ SwPaM* pCursor = GetCursor();
+ if( !pCursor->IsMultiSelection() )
+ GetDoc()->NumUpDown(*pCursor, bDown, GetLayout());
+ else
+ {
+ GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
+ SwPamRanges aRangeArr( *pCursor );
+ SwPaM aPam( *pCursor->GetPoint() );
+ for( size_t n = 0; n < aRangeArr.Count(); ++n )
+ GetDoc()->NumUpDown(aRangeArr.SetPam( n, aPam ), bDown, GetLayout());
+ GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
+ }
+ GetDoc()->getIDocumentState().SetModified();
+
+ // #i54693# Update marked numbering levels
+ if ( IsInFrontOfLabel() )
+ UpdateMarkedListLevel();
+
+ CallChgLnk();
+
+ EndAllAction();
+}
+
+bool SwEditShell::IsFirstOfNumRuleAtCursorPos() const
+{
+ return SwDoc::IsFirstOfNumRuleAtPos(*GetCursor()->GetPoint(), *GetLayout());
+}
+
+// -> #i23725#, #i90078#
+void SwEditShell::ChangeIndentOfAllListLevels( const sal_Int32 nDiff )
+{
+ StartAllAction();
+
+ const SwNumRule *pCurNumRule = GetNumRuleAtCurrCursorPos();
+ if ( pCurNumRule != nullptr )
+ {
+ SwNumRule aRule(*pCurNumRule);
+ const SwNumFormat& aRootNumFormat(aRule.Get(0));
+ if( nDiff > 0 || aRootNumFormat.GetIndentAt() + nDiff > 0) // fdo#42708
+ {
+ // #i90078#
+ aRule.ChangeIndent( nDiff );
+ }
+ // no start of new list
+ SetCurNumRule( aRule, false );
+ }
+
+ EndAllAction();
+}
+
+// #i90078#
+void SwEditShell::SetIndent(short nIndent, const SwPosition & rPos)
+{
+ StartAllAction();
+
+ SwPosition pos(rPos);
+ SwNumRule *pCurNumRule = SwDoc::GetNumRuleAtPos(pos, GetLayout());
+
+ if (pCurNumRule)
+ {
+ SwNumRule aRule(*pCurNumRule);
+ if ( !IsMultiSelection() && IsFirstOfNumRuleAtCursorPos() )
+ {
+ aRule.SetIndentOfFirstListLevelAndChangeOthers( nIndent );
+ }
+ else
+ {
+ const SwTextNode* pTextNode = pos.GetNode().GetTextNode();
+ if ( pTextNode != nullptr
+ && pTextNode->GetActualListLevel() >= 0 )
+ {
+ aRule.SetIndent( nIndent, static_cast< sal_uInt16 >( pTextNode->GetActualListLevel() ) );
+ }
+ }
+
+ // change numbering rule - changed numbering rule is not applied at <aPaM>
+ SwPaM aPaM(pos);
+ GetDoc()->SetNumRule(aPaM, aRule, false, GetLayout(), OUString(), false);
+ }
+
+ EndAllAction();
+}
+
+bool SwEditShell::MoveParagraph( SwNodeOffset nOffset )
+{
+ StartAllAction();
+
+ SwPaM *pCursor = GetCursor();
+
+ bool bRet = GetDoc()->MoveParagraph( *pCursor, nOffset );
+
+ GetDoc()->getIDocumentState().SetModified();
+ EndAllAction();
+ return bRet;
+}
+
+int SwEditShell::GetCurrentParaOutlineLevel( ) const
+{
+ int nLevel = 0;
+
+ SwPaM* pCursor = GetCursor();
+ const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode());
+ if (pTextNd)
+ nLevel = pTextNd->GetAttrOutlineLevel();
+ return nLevel;
+}
+
+void SwEditShell::GetCurrentOutlineLevels( sal_uInt8& rUpper, sal_uInt8& rLower )
+{
+ SwPaM* pCursor = GetCursor();
+ SwPaM aCursor( *pCursor->Start() );
+ aCursor.SetMark();
+ if( pCursor->HasMark() )
+ *aCursor.GetPoint() = *pCursor->End();
+ SwDoc::GotoNextNum(*aCursor.GetPoint(), GetLayout(), false, &rUpper, &rLower);
+}
+
+bool SwEditShell::MoveNumParas( bool bUpperLower, bool bUpperLeft )
+{
+ StartAllAction();
+
+ // On all selections?
+ SwPaM* pCursor = GetCursor();
+ SwPaM aCursor( *pCursor->Start() );
+ aCursor.SetMark();
+
+ if( pCursor->HasMark() )
+ *aCursor.GetPoint() = *pCursor->End();
+
+ bool bRet = false;
+ sal_uInt8 nUpperLevel, nLowerLevel;
+ if (SwDoc::GotoNextNum( *aCursor.GetPoint(), GetLayout(), false,
+ &nUpperLevel, &nLowerLevel ))
+ {
+ if( bUpperLower )
+ {
+ // on top of the next numbering
+ SwNodeOffset nOffset(0);
+ const SwNode* pNd;
+
+ if( bUpperLeft ) // move up
+ {
+ SwPosition aPos( *aCursor.GetMark() );
+ if (SwDoc::GotoPrevNum( aPos, GetLayout(), false ))
+ nOffset = aPos.GetNodeIndex() -
+ aCursor.GetMark()->GetNodeIndex();
+ else
+ {
+ SwNodeOffset nStt = aPos.GetNodeIndex(), nIdx = nStt - 1;
+
+ if (SwTextNode const*const pStt = aPos.GetNode().GetTextNode())
+ {
+ std::pair<SwTextNode *, SwTextNode *> nodes(
+ sw::GetFirstAndLastNode(*GetLayout(), *pStt));
+ nIdx = nodes.first->GetIndex() - 1;
+ }
+ while( nIdx && (
+ ( pNd = GetDoc()->GetNodes()[ nIdx ])->IsSectionNode() ||
+ ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsSectionNode())))
+ --nIdx;
+ if( GetDoc()->GetNodes()[ nIdx ]->IsTextNode() )
+ nOffset = nIdx - nStt;
+ }
+ }
+ else // move down
+ {
+ assert(!aCursor.GetPointNode().IsTextNode()
+ || sw::IsParaPropsNode(*GetLayout(), *aCursor.GetPointNode().GetTextNode()));
+ const SwNumRule* pOrig = sw::GetParaPropsNode(*GetLayout(), *aCursor.GetMarkNode().GetTextNode())->GetNumRule();
+ if( aCursor.GetPointNode().IsTextNode() &&
+ pOrig == aCursor.GetPointNode().GetTextNode()->GetNumRule() )
+ {
+ SwNodeOffset nStt = aCursor.GetPoint()->GetNodeIndex(), nIdx = nStt+1;
+ if (SwTextNode const*const pStt = aCursor.GetPoint()->GetNode().GetTextNode())
+ {
+ std::pair<SwTextNode *, SwTextNode *> nodes(
+ sw::GetFirstAndLastNode(*GetLayout(), *pStt));
+ nIdx = nodes.second->GetIndex() + 1;
+ }
+
+ while (nIdx < GetDoc()->GetNodes().Count()-1)
+ {
+ pNd = GetDoc()->GetNodes()[ nIdx ];
+
+ if (pNd->IsSectionNode() ||
+ (pNd->IsEndNode() && pNd->StartOfSectionNode()->IsSectionNode()))
+ {
+ ++nIdx;
+ }
+ else if (pNd->IsTextNode())
+ {
+ SwTextNode const*const pTextNode =
+ sw::GetParaPropsNode(*GetLayout(), *pNd);
+ if (pOrig == pTextNode->GetNumRule()
+ && pTextNode->GetActualListLevel() > nUpperLevel)
+ {
+ std::pair<SwTextNode *, SwTextNode *> nodes(
+ sw::GetFirstAndLastNode(*GetLayout(), *pTextNode));
+ nIdx = nodes.second->GetIndex() + 1;
+ }
+ else
+ {
+ break;
+ }
+ }
+ // #i57856#
+ else
+ {
+ break;
+ }
+ }
+
+ if( nStt == nIdx || !GetDoc()->GetNodes()[ nIdx ]->IsTextNode() )
+ nOffset = SwNodeOffset(1);
+ else
+ nOffset = nIdx - nStt;
+ }
+ else
+ nOffset = SwNodeOffset(1);
+ }
+
+ if( nOffset )
+ {
+ aCursor.Move( fnMoveBackward, GoInNode );
+ bRet = GetDoc()->MoveParagraph( aCursor, nOffset );
+ }
+ }
+ else if( (bUpperLeft ? nUpperLevel : nLowerLevel+1) < MAXLEVEL )
+ {
+ aCursor.Move( fnMoveBackward, GoInNode );
+ bRet = GetDoc()->NumUpDown(aCursor, !bUpperLeft, GetLayout());
+ }
+ }
+
+ GetDoc()->getIDocumentState().SetModified();
+ EndAllAction();
+ return bRet;
+}
+
+bool SwEditShell::OutlineUpDown( short nOffset )
+{
+ StartAllAction();
+
+ bool bRet = true;
+ SwPaM* pCursor = GetCursor();
+ if( !pCursor->IsMultiSelection() )
+ bRet = GetDoc()->OutlineUpDown(*pCursor, nOffset, GetLayout());
+ else
+ {
+ GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
+ SwPamRanges aRangeArr( *pCursor );
+ SwPaM aPam( *pCursor->GetPoint() );
+ for( size_t n = 0; n < aRangeArr.Count(); ++n )
+ bRet = bRet && GetDoc()->OutlineUpDown(
+ aRangeArr.SetPam(n, aPam), nOffset, GetLayout());
+ GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
+ }
+ GetDoc()->getIDocumentState().SetModified();
+ EndAllAction();
+ return bRet;
+}
+
+bool SwEditShell::MoveOutlinePara( SwOutlineNodes::difference_type nOffset )
+{
+ StartAllAction();
+ bool bRet = GetDoc()->MoveOutlinePara( *GetCursor(), nOffset );
+ EndAllAction();
+ return bRet;
+}
+
+// Outlines and SubOutline are ReadOnly?
+bool SwEditShell::IsProtectedOutlinePara() const
+{
+ bool bRet = false;
+ const SwNode& rNd = GetCursor()->Start()->GetNode();
+ if( rNd.IsTextNode() )
+ {
+ const SwOutlineNodes& rOutlNd = GetDoc()->GetNodes().GetOutLineNds();
+ SwNode* pNd = const_cast<SwNode*>(&rNd);
+ bool bFirst = true;
+ SwOutlineNodes::size_type nPos;
+ int nLvl(0);
+ if( !rOutlNd.Seek_Entry( pNd, &nPos ) && nPos )
+ --nPos;
+
+ for( ; nPos < rOutlNd.size(); ++nPos )
+ {
+ SwNode* pTmpNd = rOutlNd[ nPos ];
+
+ if (!sw::IsParaPropsNode(*GetLayout(), *pTmpNd->GetTextNode()))
+ {
+ continue;
+ }
+
+ int nTmpLvl = pTmpNd->GetTextNode()->GetAttrOutlineLevel();
+
+ OSL_ENSURE( nTmpLvl >= 0 && nTmpLvl <= MAXLEVEL,
+ "<SwEditShell::IsProtectedOutlinePara()>" );
+
+ if( bFirst )
+ {
+ nLvl = nTmpLvl;
+ bFirst = false;
+ }
+ else if( nLvl >= nTmpLvl )
+ break;
+
+ if( pTmpNd->IsProtect() )
+ {
+ bRet = true;
+ break;
+ }
+ }
+ }
+#if OSL_DEBUG_LEVEL > 0
+ else
+ {
+ OSL_FAIL("Cursor not on an outline node");
+ }
+#endif
+ return bRet;
+}
+
+/** Test whether outline may be moved (bCopy == false)
+ * or copied (bCopy == true)
+ * Verify these conditions:
+ * 1) outline must be within main body (and not in redline)
+ * 2) outline must not be within table
+ * 3) if bCopy is set, outline must not be write protected
+ */
+static bool lcl_IsOutlineMoveAndCopyable(SwEditShell const& rShell,
+ SwOutlineNodes::size_type const nIdx, bool const bCopy)
+{
+ const SwNodes& rNds = rShell.GetDoc()->GetNodes();
+ const SwNode* pNd = rNds.GetOutLineNds()[ nIdx ];
+ return pNd->GetIndex() >= rNds.GetEndOfExtras().GetIndex() && // 1) body
+ !pNd->FindTableNode() && // 2) table
+ sw::IsParaPropsNode(*rShell.GetLayout(), *pNd->GetTextNode()) &&
+ ( bCopy || !pNd->IsProtect() ); // 3) write
+}
+
+bool SwEditShell::IsOutlineMovable( SwOutlineNodes::size_type nIdx ) const
+{
+ return lcl_IsOutlineMoveAndCopyable( *this, nIdx, false );
+}
+
+bool SwEditShell::IsOutlineCopyable( SwOutlineNodes::size_type nIdx ) const
+{
+ return lcl_IsOutlineMoveAndCopyable( *this, nIdx, true );
+}
+
+bool SwEditShell::NumOrNoNum(
+ bool bNumOn,
+ bool bChkStart )
+{
+ bool bRet = false;
+
+ if ( !IsMultiSelection()
+ && !HasSelection()
+ && ( !bChkStart || IsSttPara() ) )
+ {
+ StartAllAction();
+ SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *GetCursor()->GetPoint()));
+ bRet = GetDoc()->NumOrNoNum(pos.GetNode(), !bNumOn);
+ EndAllAction();
+ }
+ return bRet;
+}
+
+bool SwEditShell::IsNoNum( bool bChkStart ) const
+{
+ // a Backspace in the paragraph without number becomes a Delete
+ bool bResult = false;
+
+ if ( !IsMultiSelection()
+ && !HasSelection()
+ && ( !bChkStart || IsSttPara() ) )
+ {
+ const SwTextNode* pTextNd = sw::GetParaPropsNode(*GetLayout(), GetCursor()->GetPoint()->GetNode());
+ if ( pTextNd != nullptr )
+ {
+ bResult = !pTextNd->IsCountedInList();
+ }
+ }
+
+ return bResult;
+}
+
+sal_uInt8 SwEditShell::GetNumLevel() const
+{
+ // return current level where the point of the cursor is
+ sal_uInt8 nLevel = MAXLEVEL;
+
+ SwPaM* pCursor = GetCursor();
+ const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode());
+
+ OSL_ENSURE( pTextNd, "GetNumLevel() without text node" );
+ if ( pTextNd == nullptr )
+ return nLevel;
+
+ const SwNumRule* pRule = pTextNd->GetNumRule();
+ if ( pRule != nullptr )
+ {
+ const int nListLevelOfTextNode( pTextNd->GetActualListLevel() );
+ if ( nListLevelOfTextNode >= 0 )
+ {
+ nLevel = static_cast<sal_uInt8>( nListLevelOfTextNode );
+ }
+ }
+
+ return nLevel;
+}
+
+const SwNumRule* SwEditShell::GetNumRuleAtCurrCursorPos() const
+{
+ SwPosition pos(*GetCursor()->GetPoint());
+ return SwDoc::GetNumRuleAtPos( pos, GetLayout() );
+}
+
+const SwNumRule* SwEditShell::GetNumRuleAtCurrentSelection() const
+{
+ const SwNumRule* pNumRuleAtCurrentSelection = nullptr;
+
+ bool bDifferentNumRuleFound = false;
+ for(const SwPaM& rCurrentCursor : GetCursor()->GetRingContainer())
+ {
+ const SwNode& rEndNode(rCurrentCursor.End()->GetNode());
+
+ for ( SwNodeIndex aNode(rCurrentCursor.Start()->GetNode()); aNode <= rEndNode; ++aNode )
+ {
+ SwPosition pos(aNode);
+ const SwNumRule* pNumRule = SwDoc::GetNumRuleAtPos(pos, GetLayout());
+ if ( pNumRule == nullptr )
+ {
+ continue;
+ }
+ else if ( pNumRule != pNumRuleAtCurrentSelection )
+ {
+ if ( pNumRuleAtCurrentSelection == nullptr )
+ {
+ pNumRuleAtCurrentSelection = pNumRule;
+ }
+ else
+ {
+ pNumRuleAtCurrentSelection = nullptr;
+ bDifferentNumRuleFound = true;
+ break;
+ }
+ }
+ }
+ if(bDifferentNumRuleFound)
+ break;
+ }
+
+ return pNumRuleAtCurrentSelection;
+}
+
+void SwEditShell::SetCurNumRule( const SwNumRule& rRule,
+ bool bCreateNewList,
+ const OUString& rContinuedListId,
+ const bool bResetIndentAttrs )
+{
+ StartAllAction();
+
+ GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::INSATTR, nullptr );
+
+ SwPaM* pCursor = GetCursor();
+ if( IsMultiSelection() )
+ {
+ OUString sContinuedListId(rContinuedListId);
+ for (SwPaM& rPaM : pCursor->GetRingContainer())
+ {
+ OUString sListId = GetDoc()->SetNumRule(rPaM, rRule,
+ bCreateNewList, GetLayout(), sContinuedListId,
+ true, bResetIndentAttrs );
+
+ //tdf#87548 On creating a new list for a multi-selection only
+ //create a single new list for the multi-selection, not one per selection
+ if (bCreateNewList)
+ {
+ sContinuedListId = sListId;
+ bCreateNewList = false;
+ }
+
+ GetDoc()->SetCounted(rPaM, true, GetLayout());
+ }
+ }
+ else
+ {
+ GetDoc()->SetNumRule( *pCursor, rRule,
+ bCreateNewList, GetLayout(), rContinuedListId,
+ true, bResetIndentAttrs );
+ GetDoc()->SetCounted( *pCursor, true, GetLayout() );
+ }
+ GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::INSATTR, nullptr );
+
+ EndAllAction();
+}
+
+OUString SwEditShell::GetUniqueNumRuleName() const
+{
+ return GetDoc()->GetUniqueNumRuleName();
+}
+
+void SwEditShell::ChgNumRuleFormats( const SwNumRule& rRule )
+{
+ StartAllAction();
+ GetDoc()->ChgNumRuleFormats( rRule );
+ EndAllAction();
+}
+
+void SwEditShell::ReplaceNumRule( const OUString& rOldRule, const OUString& rNewRule )
+{
+ StartAllAction();
+ SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *GetCursor()->GetPoint()));
+ GetDoc()->ReplaceNumRule( pos, rOldRule, rNewRule );
+ EndAllAction();
+}
+
+void SwEditShell::SetNumRuleStart( bool bFlag, SwPaM* pPaM )
+{
+ StartAllAction();
+ SwPaM* pCursor = pPaM ? pPaM : GetCursor();
+ if( pCursor->IsMultiSelection() ) // multiple selection ?
+ {
+ GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
+ SwPamRanges aRangeArr( *pCursor );
+ SwPaM aPam( *pCursor->GetPoint() );
+ for( size_t n = 0; n < aRangeArr.Count(); ++n )
+ {
+ SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *aRangeArr.SetPam( n, aPam ).GetPoint()));
+ GetDoc()->SetNumRuleStart( pos, bFlag );
+ }
+ GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
+ }
+ else
+ {
+ SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *GetCursor()->GetPoint()));
+ GetDoc()->SetNumRuleStart(pos, bFlag);
+ }
+
+ EndAllAction();
+}
+
+bool SwEditShell::IsNumRuleStart( SwPaM* pPaM ) const
+{
+ SwPaM* pCursor = pPaM ? pPaM : GetCursor( );
+ const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode());
+ return pTextNd && pTextNd->IsListRestart();
+}
+
+void SwEditShell::SetNodeNumStart( sal_uInt16 nStt )
+{
+ StartAllAction();
+
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->IsMultiSelection() ) // multiple selection ?
+ {
+ GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
+ SwPamRanges aRangeArr( *pCursor );
+ SwPaM aPam( *pCursor->GetPoint() );
+ for( size_t n = 0; n < aRangeArr.Count(); ++n )
+ {
+ SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *aRangeArr.SetPam( n, aPam ).GetPoint()));
+ GetDoc()->SetNodeNumStart( pos, nStt );
+ }
+ GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
+ }
+ else
+ {
+ SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *pCursor->GetPoint()));
+ GetDoc()->SetNodeNumStart( pos, nStt );
+ }
+
+ EndAllAction();
+}
+
+sal_uInt16 SwEditShell::GetNodeNumStart( SwPaM* pPaM ) const
+{
+ SwPaM* pCursor = pPaM ? pPaM : GetCursor();
+ const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode());
+ // correction: check, if list restart value is set at text node and
+ // use new method <SwTextNode::GetAttrListRestartValue()>.
+ // return USHRT_MAX, if no list restart value is found.
+ if ( pTextNd && pTextNd->HasAttrListRestartValue() )
+ {
+ return o3tl::narrowing<sal_uInt16>(pTextNd->GetAttrListRestartValue());
+ }
+ return USHRT_MAX;
+}
+
+const SwNumRule * SwEditShell::SearchNumRule( const bool bNum,
+ OUString& sListId )
+{
+ return GetDoc()->SearchNumRule( *(GetCursor()->Start()),
+ false/*bForward*/, bNum, false/*bOutline*/, -1/*nNonEmptyAllowe*/,
+ sListId, GetLayout() );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/edredln.cxx b/sw/source/core/edit/edredln.cxx
new file mode 100644
index 0000000000..1778745f57
--- /dev/null
+++ b/sw/source/core/edit/edredln.cxx
@@ -0,0 +1,196 @@
+/* -*- 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 <IDocumentRedlineAccess.hxx>
+#include <docary.hxx>
+#include <redline.hxx>
+#include <doc.hxx>
+#include <editsh.hxx>
+#include <frmtool.hxx>
+
+RedlineFlags SwEditShell::GetRedlineFlags() const
+{
+ return GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags();
+}
+
+void SwEditShell::SetRedlineFlags( RedlineFlags eMode )
+{
+ if( eMode != GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags() )
+ {
+ CurrShell aCurr( this );
+ StartAllAction();
+ GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eMode );
+ EndAllAction();
+ }
+}
+
+bool SwEditShell::IsRedlineOn() const
+{
+ return GetDoc()->getIDocumentRedlineAccess().IsRedlineOn();
+}
+
+SwRedlineTable::size_type SwEditShell::GetRedlineCount() const
+{
+ return GetDoc()->getIDocumentRedlineAccess().GetRedlineTable().size();
+}
+
+const SwRangeRedline& SwEditShell::GetRedline( SwRedlineTable::size_type nPos ) const
+{
+ return *GetDoc()->getIDocumentRedlineAccess().GetRedlineTable()[ nPos ];
+}
+
+static void lcl_InvalidateAll( SwViewShell* pSh )
+{
+ for(SwViewShell& rCurrentShell : pSh->GetRingContainer())
+ {
+ if ( rCurrentShell.GetWin() )
+ rCurrentShell.GetWin()->Invalidate();
+ }
+}
+
+bool SwEditShell::AcceptRedline( SwRedlineTable::size_type nPos )
+{
+ CurrShell aCurr( this );
+ StartAllAction();
+ bool bRet = GetDoc()->getIDocumentRedlineAccess().AcceptRedline( nPos, true, true );
+ if( !nPos && !::IsExtraData( GetDoc() ) )
+ lcl_InvalidateAll( this );
+ EndAllAction();
+ return bRet;
+}
+
+bool SwEditShell::RejectRedline( SwRedlineTable::size_type nPos )
+{
+ CurrShell aCurr( this );
+ StartAllAction();
+ bool bRet = GetDoc()->getIDocumentRedlineAccess().RejectRedline( nPos, true, true );
+ if( !nPos && !::IsExtraData( GetDoc() ) )
+ lcl_InvalidateAll( this );
+ EndAllAction();
+ return bRet;
+}
+
+bool SwEditShell::AcceptRedlinesInSelection()
+{
+ CurrShell aCurr( this );
+ StartAllAction();
+ // in table selection mode, process the selected boxes in reverse order
+ // to allow accepting their text changes and the tracked row deletions
+ bool bRet = false;
+ if ( IsTableMode() )
+ {
+ const SwSelBoxes& rBoxes = GetTableCursor()->GetSelectedBoxes();
+ std::vector<std::unique_ptr<SwPaM>> vBoxes;
+ for(auto pBox : rBoxes)
+ {
+ if ( !pBox->IsEmpty() )
+ {
+ const SwStartNode *pSttNd = pBox->GetSttNd();
+ SwNode* pEndNode = pSttNd->GetNodes()[pSttNd->EndOfSectionIndex()];
+ vBoxes.push_back(std::unique_ptr<SwPaM>(new SwPaM(*pEndNode, 0, *pSttNd, 0)));
+ }
+ }
+
+ for (size_t i = 0; i < vBoxes.size(); ++i)
+ bRet |= GetDoc()->getIDocumentRedlineAccess().AcceptRedline( *vBoxes[vBoxes.size()-i-1], true );
+ }
+ else
+ bRet = GetDoc()->getIDocumentRedlineAccess().AcceptRedline( *GetCursor(), true );
+ EndAllAction();
+ return bRet;
+}
+
+bool SwEditShell::RejectRedlinesInSelection()
+{
+ CurrShell aCurr( this );
+ StartAllAction();
+ bool bRet = false;
+ // in table selection mode, process the selected boxes in reverse order
+ // to allow rejecting their text changes and the tracked row insertions
+ if ( IsTableMode() )
+ {
+ const SwSelBoxes& rBoxes = GetTableCursor()->GetSelectedBoxes();
+ std::vector<std::unique_ptr<SwPaM>> vBoxes;
+ for(auto pBox : rBoxes)
+ {
+ if ( !pBox->IsEmpty() )
+ {
+ const SwStartNode *pSttNd = pBox->GetSttNd();
+ SwNode* pEndNode = pSttNd->GetNodes()[pSttNd->EndOfSectionIndex()];
+ vBoxes.push_back(std::unique_ptr<SwPaM>(new SwPaM(*pEndNode, 0, *pSttNd, 0)));
+ }
+ }
+
+ for (size_t i = 0; i < vBoxes.size(); ++i)
+ bRet |= GetDoc()->getIDocumentRedlineAccess().RejectRedline( *vBoxes[vBoxes.size()-i-1], true );
+ }
+ else
+ bRet = GetDoc()->getIDocumentRedlineAccess().RejectRedline( *GetCursor(), true );
+ EndAllAction();
+ return bRet;
+}
+
+// Set the comment at the Redline
+bool SwEditShell::SetRedlineComment( const OUString& rS )
+{
+ bool bRet = false;
+ for(const SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ bRet = bRet || GetDoc()->getIDocumentRedlineAccess().SetRedlineComment( rPaM, rS );
+ }
+
+ return bRet;
+}
+
+const SwRangeRedline* SwEditShell::GetCurrRedline() const
+{
+ if (const SwRangeRedline* pRed = GetDoc()->getIDocumentRedlineAccess().GetRedline( *GetCursor()->GetPoint(), nullptr ))
+ return pRed;
+ // check the other side of the selection to handle completely selected changes, where the Point is at the end
+ return GetDoc()->getIDocumentRedlineAccess().GetRedline( *GetCursor()->GetMark(), nullptr );
+}
+
+void SwEditShell::UpdateRedlineAttr()
+{
+ if( IDocumentRedlineAccess::IsShowChanges(GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags()) )
+ {
+ CurrShell aCurr( this );
+ StartAllAction();
+
+ GetDoc()->getIDocumentRedlineAccess().UpdateRedlineAttr();
+
+ EndAllAction();
+ }
+}
+
+/** Search the Redline of the data given
+ *
+ * @return Returns the Pos of the Array, or SwRedlineTable::npos if not present
+ */
+SwRedlineTable::size_type SwEditShell::FindRedlineOfData( const SwRedlineData& rData ) const
+{
+ const SwRedlineTable& rTable = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable();
+
+ for( SwRedlineTable::size_type i = 0, nCnt = rTable.size(); i < nCnt; ++i )
+ if( &rTable[ i ]->GetRedlineData() == &rData )
+ return i;
+ return SwRedlineTable::npos;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/edsect.cxx b/sw/source/core/edit/edsect.cxx
new file mode 100644
index 0000000000..a7e652aea9
--- /dev/null
+++ b/sw/source/core/edit/edsect.cxx
@@ -0,0 +1,439 @@
+/* -*- 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 <editsh.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentContentOperations.hxx>
+#include <pam.hxx>
+#include <docary.hxx>
+#include <swundo.hxx>
+#include <section.hxx>
+#include <sectfrm.hxx>
+#include <cntfrm.hxx>
+#include <tabfrm.hxx>
+#include <rootfrm.hxx>
+#include <osl/diagnose.h>
+
+SwSection const*
+SwEditShell::InsertSection(
+ SwSectionData & rNewData, SfxItemSet const*const pAttr)
+{
+ const SwSection* pRet = nullptr;
+ if( !IsTableMode() )
+ {
+ StartAllAction();
+ GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::INSSECTION, nullptr );
+
+ for(const SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ SwSection const*const pNew =
+ GetDoc()->InsertSwSection( rPaM, rNewData, nullptr, pAttr );
+ if( !pRet )
+ pRet = pNew;
+ }
+
+ GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::INSSECTION, nullptr );
+ EndAllAction();
+ }
+ return pRet;
+}
+
+bool SwEditShell::IsInsRegionAvailable() const
+{
+ if( IsTableMode() )
+ return false;
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->GetNext() != pCursor )
+ return false;
+ if( pCursor->HasMark() )
+ return 0 != SwDoc::IsInsRegionAvailable( *pCursor );
+
+ return true;
+}
+
+const SwSection* SwEditShell::GetCurrSection() const
+{
+ if( IsTableMode() )
+ return nullptr;
+
+ return SwDoc::GetCurrSection( *GetCursor()->GetPoint() );
+}
+
+/** Deliver the responsible area of the columns.
+ *
+ * In footnotes it may not be the area within the footnote.
+ */
+SwSection* SwEditShell::GetAnySection( bool bOutOfTab, const Point* pPt )
+{
+ SwFrame *pFrame;
+ if ( pPt )
+ {
+ SwPosition aPos( *GetCursor()->GetPoint() );
+ Point aPt( *pPt );
+ GetLayout()->GetModelPositionForViewPoint( &aPos, aPt );
+ SwContentNode *pNd = aPos.GetNode().GetContentNode();
+ std::pair<Point, bool> const tmp(*pPt, true);
+ pFrame = pNd->getLayoutFrame(GetLayout(), nullptr, &tmp);
+ }
+ else
+ pFrame = GetCurrFrame( false );
+
+ if( bOutOfTab && pFrame )
+ pFrame = pFrame->FindTabFrame();
+ if( pFrame && pFrame->IsInSct() )
+ {
+ SwSectionFrame* pSect = pFrame->FindSctFrame();
+ OSL_ENSURE( pSect, "GetAnySection: Where's my Sect?" );
+ if( pSect->IsInFootnote() && pSect->GetUpper()->IsInSct() )
+ {
+ pSect = pSect->GetUpper()->FindSctFrame();
+ OSL_ENSURE( pSect, "GetAnySection: Where's my SectFrame?" );
+ }
+ return pSect->GetSection();
+ }
+ return nullptr;
+}
+
+size_t SwEditShell::GetSectionFormatCount() const
+{
+ return GetDoc()->GetSections().size();
+}
+
+bool SwEditShell::IsAnySectionInDoc() const
+{
+ const SwSectionFormats& rFormats = GetDoc()->GetSections();
+
+ for( const SwSectionFormat* pFormat : rFormats )
+ {
+ SectionType eTmpType;
+ if( pFormat->IsInNodesArr() &&
+ ( (eTmpType = pFormat->GetSection()->GetType()) != SectionType::ToxContent
+ && SectionType::ToxHeader != eTmpType ) )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+size_t SwEditShell::GetSectionFormatPos( const SwSectionFormat& rFormat ) const
+{
+ SwSectionFormat* pFormat = const_cast<SwSectionFormat*>(&rFormat);
+ return GetDoc()->GetSections().GetPos( pFormat );
+}
+
+const SwSectionFormat& SwEditShell::GetSectionFormat(size_t nFormat) const
+{
+ return *GetDoc()->GetSections()[ nFormat ];
+}
+
+void SwEditShell::DelSectionFormat(size_t nFormat)
+{
+ StartAllAction();
+ GetDoc()->DelSectionFormat( GetDoc()->GetSections()[ nFormat ] );
+ // Call the AttrChangeNotify on the UI page.
+ CallChgLnk();
+ EndAllAction();
+}
+
+void SwEditShell::UpdateSection(size_t const nSect,
+ SwSectionData & rNewData, SfxItemSet const*const pAttr)
+{
+ StartAllAction();
+ GetDoc()->UpdateSection( nSect, rNewData, pAttr );
+ // Call the AttrChangeNotify on the UI page.
+ CallChgLnk();
+ EndAllAction();
+}
+
+OUString SwEditShell::GetUniqueSectionName( const OUString* pChkStr ) const
+{
+ return GetDoc()->GetUniqueSectionName( pChkStr );
+}
+
+void SwEditShell::SetSectionAttr( const SfxItemSet& rSet,
+ SwSectionFormat* pSectFormat )
+{
+ if( pSectFormat )
+ SetSectionAttr_( *pSectFormat, rSet );
+ else
+ {
+ // for all section in the selection
+
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition*
+
+ SwSectionNode* pSttSectNd = pStt->GetNode().FindSectionNode(),
+ * pEndSectNd = pEnd->GetNode().FindSectionNode();
+
+ if( pSttSectNd || pEndSectNd )
+ {
+ if( pSttSectNd )
+ SetSectionAttr_( *pSttSectNd->GetSection().GetFormat(),
+ rSet );
+ if( pEndSectNd && pSttSectNd != pEndSectNd )
+ SetSectionAttr_( *pEndSectNd->GetSection().GetFormat(),
+ rSet );
+
+ if( pSttSectNd && pEndSectNd )
+ {
+ SwNodeIndex aSIdx( pStt->GetNode() );
+ SwNodeIndex aEIdx( pEnd->GetNode() );
+ if( pSttSectNd->EndOfSectionIndex() <
+ pEndSectNd->GetIndex() )
+ {
+ aSIdx = pSttSectNd->EndOfSectionIndex() + 1;
+ aEIdx = *pEndSectNd;
+ }
+
+ while( aSIdx < aEIdx )
+ {
+ if( nullptr != (pSttSectNd = aSIdx.GetNode().GetSectionNode())
+ || ( aSIdx.GetNode().IsEndNode() &&
+ nullptr != ( pSttSectNd = aSIdx.GetNode().
+ StartOfSectionNode()->GetSectionNode())) )
+ SetSectionAttr_( *pSttSectNd->GetSection().GetFormat(),
+ rSet );
+ ++aSIdx;
+ }
+ }
+ }
+
+ }
+ }
+}
+
+void SwEditShell::SetSectionAttr_( SwSectionFormat& rSectFormat,
+ const SfxItemSet& rSet )
+{
+ StartAllAction();
+ if(SfxItemState::SET == rSet.GetItemState(RES_CNTNT, false))
+ {
+ SfxItemSet aSet(rSet);
+ aSet.ClearItem(RES_CNTNT);
+ GetDoc()->SetAttr( aSet, rSectFormat );
+ }
+ else
+ GetDoc()->SetAttr( rSet, rSectFormat );
+
+ // Call the AttrChangeNotify on the UI page.
+ CallChgLnk();
+ EndAllAction();
+}
+
+/** Search inside the cursor selection for full selected sections.
+ *
+ * @return If any part of section in the selection return 0, if more than one return the count.
+ */
+sal_uInt16 SwEditShell::GetFullSelectedSectionCount() const
+{
+ sal_uInt16 nRet = 0;
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+
+ auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition*
+ const SwContentNode* pCNd;
+ // check the selection, if Start at Node begin and End at Node end
+ if( pStt->GetContentIndex() ||
+ ( nullptr == ( pCNd = pEnd->GetNode().GetContentNode() )) ||
+ pCNd->Len() != pEnd->GetContentIndex() )
+ {
+ nRet = 0;
+ break;
+ }
+
+// !!!
+// what about table at start or end ?
+// There is no selection possible!
+// What about only a table inside the section ?
+// There is only a table selection possible!
+
+ SwNodeIndex aSIdx( pStt->GetNode(), -1 ), aEIdx( pEnd->GetNode(), +1 );
+ if( !aSIdx.GetNode().IsSectionNode() ||
+ !aEIdx.GetNode().IsEndNode() ||
+ !aEIdx.GetNode().StartOfSectionNode()->IsSectionNode() )
+ {
+ nRet = 0;
+ break;
+ }
+
+ ++nRet;
+ if( &aSIdx.GetNode() != aEIdx.GetNode().StartOfSectionNode() )
+ ++nRet;
+
+ }
+ return nRet;
+}
+
+/** Find the suitable node for a special insert (alt-enter).
+ *
+ * This should enable inserting text before/after sections and tables.
+ *
+ * A node is found if:
+ * 1) the innermost table/section is not in a write-protected area
+ * 2) pCurrentPos is at or just before an end node
+ * (or at or just after a start node)
+ * 3) there are only start/end nodes between pCurrentPos and the innermost
+ * table/section
+ *
+ * If a suitable node is found, an SwNode* is returned; else it is NULL.
+ */
+static const SwNode* lcl_SpecialInsertNode(const SwPosition* pCurrentPos)
+{
+ const SwNode* pReturn = nullptr;
+
+ // the current position
+ OSL_ENSURE( pCurrentPos != nullptr, "Strange, we have no position!" );
+ const SwNode& rCurrentNode = pCurrentPos->GetNode();
+
+ // find innermost section or table. At the end of this scope,
+ // pInnermostNode contains the section/table before/after which we should
+ // insert our empty paragraph, or it will be NULL if none is found.
+ const SwNode* pInnermostNode = nullptr;
+ {
+ const SwNode* pTableNode = rCurrentNode.FindTableNode();
+ const SwNode* pSectionNode = rCurrentNode.FindSectionNode();
+
+ // find the table/section which is close
+ if( pTableNode == nullptr )
+ pInnermostNode = pSectionNode;
+ else if ( pSectionNode == nullptr )
+ pInnermostNode = pTableNode;
+ else
+ {
+ // compare and choose the larger one
+ pInnermostNode =
+ ( pSectionNode->GetIndex() > pTableNode->GetIndex() )
+ ? pSectionNode : pTableNode;
+ }
+ }
+
+ // The previous version had a check to skip empty read-only sections. Those
+ // shouldn't occur, so we only need to check whether our pInnermostNode is
+ // inside a protected area.
+
+ // Now, pInnermostNode is NULL or the innermost section or table node.
+ if( (pInnermostNode != nullptr) && !pInnermostNode->IsProtect() )
+ {
+ OSL_ENSURE( pInnermostNode->IsTableNode() ||
+ pInnermostNode->IsSectionNode(), "wrong node found" );
+ OSL_ENSURE( ( pInnermostNode->GetIndex() <= rCurrentNode.GetIndex() )&&
+ ( pInnermostNode->EndOfSectionNode()->GetIndex() >=
+ rCurrentNode.GetIndex() ), "wrong node found" );
+
+ // we now need to find the possible start/end positions
+
+ // we found a start if
+ // - we're at or just before a start node
+ // - there are only start nodes between the current and pInnermostNode
+ SwNodeIndex aBegin( pCurrentPos->GetNode() );
+ if( rCurrentNode.IsContentNode() &&
+ (pCurrentPos->GetContentIndex() == 0))
+ --aBegin;
+ while( (aBegin != pInnermostNode->GetIndex()) &&
+ aBegin.GetNode().IsStartNode() )
+ --aBegin;
+ bool bStart = ( aBegin == pInnermostNode->GetIndex() );
+
+ // we found an end if
+ // - we're at or just before an end node
+ // - there are only end nodes between the current node and
+ // pInnermostNode's end node or
+ // - there are only end nodes between the last table cell merged with
+ // the current cell and pInnermostNode's end node
+ SwNodeIndex aEnd( pCurrentPos->GetNode() );
+ if( rCurrentNode.IsContentNode() &&
+ ( pCurrentPos->GetContentIndex() ==
+ rCurrentNode.GetContentNode()->Len() ) )
+ {
+ ++aEnd;
+
+ // tdf#156492 handle cells merged vertically in the bottom right corner
+ if ( pInnermostNode->IsTableNode() )
+ {
+ const SwNode* pTableBoxStartNode = pCurrentPos->GetNode().FindTableBoxStartNode();
+ const SwTableBox* pTableBox = pTableBoxStartNode->GetTableBox();
+ if ( pTableBox && pTableBox->getRowSpan() > 1 )
+ {
+ const SwTableNode* pTableNd = pInnermostNode->FindTableNode();
+ pTableBox = & pTableBox->FindEndOfRowSpan( pTableNd->GetTable(),
+ pTableBox->getRowSpan() );
+ pTableBoxStartNode = pTableBox->GetSttNd();
+ aEnd = pTableBoxStartNode->GetIndex() + 2;
+ }
+ }
+ }
+ while( (aEnd != pInnermostNode->EndOfSectionNode()->GetIndex()) &&
+ aEnd.GetNode().IsEndNode() )
+ ++aEnd;
+ bool bEnd = ( aEnd == pInnermostNode->EndOfSectionNode()->GetIndex() );
+
+ // evaluate result: if both start + end, end is preferred
+ if( bEnd )
+ pReturn = pInnermostNode->EndOfSectionNode();
+ else if ( bStart )
+ pReturn = pInnermostNode;
+ }
+
+ OSL_ENSURE( ( pReturn == nullptr ) || pReturn->IsStartNode() ||
+ pReturn->IsEndNode(),
+ "SpecialInsertNode failed" );
+ return pReturn;
+}
+
+/** a node can be special-inserted (alt-Enter) whenever lcl_SpecialInsertNode
+ finds a suitable position
+*/
+bool SwEditShell::CanSpecialInsert() const
+{
+ return nullptr != lcl_SpecialInsertNode( GetCursor()->GetPoint() );
+}
+
+/** check whether a node can be special-inserted (alt-Enter), and do so. Return
+ whether insertion was possible.
+ */
+void SwEditShell::DoSpecialInsert()
+{
+ // get current node
+ SwPosition* pCursorPos = GetCursor()->GetPoint();
+ const SwNode* pInsertNode = lcl_SpecialInsertNode( pCursorPos );
+ if( pInsertNode == nullptr )
+ return;
+
+ StartAllAction();
+
+ // adjust insert position to insert before start nodes and after end
+ // nodes
+ SwNodeIndex aInsertIndex( *pInsertNode,
+ SwNodeOffset(pInsertNode->IsStartNode() ? -1 : 0) );
+ SwPosition aInsertPos( aInsertIndex );
+
+ // insert a new text node, and set the cursor
+ GetDoc()->getIDocumentContentOperations().AppendTextNode( aInsertPos );
+ *pCursorPos = aInsertPos;
+
+ // call AttrChangeNotify for the UI
+ CallChgLnk();
+
+ EndAllAction();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/edtab.cxx b/sw/source/core/edit/edtab.cxx
new file mode 100644
index 0000000000..5e318ea748
--- /dev/null
+++ b/sw/source/core/edit/edtab.cxx
@@ -0,0 +1,527 @@
+/* -*- 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 <fesh.hxx>
+#include <hintids.hxx>
+#include <hints.hxx>
+
+#include <swwait.hxx>
+#include <editsh.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentChartDataProviderAccess.hxx>
+#include <IDocumentFieldsAccess.hxx>
+#include <IDocumentState.hxx>
+#include <cntfrm.hxx>
+#include <pam.hxx>
+#include <ndtxt.hxx>
+#include <swtable.hxx>
+#include <swundo.hxx>
+#include <tblsel.hxx>
+#include <cellfrm.hxx>
+#include <cellatr.hxx>
+#include <swtblfmt.hxx>
+#include <swddetbl.hxx>
+#include <mdiexp.hxx>
+#include <itabenum.hxx>
+#include <svl/numformat.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <vcl/uitest/eventdescription.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+namespace {
+
+void collectUIInformation(const OUString& rAction, const OUString& aParameters)
+{
+ EventDescription aDescription;
+ aDescription.aAction = rAction;
+ aDescription.aParameters = {{"parameters", aParameters}};
+ aDescription.aID = "writer_edit";
+ aDescription.aKeyWord = "SwEditWinUIObject";
+ aDescription.aParent = "MainWindow";
+ UITestLogger::getInstance().logEvent(aDescription);
+}
+
+}
+
+//Added for bug #i119954# Application crashed if undo/redo convert nest table to text
+static bool ConvertTableToText( const SwTableNode *pTableNode, sal_Unicode cCh );
+
+static void ConvertNestedTablesToText( const SwTableLines &rTableLines, sal_Unicode cCh )
+{
+ for (size_t n = 0; n < rTableLines.size(); ++n)
+ {
+ SwTableLine* pTableLine = rTableLines[ n ];
+ for (size_t i = 0; i < pTableLine->GetTabBoxes().size(); ++i)
+ {
+ SwTableBox* pTableBox = pTableLine->GetTabBoxes()[ i ];
+ if (pTableBox->GetTabLines().empty())
+ {
+ SwNodeIndex nodeIndex( *pTableBox->GetSttNd(), 1 );
+ SwNodeIndex endNodeIndex( *pTableBox->GetSttNd()->EndOfSectionNode() );
+ for( ; nodeIndex < endNodeIndex ; ++nodeIndex )
+ {
+ if ( SwTableNode* pTableNode = nodeIndex.GetNode().GetTableNode() )
+ ConvertTableToText( pTableNode, cCh );
+ }
+ }
+ else
+ {
+ ConvertNestedTablesToText( pTableBox->GetTabLines(), cCh );
+ }
+ }
+ }
+}
+
+bool ConvertTableToText( const SwTableNode *pConstTableNode, sal_Unicode cCh )
+{
+ SwTableNode *pTableNode = const_cast< SwTableNode* >( pConstTableNode );
+ ConvertNestedTablesToText( pTableNode->GetTable().GetTabLines(), cCh );
+ return pTableNode->GetDoc().TableToText( pTableNode, cCh );
+}
+//End for bug #i119954#
+
+const SwTable& SwEditShell::InsertTable( const SwInsertTableOptions& rInsTableOpts,
+ sal_uInt16 nRows, sal_uInt16 nCols,
+ const SwTableAutoFormat* pTAFormat )
+{
+ StartAllAction();
+ SwPosition* pPos = GetCursor()->GetPoint();
+
+ bool bEndUndo = 0 != pPos->GetContentIndex();
+ if( bEndUndo )
+ {
+ StartUndo( SwUndoId::START );
+ GetDoc()->getIDocumentContentOperations().SplitNode( *pPos, false );
+ }
+
+ // If called from a shell the adjust item is propagated
+ // from pPos to the new content nodes in the table.
+ const SwTable *pTable = GetDoc()->InsertTable( rInsTableOpts, *pPos,
+ nRows, nCols,
+ css::text::HoriOrientation::FULL, pTAFormat,
+ nullptr, true );
+ if( bEndUndo )
+ EndUndo( SwUndoId::END );
+
+ EndAllAction();
+
+ OUString parameter = " Columns : " + OUString::number( nCols ) + " , Rows : " + OUString::number( nRows ) + " ";
+ collectUIInformation("CREATE_TABLE", parameter);
+
+ return *pTable;
+}
+
+bool SwEditShell::TextToTable( const SwInsertTableOptions& rInsTableOpts,
+ sal_Unicode cCh,
+ const SwTableAutoFormat* pTAFormat )
+{
+ SwWait aWait( *GetDoc()->GetDocShell(), true );
+ bool bRet = false;
+ StartAllAction();
+ for(const SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ if( rPaM.HasMark() )
+ bRet |= nullptr != GetDoc()->TextToTable( rInsTableOpts, rPaM, cCh,
+ css::text::HoriOrientation::FULL, pTAFormat );
+ }
+ EndAllAction();
+ return bRet;
+}
+
+bool SwEditShell::TableToText( sal_Unicode cCh )
+{
+ SwWait aWait( *GetDoc()->GetDocShell(), true );
+ SwPaM* pCursor = GetCursor();
+ const SwTableNode* pTableNd =
+ SwDoc::IsInTable( pCursor->GetPoint()->GetNode() );
+ if (!pTableNd)
+ return false;
+
+ if( IsTableMode() )
+ {
+ ClearMark();
+ pCursor = GetCursor();
+ }
+ else if (pCursor->GetNext() != pCursor)
+ return false;
+
+ // TL_CHART2:
+ // tell the charts about the table to be deleted and have them use their own data
+ GetDoc()->getIDocumentChartDataProviderAccess().CreateChartInternalDataProviders( &pTableNd->GetTable() );
+
+ StartAllAction();
+
+ // move current Cursor out of the listing area
+ SwNodeIndex aTabIdx( *pTableNd );
+ pCursor->DeleteMark();
+ pCursor->GetPoint()->Assign(*pTableNd->EndOfSectionNode());
+ // move sPoint and Mark out of the area!
+ pCursor->SetMark();
+ pCursor->DeleteMark();
+
+ //Modified for bug #i119954# Application crashed if undo/redo convert nest table to text
+ StartUndo();
+ bool bRet = ConvertTableToText( pTableNd, cCh );
+ EndUndo();
+ //End for bug #i119954#
+ pCursor->GetPoint()->Assign(aTabIdx);
+
+ SwContentNode* pCNd = pCursor->GetPointContentNode();
+ if( !pCNd )
+ pCursor->Move( fnMoveForward, GoInContent );
+
+ EndAllAction();
+ return bRet;
+}
+
+bool SwEditShell::IsTextToTableAvailable() const
+{
+ bool bOnlyText = false;
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ if( rPaM.HasMark() && *rPaM.GetPoint() != *rPaM.GetMark() )
+ {
+ bOnlyText = true;
+
+ // check if selection is in listing
+ SwNodeOffset nStt = rPaM.Start()->GetNodeIndex(),
+ nEnd = rPaM.End()->GetNodeIndex();
+
+ for( ; nStt <= nEnd; ++nStt )
+ if( !GetDoc()->GetNodes()[ nStt ]->IsTextNode() )
+ {
+ bOnlyText = false;
+ break;
+ }
+
+ if( !bOnlyText )
+ break;
+ }
+ }
+
+ return bOnlyText;
+}
+
+void SwEditShell::InsertDDETable( const SwInsertTableOptions& rInsTableOpts,
+ SwDDEFieldType* pDDEType,
+ sal_uInt16 nRows, sal_uInt16 nCols )
+{
+ SwPosition* pPos = GetCursor()->GetPoint();
+ // Do not try to insert table into Footnotes/Endnotes! tdf#76007 prevents that.
+ if (pPos->GetNode() < pPos->GetNodes().GetEndOfInserts()
+ && pPos->GetNode().GetIndex() >= pPos->GetNodes().GetEndOfInserts().StartOfSectionIndex())
+ return;
+
+ StartAllAction();
+
+ bool bEndUndo = 0 != pPos->GetContentIndex();
+ if( bEndUndo )
+ {
+ StartUndo( SwUndoId::START );
+ GetDoc()->getIDocumentContentOperations().SplitNode( *pPos, false );
+ }
+
+ const SwInsertTableOptions aInsTableOpts( rInsTableOpts.mnInsMode | SwInsertTableFlags::DefaultBorder,
+ rInsTableOpts.mnRowsToRepeat );
+ SwTable* pTable = const_cast<SwTable*>(GetDoc()->InsertTable( aInsTableOpts, *pPos,
+ nRows, nCols, css::text::HoriOrientation::FULL ));
+
+ SwTableNode* pTableNode = const_cast<SwTableNode*>(pTable->GetTabSortBoxes()[ 0 ]->
+ GetSttNd()->FindTableNode());
+ std::unique_ptr<SwDDETable> pDDETable(new SwDDETable( *pTable, pDDEType ));
+ pTableNode->SetNewTable( std::move(pDDETable) ); // set the DDE table
+
+ if( bEndUndo )
+ EndUndo( SwUndoId::END );
+
+ EndAllAction();
+}
+
+/** update fields of a listing */
+void SwEditShell::UpdateTable()
+{
+ const SwTableNode* pTableNd = IsCursorInTable();
+
+ if( pTableNd )
+ {
+ StartAllAction();
+ if( DoesUndo() )
+ StartUndo();
+ EndAllTableBoxEdit();
+ GetDoc()->getIDocumentFieldsAccess().UpdateTableFields(&pTableNd->GetTable());
+ if( DoesUndo() )
+ EndUndo();
+ EndAllAction();
+ }
+}
+
+// get/set Change Mode
+
+TableChgMode SwEditShell::GetTableChgMode() const
+{
+ TableChgMode eMode;
+ const SwTableNode* pTableNd = IsCursorInTable();
+ if( pTableNd )
+ eMode = pTableNd->GetTable().GetTableChgMode();
+ else
+ eMode = GetTableChgDefaultMode();
+ return eMode;
+}
+
+void SwEditShell::SetTableChgMode( TableChgMode eMode )
+{
+ const SwTableNode* pTableNd = IsCursorInTable();
+
+ if( pTableNd )
+ {
+ const_cast<SwTable&>(pTableNd->GetTable()).SetTableChgMode( eMode );
+ if( !GetDoc()->getIDocumentState().IsModified() ) // Bug 57028
+ {
+ GetDoc()->GetIDocumentUndoRedo().SetUndoNoResetModified();
+ }
+ GetDoc()->getIDocumentState().SetModified();
+ }
+}
+
+bool SwEditShell::GetTableBoxFormulaAttrs( SfxItemSet& rSet ) const
+{
+ SwSelBoxes aBoxes;
+ if( IsTableMode() )
+ ::GetTableSelCrs( *this, aBoxes );
+ else
+ {
+ SwFrame* pFrame = GetCurrFrame()->GetUpper();
+ while (pFrame && !pFrame->IsCellFrame())
+ pFrame = pFrame->GetUpper();
+
+ if (pFrame)
+ {
+ auto pBox = const_cast<SwTableBox*>(static_cast<SwCellFrame*>(pFrame)->GetTabBox());
+ aBoxes.insert(pBox);
+ }
+ }
+
+ for (size_t n = 0; n < aBoxes.size(); ++n)
+ {
+ const SwTableBox* pSelBox = aBoxes[ n ];
+ const SwTableBoxFormat* pTableFormat = static_cast<SwTableBoxFormat*>(pSelBox->GetFrameFormat());
+ if( !n )
+ {
+ // Convert formulae into external presentation
+ const SwTable& rTable = pSelBox->GetSttNd()->FindTableNode()->GetTable();
+
+ const_cast<SwTable*>(&rTable)->SwitchFormulasToExternalRepresentation();
+ rSet.Put( pTableFormat->GetAttrSet() );
+ }
+ else
+ rSet.MergeValues( pTableFormat->GetAttrSet() );
+ }
+ return 0 != rSet.Count();
+}
+
+void SwEditShell::SetTableBoxFormulaAttrs( const SfxItemSet& rSet )
+{
+ CurrShell aCurr( this );
+ SwSelBoxes aBoxes;
+ if( IsTableMode() )
+ ::GetTableSelCrs( *this, aBoxes );
+ else
+ {
+ do {
+ SwFrame *pFrame = GetCurrFrame();
+ do {
+ pFrame = pFrame->GetUpper();
+ } while ( pFrame && !pFrame->IsCellFrame() );
+ if ( pFrame )
+ {
+ SwTableBox *pBox = const_cast<SwTableBox*>(static_cast<SwCellFrame*>(pFrame)->GetTabBox());
+ aBoxes.insert( pBox );
+ }
+ } while( false );
+ }
+
+ // When setting a formula, do not check further!
+ if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMULA ))
+ ClearTableBoxContent();
+
+ StartAllAction();
+ GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
+ for (size_t n = 0; n < aBoxes.size(); ++n)
+ {
+ GetDoc()->SetTableBoxFormulaAttrs( *aBoxes[ n ], rSet );
+ }
+ GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
+ EndAllAction();
+}
+
+bool SwEditShell::IsTableBoxTextFormat() const
+{
+ if( IsTableMode() )
+ return false;
+
+ const SwTableBox *pBox = nullptr;
+ {
+ SwFrame *pFrame = GetCurrFrame();
+ do {
+ pFrame = pFrame->GetUpper();
+ } while ( pFrame && !pFrame->IsCellFrame() );
+ if ( pFrame )
+ pBox = static_cast<SwCellFrame*>(pFrame)->GetTabBox();
+ }
+
+ if( !pBox )
+ return false;
+
+ sal_uInt32 nFormat = 0;
+ if( const SwTableBoxNumFormat* pItem = pBox->GetFrameFormat()->GetAttrSet().GetItemIfSet(
+ RES_BOXATR_FORMAT ))
+ {
+ nFormat = pItem->GetValue();
+ return GetDoc()->GetNumberFormatter()->IsTextFormat( nFormat );
+ }
+
+ SwNodeOffset nNd = pBox->IsValidNumTextNd();
+ if( NODE_OFFSET_MAX == nNd )
+ return true;
+
+ const OUString& rText = GetDoc()->GetNodes()[ nNd ]->GetTextNode()->GetText();
+ if( rText.isEmpty() )
+ return false;
+
+ double fVal;
+ return !GetDoc()->IsNumberFormat( rText, nFormat, fVal );
+}
+
+OUString SwEditShell::GetTableBoxText() const
+{
+ OUString sRet;
+ if( !IsTableMode() )
+ {
+ const SwTableBox *pBox = nullptr;
+ {
+ SwFrame *pFrame = GetCurrFrame();
+ do {
+ pFrame = pFrame->GetUpper();
+ } while ( pFrame && !pFrame->IsCellFrame() );
+ if ( pFrame )
+ pBox = static_cast<SwCellFrame*>(pFrame)->GetTabBox();
+ }
+
+ SwNodeOffset nNd;
+ if( pBox && NODE_OFFSET_MAX != ( nNd = pBox->IsValidNumTextNd() ) )
+ sRet = GetDoc()->GetNodes()[ nNd ]->GetTextNode()->GetText();
+ }
+ return sRet;
+}
+
+void SwEditShell::SplitTable( SplitTable_HeadlineOption eMode )
+{
+ SwPaM *pCursor = GetCursor();
+ if( pCursor->GetPointNode().FindTableNode() )
+ {
+ StartAllAction();
+ GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);
+
+ GetDoc()->SplitTable( *pCursor->GetPoint(), eMode, true );
+
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
+ ClearFEShellTabCols(*GetDoc(), nullptr);
+ EndAllAction();
+ }
+}
+
+bool SwEditShell::MergeTable( bool bWithPrev )
+{
+ bool bRet = false;
+ SwPaM *pCursor = GetCursor();
+ if( pCursor->GetPointNode().FindTableNode() )
+ {
+ StartAllAction();
+ GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr);
+
+ bRet = GetDoc()->MergeTable( *pCursor->GetPoint(), bWithPrev );
+
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr);
+ ClearFEShellTabCols(*GetDoc(), nullptr);
+ EndAllAction();
+ }
+ return bRet;
+}
+
+bool SwEditShell::CanMergeTable( bool bWithPrev, bool* pChkNxtPrv ) const
+{
+ bool bRet = false;
+ const SwPaM *pCursor = GetCursor();
+ const SwTableNode* pTableNd = pCursor->GetPointNode().FindTableNode();
+ if( pTableNd && dynamic_cast< const SwDDETable* >(&pTableNd->GetTable()) == nullptr)
+ {
+ bool bNew = pTableNd->GetTable().IsNewModel();
+ const SwNodes& rNds = GetDoc()->GetNodes();
+ if( pChkNxtPrv )
+ {
+ const SwTableNode* pChkNd = rNds[ pTableNd->GetIndex() - 1 ]->FindTableNode();
+ if( pChkNd && dynamic_cast< const SwDDETable* >(&pChkNd->GetTable()) == nullptr &&
+ bNew == pChkNd->GetTable().IsNewModel() &&
+ // Consider table in table case
+ pChkNd->EndOfSectionIndex() == pTableNd->GetIndex() - 1 )
+ {
+ *pChkNxtPrv = true;
+ bRet = true; // using Prev is possible
+ }
+ else
+ {
+ pChkNd = rNds[ pTableNd->EndOfSectionIndex() + 1 ]->GetTableNode();
+ if( pChkNd && dynamic_cast< const SwDDETable* >(&pChkNd->GetTable()) == nullptr &&
+ bNew == pChkNd->GetTable().IsNewModel() )
+ {
+ *pChkNxtPrv = false;
+ bRet = true; // using Next is possible
+ }
+ }
+ }
+ else
+ {
+ const SwTableNode* pTmpTableNd = nullptr;
+
+ if( bWithPrev )
+ {
+ pTmpTableNd = rNds[ pTableNd->GetIndex() - 1 ]->FindTableNode();
+ // Consider table in table case
+ if ( pTmpTableNd && pTmpTableNd->EndOfSectionIndex() != pTableNd->GetIndex() - 1 )
+ pTmpTableNd = nullptr;
+ }
+ else
+ pTmpTableNd = rNds[ pTableNd->EndOfSectionIndex() + 1 ]->GetTableNode();
+
+ bRet = pTmpTableNd && dynamic_cast< const SwDDETable* >(&pTmpTableNd->GetTable()) == nullptr &&
+ bNew == pTmpTableNd->GetTable().IsNewModel();
+ }
+ }
+ return bRet;
+}
+
+/** create InsertDB as table Undo */
+void SwEditShell::AppendUndoForInsertFromDB( bool bIsTable )
+{
+ GetDoc()->AppendUndoForInsertFromDB( *GetCursor(), bIsTable );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/edtox.cxx b/sw/source/core/edit/edtox.cxx
new file mode 100644
index 0000000000..602224c80e
--- /dev/null
+++ b/sw/source/core/edit/edtox.cxx
@@ -0,0 +1,394 @@
+/* -*- 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 <com/sun/star/util/SearchAlgorithms2.hpp>
+#include <com/sun/star/util/SearchFlags.hpp>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nutil/transliteration.hxx>
+#include <i18nutil/searchopt.hxx>
+#include <svl/fstathelper.hxx>
+#include <osl/diagnose.h>
+#include <osl/thread.h>
+#include <unotools/syslocale.hxx>
+
+#include <sfx2/docfile.hxx>
+
+#include <swtypes.hxx>
+#include <rootfrm.hxx>
+#include <editsh.hxx>
+#include <doc.hxx>
+#include <IDocumentContentOperations.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <pam.hxx>
+#include <swundo.hxx>
+#include <tox.hxx>
+#include <doctxm.hxx>
+#include <docary.hxx>
+#include <mdiexp.hxx>
+#include <strings.hrc>
+#include <iodetect.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::i18n;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::util;
+
+// Add/delete listing markers to a document
+
+void SwEditShell::Insert(const SwTOXMark& rMark)
+{
+ bool bInsAtPos = rMark.IsAlternativeText();
+ StartAllAction();
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition*
+ if( bInsAtPos )
+ {
+ SwPaM aTmp( *pStt );
+ GetDoc()->getIDocumentContentOperations().InsertPoolItem( aTmp, rMark );
+ }
+ else if( *pEnd != *pStt )
+ {
+ GetDoc()->getIDocumentContentOperations().InsertPoolItem(
+ rPaM, rMark, SetAttrMode::DONTEXPAND );
+ }
+
+ }
+ EndAllAction();
+}
+
+void SwEditShell::DeleteTOXMark( SwTOXMark const * pMark )
+{
+ CurrShell aCurr( this );
+ StartAllAction();
+
+ mxDoc->DeleteTOXMark( pMark );
+
+ EndAllAction();
+}
+
+/// Collect all listing markers
+void SwEditShell::GetCurTOXMarks(SwTOXMarks& rMarks) const
+{
+ SwDoc::GetCurTOXMark( *GetCursor()->Start(), rMarks );
+}
+
+bool SwEditShell::IsTOXBaseReadonly(const SwTOXBase& rTOXBase)
+{
+ assert( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) && "no TOXBaseSection!" );
+ const SwTOXBaseSection& rTOXSect = static_cast<const SwTOXBaseSection&>(rTOXBase);
+ return rTOXSect.IsProtect();
+}
+
+void SwEditShell::SetTOXBaseReadonly(const SwTOXBase& rTOXBase, bool bReadonly)
+{
+ assert( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) && "no TOXBaseSection!" );
+ const SwTOXBaseSection& rTOXSect = static_cast<const SwTOXBaseSection&>(rTOXBase);
+ const_cast<SwTOXBase&>(rTOXBase).SetProtected(bReadonly);
+ OSL_ENSURE( rTOXSect.SwSection::GetType() == SectionType::ToxContent, "not a TOXContentSection" );
+
+ SwSectionData aSectionData(rTOXSect);
+ aSectionData.SetProtectFlag(bReadonly);
+ UpdateSection( GetSectionFormatPos( *rTOXSect.GetFormat() ), aSectionData );
+}
+
+const SwTOXBase* SwEditShell::GetDefaultTOXBase( TOXTypes eTyp, bool bCreate )
+{
+ return GetDoc()->GetDefaultTOXBase( eTyp, bCreate );
+}
+
+void SwEditShell::SetDefaultTOXBase(const SwTOXBase& rBase)
+{
+ GetDoc()->SetDefaultTOXBase(rBase);
+}
+
+/// Insert listing and create content
+void SwEditShell::InsertTableOf( const SwTOXBase& rTOX, const SfxItemSet* pSet )
+{
+ CurrShell aCurr( this );
+ StartAllAction();
+
+ SwDocShell* pDocSh = GetDoc()->GetDocShell();
+ ::StartProgress( STR_STATSTR_TOX_INSERT, 0, 0, pDocSh );
+
+ // Insert listing
+ const SwTOXBaseSection* pTOX = mxDoc->InsertTableOf(
+ *GetCursor()->GetPoint(), rTOX, pSet, true, GetLayout() );
+ OSL_ENSURE(pTOX, "No current TOX");
+
+ // start formatting
+ CalcLayout();
+
+ // insert page numbering
+ const_cast<SwTOXBaseSection*>(pTOX)->UpdatePageNum();
+
+ pTOX->SetPosAtStartEnd( *GetCursor()->GetPoint() );
+
+ // Fix for empty listing
+ InvalidateWindows( maVisArea );
+ ::EndProgress( pDocSh );
+ EndAllAction();
+}
+
+/// update tables of content
+void SwEditShell::UpdateTableOf(const SwTOXBase& rTOX, const SfxItemSet* pSet)
+{
+ assert(dynamic_cast<const SwTOXBaseSection*>(&rTOX) && "no TOXBaseSection!");
+ SwTOXBaseSection& rTOXSect = static_cast<SwTOXBaseSection&>(const_cast<SwTOXBase&>(rTOX));
+ if (!rTOXSect.GetFormat()->GetSectionNode())
+ return;
+
+ SwDoc* pMyDoc = GetDoc();
+ SwDocShell* pDocSh = pMyDoc->GetDocShell();
+
+ bool bInIndex = &rTOX == GetCurTOX();
+ CurrShell aCurr( this );
+ StartAllAction();
+
+ ::StartProgress( STR_STATSTR_TOX_UPDATE, 0, 0, pDocSh );
+
+ pMyDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::TOXCHANGE, nullptr);
+
+ // create listing stub
+ rTOXSect.Update(pSet, GetLayout());
+
+ // correct Cursor
+ if( bInIndex )
+ rTOXSect.SetPosAtStartEnd(*GetCursor()->GetPoint());
+
+ // start formatting
+ // tdf#139426 ...but allow suppression of AssertFlyPages
+ GetLayout()->SetTableUpdateInProgress(true);
+ CalcLayout();
+ GetLayout()->SetTableUpdateInProgress(false);
+
+ // insert page numbering
+ rTOXSect.UpdatePageNum();
+
+ pMyDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::TOXCHANGE, nullptr);
+
+ ::EndProgress( pDocSh );
+ EndAllAction();
+}
+
+/// Get current listing before or at the Cursor
+const SwTOXBase* SwEditShell::GetCurTOX() const
+{
+ return SwDoc::GetCurTOX( *GetCursor()->GetPoint() );
+}
+
+bool SwEditShell::DeleteTOX( const SwTOXBase& rTOXBase, bool bDelNodes )
+{
+ return GetDoc()->DeleteTOX( rTOXBase, bDelNodes );
+}
+
+// manage types of listings
+
+const SwTOXType* SwEditShell::GetTOXType(TOXTypes eTyp, sal_uInt16 nId) const
+{
+ return mxDoc->GetTOXType(eTyp, nId);
+}
+
+// manage keys for the alphabetical index
+
+void SwEditShell::GetTOIKeys( SwTOIKeyType eTyp, std::vector<OUString>& rArr ) const
+{
+ GetDoc()->GetTOIKeys( eTyp, rArr, *GetLayout() );
+}
+
+sal_uInt16 SwEditShell::GetTOXCount() const
+{
+ const SwSectionFormats& rFormats = GetDoc()->GetSections();
+ sal_uInt16 nRet = 0;
+ for( auto n = rFormats.size(); n; )
+ {
+ const SwSection* pSect = rFormats[ --n ]->GetSection();
+ if( SectionType::ToxContent == pSect->GetType() &&
+ pSect->GetFormat()->GetSectionNode() )
+ ++nRet;
+ }
+ return nRet;
+}
+
+const SwTOXBase* SwEditShell::GetTOX( sal_uInt16 nPos ) const
+{
+ const SwSectionFormats& rFormats = GetDoc()->GetSections();
+ sal_uInt16 nCnt {0};
+ for( const SwSectionFormat *pFormat : rFormats )
+ {
+ const SwSection* pSect = pFormat->GetSection();
+ if( SectionType::ToxContent == pSect->GetType() &&
+ pSect->GetFormat()->GetSectionNode() &&
+ nCnt++ == nPos )
+ {
+ assert( dynamic_cast<const SwTOXBaseSection*>( pSect) && "no TOXBaseSection!" );
+ return static_cast<const SwTOXBaseSection*>(pSect);
+ }
+ }
+ return nullptr;
+}
+
+/** Update of all listings after reading-in a file */
+void SwEditShell::SetUpdateTOX( bool bFlag )
+{
+ GetDoc()->SetUpdateTOX( bFlag );
+}
+
+bool SwEditShell::IsUpdateTOX() const
+{
+ return GetDoc()->IsUpdateTOX();
+}
+
+OUString const & SwEditShell::GetTOIAutoMarkURL() const
+{
+ return GetDoc()->GetTOIAutoMarkURL();
+}
+
+void SwEditShell::SetTOIAutoMarkURL(const OUString& rSet)
+{
+ GetDoc()->SetTOIAutoMarkURL(rSet);
+}
+
+void SwEditShell::ApplyAutoMark()
+{
+ StartAllAction();
+ bool bDoesUndo = DoesUndo();
+ DoUndo(false);
+ //1. remove all automatic generated index entries if AutoMarkURL has a
+ // length and the file exists
+ //2. load file
+ //3. select all occurrences of the searched words
+ //4. apply index entries
+
+ OUString sAutoMarkURL(GetDoc()->GetTOIAutoMarkURL());
+ if( !sAutoMarkURL.isEmpty() && FStatHelper::IsDocument( sAutoMarkURL ))
+ {
+ //1.
+ const SwTOXType* pTOXType = GetTOXType(TOX_INDEX, 0);
+
+ SwTOXMarks aMarks;
+ pTOXType->CollectTextMarks(aMarks);
+ for( SwTOXMark* pMark : aMarks )
+ {
+ if(pMark->IsAutoGenerated() && pMark->GetTextTOXMark())
+ // mba: test iteration; objects are deleted in iteration
+ DeleteTOXMark(pMark);
+ }
+
+ //2.
+ SfxMedium aMedium( sAutoMarkURL, StreamMode::STD_READ );
+ SvStream& rStrm = *aMedium.GetInStream();
+ Push();
+ // tdf#106899 - import tox concordance file using the appropriate character set
+ rtl_TextEncoding eChrSet = SwIoSystem::GetTextEncoding(rStrm);
+ if (eChrSet == RTL_TEXTENCODING_DONTKNOW)
+ eChrSet = ::osl_getThreadTextEncoding();
+
+ // SearchOptions to be used in loop below
+ sal_Int32 const nLEV_Other = 2; // -> changedChars;
+ sal_Int32 const nLEV_Longer = 3; //! -> deletedChars;
+ sal_Int32 const nLEV_Shorter = 1; //! -> insertedChars;
+
+ i18nutil::SearchOptions2 aSearchOpt(
+ SearchFlags::LEV_RELAXED,
+ "", "",
+ SvtSysLocale().GetLanguageTag().getLocale(),
+ nLEV_Other, nLEV_Longer, nLEV_Shorter,
+ TransliterationFlags::NONE,
+ SearchAlgorithms2::ABSOLUTE,
+ '\\' );
+
+ OStringBuffer aRdLine;
+ while (rStrm.good())
+ {
+ rStrm.ReadLine( aRdLine );
+
+ // # -> comment
+ // ; -> delimiter between entries ->
+ // Format: TextToSearchFor;AlternativeString;PrimaryKey;SecondaryKey;CaseSensitive;WordOnly
+ // Leading and trailing blanks are ignored
+ if( !aRdLine.isEmpty() && '#' != aRdLine[0] )
+ {
+ OUString sLine(OStringToOUString(aRdLine, eChrSet));
+
+ sal_Int32 nTokenPos = 0;
+ OUString sToSelect( sLine.getToken(0, ';', nTokenPos ) );
+ if( !sToSelect.isEmpty() )
+ {
+ OUString sAlternative = sLine.getToken(0, ';', nTokenPos);
+ OUString sPrimary = sLine.getToken(0, ';', nTokenPos);
+ OUString sSecondary = sLine.getToken(0, ';', nTokenPos);
+ OUString sCase = sLine.getToken(0, ';', nTokenPos);
+ OUString sWordOnly = sLine.getToken(0, ';', nTokenPos);
+
+ //3.
+ bool bCaseSensitive = !sCase.isEmpty() && sCase != "0";
+ bool bWordOnly = !sWordOnly.isEmpty() && sWordOnly != "0";
+
+ if (!bCaseSensitive)
+ {
+ aSearchOpt.transliterateFlags |=
+ TransliterationFlags::IGNORE_CASE;
+ }
+ else
+ {
+ aSearchOpt.transliterateFlags &=
+ ~TransliterationFlags::IGNORE_CASE;
+ }
+ if ( bWordOnly)
+ aSearchOpt.searchFlag |= SearchFlags::NORM_WORD_ONLY;
+ else
+ aSearchOpt.searchFlag &= ~SearchFlags::NORM_WORD_ONLY;
+
+ aSearchOpt.searchString = sToSelect;
+
+ KillPams();
+ bool bCancel;
+
+ // todo/mba: assuming that notes shouldn't be searched
+ sal_Int32 nRet = Find_Text(aSearchOpt, false/*bSearchInNotes*/, SwDocPositions::Start, SwDocPositions::End, bCancel,
+ FindRanges::InSelAll );
+
+ if(nRet)
+ {
+ SwTOXMark* pTmpMark = new SwTOXMark(pTOXType);
+ if( !sPrimary.isEmpty() )
+ {
+ pTmpMark->SetPrimaryKey( sPrimary );
+ if( !sSecondary.isEmpty() )
+ pTmpMark->SetSecondaryKey( sSecondary );
+ }
+ if( !sAlternative.isEmpty() )
+ pTmpMark->SetAlternativeText(sAlternative);
+ pTmpMark->SetMainEntry(false);
+ pTmpMark->SetAutoGenerated(true);
+ //4.
+ SwEditShell::Insert(*pTmpMark);
+ }
+ }
+ }
+ }
+ KillPams();
+ Pop(PopMode::DeleteCurrent);
+ }
+ DoUndo(bDoesUndo);
+ EndAllAction();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/edundo.cxx b/sw/source/core/edit/edundo.cxx
new file mode 100644
index 0000000000..576b092214
--- /dev/null
+++ b/sw/source/core/edit/edundo.cxx
@@ -0,0 +1,257 @@
+/* -*- 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 <svx/svdmark.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <editsh.hxx>
+#include <fesh.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <UndoCore.hxx>
+#include <swundo.hxx>
+#include <flyfrm.hxx>
+#include <frmfmt.hxx>
+#include <docsh.hxx>
+#include <pagefrm.hxx>
+#include <textboxhelper.hxx>
+#include <fmtanchr.hxx>
+
+#include <wrtsh.hxx>
+
+/** helper function to select all objects in an SdrMarkList;
+ * implementation: see below */
+static void lcl_SelectSdrMarkList( SwEditShell* pShell,
+ const SdrMarkList* pSdrMarkList );
+
+bool SwEditShell::CursorsLocked() const
+{
+ return GetDoc()->GetDocShell()->GetModel()->hasControllersLocked();
+}
+
+void SwEditShell::HandleUndoRedoContext(::sw::UndoRedoContext & rContext)
+{
+ // do nothing if somebody has locked controllers!
+ if (CursorsLocked())
+ {
+ return;
+ }
+
+ SwFrameFormat * pSelFormat(nullptr);
+ SdrMarkList * pMarkList(nullptr);
+ rContext.GetSelections(pSelFormat, pMarkList);
+
+ if (pSelFormat) // select frame
+ {
+ if (RES_DRAWFRMFMT == pSelFormat->Which())
+ {
+ SdrObject* pSObj = pSelFormat->FindSdrObject();
+
+ // Before layout calc, inline anchored textboxes have to be synced unless crash.
+ if (pSelFormat->GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR
+ && pSelFormat->GetOtherTextBoxFormats())
+ SwTextBoxHelper::synchronizeGroupTextBoxProperty(SwTextBoxHelper::changeAnchor,
+ pSelFormat, pSObj);
+
+ static_cast<SwFEShell*>(this)->SelectObj(
+ pSObj->GetCurrentBoundRect().Center() );
+ }
+ else
+ {
+ Point aPt;
+ SwFlyFrame *const pFly =
+ static_cast<SwFlyFrameFormat*>(pSelFormat)->GetFrame(& aPt);
+ if (pFly)
+ {
+ // fdo#36681: Invalidate the content and layout to refresh
+ // the picture anchoring properly
+ SwPageFrame* pPageFrame = pFly->FindPageFrameOfAnchor();
+ pPageFrame->InvalidateFlyLayout();
+ pPageFrame->InvalidateContent();
+
+ static_cast<SwFEShell*>(this)->SelectFlyFrame(*pFly);
+ }
+ }
+ }
+ else if (pMarkList)
+ {
+ lcl_SelectSdrMarkList( this, pMarkList );
+ }
+ else if (GetCursor()->GetNext() != GetCursor())
+ {
+ // current cursor is the last one:
+ // go around the ring, to the first cursor
+ GoNextCursor();
+ }
+}
+
+void SwEditShell::Undo(sal_uInt16 const nCount, sal_uInt16 nOffset)
+{
+ MakeAllOutlineContentTemporarilyVisible a(GetDoc(), true);
+
+ CurrShell aCurr( this );
+
+ // current undo state was not saved
+ ::sw::UndoGuard const undoGuard(GetDoc()->GetIDocumentUndoRedo());
+ bool bRet = false;
+
+ StartAllAction();
+ {
+ // Actually it should be enough to just work on the current Cursor, i.e. if there is a cycle
+ // cancel the latter temporarily, so that an insert during Undo is not done in all areas.
+ KillPams();
+ SetMark(); // Bound1 and Bound2 in the same Node
+ ClearMark();
+
+ // Keep Cursor - so that we're able to set it at
+ // the same position for autoformat or autocorrection
+ SwUndoId nLastUndoId(SwUndoId::EMPTY);
+ GetLastUndoInfo(nullptr, & nLastUndoId);
+ const bool bRestoreCursor = nCount == 1
+ && ( SwUndoId::AUTOFORMAT == nLastUndoId
+ || SwUndoId::AUTOCORRECT == nLastUndoId
+ || SwUndoId::SETDEFTATTR == nLastUndoId );
+ Push();
+
+ // Destroy stored TableBoxPtr. A detection is only permitted for the new "Box"!
+ ClearTableBoxContent();
+
+ const RedlineFlags eOld = GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags();
+
+ try {
+ for (sal_uInt16 i = 0; i < nCount; ++i)
+ {
+ bRet = GetDoc()->GetIDocumentUndoRedo().UndoWithOffset(nOffset) || bRet;
+ }
+ } catch (const css::uno::Exception &) {
+ TOOLS_WARN_EXCEPTION("sw.core", "SwEditShell::Undo()");
+ }
+
+ if (bRestoreCursor)
+ { // fdo#39003 Pop does not touch the rest of the cursor ring
+ KillPams(); // so call this first to get rid of unwanted cursors
+ }
+ Pop(bRestoreCursor ? PopMode::DeleteCurrent : PopMode::DeleteStack);
+
+ GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eOld );
+ GetDoc()->getIDocumentRedlineAccess().CompressRedlines();
+
+ // automatic detection of the new "Box"
+ SaveTableBoxContent();
+ }
+ EndAllAction();
+}
+
+void SwEditShell::Redo(sal_uInt16 const nCount)
+{
+ MakeAllOutlineContentTemporarilyVisible a(GetDoc(), true);
+
+ CurrShell aCurr( this );
+
+ bool bRet = false;
+
+ // undo state was not saved
+ ::sw::UndoGuard const undoGuard(GetDoc()->GetIDocumentUndoRedo());
+
+ StartAllAction();
+
+ {
+ // Actually it should be enough to just work on the current Cursor, i.e. if there is a cycle
+ // cancel the latter temporarily, so that an insert during Undo is not done in all areas.
+ KillPams();
+ SetMark(); // Bound1 and Bound2 in the same Node
+ ClearMark();
+
+ SwUndoId nFirstRedoId(SwUndoId::EMPTY);
+ GetDoc()->GetIDocumentUndoRedo().GetFirstRedoInfo(nullptr, & nFirstRedoId);
+ const bool bRestoreCursor = nCount == 1 && SwUndoId::SETDEFTATTR == nFirstRedoId;
+ Push();
+
+ // Destroy stored TableBoxPtr. A detection is only permitted for the new "Box"!
+ ClearTableBoxContent();
+
+ RedlineFlags eOld = GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags();
+
+ try {
+ for (sal_uInt16 i = 0; i < nCount; ++i)
+ {
+ bRet = GetDoc()->GetIDocumentUndoRedo().Redo()
+ || bRet;
+ }
+ } catch (const css::uno::Exception &) {
+ TOOLS_WARN_EXCEPTION("sw.core", "SwEditShell::Redo()");
+ }
+
+ Pop(bRestoreCursor ? PopMode::DeleteCurrent : PopMode::DeleteStack);
+
+ GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eOld );
+ GetDoc()->getIDocumentRedlineAccess().CompressRedlines();
+
+ // automatic detection of the new "Box"
+ SaveTableBoxContent();
+ }
+
+ EndAllAction();
+}
+
+void SwEditShell::Repeat(sal_uInt16 const nCount)
+{
+ CurrShell aCurr( this );
+
+ StartAllAction();
+
+ try {
+ ::sw::RepeatContext context(*GetDoc(), *GetCursor());
+ GetDoc()->GetIDocumentUndoRedo().Repeat( context, nCount );
+ } catch (const css::uno::Exception &) {
+ TOOLS_WARN_EXCEPTION("sw.core", "SwEditShell::Repeat()");
+ }
+
+ EndAllAction();
+}
+
+static void lcl_SelectSdrMarkList( SwEditShell* pShell,
+ const SdrMarkList* pSdrMarkList )
+{
+ OSL_ENSURE( pShell != nullptr, "need shell!" );
+ OSL_ENSURE( pSdrMarkList != nullptr, "need mark list" );
+
+ SwFEShell* pFEShell = dynamic_cast<SwFEShell*>( pShell );
+ if( !pFEShell )
+ return;
+
+ bool bFirst = true;
+ for( size_t i = 0; i < pSdrMarkList->GetMarkCount(); ++i )
+ {
+ SdrObject *pObj = pSdrMarkList->GetMark( i )->GetMarkedSdrObj();
+ if( pObj )
+ {
+ pFEShell->SelectObj( Point(), bFirst ? 0 : SW_ADD_SELECT, pObj );
+ bFirst = false;
+ }
+ }
+
+ // the old implementation would always unselect
+ // objects, even if no new ones were selected. If this
+ // is a problem, we need to re-work this a little.
+ OSL_ENSURE( pSdrMarkList->GetMarkCount() != 0, "empty mark list" );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/edit/edws.cxx b/sw/source/core/edit/edws.cxx
new file mode 100644
index 0000000000..4e46ae2daf
--- /dev/null
+++ b/sw/source/core/edit/edws.cxx
@@ -0,0 +1,331 @@
+/* -*- 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 <editsh.hxx>
+
+#include <officecfg/Office/Common.hxx>
+#include <osl/diagnose.h>
+#include <unotools/configmgr.hxx>
+#include <vcl/window.hxx>
+
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentState.hxx>
+#include <pam.hxx>
+#include <acorrect.hxx>
+#include <swtable.hxx>
+#include <ndtxt.hxx>
+#include <txtfrm.hxx>
+#include <swundo.hxx>
+#include <SwRewriter.hxx>
+#include <frameformats.hxx>
+
+// masqueraded copy constructor
+SwEditShell::SwEditShell( SwEditShell& rEdSH, vcl::Window *pWindow )
+ : SwCursorShell( rEdSH, pWindow )
+ , m_bNbspRunNext(false) // TODO: would copying that make sense? only if editing continues
+ , m_bDoParagraphSignatureValidation(true)
+{
+}
+
+SwEditShell::SwEditShell( SwDoc& rDoc, vcl::Window *pWindow, const SwViewOption *pOptions )
+ : SwCursorShell( rDoc, pWindow, pOptions )
+ , m_bNbspRunNext(false)
+ , m_bDoParagraphSignatureValidation(true)
+{
+ if (!utl::ConfigManager::IsFuzzing() && 0 < officecfg::Office::Common::Undo::Steps::get())
+ {
+ GetDoc()->GetIDocumentUndoRedo().DoUndo(true);
+ }
+
+ // Restore the paragraph metadata fields and validate signatures.
+ RestoreMetadataFieldsAndValidateParagraphSignatures();
+}
+
+SwEditShell::~SwEditShell() // USED
+{
+}
+
+bool SwEditShell::IsModified() const
+{
+ return GetDoc()->getIDocumentState().IsModified();
+}
+
+void SwEditShell::SetModified()
+{
+ GetDoc()->getIDocumentState().SetModified();
+}
+
+void SwEditShell::ResetModified()
+{
+ GetDoc()->getIDocumentState().ResetModified();
+}
+
+void SwEditShell::SetUndoNoResetModified()
+{
+ GetDoc()->getIDocumentState().SetModified();
+ GetDoc()->GetIDocumentUndoRedo().SetUndoNoResetModified();
+}
+
+void SwEditShell::StartAllAction()
+{
+ for(SwViewShell& rCurrentShell : GetRingContainer())
+ {
+ if (SwEditShell* pEditShell = dynamic_cast<SwEditShell*>(&rCurrentShell))
+ pEditShell->StartAction();
+ else
+ rCurrentShell.StartAction();
+ }
+}
+
+void SwEditShell::EndAllAction()
+{
+ for(SwViewShell& rCurrentShell : GetRingContainer())
+ {
+ if( auto pEditShell = dynamic_cast<SwEditShell *>(&rCurrentShell))
+ pEditShell->EndAction();
+ else
+ rCurrentShell.EndAction();
+ }
+}
+
+void SwEditShell::CalcLayout()
+{
+ StartAllAction();
+ SwViewShell::CalcLayout();
+
+ for(SwViewShell& rCurrentShell : GetRingContainer())
+ {
+ if ( rCurrentShell.GetWin() )
+ rCurrentShell.GetWin()->Invalidate();
+ }
+
+ EndAllAction();
+}
+
+/** Get the content type of a shell
+ *
+ * @todo Is this called for every attribute?
+ */
+sal_uInt16 SwEditShell::GetCntType() const
+{
+ sal_uInt16 nRet = 0;
+ if( IsTableMode() )
+ nRet = CNT_TXT;
+ else
+ switch( GetCursor()->GetPointNode().GetNodeType() )
+ {
+ case SwNodeType::Text: nRet = CNT_TXT; break;
+ case SwNodeType::Grf: nRet = CNT_GRF; break;
+ case SwNodeType::Ole: nRet = CNT_OLE; break;
+ default: break;
+ }
+
+ OSL_ASSERT( nRet );
+ return nRet;
+}
+
+bool SwEditShell::HasOtherCnt() const
+
+{
+ if ( !GetDoc()->GetSpzFrameFormats()->empty() )
+ return true;
+
+ const SwNodes &rNds = GetDoc()->GetNodes();
+ const SwNode *pNd;
+
+ pNd = &rNds.GetEndOfInserts();
+ if ( SwNodeOffset(1) != (pNd->GetIndex() - pNd->StartOfSectionIndex()) )
+ return true;
+
+ pNd = &rNds.GetEndOfAutotext();
+ return SwNodeOffset(1) != (pNd->GetIndex() - pNd->StartOfSectionIndex());
+}
+
+SwActContext::SwActContext(SwEditShell *pShell)
+ : m_rShell(*pShell)
+{
+ m_rShell.StartAction();
+}
+
+SwActContext::~SwActContext() COVERITY_NOEXCEPT_FALSE
+{
+ m_rShell.EndAction();
+}
+
+SwMvContext::SwMvContext(SwEditShell *pShell)
+ : m_rShell(*pShell)
+{
+ m_rShell.SttCursorMove();
+}
+
+SwMvContext::~SwMvContext() COVERITY_NOEXCEPT_FALSE
+{
+ m_rShell.EndCursorMove();
+}
+
+SwFrameFormat *SwEditShell::GetTableFormat() // fastest test on a table
+{
+ const SwTableNode* pTableNd = IsCursorInTable();
+ return pTableNd ? static_cast<SwFrameFormat*>(pTableNd->GetTable().GetFrameFormat()) : nullptr;
+}
+
+// TODO: Why is this called 3x for a new document?
+sal_uInt16 SwEditShell::GetTOXTypeCount(TOXTypes eTyp) const
+{
+ return mxDoc->GetTOXTypeCount(eTyp);
+}
+
+void SwEditShell::InsertTOXType(const SwTOXType& rTyp)
+{
+ mxDoc->InsertTOXType(rTyp);
+}
+
+void SwEditShell::DoUndo( bool bOn )
+{ GetDoc()->GetIDocumentUndoRedo().DoUndo( bOn ); }
+
+bool SwEditShell::DoesUndo() const
+{ return GetDoc()->GetIDocumentUndoRedo().DoesUndo(); }
+
+void SwEditShell::DoGroupUndo( bool bOn )
+{ GetDoc()->GetIDocumentUndoRedo().DoGroupUndo( bOn ); }
+
+bool SwEditShell::DoesGroupUndo() const
+{ return GetDoc()->GetIDocumentUndoRedo().DoesGroupUndo(); }
+
+void SwEditShell::DelAllUndoObj()
+{
+ GetDoc()->GetIDocumentUndoRedo().DelAllUndoObj();
+}
+
+// Combine continuous calls of Insert/Delete/Overwrite on characters. Default: sdbcx::Group-Undo.
+
+/** open undo container
+ *
+ * @return nUndoId ID of the container
+ */
+SwUndoId SwEditShell::StartUndo( SwUndoId eUndoId,
+ const SwRewriter *pRewriter )
+{ return GetDoc()->GetIDocumentUndoRedo().StartUndo( eUndoId, pRewriter ); }
+
+/** close undo container
+ *
+ * not used by UI
+ *
+ * @param eUndoId ID of the undo container
+ * @param pRewriter ?
+*/
+SwUndoId SwEditShell::EndUndo(SwUndoId eUndoId, const SwRewriter *pRewriter)
+{ return GetDoc()->GetIDocumentUndoRedo().EndUndo(eUndoId, pRewriter); }
+
+bool SwEditShell::GetLastUndoInfo(OUString *const o_pStr,
+ SwUndoId *const o_pId,
+ const SwView* pView) const
+{
+ return GetDoc()->GetIDocumentUndoRedo().GetLastUndoInfo(o_pStr, o_pId, pView);
+}
+
+bool SwEditShell::GetFirstRedoInfo(OUString *const o_pStr,
+ SwUndoId *const o_pId,
+ const SwView* pView) const
+{
+ return GetDoc()->GetIDocumentUndoRedo().GetFirstRedoInfo(o_pStr, o_pId, pView);
+}
+
+SwUndoId SwEditShell::GetRepeatInfo(OUString *const o_pStr) const
+{ return GetDoc()->GetIDocumentUndoRedo().GetRepeatInfo(o_pStr); }
+
+/** Auto correction */
+void SwEditShell::AutoCorrect( SvxAutoCorrect& rACorr, bool bInsert,
+ sal_Unicode cChar )
+{
+ CurrShell aCurr( this );
+
+ StartAllAction();
+
+ SwPaM* pCursor = getShellCursor( true );
+ SwTextNode* pTNd = pCursor->GetPointNode().GetTextNode();
+
+ SwAutoCorrDoc aSwAutoCorrDoc( *this, *pCursor, cChar );
+ // FIXME: this _must_ be called with reference to the actual node text!
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTNd->getLayoutFrame(GetLayout())));
+ TextFrameIndex const nPos(pFrame->MapModelToViewPos(*pCursor->GetPoint()));
+ // tdf#147414 sw_redlinehide: if cursor moved backward, it may be at the
+ // start of a delete redline - but MapViewToModelPos() always returns end
+ // of redline and it will be called when AutoCorrect actually inserts
+ // something - so first normalize cursor point to end of redline so that
+ // point will then be moved forward when something is inserted.
+ *pCursor->GetPoint() = pFrame->MapViewToModelPos(nPos);
+ // The hope is that the AutoCorrect never deletes nodes, hence never
+ // deletes SwTextFrames, hence we can pass in the SwTextFrame::GetText()
+ // result and it will be updated via the SwTextFrame::SwClientNotify()
+ // on editing operations.
+ OUString const& rMergedText(pFrame->GetText());
+ rACorr.DoAutoCorrect( aSwAutoCorrDoc,
+ rMergedText, sal_Int32(nPos),
+ cChar, bInsert, m_bNbspRunNext, GetWin() );
+ if( cChar )
+ SaveTableBoxContent( pCursor->GetPoint() );
+ EndAllAction();
+}
+
+void SwEditShell::SetNewDoc()
+{
+ GetDoc()->getIDocumentState().SetNewDoc(true);
+}
+
+OUString SwEditShell::GetPrevAutoCorrWord(SvxAutoCorrect& rACorr)
+{
+ CurrShell aCurr( this );
+
+ OUString sRet;
+ SwPaM* pCursor = getShellCursor( true );
+ SwTextNode* pTNd = pCursor->GetPointNode().GetTextNode();
+ if (pTNd)
+ {
+ SwAutoCorrDoc aSwAutoCorrDoc( *this, *pCursor, 0 );
+ SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTNd->getLayoutFrame(GetLayout())));
+ TextFrameIndex const nPos(pFrame->MapModelToViewPos(*pCursor->GetPoint()));
+ sRet = rACorr.GetPrevAutoCorrWord(aSwAutoCorrDoc, pFrame->GetText(), sal_Int32(nPos));
+ }
+ return sRet;
+}
+
+std::vector<OUString> SwEditShell::GetChunkForAutoText()
+{
+ CurrShell aCurr(this);
+
+ std::vector<OUString> aRet;
+ SwPaM* pCursor = getShellCursor(true);
+ SwTextNode* pTNd = pCursor->GetPointNode().GetTextNode();
+ if (pTNd)
+ {
+ const auto pFrame = static_cast<SwTextFrame const*>(pTNd->getLayoutFrame(GetLayout()));
+ TextFrameIndex const nPos(pFrame->MapModelToViewPos(*pCursor->GetPoint()));
+ aRet = SvxAutoCorrect::GetChunkForAutoText(pFrame->GetText(), sal_Int32(nPos));
+ }
+ return aRet;
+}
+
+SwAutoCompleteWord& SwEditShell::GetAutoCompleteWords()
+{
+ return SwDoc::GetAutoCompleteWords();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */