summaryrefslogtreecommitdiffstats
path: root/sw/source/core/edit
diff options
context:
space:
mode:
Diffstat (limited to 'sw/source/core/edit')
-rw-r--r--sw/source/core/edit/acorrect.cxx693
-rw-r--r--sw/source/core/edit/autofmt.cxx2834
-rw-r--r--sw/source/core/edit/edatmisc.cxx190
-rw-r--r--sw/source/core/edit/edattr.cxx853
-rw-r--r--sw/source/core/edit/eddel.cxx363
-rw-r--r--sw/source/core/edit/edfcol.cxx2287
-rw-r--r--sw/source/core/edit/edfld.cxx417
-rw-r--r--sw/source/core/edit/edfldexp.cxx58
-rw-r--r--sw/source/core/edit/edfmt.cxx159
-rw-r--r--sw/source/core/edit/edglbldc.cxx384
-rw-r--r--sw/source/core/edit/edglss.cxx337
-rw-r--r--sw/source/core/edit/editsh.cxx1056
-rw-r--r--sw/source/core/edit/edlingu.cxx1712
-rw-r--r--sw/source/core/edit/ednumber.cxx912
-rw-r--r--sw/source/core/edit/edredln.cxx153
-rw-r--r--sw/source/core/edit/edsect.cxx421
-rw-r--r--sw/source/core/edit/edtab.cxx531
-rw-r--r--sw/source/core/edit/edtox.cxx388
-rw-r--r--sw/source/core/edit/edundo.cxx246
-rw-r--r--sw/source/core/edit/edws.cxx321
20 files changed, 14315 insertions, 0 deletions
diff --git a/sw/source/core/edit/acorrect.cxx b/sw/source/core/edit/acorrect.cxx
new file mode 100644
index 000000000..f26f23732
--- /dev/null
+++ b/sw/source/core/edit/acorrect.cxx
@@ -0,0 +1,693 @@
+/* -*- 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/i18n/BreakType.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <com/sun/star/linguistic2/XHyphenator.hpp>
+#include <com/sun/star/linguistic2/XHyphenatedWord.hpp>
+#include <svl/zforlist.hxx>
+#include <svl/zformat.hxx>
+
+#include <editeng/acorrcfg.hxx>
+
+using namespace ::com::sun::star;
+
+namespace {
+
+class PaMIntoCursorShellRing
+{
+ SwPaM &rDelPam, &rCursor;
+ SwPaM* pPrevDelPam;
+ SwPaM* 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 )
+ : rDelPam( rPam ), rCursor( rShCursor )
+{
+ SwPaM* pShCursor = rCSh.GetCursor_();
+
+ pPrevDelPam = rDelPam.GetPrev();
+ pPrevCursor = rCursor.GetPrev();
+
+ rDelPam.GetRingContainer().merge( pShCursor->GetRingContainer() );
+ rCursor.GetRingContainer().merge( pShCursor->GetRingContainer() );
+}
+
+PaMIntoCursorShellRing::~PaMIntoCursorShellRing()
+{
+ // and take out the Pam again:
+ RemoveFromRing( rDelPam, pPrevDelPam );
+ RemoveFromRing( rCursor, 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()->nNode == rDelPam.GetMark()->nNode);
+ pDoc->getIDocumentContentOperations().DeleteAndJoin( rDelPam );
+ }
+ else
+ {
+ pDoc->getIDocumentContentOperations().DeleteRange( rDelPam );
+ }
+}
+
+bool SwAutoCorrDoc::Delete( sal_Int32 nStt, sal_Int32 nEnd )
+{
+ SwTextNode const*const pTextNd = m_rCursor.GetNode().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.GetNode().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.GetNode().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()->nNode != *pos.first
+ || pPam->GetPoint()->nContent != 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;
+ }
+ }
+
+ 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()->nContent = std::min<sal_Int32>(
+ pos.first->GetText().getLength(), pos.second + nSourceLength);
+ pDoc->getIDocumentContentOperations().ReplaceRange( *pPam, rText, false );
+ pPam->Exchange();
+ pPam->DeleteMark();
+ }
+ }
+ else
+ {
+ if( nSourceLength != rText.getLength() )
+ {
+ pPam->SetMark();
+ pPam->GetPoint()->nContent = std::min<sal_Int32>(
+ pos.first->GetText().getLength(), pos.second + nSourceLength);
+ pDoc->getIDocumentContentOperations().ReplaceRange( *pPam, rText, false );
+ pPam->Exchange();
+ pPam->DeleteMark();
+ }
+ else
+ pDoc->getIDocumentContentOperations().Overwrite( *pPam, rText );
+ }
+
+ 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.GetNode().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.GetNode().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)));
+
+ SfxItemSet aSet( m_rEditSh.GetDoc()->GetAttrPool(),
+ svl::Items<RES_TXTATR_INETFMT, RES_TXTATR_INETFMT>{} );
+ 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_pIndex )
+ {
+ m_pIndex.reset(new SwNodeIndex(m_rCursor.GetPoint()->nNode));
+ }
+ sw::GotoPrevLayoutTextFrame(*m_pIndex, m_rEditSh.GetLayout());
+
+ SwTextFrame const* pFrame(nullptr);
+ for (SwTextNode * pTextNd = m_pIndex->GetNode().GetTextNode();
+ pTextNd; pTextNd = m_pIndex->GetNode().GetTextNode())
+ {
+ pFrame = static_cast<SwTextFrame const*>(
+ pTextNd->getLayoutFrame(m_rEditSh.GetLayout()));
+ if (pFrame && !pFrame->GetText().isEmpty())
+ {
+ break;
+ }
+ sw::GotoPrevLayoutTextFrame(*m_pIndex, m_rEditSh.GetLayout());
+ }
+ if (pFrame && 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.GetNode().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(":");
+
+ SwPaM aPam(pFrame->MapViewToModelPos(TextFrameIndex(rSttPos)),
+ pFrame->MapViewToModelPos(TextFrameIndex(nEndPos + (replaceLastChar ? 1 : 0))));
+
+ 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()->nNode == ranges.front()->GetMark()->nNode);
+ 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.GetNode().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_pIndex, "who has not deleted his Index?" );
+ m_pIndex.reset(new SwNodeIndex( m_rCursor.GetPoint()->nNode ));
+ sw::GotoPrevLayoutTextFrame(*m_pIndex, 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()->nContent.Assign( nullptr, 0 );
+ aCpyPam.GetPoint()->nNode = *pTableNd;
+ }
+ aCpyPam.SetMark();
+
+ // then until the end of the Nodes Array
+ aCpyPam.GetPoint()->nNode.Assign( pAutoDoc->GetNodes().GetEndOfContent(), -1 );
+ pContentNd = aCpyPam.GetContentNode();
+ aCpyPam.GetPoint()->nContent.Assign(
+ pContentNd, pContentNd ? pContentNd->Len() : 0);
+
+ SwDontExpandItem aExpItem;
+ aExpItem.SaveDontExpandItems( *aPam.GetPoint() );
+
+ pAutoDoc->getIDocumentContentOperations().CopyRange(aCpyPam, *aPam.GetPoint(), SwCopyFlags::CheckPosInFly);
+
+ aExpItem.RestoreDontExpandItems( *aPam.GetPoint() );
+
+ if( pPara )
+ {
+ sw::GotoNextLayoutTextFrame(*m_pIndex, m_rEditSh.GetLayout());
+ pTextNd = m_pIndex->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 )
+{
+ if( m_bUndoIdInitialized )
+ m_bUndoIdInitialized = true;
+
+ SwTextNode* pTextNd = m_rCursor.GetNode().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() )
+ {
+ // 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())
+ {
+ Color* pColor = nullptr;
+ Color** ppColor = &pColor;
+ // Send text as NatNum12 prefix
+ OUString sPrefix("[NatNum12 " + sDisambiguatedWord.makeStringAndClear() + "]0");
+ if (pFormatter->GetPreviewString(sPrefix, 0, sConverted, ppColor, LANGUAGE_USER_HUNGARIAN_ROVAS))
+ 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 )
+{
+ sal_uLong nNode = m_pIndex ? m_pIndex->GetIndex() : m_rCursor.GetPoint()->nNode.GetIndex();
+ 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()->nNode.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.nNode.GetIndex() == m_nNode && rPos.nContent.GetIndex() == m_nContent)
+ {
+ // get the current autocorrection:
+ SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect();
+
+ // then add to the list:
+ if (ACFlags::CapitalStartWord & m_nFlags)
+ pACorr->AddWrtSttException(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.nNode.GetIndex() == m_nNode && rPos.nContent.GetIndex() == m_nContent)
+ m_bDeleted = bRet = true;
+ return bRet;
+}
+
+SwDontExpandItem::~SwDontExpandItem()
+{
+}
+
+void SwDontExpandItem::SaveDontExpandItems( const SwPosition& rPos )
+{
+ const SwTextNode* pTextNd = rPos.nNode.GetNode().GetTextNode();
+ if( pTextNd )
+ {
+ m_pDontExpandItems.reset( new SfxItemSet( const_cast<SwDoc*>(pTextNd->GetDoc())->GetAttrPool(),
+ aCharFormatSetRange ) );
+ const sal_Int32 n = rPos.nContent.GetIndex();
+ if (!pTextNd->GetParaAttr( *m_pDontExpandItems, n, n,
+ n != pTextNd->GetText().getLength() ))
+ {
+ m_pDontExpandItems.reset();
+ }
+ }
+}
+
+void SwDontExpandItem::RestoreDontExpandItems( const SwPosition& rPos )
+{
+ SwTextNode* pTextNd = rPos.nNode.GetNode().GetTextNode();
+ if( pTextNd )
+ {
+ const sal_Int32 nStart = rPos.nContent.GetIndex();
+ if( nStart == pTextNd->GetText().getLength() )
+ pTextNd->FormatToTextAttr( pTextNd );
+
+ if( pTextNd->GetpSwpHints() && pTextNd->GetpSwpHints()->Count() )
+ {
+ 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 000000000..7588104d5
--- /dev/null
+++ b/sw/source/core/edit/autofmt.cxx
@@ -0,0 +1,2834 @@
+/* -*- 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 <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 <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 <index.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>
+
+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
+ sal_uLong m_nEndNdIdx; // for the percentage-display
+ mutable std::unique_ptr<CharClass> m_pCharClass; // 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_pCharClass || eLang != m_eCharClassLang )
+ {
+ m_pCharClass.reset( new CharClass( LanguageTag( eLang ) ) );
+ m_eCharClassLang = eLang;
+ }
+ return *m_pCharClass;
+ }
+
+ 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( const OUString& rStr );
+ static sal_Int32 GetTrailingBlanks( const OUString& rStr );
+
+ 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 const & rFlags,
+ SwNodeIndex const * pSttNd = nullptr, SwNodeIndex 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:
+ 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( m_aNdIdx.GetIndex() + m_nEndNdIdx - m_aEndNdIdx.GetIndex(),
+ m_pDoc->GetDocShell() );
+
+ m_pCurTextNd = static_cast<SwTextNode*>(pNewNd);
+ m_pCurTextFrame = GetFrame( *m_pCurTextNd );
+}
+
+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() != nullptr)
+ {
+ assert(!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();
+ TextFrameIndex nBlanks(GetLeadingBlanks(rText));
+ const TextFrameIndex nLen = TextFrameIndex(rText.getLength()) - nBlanks;
+ if( !nLen )
+ return false;
+
+ // -, +, * separated by blank ??
+ if (TextFrameIndex(2) < 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
+ m_aDelPam.GetMark()->nContent = 0;
+
+ editeng::SvxBorderLine aLine;
+ switch( eState )
+ {
+ case 1: // single, 0.05 pt
+ aLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID);
+ aLine.SetWidth( DEF_LINE_WIDTH_0 );
+ break;
+ case 2: // single, 1.0 pt
+ aLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID);
+ aLine.SetWidth( DEF_LINE_WIDTH_1 );
+ break;
+ case 3: // double, 1.0 pt
+ aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
+ aLine.SetWidth( DEF_LINE_WIDTH_1 );
+ break;
+ case 4: // double (thick/thin), 4.0 pt
+ aLine.SetBorderLineStyle(SvxBorderLineStyle::THICKTHIN_SMALLGAP);
+ aLine.SetWidth( DEF_LINE_WIDTH_3 );
+ break;
+ case 5: // double (thin/thick), 4.0 pt
+ aLine.SetBorderLineStyle(SvxBorderLineStyle::THINTHICK_SMALLGAP);
+ aLine.SetWidth( DEF_LINE_WIDTH_3 );
+ break;
+ case 6: // double, 2.5 pt
+ aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
+ aLine.SetWidth( DEF_LINE_WIDTH_2 );
+ break;
+ }
+ SfxItemSet aSet(m_pDoc->GetAttrPool(),
+ svl::Items<RES_PARATR_CONNECT_BORDER, RES_PARATR_CONNECT_BORDER,
+ RES_BOX, RES_BOX>{});
+ 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( static_cast<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( static_cast<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()->nNode );
+ m_aDelPam.Move( fnMoveForward );
+ m_pDoc->InsertTable( SwInsertTableOptions( SwInsertTableFlags::All , 1 ),
+ *m_aDelPam.GetPoint(), 1, nColCnt, eHori,
+ nullptr, &aPosArr );
+ m_aDelPam.GetPoint()->nNode = 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( const OUString& rStr )
+{
+ sal_Int32 nL;
+ sal_Int32 n;
+
+ for( nL = rStr.getLength(), n = 0; n < nL && IsSpace( rStr[ n ] ); ++n )
+ ;
+ return n;
+}
+
+sal_Int32 SwAutoFormat::GetTrailingBlanks( const OUString& rStr )
+{
+ sal_Int32 nL = rStr.getLength(), n = nL;
+ if( !nL )
+ return 0;
+
+ while( --n && IsSpace( rStr[ 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, OUString(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 = static_cast<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 = static_cast<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()->nNode = *m_pCurTextFrame->GetTextNodeForParaProps();
+ m_aDelPam.GetPoint()->nContent.Assign(m_aDelPam.GetPoint()->nNode.GetNode().GetContentNode(), 0);
+
+ // keep hard tabs, alignment, language, hyphenation, DropCaps and nearly all frame attributes
+ SfxItemSet aSet(
+ m_pDoc->GetAttrPool(),
+ svl::Items<
+ RES_CHRATR_LANGUAGE, RES_CHRATR_LANGUAGE,
+ RES_PARATR_ADJUST, RES_PARATR_ADJUST,
+ RES_PARATR_TABSTOP, RES_PARATR_DROP,
+ RES_BACKGROUND, RES_SHADOW>{});
+
+ if (m_aDelPam.GetPoint()->nNode.GetNode().GetTextNode()->HasSwAttrSet())
+ {
+ aSet.Put(*m_aDelPam.GetPoint()->nNode.GetNode().GetTextNode()->GetpSwAttrSet());
+ // take HeaderLine/TextBody only if centered or right aligned, otherwise only justification
+ SvxAdjustItem const * pAdj;
+ if( SfxItemState::SET == aSet.GetItemState( RES_PARATR_ADJUST,
+ false, reinterpret_cast<const SfxPoolItem**>(&pAdj) ))
+ {
+ 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 SfxPoolItem* pItem;
+ if( SfxItemState::SET == pSet->GetItemState( RES_BREAK, false, &pItem )
+ && SvxBreak::NONE != static_cast<const SvxFormatBreakItem*>(pItem)->GetBreak() )
+ return true;
+
+ if( SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC, false, &pItem )
+ && static_cast<const SwFormatPageDesc*>(pItem)->GetPageDesc()
+ && UseOnPage::NONE != static_cast<const SwFormatPageDesc*>(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 )
+ {
+ // 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 );
+
+ // 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()->nNode;
+ m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
+ m_pCurTextFrame = GetFrame(*m_pCurTextNd); // keep it up to date
+ }
+ else
+ m_pEditShell->DeleteSel( rDelPam );
+}
+
+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()->nNode = m_pCurTextFrame->GetTextNodeFirst()->GetIndex() - 1;
+ SwTextNode* pTNd = m_aDelPam.GetNode( false ).GetTextNode();
+ if( pTNd )
+ // first use the previous text node
+ m_aDelPam.GetMark()->nContent.Assign(pTNd, pTNd->GetText().getLength());
+ else if( bTstNextPara )
+ {
+ // then try the next (at the beginning of a Doc, table cells, frames, ...)
+ m_aDelPam.GetMark()->nNode = (m_pCurTextFrame->GetMergedPara()
+ ? m_pCurTextFrame->GetMergedPara()->pLastNode
+ : m_pCurTextNd
+ )->GetIndex() + 1;
+ pTNd = m_aDelPam.GetNode( false ).GetTextNode();
+ if( pTNd )
+ {
+ m_aDelPam.GetMark()->nContent.Assign( pTNd, 0 );
+ *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.GetNode().IsTextNode());
+ assert(!m_aDelPam.HasMark());
+ m_aDelPam.SetMark(); // mark remains at join position
+ m_pCurTextFrame = GetFrame(*m_aDelPam.GetNode().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 )
+ {
+ // 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()->nNode = *m_pCurTextFrame->GetTextNodeFirst();
+ m_aDelPam.GetPoint()->nContent.Assign(m_pCurTextFrame->GetTextNodeFirst(), 0);
+ m_aDelPam.SetMark();
+
+ --m_aDelPam.GetPoint()->nNode;
+ SwTextNode* pTNd = m_aDelPam.GetNode().GetTextNode();
+ if( pTNd )
+ {
+ // use the previous text node first
+ m_aDelPam.GetPoint()->nContent.Assign(pTNd, 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);
+ SetColl( RES_POOLCOLL_TEXT, true );
+ 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().GetLRSpace().GetLeft();
+ }
+
+ if( m_bMoreLines )
+ DelMoreLinesBlanks();
+ else
+ bBreak = !IsFastFullLine(*m_pCurTextFrame)
+ || IsBlanksInString(*m_pCurTextFrame)
+ || IsSentenceAtEnd(*m_pCurTextFrame);
+ bool bRTL = m_pEditShell->IsInRightToLeftText();
+ 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 = m_pCurTextFrame->GetText();
+ TextFrameIndex nTextStt(0);
+ const sal_Unicode* pFndBulletChr = nullptr;
+ if (m_aFlags.bChgEnumNum && 2 < 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_Unicode 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.SetPrefix(OUString());
+ aFormat.SetSuffix(OUString());
+ 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( static_cast<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 ) );
+ aFormat.SetPrefix( aPrefix.getToken( 0, u'\x0001', nPrefixIdx ));
+ aFormat.SetStart( static_cast<sal_uInt16>(aPrefix.getToken( 0, u'\x0001', nPrefixIdx ).toInt32()));
+ aFormat.SetSuffix( aPostfix.getToken( 0, u'\x0001' ));
+ 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 ) );
+
+ if( !n )
+ aFormat.SetPrefix( aPrefix.getToken( 0, u'\x0001', nPrefixIdx )); // token 0, read only on first loop
+ aFormat.SetStart( static_cast<sal_uInt16>(aPrefix.getToken( 0, u'\x0001', nPrefixIdx ).toInt32() ));
+ aFormat.SetSuffix( aPostfix.getToken( 0, u'\x0001', nPostfixIdx ));
+ 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( static_cast<sal_uInt16>(RES_POOLCOLL_NUM_LEVEL1 + ( std::min( nLvl, cnNumBullColls ) * 4 ) ));
+ else
+ bChgEnum = false;
+ }
+
+ if ( bChgEnum || bChgBullet )
+ {
+ m_aDelPam.DeleteMark();
+ m_aDelPam.GetPoint()->nNode = *m_pCurTextFrame->GetTextNodeForParaProps();
+
+ if( m_aFlags.bSetNumRule )
+ {
+ if( m_aFlags.bAFormatByInput )
+ {
+ m_aDelPam.SetMark();
+ SwTextFrame const*const pNextFrame = GetNextNode(false);
+ assert(pNextFrame);
+ m_aDelPam.GetMark()->nNode = *pNextFrame->GetTextNodeForParaProps();
+ m_aDelPam.GetNode(false).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(2);
+
+ while (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 = OUStringChar( m_aFlags.cBullet ) + 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()->nNode.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()->nNode.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( static_cast<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<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( static_cast<sal_uInt16>(RES_POOLCOLL_HEADLINE1 + nLvl ), true );
+ if( m_aFlags.bAFormatByInput )
+ {
+ SwTextFormatColl& rNxtColl = m_pCurTextFrame->GetTextNodeForParaProps()->GetTextColl()->GetNextTextFormatColl();
+
+ JoinPrevPara();
+
+ DeleteLeadingTrailingBlanks( true, false );
+ const SwTextFrame *const pNextFrame = GetNextNode(false);
+ (void)DeleteJoinCurNextPara(pNextFrame, true);
+
+ m_aDelPam.DeleteMark();
+ m_aDelPam.GetPoint()->nNode = *GetNextNode(false)->GetTextNodeForParaProps();
+ m_aDelPam.GetPoint()->nContent.Assign( m_aDelPam.GetContentNode(), 0 );
+ 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()->nContent = m_aDelPam.GetMark()->nContent.GetIndex() + 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()->nNode;
+ 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()->nContent = m_aDelPam.GetMark()->nContent.GetIndex() + 1;
+ m_pDoc->getIDocumentContentOperations().ReplaceRange( m_aDelPam, sReplace, false );
+
+ if( m_aFlags.bWithRedlining )
+ {
+ m_aNdIdx = m_aDelPam.GetPoint()->nNode;
+ 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()->nNode;
+ 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);
+ // 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 (pATst->FnAddNonBrkSpace(aACorrDoc, *pText, sal_Int32(nPos), eLang, bNbspRunNext))
+ --nPos;
+ }
+ break;
+
+ case '.':
+ case '!':
+ case '?':
+ if( m_aFlags.bCapitalStartSentence )
+ bFirstSent = true;
+ [[fallthrough]];
+ default:
+ if (!(rAppCC.isLetterNumeric(*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 );
+ 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()->nNode;
+ 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
+ }
+
+ LanguageType eLang = bGetLanguage
+ ? m_pCurTextFrame->GetLangOfChar(nSttPos, 0, true)
+ : LANGUAGE_SYSTEM;
+
+ if( m_aFlags.bTransliterateRTL && eLang == LANGUAGE_HUNGARIAN &&
+ SetRedlineText( STR_AUTOFMTREDL_TRANSLITERATE_RTL ) &&
+ aACorrDoc.TransliterateRTLWord(reinterpret_cast<sal_Int32&>(nSttPos), sal_Int32(nPos)))
+ {
+ nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint());
+ if( m_aFlags.bWithRedlining )
+ {
+ m_aNdIdx = m_aDelPam.GetPoint()->nNode;
+ 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 )
+ {
+ SetRedlineText( STR_AUTOFMTREDL_NON_BREAK_SPACE );
+ pATst->FnAddNonBrkSpace(aACorrDoc, *pText, sal_Int32(nPos), eLang, bNbspRunNext);
+ }
+
+ 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)))
+ {
+ 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()->nNode;
+ 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 const & rFlags,
+ SwNodeIndex const * pSttNd, SwNodeIndex const * pEndNd )
+ : m_aFlags( rFlags ),
+ m_aDelPam( pEdShell->GetDoc()->GetNodes().GetEndOfExtras() ),
+ m_aNdIdx( pEdShell->GetDoc()->GetNodes().GetEndOfExtras(), +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 )
+ {
+ m_nEndNdIdx = m_aEndNdIdx.GetIndex();
+ ::StartProgress( STR_STATSTR_AUTOFORMAT, m_aNdIdx.GetIndex(),
+ 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, 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;
+ sal_uLong 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);
+ *pEdShell->GetCursor() = m_aDelPam;
+ pEdShell->Push();
+
+ eStat = IS_END;
+ 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();
+ 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;
+ }
+
+ // check for hard spaces or LRSpaces set by the template
+ if( IsPoolUserFormat( nPoolId ) ||
+ RES_POOLCOLL_STANDARD == nPoolId )
+ {
+ short nSz;
+ SvxLRSpaceItem const * pLRSpace;
+ if (SfxItemState::SET == m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().
+ GetItemState( RES_LR_SPACE, true,
+ reinterpret_cast<const SfxPoolItem**>(&pLRSpace) ) &&
+ ( 0 != (nSz = pLRSpace->GetTextFirstLineOffset()) ||
+ 0 != pLRSpace->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( pLRSpace->GetTextLeft() ) // 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())
+ {
+ short nSz;
+ SvxLRSpaceItem const * pLRSpace;
+ if( bReplaceStyles &&
+ SfxItemState::SET == m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().
+ GetItemState( RES_LR_SPACE, false,
+ reinterpret_cast<const SfxPoolItem**>(&pLRSpace) ) &&
+ ( 0 != (nSz = pLRSpace->GetTextFirstLineOffset()) ||
+ 0 != pLRSpace->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( pLRSpace->GetTextLeft() ) // is indentation
+ BuildTextIndent();
+ else
+ BuildText();
+ }
+ }
+ }
+ 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 )
+{
+ std::unique_ptr<SwWait> pWait;
+
+ SET_CURR_SHELL( this );
+ StartAllAction();
+ StartUndo( SwUndoId::AUTOFORMAT );
+
+ SvxSwAutoFormatFlags aAFFlags; // use default values or add params?
+ if( pAFlags )
+ {
+ aAFFlags = *pAFlags;
+ if( !aAFFlags.bAFormatByInput )
+ pWait.reset(new SwWait( *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 aFormat( this, aAFFlags, &(rPaM.Start()->nNode),
+ &(rPaM.End()->nNode) );
+ }
+ }
+ }
+ else
+ {
+ SwAutoFormat aFormat( this, aAFFlags );
+ }
+
+ EndUndo( SwUndoId::AUTOFORMAT );
+ EndAllAction();
+}
+
+void SwEditShell::AutoFormatBySplitNode()
+{
+ SET_CURR_SHELL( this );
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->IsMultiSelection() || !pCursor->Move( fnMoveBackward, GoInNode ) )
+ return;
+
+ StartAllAction();
+ StartUndo( SwUndoId::AUTOFORMAT );
+
+ bool bRange = false;
+ pCursor->SetMark();
+ SwIndex* pContent = &pCursor->GetMark()->nContent;
+ if( pContent->GetIndex() )
+ {
+ *pContent = 0;
+ bRange = true;
+ }
+ else
+ {
+ // then go one node backwards
+ SwNodeIndex aNdIdx(pCursor->GetMark()->nNode);
+ sw::GotoPrevLayoutTextFrame(aNdIdx, GetLayout());
+ SwTextNode* pTextNd = aNdIdx.GetNode().GetTextNode();
+ if (pTextNd && !pTextNd->GetText().isEmpty())
+ {
+ pContent->Assign( pTextNd, 0 );
+ pCursor->GetMark()->nNode = aNdIdx;
+ bRange = true;
+ }
+ }
+
+ if( bRange )
+ {
+ Push(); // save cursor
+
+ SvxSwAutoFormatFlags aAFFlags = *GetAutoFormatFlags(); // use default values so far
+
+ SwAutoFormat aFormat( this, aAFFlags, &pCursor->GetMark()->nNode,
+ &pCursor->GetPoint()->nNode );
+ 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 ))
+ 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 000000000..06546d81d
--- /dev/null
+++ b/sw/source/core/edit/edatmisc.cxx
@@ -0,0 +1,190 @@
+/* -*- 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 std::set<sal_uInt16> &attrs, SwPaM* pPaM )
+{
+ SET_CURR_SHELL( 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()->nNode.GetNode().GetTextNode();
+ if (pTextNode)
+ {
+ pTextNode->GCAttr();
+ }
+ }
+ else
+ {
+ const SwNodeIndex& rEnd = rPaM.End()->nNode;
+ SwNodeIndex aIdx( rPaM.Start()->nNode );
+ 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 );
+}
+
+// tdf#122893 turn off ShowChanges mode to apply paragraph formatting permanently with redlining
+// ie. in all directly preceding deleted paragraphs at the actual cursor positions
+static void lcl_disableShowChangesIfNeeded( SwDoc *const pDoc, const SwNode& rNode, RedlineFlags &eRedlMode )
+{
+ if ( IDocumentRedlineAccess::IsShowChanges(eRedlMode) &&
+ // is there redlining at beginning of the position (possible redline block before the modified node)
+ pDoc->getIDocumentRedlineAccess().GetRedlinePos( rNode, RedlineType::Any ) <
+ pDoc->getIDocumentRedlineAccess().GetRedlineTable().size() )
+ {
+ eRedlMode = RedlineFlags::ShowInsert | RedlineFlags::Ignore;
+ pDoc->getIDocumentRedlineAccess().SetRedlineFlags( eRedlMode );
+ }
+}
+
+void SwEditShell::SetAttrItem( const SfxPoolItem& rHint, SetAttrMode nFlags, const bool bParagraphSetting )
+{
+ SET_CURR_SHELL( this );
+ StartAllAction();
+ RedlineFlags eRedlMode = GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags(), eOldMode = eRedlMode;
+ 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() ))
+ {
+ if (bParagraphSetting)
+ lcl_disableShowChangesIfNeeded( GetDoc(), (*rPaM.Start()).nNode.GetNode(), eRedlMode);
+
+ GetDoc()->getIDocumentContentOperations().InsertPoolItem(rPaM, rHint, nFlags, GetLayout());
+ }
+ }
+
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSATTR, nullptr);
+ }
+ else
+ {
+ if( !HasSelection() )
+ UpdateAttr();
+
+ if (bParagraphSetting)
+ lcl_disableShowChangesIfNeeded( GetDoc(), (*pCursor->Start()).nNode.GetNode(), eRedlMode);
+
+ GetDoc()->getIDocumentContentOperations().InsertPoolItem(*pCursor, rHint, nFlags, GetLayout());
+ }
+ EndAllAction();
+ GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eOldMode );
+}
+
+void SwEditShell::SetAttrSet( const SfxItemSet& rSet, SetAttrMode nFlags, SwPaM* pPaM, const bool bParagraphSetting )
+{
+ SET_CURR_SHELL( this );
+ SwPaM* pCursor = pPaM ? pPaM : GetCursor();
+ StartAllAction();
+ RedlineFlags eRedlMode = GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags(), eOldMode = eRedlMode;
+ 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() ))
+ {
+ if (bParagraphSetting)
+ lcl_disableShowChangesIfNeeded( GetDoc(), (*rTmpCursor.Start()).nNode.GetNode(), eRedlMode);
+
+ GetDoc()->getIDocumentContentOperations().InsertItemSet(rTmpCursor, rSet, nFlags, GetLayout());
+ }
+ }
+
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSATTR, nullptr);
+ }
+ else
+ {
+ if( !HasSelection() )
+ UpdateAttr();
+
+ if (bParagraphSetting)
+ lcl_disableShowChangesIfNeeded( GetDoc(), (*pCursor->Start()).nNode.GetNode(), eRedlMode);
+
+ GetDoc()->getIDocumentContentOperations().InsertItemSet(*pCursor, rSet, nFlags, GetLayout());
+ }
+ EndAllAction();
+ GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eOldMode );
+}
+
+/* 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 000000000..20abffc64
--- /dev/null
+++ b/sw/source/core/edit/edattr.cxx
@@ -0,0 +1,853 @@
+/* -*- 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 <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()->nNode);
+
+ 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(static_cast<sal_uInt16>(nListLevel)).GetCharFormatName();
+ SwCharFormat * pCharFormat =
+ GetDoc()->FindCharFormatByName(aCharFormatName);
+
+ if (pCharFormat)
+ rSet.Put(pCharFormat->GetAttrSet());
+ }
+ }
+
+ continue;
+ }
+
+ sal_uLong nSttNd = rCurrentPaM.GetMark()->nNode.GetIndex(),
+ nEndNd = rCurrentPaM.GetPoint()->nNode.GetIndex();
+ sal_Int32 nSttCnt = rCurrentPaM.GetMark()->nContent.GetIndex();
+ sal_Int32 nEndCnt = rCurrentPaM.GetPoint()->nContent.GetIndex();
+
+ if( nSttNd > nEndNd || ( nSttNd == nEndNd && nSttCnt > nEndCnt ))
+ {
+ std::swap(nSttNd, nEndNd);
+ std::swap(nSttCnt, nEndCnt);
+ }
+
+ if( 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( sal_uLong 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()->IsHideRedlines()
+ || 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 );
+}
+
+bool 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
+ sal_uLong nSttNd = rCurrentPaM.GetMark()->nNode.GetIndex(),
+ nEndNd = rCurrentPaM.GetPoint()->nNode.GetIndex();
+
+ // 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( sal_uLong n = nSttNd; n <= nEndNd; ++n )
+ {
+ // get the node
+ SwNode* pNd = GetDoc()->GetNodes()[ n ];
+
+ if (GetLayout()->IsHideRedlines()
+ && 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 false;
+ }
+ }
+
+ return true;
+}
+
+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
+ sal_uLong nSttNd = rCurrentPaM.GetMark()->nNode.GetIndex(),
+ nEndNd = rCurrentPaM.GetPoint()->nNode.GetIndex();
+
+ // reverse start and end if they aren't sorted correctly
+ if( nSttNd > nEndNd )
+ std::swap(nSttNd, nEndNd);
+
+ // for all the nodes in the current Point and Mark
+ for( sal_uLong 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(), SwNodeIndex(*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
+ sal_uLong nSttNd = rCurrentPaM.Start()->nNode.GetIndex(),
+ nEndNd = rCurrentPaM.End()->nNode.GetIndex();
+ sal_Int32 nSttCnt = rCurrentPaM.Start()->nContent.GetIndex();
+ sal_Int32 nEndCnt = rCurrentPaM.End()->nContent.GetIndex();
+
+ SwPaM* pNewPaM = nullptr;
+ const SfxPoolItem* pItem = nullptr;
+
+ // for all the nodes in the current selection
+ for( sal_uLong 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->GetNode().GetTextNode();
+ if( !pTextNd )
+ return false;
+
+ SwTextAttr *const pFootnote = pTextNd->GetTextAttrForCharAt(
+ pCursor->GetPoint()->nContent.GetIndex(), 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;
+
+ 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;
+
+ const SvxTabStopItem& rTabItem = GetDoc()->GetDefault( RES_PARATR_TABSTOP );
+ sal_uInt16 nDefDist = static_cast<sal_uInt16>(rTabItem.Count() ? rTabItem[0].GetTabPos() : 1134);
+ if( !nDefDist )
+ return false;
+
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ sal_uLong nSttNd = rPaM.GetMark()->nNode.GetIndex(),
+ nEndNd = rPaM.GetPoint()->nNode.GetIndex();
+
+ if( nSttNd > nEndNd )
+ std::swap(nSttNd, nEndNd);
+
+ SwContentNode* pCNd;
+ for( sal_uLong n = nSttNd; bRet && n <= nEndNd; ++n )
+ if( nullptr != ( pCNd = GetDoc()->GetNodes()[ n ]->GetTextNode() ))
+ {
+ pCNd = sw::GetParaPropsNode(*GetLayout(), *pCNd);
+ const SvxLRSpaceItem& rLS = static_cast<const SvxLRSpaceItem&>(
+ pCNd->GetAttr( RES_LR_SPACE ));
+ if( bRight )
+ {
+ long nNext = rLS.GetTextLeft() + nDefDist;
+ if( bModulus )
+ nNext = ( nNext / nDefDist ) * nDefDist;
+ SwFrame* pFrame = pCNd->getLayoutFrame( GetLayout() );
+ if ( pFrame )
+ {
+ const sal_uInt16 nFrameWidth = static_cast<sal_uInt16>( pFrame->IsVertical() ?
+ pFrame->getFrameArea().Height() :
+ pFrame->getFrameArea().Width() );
+ bRet = nFrameWidth > ( nNext + MM50 );
+ }
+ 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( static_cast<sal_uInt16>(nListLevel) );
+ if( SVX_NUM_BITMAP != rNumFormat.GetNumberingType() )
+ {
+ if ( SVX_NUM_CHAR_SPECIAL == rNumFormat.GetNumberingType() )
+ sExp = OUString(rNumFormat.GetBulletChar());
+ 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())
+ {
+ const SwPosition *pStt = rPaM.Start(),
+ *pEnd = pStt == rPaM.GetMark()
+ ? rPaM.GetPoint()
+ : rPaM.GetMark();
+ if( pStt == pEnd || *pStt == *pEnd )
+ {
+ const SwTextNode* pTNd = pStt->nNode.GetNode().GetTextNode();
+ if( pTNd )
+ {
+ // try to get SwScriptInfo
+ SwTextFrame const* pFrame;
+ const SwScriptInfo *const pScriptInfo =
+ SwScriptInfo::GetScriptInfo(*pTNd, &pFrame);
+
+ sal_Int32 nPos = pStt->nContent.GetIndex();
+ //Task 90448: we need the scripttype of the previous
+ // position, if no selection exist!
+ if( nPos )
+ {
+ SwIndex aIdx( pStt->nContent );
+ if( pTNd->GoPrevious( &aIdx, CRSR_SKIP_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
+ {
+ sal_uLong nEndIdx = pEnd->nNode.GetIndex();
+ SwNodeIndex aIdx( pStt->nNode );
+ 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->nNode
+ ? pStt->nContent.GetIndex()
+ : 0;
+ sal_Int32 nEndPos = aIdx == nEndIdx
+ ? pEnd->nContent.GetIndex()
+ : 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.nNode.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.nContent.GetIndex();
+ 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->nNode.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()->nNode.GetIndex())
+ ? 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 000000000..660f1f79a
--- /dev/null
+++ b/sw/source/core/edit/eddel.cxx
@@ -0,0 +1,363 @@
+/* -*- 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 <strings.hrc>
+#include <vector>
+
+void SwEditShell::DeleteSel( SwPaM& rPam, bool* pUndo )
+{
+ bool bSelectAll = StartsWithTable() && ExtendedSelectedAll();
+ // 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.GetNode().FindTableNode() &&
+ rPam.GetNode().StartOfSectionNode() !=
+ rPam.GetNode(false).StartOfSectionNode() && !bSelectAll )
+ {
+ // 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.GetNode();
+ const SwNode& rEndNd = *rNd.EndOfSectionNode();
+ if( pEndSelPos->nNode.GetIndex() <= rEndNd.GetIndex() )
+ {
+ *aDelPam.GetPoint() = *pEndSelPos;
+ pEndSelPos = nullptr; // misuse a pointer as a flag
+ }
+ else
+ {
+ // then go to the end of the selection
+ aDelPam.GetPoint()->nNode = 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::unique_ptr<SwPaM> pNewPam;
+ SwPaM * pPam = &rPam;
+ if (bSelectAll)
+ {
+ assert(dynamic_cast<SwShellCursor*>(&rPam)); // must be corrected pam
+ pNewPam.reset(new SwPaM(*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.
+ while (SwTableNode const* pTableNode =
+ pNewPam->Start()->nNode.GetNode().StartOfSectionNode()->FindTableNode())
+ {
+ pNewPam->Start()->nNode = *pTableNode;
+ }
+ // tdf#133990 ensure section is included in SwUndoDelete
+ while (SwSectionNode const* pSectionNode =
+ pNewPam->Start()->nNode.GetNode().StartOfSectionNode()->FindSectionNode())
+ {
+ pNewPam->Start()->nNode = *pSectionNode;
+ }
+ pNewPam->Start()->nContent.Assign(nullptr, 0);
+ pPam = pNewPam.get();
+ }
+ // delete everything
+ GetDoc()->getIDocumentContentOperations().DeleteAndJoin(*pPam);
+ SaveTableBoxContent( pPam->GetPoint() );
+ }
+
+ // Selection is not needed anymore
+ rPam.DeleteMark();
+}
+
+bool SwEditShell::Delete()
+{
+ SET_CURR_SHELL( this );
+ bool bRet = false;
+ if ( !HasReadonlySel() || CursorInsideInputField() )
+ {
+ 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, &bUndo );
+ }
+
+ // If undo container then close here
+ if( bUndo )
+ {
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr);
+ }
+ EndAllAction();
+ bRet = true;
+ }
+ else
+ {
+ bRet = RemoveParagraphMetadataFieldAtCursor();
+ }
+
+ return bRet;
+}
+
+bool SwEditShell::Copy( SwEditShell* pDestShell )
+{
+ if( !pDestShell )
+ pDestShell = this;
+
+ SET_CURR_SHELL( pDestShell );
+
+ // 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( pDestShell == this )
+ {
+ // First cursor represents the target position!!
+ rPaM.DeleteMark();
+ pPos = rPaM.GetPoint();
+ continue;
+ }
+ else
+ pPos = pDestShell->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( pDestShell->GetDoc() == GetDoc() &&
+ *rPaM.Start() <= *pTmp && *pTmp < *rPaM.End() )
+ return false;
+ }
+ }
+
+ pDestShell->StartAllAction();
+ SwPosition *pPos = nullptr;
+ bool bRet = false;
+ bool bFirstMove = true;
+ SwNodeIndex aSttNdIdx( pDestShell->GetDoc()->GetNodes() );
+ sal_Int32 nSttCntIdx = 0;
+ // For block selection this list is filled with the insert positions
+ auto pNextInsert = aInsertList.begin();
+
+ pDestShell->GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ if( !pPos )
+ {
+ if( pDestShell == this )
+ {
+ // First cursor represents the target position!!
+ rPaM.DeleteMark();
+ pPos = rPaM.GetPoint();
+ continue;
+ }
+ else
+ pPos = pDestShell->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->nNode.GetIndex()-1;
+ nSttCntIdx = pPos->nContent.GetIndex();
+ bFirstMove = false;
+ }
+
+ const bool bSuccess( GetDoc()->getIDocumentContentOperations().CopyRange(rPaM, *pPos, SwCopyFlags::CheckPosInFly) );
+ if (!bSuccess)
+ continue;
+
+ SwPaM aInsertPaM(*pPos, SwPosition(aSttNdIdx));
+ pDestShell->GetDoc()->MakeUniqueNumRules(aInsertPaM);
+
+ bRet = true;
+ }
+
+ // Maybe nothing has been moved?
+ if( !bFirstMove )
+ {
+ SwPaM* pCursor = pDestShell->GetCursor();
+ pCursor->SetMark();
+ pCursor->GetPoint()->nNode = aSttNdIdx.GetIndex()+1;
+ pCursor->GetPoint()->nContent.Assign( pCursor->GetContentNode(),nSttCntIdx);
+ pCursor->Exchange();
+ }
+ else
+ {
+ // If the cursor moved during move process, move also its GetMark
+ pDestShell->GetCursor()->SetMark();
+ pDestShell->GetCursor()->DeleteMark();
+ }
+#if OSL_DEBUG_LEVEL > 0
+ // check if the indices are registered in the correct nodes
+ {
+ for(SwPaM& rCmp : pDestShell->GetCursor()->GetRingContainer())
+ {
+ OSL_ENSURE( rCmp.GetPoint()->nContent.GetIdxReg()
+ == rCmp.GetContentNode(), "Point in wrong Node" );
+ OSL_ENSURE( rCmp.GetMark()->nContent.GetIdxReg()
+ == rCmp.GetContentNode(false), "Mark in wrong Node" );
+ }
+ }
+#endif
+
+ // close Undo container here
+ pDestShell->GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr );
+ pDestShell->EndAllAction();
+
+ pDestShell->SaveTableBoxContent( pDestShell->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 )
+{
+ SET_CURR_SHELL( this );
+
+ bool bRet = false;
+ if( !HasReadonlySel() )
+ {
+ 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;
+}
+
+/// special method for JOE's wizards
+bool SwEditShell::DelFullPara()
+{
+ bool bRet = false;
+ if( !IsTableMode() )
+ {
+ SwPaM* pCursor = GetCursor();
+ // no multi selection
+ if( !pCursor->IsMultiSelection() && !HasReadonlySel() )
+ {
+ SET_CURR_SHELL( 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 000000000..d0154b488
--- /dev/null
+++ b/sw/source/core/edit/edfcol.cxx
@@ -0,0 +1,2287 @@
+/* -*- 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 <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 <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 <tools/diagnose_ex.h>
+#include <IDocumentRedlineAccess.hxx>
+
+#define WATERMARK_NAME "PowerPlusWaterMarkObject"
+#define WATERMARK_AUTO_SIZE sal_uInt32(1)
+
+namespace
+{
+static const OUString MetaFilename("tscp/bails.rdf");
+static const OUString MetaNS("urn:bails");
+static const OUString ParagraphSignatureRDFNamespace = "urn:bails:loext:paragraph:signature:";
+static const OUString ParagraphSignatureIdRDFName = "urn:bails:loext:paragraph:signature:id";
+static const OUString ParagraphSignatureDigestRDFName = ":digest";
+static const OUString ParagraphSignatureDateRDFName = ":date";
+static const OUString ParagraphSignatureUsageRDFName = ":usage";
+static const OUString ParagraphSignatureLastIdRDFName = "urn:bails:loext:paragraph:signature:lastid";
+static const OUString ParagraphClassificationNameRDFName = "urn:bails:loext:paragraph:classification:name";
+static const OUString ParagraphClassificationValueRDFName = "urn:bails:loext:paragraph:classification:value";
+static const OUString ParagraphClassificationAbbrRDFName = "urn:bails:loext:paragraph:classification:abbreviation";
+static const OUString ParagraphClassificationFieldNamesRDFName = "urn:bails:loext:paragraph:classification:fields";
+static const OUString MetadataFieldServiceName = "com.sun.star.text.textfield.MetadataField";
+static const OUString DocInfoServiceName = "com.sun.star.text.TextField.DocInfo.Custom";
+
+/// 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, const OUString& 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, const OUString& 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,
+ const OUString& 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,
+ const OUString& 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.isEmpty() || 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,
+ const OUString& 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;
+
+ msg = SwResId(STR_SIGNED_BY) + ": " + aInfo.ouSubject + ", " +
+ 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* pDoc,
+ 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 = pDoc->GetIDocumentUndoRedo().DoesUndo();
+ pDoc->GetIDocumentUndoRedo().DoUndo(false);
+ comphelper::ScopeGuard const g([pDoc, isUndoEnabled]() {
+ pDoc->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* pDoc,
+ 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(pDoc, 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,
+ const OUString& sKey)
+{
+ const std::pair<OUString, OUString> rdfPair = lcl_getRDF(xModel, xField, ParagraphClassificationNameRDFName);
+ return rdfPair.first == ParagraphClassificationNameRDFName && (sKey.isEmpty() || rdfPair.second == sKey);
+}
+
+uno::Reference<text::XTextField> lcl_FindParagraphClassificationField(const uno::Reference<frame::XModel>& xModel,
+ const uno::Reference<text::XTextContent>& xParagraph,
+ const OUString& sKey = OUString())
+{
+ uno::Reference<text::XTextField> xTextField;
+
+ uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY);
+ if (!xTextPortionEnumerationAccess.is())
+ return xTextField;
+
+ // Enumerate text portions to find metadata fields. This is expensive, best to enumerate fields only.
+ 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> 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* pDoc, const uno::Reference<text::XTextContent>& xParagraph, const bool updateDontRemove)
+{
+ SwDocShell* pDocShell = pDoc->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(pDoc, xModel, xParagraph, xField, utf8Text);
+ }
+ else if (!lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).first)
+ {
+ pDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr);
+ pDoc->GetIDocumentUndoRedo().AppendUndo(
+ std::make_unique<SwUndoParagraphSigning>(pDoc, xField, xParagraph, false));
+ lcl_RemoveParagraphMetadataField(xField);
+ pDoc->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::makeAny(rsKey));
+ uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY);
+
+ rxText->insertTextContent(rxParagraphCursor, xTextContent, false);
+}
+
+static void removeAllClassificationFields(OUString const & 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 || !SfxObjectShell::Current())
+ 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 = SfxObjectShell::Current()->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::makeAny(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::makeAny(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::makeAny(awt::FontWeight::BOLD));
+ xFooterPropertySet->setPropertyValue("CharWeight", uno::makeAny(awt::FontWeight::BOLD));
+ }
+ else
+ {
+ xHeaderPropertySet->setPropertyValue("CharWeight", uno::makeAny(awt::FontWeight::NORMAL));
+ xFooterPropertySet->setPropertyValue("CharWeight", uno::makeAny(awt::FontWeight::NORMAL));
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
+
+std::vector<svx::ClassificationResult> SwEditShell::CollectAdvancedClassification()
+{
+ std::vector<svx::ClassificationResult> aResult;
+
+ SwDocShell* pDocShell = GetDoc()->GetDocShell();
+ if (!pDocShell || !SfxObjectShell::Current())
+ return aResult;
+
+ const OUString sBlank;
+
+ uno::Reference<document::XDocumentProperties> xDocumentProperties = SfxObjectShell::Current()->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::makeAny(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, 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::makeAny(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::makeAny(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::makeAny(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 uno::Reference<text::XTextContent>& 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
+ aResults.erase(std::remove_if(aResults.begin(),
+ aResults.end(),
+ [&](const svx::ClassificationResult& rResult)-> bool
+ { return rResult.meType == svx::ClassificationType::PARAGRAPH; }),
+ aResults.end());
+
+ 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()->nNode.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();
+ uno::Reference<text::XTextContent> xParent = SwXParagraph::CreateXParagraph(*pNode->GetDoc(), pNode);
+ lcl_ApplyParagraphClassification(GetDoc(), xModel, xParent, css::uno::Reference<css::rdf::XResource>(xParent, uno::UNO_QUERY), 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;
+ const OUString sBlank("");
+ 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()->nNode.GetNode().GetTextNode();
+ if (pNode == nullptr)
+ return aResult;
+
+ uno::Reference<text::XTextContent> xParent = SwXParagraph::CreateXParagraph(*pNode->GetDoc(), pNode);
+ 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);
+ aTransformation.set(2, 0, rMatrix.Line3.Column1);
+ aTransformation.set(2, 1, rMatrix.Line3.Column2);
+ aTransformation.set(2, 2, rMatrix.Line3.Column3);
+
+ 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 aShapeServiceName = "com.sun.star.drawing.CustomShape";
+ OUString sWatermark = "";
+ bool bSuccess = false;
+ uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, aShapeServiceName, 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 = aTransformation.get(2, 0);
+ aMatrix.Line3.Column2 = aTransformation.get(2, 1);
+ aMatrix.Line3.Column3 = aTransformation.get(2, 2);
+ uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
+ xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, uno::makeAny(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::makeAny(static_cast<sal_Int32>(nColor)));
+ xPropertySet->setPropertyValue(UNO_NAME_FILLSTYLE, uno::makeAny(drawing::FillStyle_SOLID));
+ xPropertySet->setPropertyValue(UNO_NAME_FILL_TRANSPARENCE, uno::makeAny(nTransparency));
+ xPropertySet->setPropertyValue(UNO_NAME_LINESTYLE, uno::makeAny(drawing::LineStyle_NONE));
+ xPropertySet->setPropertyValue(UNO_NAME_OPAQUE, uno::makeAny(false));
+ xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT, uno::makeAny(false));
+ xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWWIDTH, uno::makeAny(false));
+ xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEHEIGHT, uno::makeAny(nHeight));
+ xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEWIDTH, uno::makeAny(nWidth));
+ xPropertySet->setPropertyValue(UNO_NAME_TEXT_WRAP, uno::makeAny(text::WrapTextMode_THROUGH));
+ xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION, uno::makeAny(text::RelOrientation::PAGE_PRINT_AREA));
+ xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT_RELATION, uno::makeAny(text::RelOrientation::PAGE_PRINT_AREA));
+ xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME, uno::makeAny(sFont));
+ xPropertySet->setPropertyValue(UNO_NAME_CHAR_HEIGHT, uno::makeAny(WATERMARK_AUTO_SIZE));
+ xPropertySet->setPropertyValue("Transformation", uno::makeAny(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::makeAny(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::makeAny(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::makeAny(aMatrix));
+
+ xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT, uno::makeAny(text::HoriOrientation::CENTER));
+ xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT, uno::makeAny(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::makeAny(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::makeAny(sal_Int32(11)));
+ xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::makeAny(nOldValue));
+ xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(bDynamicHeight));
+ }
+}
+
+SwUndoParagraphSigning::SwUndoParagraphSigning(SwDoc* pDoc,
+ const uno::Reference<text::XTextField>& xField,
+ const uno::Reference<text::XTextContent>& xParent,
+ const bool bRemove)
+ : SwUndo(SwUndoId::PARA_SIGN_ADD, pDoc),
+ m_pDoc(pDoc),
+ m_xField(xField),
+ m_xParent(xParent),
+ m_bRemove(bRemove)
+{
+ // Save the metadata and field content to undo/redo.
+ uno::Reference<frame::XModel> xModel = m_pDoc->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_pDoc->GetIDocumentUndoRedo().DoesUndo();
+ m_pDoc->GetIDocumentUndoRedo().DoUndo(false);
+
+ // Prevent validation since this will trigger a premature validation
+ // upon inserting, but before setting the metadata.
+ SwEditShell* pEditSh = m_pDoc->GetEditShell();
+ const bool bOldValidationFlag = pEditSh->SetParagraphSignatureValidation(false);
+ comphelper::ScopeGuard const g([&] () {
+ pEditSh->SetParagraphSignatureValidation(bOldValidationFlag);
+ m_pDoc->GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
+ });
+
+ m_xField = lcl_InsertParagraphSignature(m_pDoc->GetDocShell()->GetBaseModel(), m_xParent, m_signature, m_usage);
+ lcl_DoUpdateParagraphSignatureField(m_pDoc, m_xField, m_display);
+}
+
+void SwUndoParagraphSigning::Remove()
+{
+ // Disable undo to avoid introducing noise when we edit the metadata field.
+ const bool isUndoEnabled = m_pDoc->GetIDocumentUndoRedo().DoesUndo();
+ m_pDoc->GetIDocumentUndoRedo().DoUndo(false);
+
+ // Prevent validation since this will trigger a premature validation
+ // upon removing.
+ SwEditShell* pEditSh = m_pDoc->GetEditShell();
+ const bool bOldValidationFlag = pEditSh->SetParagraphSignatureValidation(false);
+ comphelper::ScopeGuard const g([&] () {
+ pEditSh->SetParagraphSignatureValidation(bOldValidationFlag);
+ m_pDoc->GetIDocumentUndoRedo().DoUndo(isUndoEnabled);
+ });
+
+ lcl_RemoveParagraphMetadataField(m_xField);
+}
+
+void SwEditShell::SignParagraph()
+{
+ SwDocShell* pDocShell = GetDoc()->GetDocShell();
+ if (!pDocShell || !GetCursor() || !GetCursor()->Start())
+ return;
+ const SwPosition* pPosStart = GetCursor()->Start();
+ if (!pPosStart)
+ return;
+ SwTextNode* pNode = pPosStart->nNode.GetNode().GetTextNode();
+ if (!pNode)
+ return;
+
+ // Table text signing is not supported.
+ if (pNode->FindTableNode() != nullptr)
+ return;
+
+ // 1. Get the text (without fields).
+ const uno::Reference<text::XTextContent> xParagraph = SwXParagraph::CreateXParagraph(*pNode->GetDoc(), pNode);
+ 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.makeStringAndClear(), RTL_TEXTENCODING_UTF8, 0);
+
+ std::vector<css::beans::PropertyValue> vec = comphelper::sequenceToContainer<std::vector<css::beans::PropertyValue>>(aProperties);
+ auto it = std::find_if(vec.begin(), vec.end(), [](const beans::PropertyValue& rValue)
+ {
+ return rValue.Name == "Usage";
+ });
+
+ OUString aUsage;
+ if (it != vec.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);
+ });
+
+ GetDoc()->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);
+
+ GetDoc()->GetIDocumentUndoRedo().AppendUndo(
+ std::make_unique<SwUndoParagraphSigning>(GetDoc(), xField, xParagraph, true));
+
+ GetDoc()->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);
+ });
+
+ uno::Reference<text::XTextContent> xParentText = SwXParagraph::CreateXParagraph(*GetDoc(), pNode);
+ 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->nNode.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 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;
+
+ static const OUString sBlank("");
+ 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> xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY);
+
+ try
+ {
+ const css::uno::Reference<css::rdf::XResource> xSubject(xParagraph, uno::UNO_QUERY);
+ 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())
+ {
+ // 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);
+ const OUString sName = (it2 != aStatements.end() ? it->first : sBlank);
+ const OUString sValue = (it2 != aStatements.end() ? 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, 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()->nNode.GetNode().GetTextNode();
+ const sal_uLong index = GetCursor()->Start()->nContent.GetIndex();
+ 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()->nNode.GetNode().GetTextNode();
+ sal_uLong index = GetCursor()->Start()->nContent.GetIndex();
+ 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 uno::Reference<text::XTextContent>& 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()->nNode.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 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);
+ uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration();
+ while (xParagraphs->hasMoreElements())
+ {
+ uno::Reference<text::XTextContent> xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY);
+ 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();
+
+ RedlineFlags eRedlMode = GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags(), eOldMode = eRedlMode;
+
+ SwRewriter aRewriter;
+ aRewriter.AddRule(UndoArg1, pLocal->GetName());
+
+ GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::SETFMTCOLL, &aRewriter);
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+
+ if ( !rPaM.HasReadonlySel( GetViewOptions()->IsFormView() ) )
+ {
+ // tdf#105413 turn off ShowChanges mode for the next loops to apply styles permanently with redlining,
+ // ie. in all directly preceding deleted paragraphs at the actual cursor positions
+ if ( IDocumentRedlineAccess::IsShowChanges(eRedlMode) &&
+ // is there redlining at beginning of the position (possible redline block before the modified node)
+ GetDoc()->getIDocumentRedlineAccess().GetRedlinePos( (*rPaM.Start()).nNode.GetNode(), RedlineType::Any ) <
+ GetDoc()->getIDocumentRedlineAccess().GetRedlineTable().size() )
+ {
+ eRedlMode = RedlineFlags::ShowInsert | RedlineFlags::Ignore;
+ GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eRedlMode );
+ }
+
+ // 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()->nNode.GetNode().GetTextNode())
+ {
+ aPaM.Start()->nContent = 0;
+ aPaM.End()->nContent = pEndTextNode->GetText().getLength();
+ }
+ GetDoc()->RstTextAttrs(aPaM, /*bInclRefToxMark=*/false, /*bExactRange=*/true, GetLayout());
+ }
+
+ }
+ GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::SETFMTCOLL, &aRewriter);
+ EndAllAction();
+
+ GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eOldMode );
+}
+
+SwTextFormatColl* SwEditShell::MakeTextFormatColl(const OUString& rFormatCollName,
+ SwTextFormatColl* pParent)
+{
+ SwTextFormatColl *pColl;
+ if ( pParent == nullptr )
+ pParent = &GetTextFormatColl(0);
+ if ( (pColl=GetDoc()->MakeTextFormatColl(rFormatCollName, pParent)) == nullptr )
+ {
+ OSL_FAIL( "MakeTextFormatColl failed" );
+ }
+ return pColl;
+
+}
+
+void SwEditShell::FillByEx(SwTextFormatColl* pColl)
+{
+ SwPaM * pCursor = GetCursor();
+ SwContentNode * pCnt = pCursor->GetContentNode();
+ 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()->nNode);
+ }
+ const SfxItemSet* pSet = pCnt->GetpSwAttrSet();
+ if( pSet )
+ {
+ // 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 SfxPoolItem* pItem;
+ const SwNumRule* pRule = nullptr;
+ if (SfxItemState::SET == pSet->GetItemState(RES_BREAK, false)
+ || SfxItemState::SET == pSet->GetItemState(RES_PAGEDESC, false)
+ || (SfxItemState::SET == pSet->GetItemState(RES_PARATR_NUMRULE, false, &pItem)
+ && nullptr != (pRule = GetDoc()->FindNumRulePtr(
+ static_cast<const SwNumRuleItem*>(pItem)->GetValue()))
+ && pRule->IsAutoRule()))
+ {
+ SfxItemSet aSet( *pSet );
+ aSet.ClearItem( RES_BREAK );
+ aSet.ClearItem( RES_PAGEDESC );
+
+ if (pRule
+ || (SfxItemState::SET == pSet->GetItemState(RES_PARATR_NUMRULE, false, &pItem)
+ && nullptr != (pRule = GetDoc()->FindNumRulePtr(
+ static_cast<const SwNumRuleItem*>(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 000000000..dd05535e4
--- /dev/null
+++ b/sw/source/core/edit/edfld.cxx
@@ -0,0 +1,417 @@
+/* -*- 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 <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 <calbck.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 static_cast<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;
+
+ SET_CURR_SHELL( this );
+ StartAllAction();
+ StartUndo( SwUndoId::DELETE );
+ Push();
+ SwPaM* pPaM = GetCursor();
+ // TODO: this is really hackish
+ SwFieldHint aHint(pPaM, GetLayout());
+ SwIterator<SwClient,SwFieldType> aIter(*pType);
+ for( SwClient* pClient = aIter.First(); pClient; pClient = aIter.Next() )
+ {
+ pPaM->DeleteMark();
+ pClient->SwClientNotifyCall( *pType, aHint );
+ }
+
+ Pop(PopMode::DeleteCurrent);
+ EndAllAction();
+ EndUndo( SwUndoId::DELETE );
+}
+
+/// add a field at the cursor position
+void SwEditShell::Insert2(SwField const & rField, const bool bForceExpandHints)
+{
+ SET_CURR_SHELL( this );
+ StartAllAction();
+ SwFormatField aField( rField );
+
+ const SetAttrMode nInsertFlags = bForceExpandHints
+ ? SetAttrMode::FORCEHINTEXPAND
+ : SetAttrMode::DEFAULT;
+
+ for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) // for each PaM
+ {
+ const bool bSuccess(GetDoc()->getIDocumentContentOperations().InsertPoolItem(rPaM, aField, nInsertFlags));
+ OSL_ENSURE( bSuccess, "Doc->Insert(Field) failed");
+ }
+
+ EndAllAction();
+}
+
+/// Are the PaMs positioned on fields?
+static SwTextField* lcl_FindInputField( SwDoc* pDoc, 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<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<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)
+{
+ SET_CURR_SHELL( this );
+ StartAllAction();
+ {
+ // If there are no selections so take the value of the current cursor position.
+ SwMsgPoolItem* pMsgHint = nullptr;
+ SwRefMarkFieldUpdate aRefMkHt( GetOut() );
+ SwFieldIds nFieldWhich = rField.GetTyp()->Which();
+ if( SwFieldIds::GetRef == nFieldWhich )
+ pMsgHint = &aRefMkHt;
+
+ SwPaM* pCursor = GetCursor();
+ SwTextField *pTextField;
+ SwFormatField *pFormatField;
+
+ if ( !pCursor->IsMultiSelection() && !pCursor->HasMark())
+ {
+ pTextField = GetTextFieldAtPos( pCursor->Start(), true );
+
+ if (!pTextField) // #i30221#
+ pTextField = lcl_FindInputField( GetDoc(), rField);
+
+ if (pTextField != nullptr)
+ GetDoc()->getIDocumentFieldsAccess().UpdateField(pTextField, rField, pMsgHint, 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->nContent != pCurEnd->nContent
+ && (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()->nContent != pCurStt->nContent )
+ bOkay = false;
+
+ if( nullptr != (pTextField = GetTextFieldAtPos( pCurStt, true )) )
+ {
+ 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,
+ pMsgHint, false);
+ }
+ // The search area is reduced by the found area:
+ ++pCurStt->nContent;
+ }
+ }
+
+ if( bTableSelBreak ) // If table section and table formula are updated -> finish
+ break;
+
+ }
+ }
+ GetDoc()->getIDocumentState().SetModified();
+ EndAllAction();
+}
+
+SwDBData const & SwEditShell::GetDBData() const
+{
+ return GetDoc()->GetDBData();
+}
+
+const SwDBData& SwEditShell::GetDBDesc() const
+{
+ return GetDoc()->GetDBDesc();
+}
+
+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)
+{
+ SET_CURR_SHELL( this );
+ StartAllAction();
+ GetDoc()->getIDocumentFieldsAccess().UpdateExpFields(nullptr, true);
+ if (bCloseDB)
+ {
+#if HAVE_FEATURE_DBCONNECTIVITY
+ GetDoc()->GetDBManager()->CloseAll(); // close all database connections
+#endif
+ }
+ EndAllAction();
+}
+
+SwDBManager* SwEditShell::GetDBManager() const
+{
+#if HAVE_FEATURE_DBCONNECTIVITY
+ 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
+{
+ 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 000000000..c0a8fef2a
--- /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>
+#include <calbck.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 000000000..de6e0a18a
--- /dev/null
+++ b/sw/source/core/edit/edfmt.cxx
@@ -0,0 +1,159 @@
+/* -*- 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 <docary.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;
+ SfxItemSet aSet( GetDoc()->GetAttrPool(), svl::Items<RES_TXTATR_CHARFMT,
+ RES_TXTATR_CHARFMT>{} );
+ const SfxPoolItem* pItem;
+ if( GetCurAttr( aSet ) && SfxItemState::SET ==
+ aSet.GetItemState( RES_TXTATR_CHARFMT, false, &pItem ) )
+ pFormat = static_cast<const SwFormatCharFormat*>(pItem)->GetCharFormat();
+
+ return pFormat;
+}
+
+void SwEditShell::FillByEx(SwCharFormat* pCharFormat)
+{
+ SwPaM* pPam = GetCursor();
+ const SwContentNode* pCNd = pPam->GetContentNode();
+ 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->nNode == pMkPos->nNode ) // in the same node?
+ {
+ nStt = pPtPos->nContent.GetIndex();
+ if( nStt < pMkPos->nContent.GetIndex() )
+ nEnd = pMkPos->nContent.GetIndex();
+ else
+ {
+ nEnd = nStt;
+ nStt = pMkPos->nContent.GetIndex();
+ }
+ }
+ else
+ {
+ nStt = pMkPos->nContent.GetIndex();
+ if( pPtPos->nNode < pMkPos->nNode )
+ {
+ nEnd = nStt;
+ nStt = 0;
+ }
+ else
+ nEnd = pTextNode->GetText().getLength();
+ }
+ }
+ else
+ nStt = nEnd = pPam->GetPoint()->nContent.GetIndex();
+
+ 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 SwModify& 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 000000000..add383a8c
--- /dev/null
+++ b/sw/source/core/edit/edglbldc.cxx
@@ -0,0 +1,384 @@
+/* -*- 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>
+#include <osl/diagnose.h>
+
+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:
+ OSL_ENSURE( dynamic_cast<const SwTOXBaseSection*>( pSect) != nullptr, "no TOXBaseSection!" );
+ pNew.reset(new SwGlblDocContent( static_cast<const SwTOXBaseSection*>(pSect) ));
+ break;
+
+ default:
+ pNew.reset(new SwGlblDocContent( pSect ));
+ break;
+ }
+ rArr.insert( std::move(pNew) );
+ }
+ }
+
+ // and finally add the dummies (other text)
+ SwNode* pNd;
+ sal_uLong 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() )
+ {
+ sal_uLong 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;
+
+ SET_CURR_SHELL( this );
+ StartAllAction();
+
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->GetNext() != pCursor || IsTableMode() )
+ ClearMark();
+
+ SwPosition& rPos = *pCursor->GetPoint();
+ rPos.nNode = rInsPos.GetDocPos();
+
+ bool bEndUndo = false;
+ SwDoc* pMyDoc = GetDoc();
+ SwTextNode *const pTextNd = rPos.nNode.GetNode().GetTextNode();
+ if( pTextNd )
+ rPos.nContent.Assign( pTextNd, 0 );
+ else
+ {
+ bEndUndo = true;
+ pMyDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
+ --rPos.nNode;
+ pMyDoc->getIDocumentContentOperations().AppendTextNode( rPos );
+ pCursor->SetMark();
+ }
+
+ 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;
+
+ SET_CURR_SHELL( this );
+ StartAllAction();
+
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->GetNext() != pCursor || IsTableMode() )
+ ClearMark();
+
+ SwPosition& rPos = *pCursor->GetPoint();
+ rPos.nNode = rInsPos.GetDocPos();
+
+ bool bEndUndo = false;
+ SwDoc* pMyDoc = GetDoc();
+ SwTextNode* pTextNd = rPos.nNode.GetNode().GetTextNode();
+ if (pTextNd && pTextNd->GetText().getLength() && rPos.nNode.GetIndex() + 1 !=
+ pMyDoc->GetNodes().GetEndOfContent().GetIndex() )
+ rPos.nContent.Assign( pTextNd, 0 );
+ else
+ {
+ bEndUndo = true;
+ pMyDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr );
+ --rPos.nNode;
+ 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;
+
+ SET_CURR_SHELL( this );
+ StartAllAction();
+
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->GetNext() != pCursor || IsTableMode() )
+ ClearMark();
+
+ SwPosition& rPos = *pCursor->GetPoint();
+ rPos.nNode = rInsPos.GetDocPos() - 1;
+ rPos.nContent.Assign( nullptr, 0 );
+
+ 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;
+
+ SET_CURR_SHELL( 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 ];
+ sal_uLong nDelIdx = rDelPos.GetDocPos();
+ if( 1 == rArr.size() )
+ {
+ // we need at least one node!
+ rPos.nNode = nDelIdx - 1;
+ rPos.nContent.Assign( nullptr, 0 );
+
+ pMyDoc->getIDocumentContentOperations().AppendTextNode( rPos );
+ ++nDelIdx;
+ }
+
+ switch( rDelPos.GetType() )
+ {
+ case GLBLDOC_UNKNOWN:
+ {
+ rPos.nNode = nDelIdx;
+ pCursor->SetMark();
+ if( ++nDelPos < rArr.size() )
+ rPos.nNode = rArr[ nDelPos ]->GetDocPos();
+ else
+ rPos.nNode = pMyDoc->GetNodes().GetEndOfContent();
+ --rPos.nNode;
+ if( !pMyDoc->getIDocumentContentOperations().DelFullPara( *pCursor ) )
+ Delete();
+ }
+ 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;
+
+ SET_CURR_SHELL( 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,
+ SwMoveFlags::CREATEUNDOOBJ );
+
+ EndAllAction();
+ return bRet;
+}
+
+void SwEditShell::GotoGlobalDocContent( const SwGlblDocContent& rPos )
+{
+ if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) )
+ return;
+
+ SET_CURR_SHELL( this );
+ SttCursorMove();
+
+ SwPaM* pCursor = GetCursor();
+ if( pCursor->GetNext() != pCursor || IsTableMode() )
+ ClearMark();
+
+ SwPosition& rCursorPos = *pCursor->GetPoint();
+ rCursorPos.nNode = rPos.GetDocPos();
+
+ SwDoc* pMyDoc = GetDoc();
+ SwContentNode * pCNd = rCursorPos.nNode.GetNode().GetContentNode();
+ if( !pCNd )
+ pCNd = pMyDoc->GetNodes().GoNext( &rCursorPos.nNode );
+
+ rCursorPos.nContent.Assign( pCNd, 0 );
+
+ EndCursorMove();
+}
+
+SwGlblDocContent::SwGlblDocContent( sal_uLong nPos )
+{
+ eType = GLBLDOC_UNKNOWN;
+ PTR.pTOX = nullptr;
+ nDocPos = nPos;
+}
+
+SwGlblDocContent::SwGlblDocContent( const SwTOXBaseSection* pTOX )
+{
+ eType = GLBLDOC_TOXBASE;
+ PTR.pTOX = pTOX;
+
+ const SwSectionNode* pSectNd = pTOX->GetFormat()->GetSectionNode();
+ nDocPos = pSectNd ? pSectNd->GetIndex() : 0;
+}
+
+SwGlblDocContent::SwGlblDocContent( const SwSection* pSect )
+{
+ eType = GLBLDOC_SECTION;
+ PTR.pSect = pSect;
+
+ const SwSectionNode* pSectNd = pSect->GetFormat()->GetSectionNode();
+ nDocPos = pSectNd ? pSectNd->GetIndex() : 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 000000000..5c37ec22a
--- /dev/null
+++ b/sw/source/core/edit/edglss.cxx
@@ -0,0 +1,337 @@
+/* -*- 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/endian.h>
+#include <tools/urlobj.hxx>
+#include <doc.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <IDocumentFieldsAccess.hxx>
+#include <pam.hxx>
+#include <docary.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()->nNode = *pNd;
+ if( pNd == pContentNd )
+ pCursor->GetPoint()->nContent.Assign( pContentNd, 0 );
+ pCursor->SetMark();
+
+ // then until the end of the Node array
+ pCursor->GetPoint()->nNode = pMyDoc->GetNodes().GetEndOfContent().GetIndex()-1;
+ pContentNd = pCursor->GetContentNode();
+ if( pContentNd )
+ pCursor->GetPoint()->nContent.Assign( pContentNd, 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()->nNode = pMyDoc->GetNodes().GetEndOfContent().GetIndex()-1;
+ pContentNd = aCpyPam.GetContentNode();
+ aCpyPam.GetPoint()->nContent.Assign(
+ pContentNd, pContentNd ? pContentNd->Len() : 0);
+
+ aStt = pGDoc->GetNodes().GetEndOfExtras();
+ pContentNd = pGDoc->GetNodes().GoNext( &aStt );
+ SwPosition aInsPos( aStt, SwIndex( pContentNd ));
+ pMyDoc->getIDocumentContentOperations().CopyRange(aCpyPam, aInsPos, SwCopyFlags::CheckPosInFly);
+
+ nRet = rBlock.PutDoc();
+ }
+ }
+ EndAllAction();
+ return nRet;
+}
+
+/// copy all selections to the doc
+bool SwEditShell::CopySelToDoc( SwDoc* pInsDoc )
+{
+ OSL_ENSURE( pInsDoc, "no Ins.Document" );
+
+ SwNodes& rNds = pInsDoc->GetNodes();
+
+ SwNodeIndex aIdx( rNds.GetEndOfContent(), -1 );
+ SwContentNode *const pContentNode = aIdx.GetNode().GetContentNode();
+ SwPosition aPos( aIdx,
+ SwIndex(pContentNode, pContentNode ? pContentNode->Len() : 0));
+
+ bool bRet = false;
+ SET_CURR_SHELL( this );
+
+ pInsDoc->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 SwFrameFormats& rTableFormats = *pInsDoc->GetTableFrameFormats();
+ for( auto n = rTableFormats.size(); n; )
+ if( rTableFormats[ --n ]->GetName() == rTableName )
+ {
+ bCpyTableNm = false;
+ break;
+ }
+ }
+ bRet = pInsDoc->InsCopyOfTable( aPos, aBoxes, nullptr, bCpyTableNm );
+ }
+ else
+ bRet = false;
+ }
+ else
+ {
+ bool bColSel = GetCursor_()->IsColumnSelection();
+ if( bColSel && pInsDoc->IsClipBoard() )
+ pInsDoc->SetColumnSelection( true );
+ bool bSelectAll = StartsWithTable() && ExtendedSelectedAll();
+ {
+ for(SwPaM& rPaM : GetCursor()->GetRingContainer())
+ {
+ if( !rPaM.HasMark() )
+ {
+ SwContentNode *const pNd = rPaM.GetContentNode();
+ 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 (bSelectAll)
+ {
+ // 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.
+ // tdf#133982 tables can be nested
+ while (SwTableNode const* pTableNode =
+ aPaM.Start()->nNode.GetNode().StartOfSectionNode()->FindTableNode())
+ {
+ aPaM.Start()->nNode = *pTableNode;
+ }
+ aPaM.Start()->nContent.Assign(nullptr, 0);
+ }
+ bRet = GetDoc()->getIDocumentContentOperations().CopyRange( aPaM, aPos, SwCopyFlags::CheckPosInFly)
+ || bRet;
+ }
+ }
+ }
+ }
+
+ pInsDoc->getIDocumentFieldsAccess().UnlockExpFields();
+ if( !pInsDoc->getIDocumentFieldsAccess().IsExpFieldsLocked() )
+ pInsDoc->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 )
+ {
+#ifdef _WIN32
+ rBuf += "\015\012";
+#else
+ rBuf += "\012";
+#endif
+ }
+ }
+ 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 000000000..0b932fba1
--- /dev/null
+++ b/sw/source/core/edit/editsh.cxx
@@ -0,0 +1,1056 @@
+/* -*- 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 <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 <docary.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
+ 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()->nNode.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()->nNode.GetNode();
+ if ( rNode.IsTextNode() )
+ {
+ SwIndex& rIdx = pTmpCursor->GetPoint()->nContent;
+ sal_Int32 nPrevPos = rIdx.GetIndex();
+ 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()->nNode );
+ 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()->nNode );
+ 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()->nNode == pCursor->GetMark()->nNode )
+ pGrfNode = pCursor->GetPoint()->nNode.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()->nNode == pCurrentCursor->GetMark()->nNode )
+ && nullptr != ( pNoTextNd = pCurrentCursor->GetNode().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()->GetNode().GetNoTextNode();
+ return pNd->HasContour();
+}
+
+void SwEditShell::SetGraphicPolygon( const tools::PolyPolygon *pPoly )
+{
+ SwNoTextNode *pNd = GetCursor()->GetNode().GetNoTextNode();
+ StartAllAction();
+ pNd->SetContour( pPoly );
+ SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pNd->getLayoutFrame(GetLayout())->GetUpper());
+ const SwFormatSurround &rSur = pFly->GetFormat()->GetSurround();
+ pFly->GetFormat()->NotifyClients( &rSur, &rSur );
+ GetDoc()->getIDocumentState().SetModified();
+ EndAllAction();
+}
+
+void SwEditShell::ClearAutomaticContour()
+{
+ SwNoTextNode *pNd = GetCursor()->GetNode().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()->NotifyClients( &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()->nNode == GetCursor()->GetMark()->nNode),
+ "GetOLEObj: no OLENode." );
+
+ SwOLENode *pOLENode = GetCursor()->GetNode().GetOLENode();
+ OSL_ENSURE( pOLENode, "GetOLEObj: no OLENode." );
+ SwOLEObj& rOObj = pOLENode->GetOLEObj();
+ return rOObj.GetObject();
+}
+
+bool SwEditShell::HasOLEObj( const OUString &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()->GetNode().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.GetNode().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( const OUString& 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
+ sal_uLong nIndex = pCursor->GetMark()->nNode.GetIndex();
+ bool bPrev = true;
+ SwPaM* pLast = pCursor;
+ SwPaM* pTemp = pCursor;
+ while ( bPrev )
+ {
+ SwPaM* pPrev2 = pTemp->GetPrev();
+ bPrev = ( pPrev2 && pPrev2 != pLast );
+ if ( bPrev )
+ {
+ pTemp = pPrev2;
+ sal_uLong nTemp = pPrev2->GetMark()->nNode.GetIndex();
+ if ( nTemp < nIndex )
+ {
+ nIndex = nTemp;
+ pCursor = pPrev2;
+ }
+ }
+ }
+ }
+
+ SwTextNode const*const pTextNd = pCursor->GetNode(false).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()->nNode == pCursor->GetMark()->nNode &&
+ pCursor->GetNode().GetTextNode()->IsTextNode() )
+ {
+ StartAllAction();
+
+ const SwNodeIndex& rNd = pCursor->GetPoint()->nNode;
+ SwPaM aPam( rNd, rStr.getLength(), rNd, 0 );
+ SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>(
+ rNd.GetNode().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.GetNode().GetTextNode();
+ if(pTextNd)
+ {
+ const SwPosition *pStart = rCurrentPaM.Start(), *pEnd = rCurrentPaM.End();
+ const sal_Int32 nStt = pStart->nContent.GetIndex();
+ OUString aStr = pTextNd->GetExpandText(GetLayout(),
+ nStt, pEnd->nContent.GetIndex() - 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.VarLook( sVar )) )
+ {
+ if( !bValidFields )
+ {
+ GetDoc()->getIDocumentFieldsAccess().FieldsToCalc( aCalc,
+ pStart->nNode.GetIndex(),
+ pStart->nContent.GetIndex() );
+ bValidFields = true;
+ }
+ aFormel.append("(").append(aCalc.GetStrResult( aCalc.VarLook( sVar )->nValue )).append(")");
+ }
+ 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()->GetNode()));
+}
+
+// #i73788#
+Graphic SwEditShell::GetIMapGraphic() const
+{
+ // returns always a graphic if the cursor is in a Fly
+ SET_CURR_SHELL( const_cast<SwEditShell*>(this) );
+ Graphic aRet;
+ SwPaM* pCursor = GetCursor();
+ if ( !pCursor->HasMark() )
+ {
+ SwNode& rNd =pCursor->GetNode();
+ 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();
+ }
+ 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 )
+{
+ 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()))
+ {
+ 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() );
+}
+
+/// 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();
+ sal_uLong nCurrNd = pSttPos->nNode.GetIndex();
+ sal_uLong nEndNd = pEndPos->nNode.GetIndex();
+ 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:
+ if( nullptr != ( pContentFrame = static_cast<SwTextNode*>(pNd)->getLayoutFrame( GetLayout() )) )
+ {
+ // 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( static_cast<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_uInt16 SwEditShell::GetLineCount()
+{
+ sal_uInt16 nRet = 0;
+ CalcLayout();
+ SwPaM* pPam = GetCursor();
+ SwNodeIndex& rPtIdx = pPam->GetPoint()->nNode;
+ SwNodeIndex aStart( rPtIdx );
+ SwContentNode* pCNd;
+ SwContentFrame *pContentFrame = nullptr;
+
+ aStart = 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()->IsHideRedlines())
+ {
+ if (auto const*const pMerged = pFrame->GetMergedPara())
+ {
+ aStart = *pMerged->pLastNode;
+ }
+ }
+ }
+ }
+ return nRet;
+}
+
+long SwEditShell::CompareDoc( const SwDoc& rDoc )
+{
+ StartAllAction();
+ long nRet = GetDoc()->CompareDoc( rDoc );
+ EndAllAction();
+ return nRet;
+}
+
+long SwEditShell::MergeDoc( const SwDoc& rDoc )
+{
+ StartAllAction();
+ long nRet = GetDoc()->MergeDoc( rDoc );
+ EndAllAction();
+ return nRet;
+}
+
+const SwFootnoteInfo& SwEditShell::GetFootnoteInfo() const
+{
+ return GetDoc()->GetFootnoteInfo();
+}
+
+void SwEditShell::SetFootnoteInfo(const SwFootnoteInfo& rInfo)
+{
+ StartAllAction();
+ SET_CURR_SHELL( this );
+ GetDoc()->SetFootnoteInfo(rInfo);
+ CallChgLnk();
+ EndAllAction();
+}
+
+const SwEndNoteInfo& SwEditShell::GetEndNoteInfo() const
+{
+ return GetDoc()->GetEndNoteInfo();
+}
+
+void SwEditShell::SetEndNoteInfo(const SwEndNoteInfo& rInfo)
+{
+ StartAllAction();
+ SET_CURR_SHELL( this );
+ GetDoc()->SetEndNoteInfo(rInfo);
+ EndAllAction();
+}
+
+const SwLineNumberInfo& SwEditShell::GetLineNumberInfo() const
+{
+ return GetDoc()->GetLineNumberInfo();
+}
+
+void SwEditShell::SetLineNumberInfo(const SwLineNumberInfo& rInfo)
+{
+ StartAllAction();
+ SET_CURR_SHELL( 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.nNode.GetNode(),
+ rPos.nContent.GetIndex() );
+ 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;
+ SET_CURR_SHELL( 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 )
+{
+ const SwPosition& rPos = *GetCursor()->GetPoint();
+ SwExtTextInput* pInput = GetDoc()->GetExtTextInput( rPos.nNode.GetNode() );
+ if( !pInput )
+ return;
+
+ StartAllAction();
+ SET_CURR_SHELL( this );
+
+ if( !rData.IsOnlyCursorChanged() )
+ pInput->SetInputData( rData );
+ // position cursor
+ const SwPosition& rStt = *pInput->Start();
+ const sal_Int32 nNewCursorPos = rStt.nContent.GetIndex() + rData.GetCursorPos();
+
+ // ugly but works
+ ShowCursor();
+ const sal_Int32 nDiff = nNewCursorPos - rPos.nContent.GetIndex();
+ if( 0 > nDiff )
+ Left( -nDiff, CRSR_SKIP_CHARS );
+ else if( 0 < nDiff )
+ Right( nDiff, CRSR_SKIP_CHARS );
+
+ 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();
+ SET_CURR_SHELL( 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 000000000..15e8532f0
--- /dev/null
+++ b/sw/source/core/edit/edlingu.cxx
@@ -0,0 +1,1712 @@
+/* -*- 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/frame/XModel.hpp>
+#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 <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>
+
+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 *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 nCursorCnt;
+public:
+ SwLinguIter();
+
+ SwEditShell *GetSh() { return 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 nCursorCnt; }
+
+ // 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 > xSpeller;
+ svx::SpellPortions aLastPortions;
+
+ SpellContentPositions aLastPositions;
+ bool bBackToStartOfSentence;
+
+ void CreatePortion(uno::Reference< XSpellAlternatives > const & xAlt,
+ linguistic2::ProofreadingResult* pGrammarResult,
+ bool bIsField, bool bIsHidden);
+
+ void AddPortion(uno::Reference< XSpellAlternatives > const & xAlt,
+ linguistic2::ProofreadingResult* pGrammarResult,
+ const SpellContentPositions& rDeletedRedlines);
+public:
+ SwSpellIter() :
+ 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 aLastPortions;}
+ const SpellContentPositions& GetLastPositions() const {return aLastPositions;}
+};
+
+/// used for text conversion
+class SwConvIter : public SwLinguIter
+{
+ SwConversionArgs &rArgs;
+public:
+ explicit SwConvIter(SwConversionArgs &rConvArgs)
+ : 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 bOldIdle;
+ static void DelSoftHyph( SwPaM &rPam );
+
+public:
+ SwHyphIter() : m_pLastNode(nullptr), m_pLastFrame(nullptr), 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()
+ : pSh(nullptr)
+ , nCursorCnt(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( pSh )
+ return;
+
+ bool bSetCurr;
+
+ pSh = pShell;
+
+ SET_CURR_SHELL( pSh );
+
+ OSL_ENSURE(!m_pEnd, "SwLinguIter::Start_ without End?");
+
+ SwPaM *pCursor = pSh->GetCursor();
+
+ if( pShell->HasSelection() || pCursor != pCursor->GetNext() )
+ {
+ bSetCurr = nullptr != GetCurr();
+ nCursorCnt = pSh->GetCursorCnt();
+ if( pSh->IsTableMode() )
+ pSh->TableCursorToCursor();
+
+ pSh->Push();
+ sal_uInt16 n;
+ for( n = 0; n < nCursorCnt; ++n )
+ {
+ pSh->Push();
+ pSh->DestroyCursor();
+ }
+ pSh->Pop(SwCursorShell::PopMode::DeleteCurrent);
+ }
+ else
+ {
+ bSetCurr = false;
+ nCursorCnt = 1;
+ pSh->Push();
+ pSh->SetLinguRange( eStart, eEnd );
+ }
+
+ pCursor = 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( !pSh )
+ return;
+
+ OSL_ENSURE(m_pEnd, "SwLinguIter::End_ without end?");
+ if(bRestoreSelection)
+ {
+ while( nCursorCnt-- )
+ pSh->Pop(SwCursorShell::PopMode::DeleteCurrent);
+
+ pSh->KillPams();
+ pSh->ClearMark();
+ }
+ m_pStart.reset();
+ m_pEnd.reset();
+ m_pCurr.reset();
+ m_pCurrX.reset();
+
+ pSh = nullptr;
+}
+
+void SwSpellIter::Start( SwEditShell *pShell, SwDocPositions eStart,
+ SwDocPositions eEnd )
+{
+ if( GetSh() )
+ return;
+
+ xSpeller = ::GetSpellChecker();
+ if ( xSpeller.is() )
+ Start_( pShell, eStart, eEnd );
+ aLastPortions.clear();
+ 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(),
+ 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( makeAny( 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(), &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 makeAny( 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)
+ bOldIdle = pShell->GetViewOptions()->IsIdle();
+ pShell->GetViewOptions()->SetIdle( false );
+ Start_( pShell, eStart, eEnd );
+}
+
+// restore selections
+void SwHyphIter::End()
+{
+ if( !GetSh() )
+ return;
+ GetSh()->GetViewOptions()->SetIdle( 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()->nContent = pCursor->End()->nContent;
+ pCursor->SetMark();
+}
+
+void SwHyphIter::DelSoftHyph( SwPaM &rPam )
+{
+ const SwPosition* pStt = rPam.Start();
+ const sal_Int32 nStart = pStt->nContent.GetIndex();
+ const sal_Int32 nEnd = rPam.End()->nContent.GetIndex();
+ SwTextNode *pNode = pStt->nNode.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();
+ SwPosition* pSttPos = pCursor->Start();
+ SwPosition* pEndPos = pCursor->End();
+
+ const sal_Int32 nLastHyphLen = GetEnd()->nContent.GetIndex() -
+ pSttPos->nContent.GetIndex();
+
+ if( pSttPos->nNode != pEndPos->nNode || !nLastHyphLen )
+ {
+ OSL_ENSURE( pSttPos->nNode == pEndPos->nNode,
+ "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->nContent += 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 = GetSwCursor();
+
+ 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
+ Push();
+ LeftMargin();
+ const sal_Int32 nLineStart = GetCursor()->GetPoint()->nContent.GetIndex();
+ RightMargin();
+ const sal_Int32 nLineEnd = GetCursor()->GetPoint()->nContent.GetIndex();
+ 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.nContent = 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();
+ SwIndex& rContent = GetCursor()->GetPoint()->nContent;
+ rContent = nWordStart;
+ SwRect aStartRect;
+ SwCursorMoveState aState;
+ aState.m_bRealWidth = true;
+ SwContentNode* pContentNode = pCursor->GetContentNode();
+ 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 );
+ rContent = 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.nNode.GetNode().GetTextNode();
+ if (nullptr == pNode)
+ pNode = pCursor->GetNode().GetTextNode();
+ if (nullptr != pNode)
+ pWrong = pNode->GetWrong();
+ if (nullptr != pWrong && !pNode->IsInProtectSect())
+ {
+ sal_Int32 nBegin = aPos.nContent.GetIndex();
+ sal_Int32 nLen = 1;
+ if (pWrong->InWrongWord(nBegin, nLen) && !pNode->IsSymbolAt(nBegin))
+ {
+ const OUString aText(pNode->GetText().copy(nBegin, nLen));
+ OUString aWord = aText.replaceAll(OUStringChar(CH_TXTATR_BREAKWORD), "")
+ .replaceAll(OUStringChar(CH_TXTATR_INWORD), "");
+
+ 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(1);
+ PropertyValue &rVal = aPropVals.getArray()[0];
+ rVal.Name = UPN_MAX_NUMBER_OF_SUGGESTIONS;
+ rVal.Value <<= sal_Int16(7);
+
+ xSpellAlt = xSpell->spell( aWord, static_cast<sal_uInt16>(eActLang), aPropVals );
+ }
+ }
+
+ if ( xSpellAlt.is() ) // error found?
+ {
+ HandleCorrectionError( aText, 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.nNode.GetNode().GetTextNode();
+ if (nullptr == pNode)
+ pNode = pCursor->GetNode().GetTextNode();
+ if (nullptr != pNode)
+ pWrong = pNode->GetGrammarCheck();
+ if (nullptr != pWrong && !pNode->IsInProtectSect())
+ {
+ sal_Int32 nBegin = aPos.nContent.GetIndex();
+ 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(rResult.aErrors.begin(), rResult.aErrors.end(),
+ [rErrorPosInText, nLen](const linguistic2::SingleProofreadingError &rError) {
+ return rError.nErrorStart <= rErrorPosInText
+ && rErrorPosInText + nLen <= rError.nErrorStart + rError.nErrorLength; });
+ if (pError != rResult.aErrors.end())
+ {
+ rSuggestions = pError->aSuggestions;
+ rErrorIndexInResult = static_cast<sal_Int32>(std::distance(rResult.aErrors.begin(), pError));
+ }
+ }
+
+ if (rResult.aErrors.hasElements()) // error found?
+ {
+ HandleCorrectionError( aText, 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()->nContent = aCurrentOldPosition->nLeft;
+ pCursor->GetMark()->nContent = 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) );
+ mxDoc->getIDocumentContentOperations().ReplaceRange(*pCursor, aCurrentNewPortion->sText, false);
+ }
+ 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()->nContent = aCurrentStartPosition->nLeft;
+ pCursor->GetMark()->nContent = 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->GetNode().GetTextNode();
+
+ SwRedlineTable::size_type nAct = pDoc->getIDocumentRedlineAccess().GetRedlinePos( *pTextNode, RedlineType::Any );
+ const sal_Int32 nStartIndex = pStartPos->nContent.GetIndex();
+ for ( ; nAct < pDoc->getIDocumentRedlineAccess().GetRedlineTable().size(); nAct++ )
+ {
+ const SwRangeRedline* pRed = pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nAct ];
+
+ if ( pRed->Start()->nNode > pTextNode->GetIndex() )
+ 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->nContent.GetIndex();
+ 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;
+ aLastPortions.clear();
+ 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( bBackToStartOfSentence )
+ {
+ pMySh->GoStartSentence();
+ bBackToStartOfSentence = false;
+ }
+ uno::Any aSpellRet =
+ pMySh->GetDoc()->Spell(*pCursor,
+ 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->GetNode()), 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()->nContent.GetIndex() < nSentenceEnd )
+ {
+ pCursor->End()->nContent.Assign(
+ pCursor->End()->nNode.GetNode().GetContentNode(), 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,
+ 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 = 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()
+{
+ bBackToStartOfSentence = true;
+}
+
+static LanguageType lcl_GetLanguage(SwEditShell& rSh)
+{
+ SvtScriptType nScriptType = rSh.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(rSh.GetAttrPool(), {{nLangWhichId, nLangWhichId}});
+ rSh.GetCurAttr( aSet );
+ const SvxLanguageItem& rLang = static_cast<const SvxLanguageItem& >(aSet.Get(nLangWhichId));
+ return rLang.GetLanguage();
+}
+
+/// create a text portion at the given position
+void SwSpellIter::CreatePortion(uno::Reference< XSpellAlternatives > const & xAlt,
+ 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(pGrammarResult->aProperties.begin(), pGrammarResult->aProperties.end(),
+ [](const beans::PropertyValue& rProperty) { return rProperty.Name == "DialogTitle"; });
+ if (pProperty != pGrammarResult->aProperties.end())
+ 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()->nContent.GetIndex();
+ aPosition.nRight = pCursor->End()->nContent.GetIndex();
+ aLastPortions.push_back(aPortion);
+ aLastPositions.push_back(aPosition);
+
+}
+
+void SwSpellIter::AddPortion(uno::Reference< XSpellAlternatives > const & xAlt,
+ linguistic2::ProofreadingResult* pGrammarResult,
+ const SpellContentPositions& rDeletedRedlines)
+{
+ SwEditShell *pMySh = GetSh();
+ OUString sText;
+ pMySh->GetSelectedText( sText );
+ if(!sText.isEmpty())
+ {
+ 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->GetNode().GetTextNode();
+ LanguageType eStartLanguage = lcl_GetLanguage(*GetSh());
+ SpellContentPosition aNextRedline = lcl_FindNextDeletedRedline(
+ rDeletedRedlines, aStart.nContent.GetIndex() );
+ if( aNextRedline.nLeft == aStart.nContent.GetIndex() )
+ {
+ // select until the end of the current redline
+ const sal_Int32 nEnd = aEnd.nContent.GetIndex() < aNextRedline.nRight ?
+ aEnd.nContent.GetIndex() : aNextRedline.nRight;
+ pCursor->GetPoint()->nContent.Assign( pTextNode, nEnd );
+ CreatePortion(xAlt, pGrammarResult, false, true);
+ aStart = *pCursor->End();
+ // search for next redline
+ aNextRedline = lcl_FindNextDeletedRedline(
+ rDeletedRedlines, aStart.nContent.GetIndex() );
+ }
+ while(*pCursor->GetPoint() < aEnd)
+ {
+ // #125786 in table cell with fixed row height the cursor might not move forward
+ if(!GetSh()->Right(1, CRSR_SKIP_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()->nContent.GetIndex()];
+ if( CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar)
+ {
+ const SwTextAttr* pTextAttr = pTextNode->GetTextAttrForCharAt(
+ pCursor->GetMark()->nContent.GetIndex() );
+ 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()->nContent.GetIndex();
+ // 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, CRSR_SKIP_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.nContent.GetIndex() < aNextRedline.nRight ?
+ aEnd.nContent.GetIndex() : aNextRedline.nRight;
+ pCursor->GetPoint()->nContent.Assign( pTextNode, nEnd );
+ CreatePortion(xAlt, pGrammarResult, false, true);
+ aStart = *pCursor->End();
+ // search for next redline
+ aNextRedline = lcl_FindNextDeletedRedline(
+ rDeletedRedlines, aStart.nContent.GetIndex() );
+ }
+ *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()->nNode;
+ SwNodeIndex aEndIdx = rErrorPosition.Start()->nNode;
+ sal_Int32 nStart = rErrorPosition.Start()->nContent.GetIndex();
+ sal_Int32 nEnd = COMPLETE_STRING;
+ while( aIdx <= aEndIdx )
+ {
+ pNode = aIdx.GetNode().GetTextNode();
+ if( pNode ) {
+ if( aIdx == aEndIdx )
+ nEnd = rErrorPosition.End()->nContent.GetIndex();
+ 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 000000000..0b96be07b
--- /dev/null
+++ b/sw/source/core/edit/ednumber.cxx
@@ -0,0 +1,912 @@
+/* -*- 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>
+
+SwPamRanges::SwPamRanges( const SwPaM& rRing )
+{
+ for(SwPaM& rTmp : const_cast<SwPaM*>(&rRing)->GetRingContainer())
+ Insert( rTmp.GetMark()->nNode, rTmp.GetPoint()->nNode );
+}
+
+void SwPamRanges::Insert( const SwNodeIndex& rIdx1, const SwNodeIndex& 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()->nNode = rTmp.nStart;
+ rPam.GetPoint()->nContent.Assign( rPam.GetContentNode(), 0 );
+ rPam.SetMark();
+ rPam.GetPoint()->nNode = rTmp.nEnd;
+ rPam.GetPoint()->nContent.Assign( rPam.GetContentNode(), 0 );
+ 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();
+}
+
+bool SwEditShell::SelectionHasNumber() const
+{
+ bool bResult = HasNumber();
+ const SwTextNode * pTextNd = sw::GetParaPropsNode(*GetLayout(), GetCursor()->GetPoint()->nNode);
+ if (!bResult && pTextNd && pTextNd->Len()==0 && !pTextNd->GetNumRule()) {
+ SwPamRanges aRangeArr( *GetCursor() );
+ SwPaM aPam( *GetCursor()->GetPoint() );
+ for( size_t n = 0; n < aRangeArr.Count(); ++n )
+ {
+ aRangeArr.SetPam( n, aPam );
+ {
+ sal_uInt32 nStt = aPam.GetPoint()->nNode.GetIndex(),
+ nEnd = aPam.GetMark()->nNode.GetIndex();
+ if( nStt > nEnd )
+ {
+ sal_uInt32 nTmp = nStt; nStt = nEnd; nEnd = nTmp;
+ }
+ for (sal_uInt32 nPos = nStt; nPos<=nEnd; nPos++)
+ {
+ pTextNd = mxDoc->GetNodes()[nPos]->GetTextNode();
+ if (pTextNd)
+ {
+ pTextNd = sw::GetParaPropsNode(*GetLayout(), SwNodeIndex(*pTextNd));
+ }
+ if (pTextNd && 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) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ return bResult;
+}
+
+// add a new function to determine number on/off status
+bool SwEditShell::SelectionHasBullet() const
+{
+ bool bResult = HasBullet();
+ const SwTextNode * pTextNd = sw::GetParaPropsNode(*GetLayout(), GetCursor()->GetPoint()->nNode);
+ if (!bResult && pTextNd && pTextNd->Len()==0 && !pTextNd->GetNumRule()) {
+ SwPamRanges aRangeArr( *GetCursor() );
+ SwPaM aPam( *GetCursor()->GetPoint() );
+ for( size_t n = 0; n < aRangeArr.Count(); ++n )
+ {
+ aRangeArr.SetPam( n, aPam );
+ {
+ sal_uInt32 nStt = aPam.GetPoint()->nNode.GetIndex(),
+ nEnd = aPam.GetMark()->nNode.GetIndex();
+ if( nStt > nEnd )
+ {
+ sal_uInt32 nTmp = nStt; nStt = nEnd; nEnd = nTmp;
+ }
+ for (sal_uInt32 nPos = nStt; nPos<=nEnd; nPos++)
+ {
+ pTextNd = mxDoc->GetNodes()[nPos]->GetTextNode();
+ if (pTextNd)
+ {
+ pTextNd = sw::GetParaPropsNode(*GetLayout(), SwNodeIndex(*pTextNd));
+ }
+ if (pTextNd && pTextNd->Len()!=0)
+ {
+ bResult = pTextNd->HasBullet();
+
+ if (!bResult) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return bResult;
+}
+
+// -> #i29560#
+bool SwEditShell::HasNumber() const
+{
+ bool bResult = false;
+
+ const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), GetCursor()->GetPoint()->nNode);
+
+ 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()->nNode);
+
+ 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 );
+ SwPamRanges aRangeArr( *pCursor );
+ SwPaM aPam( *pCursor->GetPoint() );
+ for( size_t n = 0; n < aRangeArr.Count(); ++n )
+ {
+ GetDoc()->DelNumRules(aRangeArr.SetPam( n, aPam ), 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.nNode.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( long 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()->nNode);
+ 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
+ long nOffset = 0;
+ const SwNode* pNd;
+
+ if( bUpperLeft ) // move up
+ {
+ SwPosition aPos( *aCursor.GetMark() );
+ if (SwDoc::GotoPrevNum( aPos, GetLayout(), false ))
+ nOffset = aPos.nNode.GetIndex() -
+ aCursor.GetMark()->nNode.GetIndex();
+ else
+ {
+ sal_uLong nStt = aPos.nNode.GetIndex(), nIdx = nStt - 1;
+
+ if (SwTextNode const*const pStt = aPos.nNode.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.GetNode().IsTextNode()
+ || sw::IsParaPropsNode(*GetLayout(), *aCursor.GetNode().GetTextNode()));
+ const SwNumRule* pOrig = sw::GetParaPropsNode(*GetLayout(), *aCursor.GetNode(false).GetTextNode())->GetNumRule();
+ if( aCursor.GetNode().IsTextNode() &&
+ pOrig == aCursor.GetNode().GetTextNode()->GetNumRule() )
+ {
+ sal_uLong nStt = aCursor.GetPoint()->nNode.GetIndex(), nIdx = nStt+1;
+ if (SwTextNode const*const pStt = aCursor.GetPoint()->nNode.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(), SwNodeIndex(*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 = 1;
+ else
+ nOffset = nIdx - nStt;
+ }
+ else
+ nOffset = 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()->nNode.GetNode();
+ if( rNd.IsTextNode() )
+ {
+ const SwOutlineNodes& rOutlNd = GetDoc()->GetNodes().GetOutLineNds();
+ SwNodePtr pNd = const_cast<SwNodePtr>(&rNd);
+ bool bFirst = true;
+ SwOutlineNodes::size_type nPos;
+ int nLvl(0);
+ if( !rOutlNd.Seek_Entry( pNd, &nPos ) && nPos )
+ --nPos;
+
+ for( ; nPos < rOutlNd.size(); ++nPos )
+ {
+ SwNodePtr 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.nNode, !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()->nNode);
+ 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()->nNode);
+
+ 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 SwNodeIndex aEndNode = rCurrentCursor.End()->nNode;
+
+ for ( SwNodeIndex aNode = rCurrentCursor.Start()->nNode; aNode <= aEndNode; ++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::START, nullptr );
+
+ SwPaM* pCursor = GetCursor();
+ if( IsMultiSelection() )
+ {
+ SwPamRanges aRangeArr( *pCursor );
+ SwPaM aPam( *pCursor->GetPoint() );
+ OUString sContinuedListId(rContinuedListId);
+ for( size_t n = 0; n < aRangeArr.Count(); ++n )
+ {
+ aRangeArr.SetPam( n, aPam );
+ OUString sListId = GetDoc()->SetNumRule( aPam, 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(aPam, true, GetLayout());
+ }
+ }
+ else
+ {
+ GetDoc()->SetNumRule( *pCursor, rRule,
+ bCreateNewList, GetLayout(), rContinuedListId,
+ true, bResetIndentAttrs );
+ GetDoc()->SetCounted( *pCursor, true, GetLayout() );
+ }
+ GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, 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()->nNode);
+ 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()->nNode);
+ // 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 static_cast<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 000000000..f65f6319c
--- /dev/null
+++ b/sw/source/core/edit/edredln.cxx
@@ -0,0 +1,153 @@
+/* -*- 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 <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() )
+ {
+ SET_CURR_SHELL( 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 )
+{
+ SET_CURR_SHELL( this );
+ StartAllAction();
+ bool bRet = GetDoc()->getIDocumentRedlineAccess().AcceptRedline( nPos, true );
+ if( !nPos && !::IsExtraData( GetDoc() ) )
+ lcl_InvalidateAll( this );
+ EndAllAction();
+ return bRet;
+}
+
+bool SwEditShell::RejectRedline( SwRedlineTable::size_type nPos )
+{
+ SET_CURR_SHELL( this );
+ StartAllAction();
+ bool bRet = GetDoc()->getIDocumentRedlineAccess().RejectRedline( nPos, true );
+ if( !nPos && !::IsExtraData( GetDoc() ) )
+ lcl_InvalidateAll( this );
+ EndAllAction();
+ return bRet;
+}
+
+bool SwEditShell::AcceptRedlinesInSelection()
+{
+ SET_CURR_SHELL( this );
+ StartAllAction();
+ bool bRet = GetDoc()->getIDocumentRedlineAccess().AcceptRedline( *GetCursor(), true );
+ EndAllAction();
+ return bRet;
+}
+
+bool SwEditShell::RejectRedlinesInSelection()
+{
+ SET_CURR_SHELL( this );
+ StartAllAction();
+ bool 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()) )
+ {
+ SET_CURR_SHELL( 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 000000000..ba765eee4
--- /dev/null
+++ b/sw/source/core/edit/edsect.cxx
@@ -0,0 +1,421 @@
+/* -*- 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>
+
+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.nNode.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())
+ {
+ const SwPosition* pStt = rPaM.Start(),
+ * pEnd = rPaM.End();
+
+ SwSectionNode* pSttSectNd = pStt->nNode.GetNode().FindSectionNode(),
+ * pEndSectNd = pEnd->nNode.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->nNode );
+ SwNodeIndex aEIdx( pEnd->nNode );
+ 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())
+ {
+
+ const SwPosition* pStt = rPaM.Start(),
+ * pEnd = rPaM.End();
+ const SwContentNode* pCNd;
+ // check the selection, if Start at Node begin and End at Node end
+ if( pStt->nContent.GetIndex() ||
+ ( nullptr == ( pCNd = pEnd->nNode.GetNode().GetContentNode() )) ||
+ pCNd->Len() != pEnd->nContent.GetIndex() )
+ {
+ 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->nNode, -1 ), aEIdx( pEnd->nNode, +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->nNode.GetNode();
+
+ // find innermost section or table. At the end of this scope,
+ // pInntermostNode contain 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->nNode );
+ if( rCurrentNode.IsContentNode() &&
+ (pCurrentPos->nContent.GetIndex() == 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
+ SwNodeIndex aEnd( pCurrentPos->nNode );
+ if( rCurrentNode.IsContentNode() &&
+ ( pCurrentPos->nContent.GetIndex() ==
+ rCurrentNode.GetContentNode()->Len() ) )
+ ++aEnd;
+ 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 )
+ {
+ StartAllAction();
+
+ // adjust insert position to insert before start nodes and after end
+ // nodes
+ SwNodeIndex aInsertIndex( *pInsertNode,
+ 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 000000000..93036f735
--- /dev/null
+++ b/sw/source/core/edit/edtab.cxx
@@ -0,0 +1,531 @@
+/* -*- 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 <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->nContent.GetIndex();
+ 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 );
+ bool bRet = false;
+ SwPaM* pCursor = GetCursor();
+ const SwTableNode* pTableNd =
+ GetDoc()->IsIdxInTable( pCursor->GetPoint()->nNode );
+ if( IsTableMode() )
+ {
+ ClearMark();
+ pCursor = GetCursor();
+ }
+ else if( !pTableNd || pCursor->GetNext() != pCursor )
+ return bRet;
+
+ // 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()->nNode = *pTableNd->EndOfSectionNode();
+ pCursor->GetPoint()->nContent.Assign( nullptr, 0 );
+ // 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();
+ bRet = ConvertTableToText( pTableNd, cCh );
+ EndUndo();
+ //End for bug #i119954#
+ pCursor->GetPoint()->nNode = aTabIdx;
+
+ SwContentNode* pCNd = pCursor->GetContentNode();
+ if( !pCNd )
+ pCursor->Move( fnMoveForward, GoInContent );
+ else
+ pCursor->GetPoint()->nContent.Assign( pCNd, 0 );
+
+ 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
+ sal_uLong nStt = rPaM.GetMark()->nNode.GetIndex(),
+ nEnd = rPaM.GetPoint()->nNode.GetIndex();
+ if( nStt > nEnd ) { sal_uLong n = nStt; nStt = nEnd; nEnd = n; }
+
+ 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();
+
+ StartAllAction();
+
+ bool bEndUndo = 0 != pPos->nContent.GetIndex();
+ 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();
+ SwTableFormulaUpdate aTableUpdate( &pTableNd->GetTable() );
+ GetDoc()->getIDocumentFieldsAccess().UpdateTableFields( &aTableUpdate );
+ 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
+ {
+ 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 );
+ }
+
+ 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();
+
+ SwTableFormulaUpdate aTableUpdate( &rTable );
+ aTableUpdate.m_eFlags = TBL_BOXNAME;
+ GetDoc()->getIDocumentFieldsAccess().UpdateTableFields( &aTableUpdate );
+
+ rSet.Put( pTableFormat->GetAttrSet() );
+ }
+ else
+ rSet.MergeValues( pTableFormat->GetAttrSet() );
+ }
+ return 0 != rSet.Count();
+}
+
+void SwEditShell::SetTableBoxFormulaAttrs( const SfxItemSet& rSet )
+{
+ SET_CURR_SHELL( 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;
+ const SfxPoolItem* pItem;
+ if( SfxItemState::SET == pBox->GetFrameFormat()->GetAttrSet().GetItemState(
+ RES_BOXATR_FORMAT, true, &pItem ))
+ {
+ nFormat = static_cast<const SwTableBoxNumFormat*>(pItem)->GetValue();
+ return GetDoc()->GetNumberFormatter()->IsTextFormat( nFormat );
+ }
+
+ sal_uLong nNd = pBox->IsValidNumTextNd();
+ if( ULONG_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();
+ }
+
+ sal_uLong nNd;
+ if( pBox && ULONG_MAX != ( nNd = pBox->IsValidNumTextNd() ) )
+ sRet = GetDoc()->GetNodes()[ nNd ]->GetTextNode()->GetText();
+ }
+ return sRet;
+}
+
+void SwEditShell::SplitTable( SplitTable_HeadlineOption eMode )
+{
+ SwPaM *pCursor = GetCursor();
+ if( pCursor->GetNode().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->GetNode().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->GetNode().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 000000000..e652551d1
--- /dev/null
+++ b/sw/source/core/edit/edtox.cxx
@@ -0,0 +1,388 @@
+/* -*- 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 <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>
+
+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())
+ {
+ const SwPosition *pStt = rPaM.Start(),
+ *pEnd = rPaM.End();
+ 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 )
+{
+ SET_CURR_SHELL( 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)
+{
+ OSL_ENSURE( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) != nullptr, "no TOXBaseSection!" );
+ const SwTOXBaseSection& rTOXSect = static_cast<const SwTOXBaseSection&>(rTOXBase);
+ return rTOXSect.IsProtect();
+}
+
+void SwEditShell::SetTOXBaseReadonly(const SwTOXBase& rTOXBase, bool bReadonly)
+{
+ OSL_ENSURE( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) != nullptr, "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 )
+{
+ SET_CURR_SHELL( 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())
+ {
+ SwDoc* pMyDoc = GetDoc();
+ SwDocShell* pDocSh = pMyDoc->GetDocShell();
+
+ bool bInIndex = &rTOX == GetCurTOX();
+ SET_CURR_SHELL( 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
+ CalcLayout();
+
+ // 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 )
+ {
+ OSL_ENSURE( dynamic_cast<const SwTOXBaseSection*>( pSect) != nullptr, "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;
+ SwTOXMark::InsertTOXMarks( aMarks, *pTOXType );
+ 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();
+ rtl_TextEncoding 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(
+ SearchAlgorithms_ABSOLUTE,
+ SearchFlags::LEV_RELAXED,
+ "", "",
+ SvtSysLocale().GetLanguageTag().getLocale(),
+ nLEV_Other, nLEV_Longer, nLEV_Shorter,
+ TransliterationFlags::NONE,
+ SearchAlgorithms2::ABSOLUTE,
+ '\\' );
+
+ while (rStrm.good())
+ {
+ OString aRdLine;
+ 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_uLong 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 000000000..6a093a342
--- /dev/null
+++ b/sw/source/core/edit/edundo.cxx
@@ -0,0 +1,246 @@
+/* -*- 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 <tools/diagnose_ex.h>
+
+#include <com/sun/star/frame/XModel.hpp>
+
+#include <editsh.hxx>
+#include <fesh.hxx>
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentRedlineAccess.hxx>
+#include <pam.hxx>
+#include <UndoCore.hxx>
+#include <swundo.hxx>
+#include <flyfrm.hxx>
+#include <frmfmt.hxx>
+#include <docsh.hxx>
+#include <pagefrm.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();
+ 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)
+{
+ SET_CURR_SHELL( 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().Undo()
+ || 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)
+{
+ SET_CURR_SHELL( 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)
+{
+ SET_CURR_SHELL( 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" );
+
+ if( dynamic_cast<const SwFEShell*>( pShell) != nullptr )
+ {
+ SwFEShell* pFEShell = static_cast<SwFEShell*>( pShell );
+ 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 000000000..b17b557df
--- /dev/null
+++ b/sw/source/core/edit/edws.cxx
@@ -0,0 +1,321 @@
+/* -*- 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 <unotools/configmgr.hxx>
+#include <vcl/window.hxx>
+
+#include <doc.hxx>
+#include <IDocumentUndoRedo.hxx>
+#include <IDocumentState.hxx>
+#include <pam.hxx>
+#include <docary.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( dynamic_cast<const SwEditShell *>(&rCurrentShell) != nullptr )
+ static_cast<SwEditShell*>(&rCurrentShell)->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()->GetNode().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 ( 1 != (pNd->GetIndex() - pNd->StartOfSectionIndex()) )
+ return true;
+
+ pNd = &rNds.GetEndOfAutotext();
+ return 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 )
+{
+ SET_CURR_SHELL( this );
+
+ StartAllAction();
+
+ SwPaM* pCursor = getShellCursor( true );
+ SwTextNode* pTNd = pCursor->GetNode().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()));
+ 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)
+{
+ SET_CURR_SHELL( this );
+
+ OUString sRet;
+ SwPaM* pCursor = getShellCursor( true );
+ SwTextNode* pTNd = pCursor->GetNode().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()
+{
+ SET_CURR_SHELL(this);
+
+ std::vector<OUString> aRet;
+ SwPaM* pCursor = getShellCursor(true);
+ SwTextNode* pTNd = pCursor->GetNode().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: */