diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sw/source/core/edit | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sw/source/core/edit')
-rw-r--r-- | sw/source/core/edit/acorrect.cxx | 715 | ||||
-rw-r--r-- | sw/source/core/edit/autofmt.cxx | 2916 | ||||
-rw-r--r-- | sw/source/core/edit/edatmisc.cxx | 160 | ||||
-rw-r--r-- | sw/source/core/edit/edattr.cxx | 846 | ||||
-rw-r--r-- | sw/source/core/edit/eddel.cxx | 427 | ||||
-rw-r--r-- | sw/source/core/edit/edfcol.cxx | 2326 | ||||
-rw-r--r-- | sw/source/core/edit/edfld.cxx | 411 | ||||
-rw-r--r-- | sw/source/core/edit/edfldexp.cxx | 58 | ||||
-rw-r--r-- | sw/source/core/edit/edfmt.cxx | 157 | ||||
-rw-r--r-- | sw/source/core/edit/edglbldc.cxx | 374 | ||||
-rw-r--r-- | sw/source/core/edit/edglss.cxx | 324 | ||||
-rw-r--r-- | sw/source/core/edit/editsh.cxx | 1098 | ||||
-rw-r--r-- | sw/source/core/edit/edlingu.cxx | 1731 | ||||
-rw-r--r-- | sw/source/core/edit/ednumber.cxx | 880 | ||||
-rw-r--r-- | sw/source/core/edit/edredln.cxx | 196 | ||||
-rw-r--r-- | sw/source/core/edit/edsect.cxx | 439 | ||||
-rw-r--r-- | sw/source/core/edit/edtab.cxx | 527 | ||||
-rw-r--r-- | sw/source/core/edit/edtox.cxx | 394 | ||||
-rw-r--r-- | sw/source/core/edit/edundo.cxx | 257 | ||||
-rw-r--r-- | sw/source/core/edit/edws.cxx | 331 |
20 files changed, 14567 insertions, 0 deletions
diff --git a/sw/source/core/edit/acorrect.cxx b/sw/source/core/edit/acorrect.cxx new file mode 100644 index 0000000000..06ab1231ff --- /dev/null +++ b/sw/source/core/edit/acorrect.cxx @@ -0,0 +1,715 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <hintids.hxx> + +#include <fmtinfmt.hxx> +#include <editsh.hxx> +#include <doc.hxx> +#include <pam.hxx> +#include <unocrsr.hxx> +#include <txatbase.hxx> +#include <txtfrm.hxx> +#include <ndtxt.hxx> +#include <acorrect.hxx> +#include <shellio.hxx> +#include <swundo.hxx> +#include <viscrs.hxx> +#include <com/sun/star/linguistic2/XHyphenator.hpp> +#include <com/sun/star/linguistic2/XHyphenatedWord.hpp> +#include <osl/diagnose.h> +#include <svl/numformat.hxx> + +#include <editeng/acorrcfg.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <rootfrm.hxx> + +using namespace ::com::sun::star; + +namespace { + +class PaMIntoCursorShellRing +{ + SwPaM &m_rDelPam, &m_rCursor; + SwPaM* m_pPrevDelPam; + SwPaM* m_pPrevCursor; + + static void RemoveFromRing( SwPaM& rPam, SwPaM const * pPrev ); +public: + PaMIntoCursorShellRing( SwCursorShell& rSh, SwPaM& rCursor, SwPaM& rPam ); + ~PaMIntoCursorShellRing(); +}; + +} + +PaMIntoCursorShellRing::PaMIntoCursorShellRing(SwCursorShell& rCSh, SwPaM& rShCursor, SwPaM& rPam) + : m_rDelPam(rPam) + , m_rCursor(rShCursor) +{ + SwPaM* pShCursor = rCSh.GetCursor_(); + + m_pPrevDelPam = m_rDelPam.GetPrev(); + m_pPrevCursor = m_rCursor.GetPrev(); + + m_rDelPam.GetRingContainer().merge(pShCursor->GetRingContainer()); + m_rCursor.GetRingContainer().merge(pShCursor->GetRingContainer()); +} + +PaMIntoCursorShellRing::~PaMIntoCursorShellRing() +{ + // and take out the Pam again: + RemoveFromRing(m_rDelPam, m_pPrevDelPam); + RemoveFromRing(m_rCursor, m_pPrevCursor); +} + +void PaMIntoCursorShellRing::RemoveFromRing( SwPaM& rPam, SwPaM const * pPrev ) +{ + SwPaM* p; + SwPaM* pNext = &rPam; + do { + p = pNext; + pNext = p->GetNext(); + p->MoveTo( &rPam ); + } while( p != pPrev ); +} + +SwAutoCorrDoc::SwAutoCorrDoc( SwEditShell& rEditShell, SwPaM& rPam, + sal_Unicode cIns ) + : m_rEditSh( rEditShell ), m_rCursor( rPam ) + , m_nEndUndoCounter(0) + , m_bUndoIdInitialized( cIns == 0 ) +{ +} + +SwAutoCorrDoc::~SwAutoCorrDoc() +{ + for (int i = 0; i < m_nEndUndoCounter; ++i) + { + m_rEditSh.EndUndo(); + } +} + +void SwAutoCorrDoc::DeleteSel( SwPaM& rDelPam ) +{ + // this should work with plain SwPaM as well because start and end + // are always in same node, but since there is GetRanges already... + std::vector<std::shared_ptr<SwUnoCursor>> ranges; + if (sw::GetRanges(ranges, *m_rEditSh.GetDoc(), rDelPam)) + { + DeleteSelImpl(rDelPam); + } + else + { + for (auto const& pCursor : ranges) + { + DeleteSelImpl(*pCursor); + } + } +} + +void SwAutoCorrDoc::DeleteSelImpl(SwPaM & rDelPam) +{ + SwDoc* pDoc = m_rEditSh.GetDoc(); + if( pDoc->IsAutoFormatRedline() ) + { + // so that also the DelPam be moved, include it in the + // Shell-Cursr-Ring !! + // ??? is that really necessary - this should never join nodes, so Update should be enough? +// PaMIntoCursorShellRing aTmp( rEditSh, rCursor, rDelPam ); + assert(rDelPam.GetPoint()->GetNode() == rDelPam.GetMark()->GetNode()); + pDoc->getIDocumentContentOperations().DeleteAndJoin( rDelPam ); + } + else + { + pDoc->getIDocumentContentOperations().DeleteRange( rDelPam ); + } +} + +bool SwAutoCorrDoc::Delete( sal_Int32 nStt, sal_Int32 nEnd ) +{ + SwTextNode const*const pTextNd = m_rCursor.GetPointNode().GetTextNode(); + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + SwPaM aSel(pFrame->MapViewToModelPos(TextFrameIndex(nStt)), + pFrame->MapViewToModelPos(TextFrameIndex(nEnd))); + DeleteSel( aSel ); + + if( m_bUndoIdInitialized ) + m_bUndoIdInitialized = true; + return true; +} + +bool SwAutoCorrDoc::Insert( sal_Int32 nPos, const OUString& rText ) +{ + SwTextNode const*const pTextNd = m_rCursor.GetPointNode().GetTextNode(); + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + SwPaM aPam(pFrame->MapViewToModelPos(TextFrameIndex(nPos))); + m_rEditSh.GetDoc()->getIDocumentContentOperations().InsertString( aPam, rText ); + if( !m_bUndoIdInitialized ) + { + m_bUndoIdInitialized = true; + if( 1 == rText.getLength() ) + { + m_rEditSh.StartUndo( SwUndoId::AUTOCORRECT ); + ++m_nEndUndoCounter; + } + } + return true; +} + +bool SwAutoCorrDoc::Replace( sal_Int32 nPos, const OUString& rText ) +{ + return ReplaceRange( nPos, rText.getLength(), rText ); +} + +bool SwAutoCorrDoc::ReplaceRange( sal_Int32 nPos, sal_Int32 nSourceLength, const OUString& rText ) +{ + assert(nSourceLength == 1); // sw_redlinehide: this is currently the case, + // and ensures that the replace range cannot *contain* delete redlines, + // so we don't need something along the lines of: + // if (sw::GetRanges(ranges, *rEditSh.GetDoc(), aPam)) + // ReplaceImpl(...) + // else + // ReplaceImpl(ranges.begin()) + // for (ranges.begin() + 1; ranges.end(); ) + // DeleteImpl(*it) + + SwTextNode * const pNd = m_rCursor.GetPointNode().GetTextNode(); + if ( !pNd ) + { + return false; + } + + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + std::pair<SwTextNode *, sal_Int32> const pos(pFrame->MapViewToModel(TextFrameIndex(nPos))); + + SwPaM* pPam = &m_rCursor; + if (pPam->GetPoint()->GetNode() != *pos.first + || pPam->GetPoint()->GetContentIndex() != pos.second) + { + pPam = new SwPaM(*pos.first, pos.second); + } + + // text attributes with dummy characters must not be replaced! + bool bDoReplace = true; + sal_Int32 const nLen = rText.getLength(); + for (sal_Int32 n = 0; n < nLen && n + nPos < pFrame->GetText().getLength(); ++n) + { + sal_Unicode const Char = pFrame->GetText()[n + nPos]; + if (CH_TXTATR_BREAKWORD == Char || CH_TXTATR_INWORD == Char) + { + assert(pFrame->MapViewToModel(TextFrameIndex(n+nPos)).first->GetTextAttrForCharAt(pFrame->MapViewToModel(TextFrameIndex(n+nPos)).second)); + bDoReplace = false; + break; + } + } + + // tdf#83419 avoid bad autocorrect with visible redlines + // e.g. replacing the first letter of the tracked deletion + // with its capitalized (and not deleted) version. + if ( bDoReplace && !pFrame->getRootFrame()->IsHideRedlines() && + m_rEditSh.GetDoc()->getIDocumentRedlineAccess().HasRedline( *pPam, RedlineType::Delete, /*bStartOrEndInRange=*/false ) ) + { + bDoReplace = false; + } + + if ( bDoReplace ) + { + SwDoc* pDoc = m_rEditSh.GetDoc(); + + if( pDoc->IsAutoFormatRedline() ) + { + if (nPos == pFrame->GetText().getLength()) // at the End do an Insert + { + pDoc->getIDocumentContentOperations().InsertString( *pPam, rText ); + } + else + { + assert(pos.second != pos.first->Len()); // must be _before_ char + PaMIntoCursorShellRing aTmp( m_rEditSh, m_rCursor, *pPam ); + + pPam->SetMark(); + pPam->GetPoint()->SetContent( std::min<sal_Int32>( + pos.first->GetText().getLength(), pos.second + nSourceLength) ); + pDoc->getIDocumentContentOperations().ReplaceRange( *pPam, rText, false ); + pPam->Exchange(); + pPam->DeleteMark(); + } + } + else + { + pPam->SetMark(); + pPam->GetPoint()->SetContent( std::min<sal_Int32>( + pos.first->GetText().getLength(), pos.second + nSourceLength) ); + pDoc->getIDocumentContentOperations().ReplaceRange( *pPam, rText, false ); + pPam->Exchange(); + pPam->DeleteMark(); + } + + if( m_bUndoIdInitialized ) + { + m_bUndoIdInitialized = true; + if( 1 == rText.getLength() ) + { + m_rEditSh.StartUndo( SwUndoId::AUTOCORRECT ); + ++m_nEndUndoCounter; + } + } + } + + if( pPam != &m_rCursor ) + delete pPam; + + return true; +} + +void SwAutoCorrDoc::SetAttr( sal_Int32 nStt, sal_Int32 nEnd, sal_uInt16 nSlotId, + SfxPoolItem& rItem ) +{ + SwTextNode const*const pTextNd = m_rCursor.GetPointNode().GetTextNode(); + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + SwPaM aPam(pFrame->MapViewToModelPos(TextFrameIndex(nStt)), + pFrame->MapViewToModelPos(TextFrameIndex(nEnd))); + + SfxItemPool& rPool = m_rEditSh.GetDoc()->GetAttrPool(); + sal_uInt16 nWhich = rPool.GetWhich( nSlotId, false ); + if( nWhich ) + { + rItem.SetWhich( nWhich ); + + SfxItemSet aSet( rPool, aCharFormatSetRange ); + SetAllScriptItem( aSet, rItem ); + + m_rEditSh.GetDoc()->SetFormatItemByAutoFormat( aPam, aSet ); + + if( m_bUndoIdInitialized ) + m_bUndoIdInitialized = true; + } +} + +bool SwAutoCorrDoc::SetINetAttr( sal_Int32 nStt, sal_Int32 nEnd, const OUString& rURL ) +{ + SwTextNode const*const pTextNd = m_rCursor.GetPointNode().GetTextNode(); + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + SwPaM aPam(pFrame->MapViewToModelPos(TextFrameIndex(nStt)), + pFrame->MapViewToModelPos(TextFrameIndex(nEnd))); + + SfxItemSetFixed<RES_TXTATR_INETFMT, RES_TXTATR_INETFMT> + aSet( m_rEditSh.GetDoc()->GetAttrPool() ); + aSet.Put( SwFormatINetFormat( rURL, OUString() )); + m_rEditSh.GetDoc()->SetFormatItemByAutoFormat( aPam, aSet ); + if( m_bUndoIdInitialized ) + m_bUndoIdInitialized = true; + return true; +} + +/** Return the text of a previous paragraph + * + * @param bAtNormalPos If <true> before the normal insert position; if <false> in which the + * corrected word was inserted. (Doesn't need to be the same paragraph!) + * @return text or 0, if previous paragraph does not exists or there are only blankness + */ +OUString const* SwAutoCorrDoc::GetPrevPara(bool const bAtNormalPos) +{ + OUString const* pStr(nullptr); + + if( bAtNormalPos || !m_oIndex ) + { + m_oIndex.emplace(m_rCursor.GetPoint()->GetNode()); + } + sw::GotoPrevLayoutTextFrame(*m_oIndex, m_rEditSh.GetLayout()); + + SwTextFrame const* pFrame(nullptr); + for (SwTextNode * pTextNd = m_oIndex->GetNode().GetTextNode(); + pTextNd; pTextNd = m_oIndex->GetNode().GetTextNode()) + { + pFrame = static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout())); + if (pFrame && !pFrame->GetText().isEmpty()) + { + break; + } + sw::GotoPrevLayoutTextFrame(*m_oIndex, m_rEditSh.GetLayout()); + } + if (pFrame && !pFrame->GetText().isEmpty() && + 0 == pFrame->GetTextNodeForParaProps()->GetAttrOutlineLevel()) + { + pStr = & pFrame->GetText(); + } + + if( m_bUndoIdInitialized ) + m_bUndoIdInitialized = true; + + return pStr; +} + +bool SwAutoCorrDoc::ChgAutoCorrWord( sal_Int32& rSttPos, sal_Int32 nEndPos, + SvxAutoCorrect& rACorrect, + OUString* pPara ) +{ + if( m_bUndoIdInitialized ) + m_bUndoIdInitialized = true; + + // Found a beginning of a paragraph or a Blank, + // search for the word Kuerzel (Shortcut) in the Auto + SwTextNode* pTextNd = m_rCursor.GetPointNode().GetTextNode(); + OSL_ENSURE( pTextNd, "where is the TextNode?" ); + + bool bRet = false; + if( nEndPos == rSttPos ) + return bRet; + + LanguageType eLang = GetLanguage(nEndPos); + if(LANGUAGE_SYSTEM == eLang) + eLang = GetAppLanguage(); + LanguageTag aLanguageTag( eLang); + + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + + const OUString sFrameText = pFrame->GetText(); + const SvxAutocorrWord* pFnd = rACorrect.SearchWordsInList( + sFrameText, rSttPos, nEndPos, *this, aLanguageTag); + SwDoc* pDoc = m_rEditSh.GetDoc(); + if( pFnd ) + { + // replace also last colon of keywords surrounded by colons (for example, ":name:") + const bool replaceLastChar = sFrameText.getLength() > nEndPos && pFnd->GetShort()[0] == ':' + && pFnd->GetShort().endsWith(":"); + + SwPosition aStartPos( pFrame->MapViewToModelPos(TextFrameIndex(rSttPos) )); + SwPosition aEndPos( pFrame->MapViewToModelPos(TextFrameIndex(nEndPos + (replaceLastChar ? 1 : 0))) ); + SwPaM aPam(aStartPos, aEndPos); + + // don't replace, if a redline starts or ends within the original text + if ( pDoc->getIDocumentRedlineAccess().HasRedline( aPam, RedlineType::Any, /*bStartOrEndInRange=*/true ) ) + { + return bRet; + } + + if( pFnd->IsTextOnly() ) + { + //JP 22.04.99: Bug 63883 - Special treatment for dots. + const bool bLastCharIsPoint + = nEndPos < sFrameText.getLength() && ('.' == sFrameText[nEndPos]); + if( !bLastCharIsPoint || pFnd->GetLong().isEmpty() || + '.' != pFnd->GetLong()[ pFnd->GetLong().getLength() - 1 ] ) + { + // replace the selection + std::vector<std::shared_ptr<SwUnoCursor>> ranges; + if (sw::GetRanges(ranges, *m_rEditSh.GetDoc(), aPam)) + { + pDoc->getIDocumentContentOperations().ReplaceRange(aPam, pFnd->GetLong(), false); + bRet = true; + } + else if (!ranges.empty()) + { + assert(ranges.front()->GetPoint()->GetNode() == ranges.front()->GetMark()->GetNode()); + pDoc->getIDocumentContentOperations().ReplaceRange( + *ranges.front(), pFnd->GetLong(), false); + for (auto it = ranges.begin() + 1; it != ranges.end(); ++it) + { + DeleteSelImpl(**it); + } + bRet = true; + } + + // tdf#83260 After calling sw::DocumentContentOperationsManager::ReplaceRange + // pTextNd may become invalid when change tracking is on and Edit -> Track Changes -> Show == OFF. + // ReplaceRange shows changes, this moves deleted nodes from special section to document. + // Then Show mode is disabled again. As a result pTextNd may be invalidated. + pTextNd = m_rCursor.GetPointNode().GetTextNode(); + } + } + else + { + SwTextBlocks aTBlks( rACorrect.GetAutoCorrFileName( aLanguageTag, false, true )); + sal_uInt16 nPos = aTBlks.GetIndex( pFnd->GetShort() ); + if( USHRT_MAX != nPos && aTBlks.BeginGetDoc( nPos ) ) + { + DeleteSel( aPam ); + pDoc->DontExpandFormat( *aPam.GetPoint() ); + + if( pPara ) + { + OSL_ENSURE( !m_oIndex, "who has not deleted his Index?" ); + m_oIndex.emplace(m_rCursor.GetPoint()->GetNode()); + sw::GotoPrevLayoutTextFrame(*m_oIndex, m_rEditSh.GetLayout()); + } + + SwDoc* pAutoDoc = aTBlks.GetDoc(); + SwNodeIndex aSttIdx( pAutoDoc->GetNodes().GetEndOfExtras(), 1 ); + SwContentNode* pContentNd = pAutoDoc->GetNodes().GoNext( &aSttIdx ); + SwPaM aCpyPam( aSttIdx ); + + const SwTableNode* pTableNd = pContentNd->FindTableNode(); + if( pTableNd ) + { + aCpyPam.GetPoint()->Assign( *pTableNd ); + } + aCpyPam.SetMark(); + + // then until the end of the Nodes Array + aCpyPam.GetPoint()->Assign( pAutoDoc->GetNodes().GetEndOfContent(), SwNodeOffset(-1) ); + pContentNd = aCpyPam.GetPointContentNode(); + if (pContentNd) + aCpyPam.GetPoint()->SetContent( pContentNd->Len() ); + + SwDontExpandItem aExpItem; + aExpItem.SaveDontExpandItems( *aPam.GetPoint() ); + + pAutoDoc->getIDocumentContentOperations().CopyRange(aCpyPam, *aPam.GetPoint(), SwCopyFlags::CheckPosInFly); + + aExpItem.RestoreDontExpandItems( *aPam.GetPoint() ); + + if( pPara ) + { + sw::GotoNextLayoutTextFrame(*m_oIndex, m_rEditSh.GetLayout()); + pTextNd = m_oIndex->GetNode().GetTextNode(); + } + bRet = true; + } + aTBlks.EndGetDoc(); + } + } + + if( bRet && pPara && pTextNd ) + { + SwTextFrame const*const pNewFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout()))); + *pPara = pNewFrame->GetText(); + } + + return bRet; +} + +bool SwAutoCorrDoc::TransliterateRTLWord( sal_Int32& rSttPos, sal_Int32 nEndPos, bool bApply ) +{ + if( m_bUndoIdInitialized ) + m_bUndoIdInitialized = true; + + SwTextNode* pTextNd = m_rCursor.GetPointNode().GetTextNode(); + OSL_ENSURE( pTextNd, "where is the TextNode?" ); + + bool bRet = false; + if( nEndPos == rSttPos ) + return bRet; + + LanguageType eLang = GetLanguage(nEndPos); + if(LANGUAGE_SYSTEM == eLang) + eLang = GetAppLanguage(); + LanguageTag aLanguageTag(eLang); + + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + + const OUString sFrameText = pFrame->GetText(); + SwDoc* pDoc = m_rEditSh.GetDoc(); + if ( pFrame->IsRightToLeft() || bApply ) + { + // transliterate to Old Hungarian using Numbertext via NatNum12 number format modifier + OUString sWord(sFrameText.copy(rSttPos, nEndPos - rSttPos)); + // Consonant disambiguation using hyphenation + uno::Reference< linguistic2::XHyphenator > xHyph; + xHyph = ::GetHyphenator(); + OUStringBuffer sDisambiguatedWord; + + const ::css::uno::Sequence< ::css::beans::PropertyValue > aProperties; + css::uno::Reference< css::linguistic2::XHyphenatedWord > xHyphWord; + for (int i = 0; i+1 < sWord.getLength(); i++ ) + { + xHyphWord = xHyph->hyphenate( sWord, + aLanguageTag.getLocale(), + i, + aProperties ); + // insert ZWSP at a hyphenation point, if it's not an alternative one (i.e. ssz->sz-sz) + if (xHyphWord.is() && xHyphWord->getHyphenationPos()+1 == i && !xHyphWord->isAlternativeSpelling()) + { + sDisambiguatedWord.append(CHAR_ZWSP); + } + sDisambiguatedWord.append(sWord[i]); + } + sDisambiguatedWord.append(sWord[sWord.getLength()-1]); + + SvNumberFormatter* pFormatter = pDoc->GetNumberFormatter(); + OUString sConverted; + if (pFormatter && !sWord.isEmpty()) + { + const Color* pColor = nullptr; + + // Send text as NatNum12 prefix: "word" -> "[NatNum12 word]0" + + // Closing bracket doesn't allowed in NatNum parameters, remove it from transliteration: + // "[word]" -> "[NatNum12 [word]0" + bool bHasBracket = sWord.endsWith("]"); + if ( !bHasBracket ) + sDisambiguatedWord.append("]"); + OUString sPrefix("[NatNum12 " + sDisambiguatedWord + "0"); + if (pFormatter->GetPreviewString(sPrefix, 0, sConverted, &pColor, LANGUAGE_USER_HUNGARIAN_ROVAS)) + { + if ( bHasBracket ) + sConverted = sConverted + "]"; + bRet = true; + } + } + + SwPaM aPam(pFrame->MapViewToModelPos(TextFrameIndex(rSttPos)), + pFrame->MapViewToModelPos(TextFrameIndex(nEndPos))); + if (bRet && nEndPos <= sFrameText.getLength()) + pDoc->getIDocumentContentOperations().ReplaceRange(aPam, sConverted, false); + } + + return bRet; +} + +// Called by the functions: +// - FnCapitalStartWord +// - FnCapitalStartSentence +// after the exchange of characters. Then the words, if necessary, can be inserted +// into the exception list. +void SwAutoCorrDoc::SaveCpltSttWord( ACFlags nFlag, sal_Int32 nPos, + const OUString& rExceptWord, + sal_Unicode cChar ) +{ + SwNodeOffset nNode = m_oIndex ? m_oIndex->GetIndex() : m_rCursor.GetPoint()->GetNodeIndex(); + LanguageType eLang = GetLanguage(nPos); + m_rEditSh.GetDoc()->SetAutoCorrExceptWord( std::make_unique<SwAutoCorrExceptWord>( nFlag, + nNode, nPos, rExceptWord, cChar, eLang )); +} + +LanguageType SwAutoCorrDoc::GetLanguage( sal_Int32 nPos ) const +{ + LanguageType eRet = LANGUAGE_SYSTEM; + + SwTextNode* pNd = m_rCursor.GetPoint()->GetNode().GetTextNode(); + + if( pNd ) + { + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>( + pNd->getLayoutFrame(m_rEditSh.GetLayout()))); + assert(pFrame); + eRet = pFrame->GetLangOfChar(TextFrameIndex(nPos), 0, true); + } + if(LANGUAGE_SYSTEM == eRet) + eRet = GetAppLanguage(); + return eRet; +} + +void SwAutoCorrExceptWord::CheckChar( const SwPosition& rPos, sal_Unicode cChr ) +{ + // test only if this is an improvement. + // If yes, then add the word to the list. + if (m_cChar == cChr && rPos.GetNodeIndex() == m_nNode && rPos.GetContentIndex() == m_nContent) + { + // get the current autocorrection: + SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect(); + + // then add to the list: + if (ACFlags::CapitalStartWord & m_nFlags) + pACorr->AddWordStartException(m_sWord, m_eLanguage); + else if (ACFlags::CapitalStartSentence & m_nFlags) + pACorr->AddCplSttException(m_sWord, m_eLanguage); + } +} + +bool SwAutoCorrExceptWord::CheckDelChar( const SwPosition& rPos ) +{ + bool bRet = false; + if (!m_bDeleted && rPos.GetNodeIndex() == m_nNode && rPos.GetContentIndex() == m_nContent) + m_bDeleted = bRet = true; + return bRet; +} + +SwDontExpandItem::~SwDontExpandItem() +{ +} + +void SwDontExpandItem::SaveDontExpandItems( const SwPosition& rPos ) +{ + const SwTextNode* pTextNd = rPos.GetNode().GetTextNode(); + if( pTextNd ) + { + m_pDontExpandItems.reset( new SfxItemSet( const_cast<SwDoc&>(pTextNd->GetDoc()).GetAttrPool(), + aCharFormatSetRange ) ); + const sal_Int32 n = rPos.GetContentIndex(); + if (!pTextNd->GetParaAttr( *m_pDontExpandItems, n, n, + n != pTextNd->GetText().getLength() )) + { + m_pDontExpandItems.reset(); + } + } +} + +void SwDontExpandItem::RestoreDontExpandItems( const SwPosition& rPos ) +{ + SwTextNode* pTextNd = rPos.GetNode().GetTextNode(); + if( !pTextNd ) + return; + + const sal_Int32 nStart = rPos.GetContentIndex(); + if( nStart == pTextNd->GetText().getLength() ) + pTextNd->FormatToTextAttr( pTextNd ); + + if( !(pTextNd->GetpSwpHints() && pTextNd->GetpSwpHints()->Count()) ) + return; + + const size_t nSize = pTextNd->GetpSwpHints()->Count(); + sal_Int32 nAttrStart; + + for( size_t n = 0; n < nSize; ++n ) + { + SwTextAttr* pHt = pTextNd->GetpSwpHints()->Get( n ); + nAttrStart = pHt->GetStart(); + if( nAttrStart > nStart ) // beyond the area + break; + + const sal_Int32* pAttrEnd; + if( nullptr != ( pAttrEnd = pHt->End() ) && + ( ( nAttrStart < nStart && + ( pHt->DontExpand() ? nStart < *pAttrEnd + : nStart <= *pAttrEnd )) || + ( nStart == nAttrStart && + ( nAttrStart == *pAttrEnd || !nStart ))) ) + { + const SfxPoolItem* pItem; + if( !m_pDontExpandItems || SfxItemState::SET != m_pDontExpandItems-> + GetItemState( pHt->Which(), false, &pItem ) || + *pItem != pHt->GetAttr() ) + { + // The attribute was not previously set in this form in the + // paragraph, so it can only be created through insert/copy + // Because of that it is a candidate for DontExpand + pHt->SetDontExpand( true ); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/autofmt.cxx b/sw/source/core/edit/autofmt.cxx new file mode 100644 index 0000000000..6fafe1310d --- /dev/null +++ b/sw/source/core/edit/autofmt.cxx @@ -0,0 +1,2916 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <hintids.hxx> + +#include <osl/diagnose.h> +#include <unotools/charclass.hxx> + +#include <editeng/boxitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/formatbreakitem.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/acorrcfg.hxx> +#include <o3tl/string_view.hxx> + +#include <swwait.hxx> +#include <fmtpdsc.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentRedlineManager.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <redline.hxx> +#include <unocrsr.hxx> +#include <docary.hxx> +#include <editsh.hxx> +#include <contentindex.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <poolfmt.hxx> +#include <ndtxt.hxx> +#include <rootfrm.hxx> +#include <txtfrm.hxx> +#include <frminf.hxx> +#include <pagedesc.hxx> +#include <paratr.hxx> +#include <acorrect.hxx> +#include <shellres.hxx> +#include <section.hxx> +#include <frmatr.hxx> +#include <charatr.hxx> +#include <mdiexp.hxx> +#include <strings.hrc> +#include <comcore.hxx> +#include <numrule.hxx> +#include <itabenum.hxx> + +#include <memory> +#include <utility> + +using namespace ::com::sun::star; + +//JP 16.12.99: definition: +// from pos cPosEnDash to cPosEmDash all chars changed to em dashes, +// from pos cPosEmDash to cPosEnd all chars changed to em dashes +// all other chars are changed to the user configuration + +const sal_Unicode pBulletChar[6] = { '+', '*', '-', 0x2013, 0x2014, 0 }; +const int cnPosEnDash = 2, cnPosEmDash = 4; + +const sal_Unicode cStarSymbolEnDash = 0x2013; +const sal_Unicode cStarSymbolEmDash = 0x2014; + +SvxSwAutoFormatFlags* SwEditShell::s_pAutoFormatFlags = nullptr; + +// Number of num-/bullet-paragraph templates. MAXLEVEL will soon be raised +// to x, but not the number of templates. (Artifact from <= 4.0) +const sal_uInt16 cnNumBullColls = 4; + +class SwAutoFormat +{ + SvxSwAutoFormatFlags m_aFlags; + SwPaM m_aDelPam; // a Pam that can be used + SwNodeIndex m_aNdIdx; // the index on the current TextNode + SwNodeIndex m_aEndNdIdx; // index on the end of the area + + SwEditShell* m_pEditShell; + SwDoc* m_pDoc; + SwTextNode* m_pCurTextNd; // the current TextNode + SwTextFrame* m_pCurTextFrame; // frame of the current TextNode + bool m_bIsRightToLeft; // text direction of the current frame + SwNodeOffset m_nEndNdIdx; // for the percentage-display + mutable std::optional<CharClass> m_oCharClass; // Character classification + mutable LanguageType m_eCharClassLang; + + sal_uInt16 m_nRedlAutoFormatSeqId; + + enum + { + NONE = 0, + DELIM = 1, + DIGIT = 2, + CHG = 4, + LOWER_ALPHA = 8, + UPPER_ALPHA = 16, + LOWER_ROMAN = 32, + UPPER_ROMAN = 64, + NO_DELIM = (DIGIT|LOWER_ALPHA|UPPER_ALPHA|LOWER_ROMAN|UPPER_ROMAN) + }; + + bool m_bEnd : 1; + bool m_bMoreLines : 1; + + CharClass& GetCharClass( LanguageType eLang ) const + { + if( !m_oCharClass || eLang != m_eCharClassLang ) + { + m_oCharClass.emplace( LanguageTag( eLang ) ); + m_eCharClassLang = eLang; + } + return *m_oCharClass; + } + + static bool IsSpace( const sal_Unicode c ) + { return (' ' == c || '\t' == c || 0x0a == c|| 0x3000 == c /* Jap. space */); } + + void SetColl( sal_uInt16 nId, bool bHdLineOrText = false ); + void GoNextPara(); + static bool HasObjects(const SwTextFrame &); + + // TextNode methods + const SwTextFrame * GetNextNode(bool isCheckEnd = true) const; + static bool IsEmptyLine(const SwTextFrame & rFrame) + { + return rFrame.GetText().isEmpty() + || rFrame.GetText().getLength() == GetLeadingBlanks(rFrame.GetText()); + } + + bool IsOneLine(const SwTextFrame &) const; + bool IsFastFullLine(const SwTextFrame &) const; + bool IsNoAlphaLine(const SwTextFrame &) const; + bool IsEnumericChar(const SwTextFrame &) const; + static bool IsBlanksInString(const SwTextFrame&); + sal_uInt16 CalcLevel(const SwTextFrame&, sal_uInt16 *pDigitLvl = nullptr) const; + sal_Int32 GetBigIndent(TextFrameIndex & rCurrentSpacePos) const; + + static OUString DelLeadingBlanks(const OUString& rStr); + static OUString DelTrailingBlanks( const OUString& rStr ); + static sal_Int32 GetLeadingBlanks( std::u16string_view aStr ); + static sal_Int32 GetTrailingBlanks( std::u16string_view aStr ); + + bool IsFirstCharCapital(const SwTextFrame & rNd) const; + sal_uInt16 GetDigitLevel(const SwTextFrame& rFrame, TextFrameIndex& rPos, + OUString* pPrefix = nullptr, OUString* pPostfix = nullptr, + OUString* pNumTypes = nullptr ) const; + /// get the FORMATTED TextFrame + SwTextFrame* GetFrame( const SwTextNode& rTextNd ) const; + SwTextFrame * EnsureFormatted(SwTextFrame const&) const; + + void BuildIndent(); + void BuildText(); + void BuildTextIndent(); + void BuildEnum( sal_uInt16 nLvl, sal_uInt16 nDigitLevel ); + void BuildNegIndent( SwTwips nSpaces ); + void BuildHeadLine( sal_uInt16 nLvl ); + + static bool HasBreakAttr(const SwTextFrame &); + void DeleteSel( SwPaM& rPam ); + void DeleteSelImpl(SwPaM & rDelPam, SwPaM & rPamToCorrect); + bool DeleteJoinCurNextPara(SwTextFrame const* pNextFrame, bool bIgnoreLeadingBlanks = false); + /// delete in the node start and/or end + void DeleteLeadingTrailingBlanks( bool bStart = true, bool bEnd = true ); + void DelEmptyLine( bool bTstNextPara = true ); + /// when using multiline paragraphs delete the "left" and/or + /// "right" margins + void DelMoreLinesBlanks( bool bWithLineBreaks = false ); + /// join with the previous paragraph + void JoinPrevPara(); + /// execute AutoCorrect on current TextNode + void AutoCorrect(TextFrameIndex nSttPos = TextFrameIndex(0)); + + bool CanJoin(const SwTextFrame * pNextFrame) const + { + return !m_bEnd && pNextFrame + && !IsEmptyLine(*pNextFrame) + && !IsNoAlphaLine(*pNextFrame) + && !IsEnumericChar(*pNextFrame) + // check the last / first nodes here... + && ((COMPLETE_STRING - 50 - pNextFrame->GetTextNodeFirst()->GetText().getLength()) + > (m_pCurTextFrame->GetMergedPara() + ? m_pCurTextFrame->GetMergedPara()->pLastNode + : m_pCurTextNd)->GetText().getLength()) + && !HasBreakAttr(*pNextFrame); + } + + /// is a dot at the end ?? + static bool IsSentenceAtEnd(const SwTextFrame & rTextFrame); + + bool DoUnderline(); + bool DoTable(); + + void SetRedlineText_( sal_uInt16 nId ); + bool SetRedlineText( sal_uInt16 nId ) { + if( m_aFlags.bWithRedlining ) + SetRedlineText_( nId ); + return true; + } + void ClearRedlineText() { + if( m_aFlags.bWithRedlining ) + m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment(nullptr); + } + +public: + SwAutoFormat( SwEditShell* pEdShell, SvxSwAutoFormatFlags aFlags, + SwNode const * pSttNd = nullptr, SwNode const * pEndNd = nullptr ); +}; + +static const sal_Unicode* StrChr( const sal_Unicode* pSrc, sal_Unicode c ) +{ + while( *pSrc && *pSrc != c ) + ++pSrc; + return *pSrc ? pSrc : nullptr; +} + +SwTextFrame* SwAutoFormat::GetFrame( const SwTextNode& rTextNd ) const +{ + // get the Frame + const SwContentFrame *pFrame = rTextNd.getLayoutFrame( m_pEditShell->GetLayout() ); + assert(pFrame && "For Autoformat a Layout is needed"); + return EnsureFormatted(*static_cast<SwTextFrame const*>(pFrame)); +} + +SwTextFrame * SwAutoFormat::EnsureFormatted(SwTextFrame const& rFrame) const +{ + SwTextFrame *const pFrame(const_cast<SwTextFrame*>(&rFrame)); + if( m_aFlags.bAFormatByInput && !pFrame->isFrameAreaDefinitionValid() ) + { + DisableCallbackAction a(*pFrame->getRootFrame()); + SwRect aTmpFrame( pFrame->getFrameArea() ); + SwRect aTmpPrt( pFrame->getFramePrintArea() ); + pFrame->Calc(pFrame->getRootFrame()->GetCurrShell()->GetOut()); + + if( pFrame->getFrameArea() != aTmpFrame || pFrame->getFramePrintArea() != aTmpPrt || + !pFrame->GetPaintSwRect().IsEmpty()) + { + pFrame->SetCompletePaint(); + } + } + + return pFrame->GetFormatted(); +} + +void SwAutoFormat::SetRedlineText_( sal_uInt16 nActionId ) +{ + OUString sText; + sal_uInt16 nSeqNo = 0; + if( STR_AUTOFMTREDL_END > nActionId ) + { + sText = SwViewShell::GetShellRes()->GetAutoFormatNameLst()[ nActionId ]; + switch( nActionId ) + { + case STR_AUTOFMTREDL_SET_NUMBER_BULLET: + case STR_AUTOFMTREDL_DEL_MORELINES: + + // AutoCorrect actions + case STR_AUTOFMTREDL_USE_REPLACE: + case STR_AUTOFMTREDL_CPTL_STT_WORD: + case STR_AUTOFMTREDL_CPTL_STT_SENT: + case STR_AUTOFMTREDL_TYPO: + case STR_AUTOFMTREDL_UNDER: + case STR_AUTOFMTREDL_BOLD: + case STR_AUTOFMTREDL_FRACTION: + case STR_AUTOFMTREDL_DASH: + case STR_AUTOFMTREDL_ORDINAL: + case STR_AUTOFMTREDL_NON_BREAK_SPACE: + case STR_AUTOFMTREDL_TRANSLITERATE_RTL: + case STR_AUTOFMTREDL_ITALIC: + case STR_AUTOFMTREDL_STRIKETHROUGH: + nSeqNo = ++m_nRedlAutoFormatSeqId; + break; + } + } +#if OSL_DEBUG_LEVEL > 0 + else + sText = "Action text is missing"; +#endif + + m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment( &sText, nSeqNo ); +} + +void SwAutoFormat::GoNextPara() +{ + SwNode* pNewNd = nullptr; + do { + // has to be checked twice before and after incrementation + if( m_aNdIdx.GetIndex() >= m_aEndNdIdx.GetIndex() ) + { + m_bEnd = true; + return; + } + + sw::GotoNextLayoutTextFrame(m_aNdIdx, m_pEditShell->GetLayout()); + if( m_aNdIdx.GetIndex() >= m_aEndNdIdx.GetIndex() ) + { + m_bEnd = true; + return; + } + else + pNewNd = &m_aNdIdx.GetNode(); + + // not a TextNode -> + // TableNode : skip table + // NoTextNode : skip nodes + // EndNode : at the end, terminate + if( pNewNd->IsEndNode() ) + { + m_bEnd = true; + return; + } + else if( pNewNd->IsTableNode() ) + m_aNdIdx = *pNewNd->EndOfSectionNode(); + else if( pNewNd->IsSectionNode() ) + { + const SwSection& rSect = pNewNd->GetSectionNode()->GetSection(); + if( rSect.IsHiddenFlag() || rSect.IsProtectFlag() ) + m_aNdIdx = *pNewNd->EndOfSectionNode(); + } + } while( !pNewNd->IsTextNode() ); + + if( !m_aFlags.bAFormatByInput ) + ::SetProgressState( sal_Int32(m_aNdIdx.GetIndex() + m_nEndNdIdx - m_aEndNdIdx.GetIndex()), + m_pDoc->GetDocShell() ); + + m_pCurTextNd = static_cast<SwTextNode*>(pNewNd); + m_pCurTextFrame = GetFrame( *m_pCurTextNd ); + m_bIsRightToLeft = m_pCurTextFrame->IsRightToLeft(); +} + +bool SwAutoFormat::HasObjects(const SwTextFrame & rFrame) +{ + // Is there something bound to the paragraph in the paragraph + // like Frames, DrawObjects, ... + SwNodeIndex node(*rFrame.GetTextNodeFirst()); + do + { + if (!node.GetNode().GetAnchoredFlys().empty()) + return true; + ++node; + } + while (sw::FrameContainsNode(rFrame, node.GetIndex())); + return false; +} + +const SwTextFrame* SwAutoFormat::GetNextNode(bool const isCheckEnd) const +{ + SwNodeIndex tmp(m_aNdIdx); + sw::GotoNextLayoutTextFrame(tmp, m_pEditShell->GetLayout()); + if ((isCheckEnd && m_aEndNdIdx <= tmp) || !tmp.GetNode().IsTextNode()) + return nullptr; + // note: the returned frame is not necessarily formatted, have to call + // EnsureFormatted for that + return static_cast<SwTextFrame*>(tmp.GetNode().GetTextNode()->getLayoutFrame(m_pEditShell->GetLayout())); +} + +bool SwAutoFormat::IsOneLine(const SwTextFrame & rFrame) const +{ + SwTextFrameInfo aFInfo( EnsureFormatted(rFrame) ); + return aFInfo.IsOneLine(); +} + +bool SwAutoFormat::IsFastFullLine(const SwTextFrame & rFrame) const +{ + bool bRet = m_aFlags.bRightMargin; + if( bRet ) + { + SwTextFrameInfo aFInfo( EnsureFormatted(rFrame) ); + bRet = aFInfo.IsFilled( m_aFlags.nRightMargin ); + } + return bRet; +} + +bool SwAutoFormat::IsEnumericChar(const SwTextFrame& rFrame) const +{ + const OUString& rText = rFrame.GetText(); + bool bIsShortBullet = rText == "* " || rText == "- "; + sal_uInt16 nMinLen = bIsShortBullet ? 1 : 2; + TextFrameIndex nBlanks(GetLeadingBlanks(rText)); + const TextFrameIndex nLen = TextFrameIndex(rText.getLength()) - nBlanks; + if( !nLen ) + return false; + + // -, +, * separated by blank ?? + if (TextFrameIndex(nMinLen) < nLen && IsSpace(rText[sal_Int32(nBlanks) + 1])) + { + if (StrChr(pBulletChar, rText[sal_Int32(nBlanks)])) + return true; + // Should there be a symbol font at the position? + SwTextFrameInfo aFInfo( EnsureFormatted(rFrame) ); + if (aFInfo.IsBullet(nBlanks)) + return true; + } + + // 1.) / 1. / 1.1.1 / (1). / (1) / ... + return USHRT_MAX != GetDigitLevel(rFrame, nBlanks); +} + +bool SwAutoFormat::IsBlanksInString(const SwTextFrame& rFrame) +{ + // Search more than 5 consecutive blanks/tabs in the string. + OUString sTmp( DelLeadingBlanks(rFrame.GetText()) ); + const sal_Int32 nLen = sTmp.getLength(); + sal_Int32 nIdx = 0; + while (nIdx < nLen) + { + // Skip non-blanks + while (nIdx < nLen && !IsSpace(sTmp[nIdx])) ++nIdx; + if (nIdx == nLen) + return false; + // Then count consecutive blanks + const sal_Int32 nFirst = nIdx; + while (nIdx < nLen && IsSpace(sTmp[nIdx])) ++nIdx; + // And exit if enough consecutive blanks were found + if (nIdx-nFirst > 5) + return true; + } + return false; +} + +sal_uInt16 SwAutoFormat::CalcLevel(const SwTextFrame & rFrame, + sal_uInt16 *const pDigitLvl) const +{ + sal_uInt16 nLvl = 0, nBlnk = 0; + const OUString& rText = rFrame.GetText(); + if( pDigitLvl ) + *pDigitLvl = USHRT_MAX; + + if (RES_POOLCOLL_TEXT_MOVE == rFrame.GetTextNodeForParaProps()->GetTextColl()->GetPoolFormatId()) + { + if( m_aFlags.bAFormatByInput ) + { + // this is very non-obvious: on the *first* invocation of + // AutoFormat, the node will have the tabs (any number) converted + // to a fixed indent in BuildTextIndent(), and the number of tabs + // is stored in the node; + // on the *second* invocation of AutoFormat, CalcLevel() will + // retrieve the stored number, and it will be used by + // BuildHeadLine() to select the corresponding heading style. + nLvl = rFrame.GetTextNodeForParaProps()->GetAutoFormatLvl(); + const_cast<SwTextNode *>(rFrame.GetTextNodeForParaProps())->SetAutoFormatLvl(0); + if( nLvl ) + return nLvl; + } + ++nLvl; + } + + for (TextFrameIndex n(0), + nEnd(rText.getLength()); n < nEnd; ++n) + { + switch (rText[sal_Int32(n)]) + { + case ' ': if( 3 == ++nBlnk ) + { + ++nLvl; + nBlnk = 0; + } + break; + case '\t': ++nLvl; + nBlnk = 0; + break; + default: + if( pDigitLvl ) + // test 1.) / 1. / 1.1.1 / (1). / (1) / ... + *pDigitLvl = GetDigitLevel(rFrame, n); + return nLvl; + } + } + return nLvl; +} + +sal_Int32 SwAutoFormat::GetBigIndent(TextFrameIndex & rCurrentSpacePos) const +{ + SwTextFrameInfo aFInfo( m_pCurTextFrame ); + const SwTextFrame* pNextFrame = nullptr; + + if( !m_bMoreLines ) + { + pNextFrame = GetNextNode(); + if (!CanJoin(pNextFrame) || !IsOneLine(*pNextFrame)) + return 0; + + pNextFrame = EnsureFormatted(*pNextFrame); + } + + return aFInfo.GetBigIndent( rCurrentSpacePos, pNextFrame ); +} + +bool SwAutoFormat::IsNoAlphaLine(const SwTextFrame & rFrame) const +{ + const OUString& rStr = rFrame.GetText(); + if( rStr.isEmpty() ) + return false; + // or better: determine via number of AlphaNum and !AlphaNum characters + sal_Int32 nANChar = 0, nBlnk = 0; + + for (TextFrameIndex n(0), + nEnd(rStr.getLength()); n < nEnd; ++n) + if (IsSpace(rStr[sal_Int32(n)])) + ++nBlnk; + else + { + auto const pair = rFrame.MapViewToModel(n); + CharClass& rCC = GetCharClass(pair.first->GetSwAttrSet().GetLanguage().GetLanguage()); + if (rCC.isLetterNumeric(rStr, sal_Int32(n))) + ++nANChar; + } + + // If there are 75% of non-alphanumeric characters, then true + sal_uLong nLen = rStr.getLength() - nBlnk; + nLen = ( nLen * 3 ) / 4; // long overflow, if the strlen > sal_uInt16 + return sal_Int32(nLen) < (rStr.getLength() - nANChar - nBlnk); +} + +bool SwAutoFormat::DoUnderline() +{ + if( !m_aFlags.bSetBorder ) + return false; + + OUString const& rText(m_pCurTextFrame->GetText()); + int eState = 0; + sal_Int32 nCnt = 0; + while (nCnt < rText.getLength()) + { + int eTmp = 0; + switch (rText[nCnt]) + { + case '-': eTmp = 1; break; + case '_': eTmp = 2; break; + case '=': eTmp = 3; break; + case '*': eTmp = 4; break; + case '~': eTmp = 5; break; + case '#': eTmp = 6; break; + default: + return false; + } + if( 0 == eState ) + eState = eTmp; + else if( eState != eTmp ) + return false; + ++nCnt; + } + + if( 2 < nCnt ) + { + // then underline the previous paragraph if one exists + DelEmptyLine( false ); // -> point will be on end of current paragraph + // WARNING: rText may be deleted now, m_pCurTextFrame may be nullptr + m_aDelPam.SetMark(); + // apply to last node & rely on InsertItemSet to apply it to props-node + + editeng::SvxBorderLine aLine; + switch( eState ) + { + case 1: // single, hairline + aLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID); + aLine.SetWidth( SvxBorderLineWidth::Hairline ); + break; + case 2: // single, thin + aLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID); + aLine.SetWidth( SvxBorderLineWidth::Thin ); + break; + case 3: // double, thin + aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE); + aLine.SetWidth( SvxBorderLineWidth::Thin ); + break; + case 4: // double, thick/thin + aLine.SetBorderLineStyle(SvxBorderLineStyle::THICKTHIN_SMALLGAP); + aLine.SetWidth( SvxBorderLineWidth::Thick ); + break; + case 5: // double, thin/thick + aLine.SetBorderLineStyle(SvxBorderLineStyle::THINTHICK_SMALLGAP); + aLine.SetWidth( SvxBorderLineWidth::Thick ); + break; + case 6: // double, medium + aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE); + aLine.SetWidth( SvxBorderLineWidth::Medium ); + break; + } + SfxItemSetFixed<RES_PARATR_CONNECT_BORDER, RES_PARATR_CONNECT_BORDER, + RES_BOX, RES_BOX> aSet(m_pDoc->GetAttrPool()); + aSet.Put( SwParaConnectBorderItem( false ) ); + SvxBoxItem aBox( RES_BOX ); + aBox.SetLine( &aLine, SvxBoxItemLine::BOTTOM ); + aBox.SetDistance(42, SvxBoxItemLine::BOTTOM ); // ~0,75 mm + aSet.Put(aBox); + m_pDoc->getIDocumentContentOperations().InsertItemSet(m_aDelPam, aSet, + SetAttrMode::DEFAULT, m_pEditShell->GetLayout()); + + m_aDelPam.DeleteMark(); + } + return 2 < nCnt; +} + +bool SwAutoFormat::DoTable() +{ + if( !m_aFlags.bCreateTable || !m_aFlags.bAFormatByInput || + m_pCurTextNd->FindTableNode() ) + return false; + + const OUString& rTmp = m_pCurTextFrame->GetText(); + TextFrameIndex nSttPlus(GetLeadingBlanks(rTmp)); + TextFrameIndex nEndPlus(GetTrailingBlanks(rTmp)); + sal_Unicode cChar; + + if (TextFrameIndex(2) > nEndPlus - nSttPlus + || ('+' != (cChar = rTmp[sal_Int32(nSttPlus)]) && '|' != cChar) + || ('+' != (cChar = rTmp[sal_Int32(nEndPlus) - 1]) && '|' != cChar)) + return false; + + SwTextFrameInfo aInfo( m_pCurTextFrame ); + + TextFrameIndex n = nSttPlus; + std::vector<sal_uInt16> aPosArr; + + while (n < TextFrameIndex(rTmp.getLength())) + { + switch (rTmp[sal_Int32(n)]) + { + case '-': + case '_': + case '=': + case ' ': + case '\t': + break; + + case '+': + case '|': + aPosArr.push_back( o3tl::narrowing<sal_uInt16>(aInfo.GetCharPos(n)) ); + break; + + default: + return false; + } + if( ++n == nEndPlus ) + break; + } + + if( 1 < aPosArr.size() ) + { + // get the text node's alignment + sal_uInt16 nColCnt = aPosArr.size() - 1; + SwTwips nSttPos = aPosArr[ 0 ]; + sal_Int16 eHori; + switch (m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust().GetAdjust()) + { + case SvxAdjust::Center: eHori = text::HoriOrientation::CENTER; break; + case SvxAdjust::Right: eHori = text::HoriOrientation::RIGHT; break; + + default: + if( nSttPos ) + { + eHori = text::HoriOrientation::NONE; + // then - as last - we need to add the current frame width into the array + aPosArr.push_back( o3tl::narrowing<sal_uInt16>(m_pCurTextFrame->getFrameArea().Width()) ); + } + else + eHori = text::HoriOrientation::LEFT; + break; + } + + // then create a table that matches the character + DelEmptyLine(); + // WARNING: rTmp may be deleted now, m_pCurTextFrame may be nullptr + SwNodeIndex aIdx( m_aDelPam.GetPoint()->GetNode() ); + m_aDelPam.Move( fnMoveForward ); + m_pDoc->InsertTable( SwInsertTableOptions( SwInsertTableFlags::All , 1 ), + *m_aDelPam.GetPoint(), 1, nColCnt, eHori, + nullptr, &aPosArr ); + m_aDelPam.GetPoint()->Assign(aIdx); + } + return 1 < aPosArr.size(); +} + +OUString SwAutoFormat::DelLeadingBlanks( const OUString& rStr ) +{ + sal_Int32 nL, n; + for( nL = rStr.getLength(), n = 0; n < nL && IsSpace( rStr[n] ); ++n ) + ; + if( n ) // no Spaces + return rStr.copy(n); + return rStr; +} + +OUString SwAutoFormat::DelTrailingBlanks( const OUString& rStr ) +{ + sal_Int32 nL = rStr.getLength(), n = nL; + if( !nL ) + return rStr; + + while( --n && IsSpace( rStr[ n ] ) ) + ; + if( n+1 != nL ) // no Spaces + return rStr.copy( 0, n+1 ); + return rStr; +} + +sal_Int32 SwAutoFormat::GetLeadingBlanks( std::u16string_view aStr ) +{ + size_t nL; + size_t n; + + for( nL = aStr.size(), n = 0; n < nL && IsSpace( aStr[ n ] ); ++n ) + ; + return n; +} + +sal_Int32 SwAutoFormat::GetTrailingBlanks( std::u16string_view aStr ) +{ + size_t nL = aStr.size(), n = nL; + if( !nL ) + return 0; + + while( --n && IsSpace( aStr[ n ] ) ) + ; + return ++n; +} + +bool SwAutoFormat::IsFirstCharCapital(const SwTextFrame& rFrame) const +{ + const OUString& rText = rFrame.GetText(); + for (TextFrameIndex n(0), + nEnd(rText.getLength()); n < nEnd; ++n) + if (!IsSpace(rText[sal_Int32(n)])) + { + auto const pair = rFrame.MapViewToModel(n); + CharClass& rCC = GetCharClass( pair.first->GetSwAttrSet(). + GetLanguage().GetLanguage() ); + sal_Int32 nCharType = rCC.getCharacterType(rText, sal_Int32(n)); + return CharClass::isLetterType( nCharType ) && + 0 != ( i18n::KCharacterType::UPPER & + nCharType ); + } + return false; +} + +sal_uInt16 +SwAutoFormat::GetDigitLevel(const SwTextFrame& rFrame, TextFrameIndex& rPos, + OUString* pPrefix, OUString* pPostfix, OUString* pNumTypes ) const +{ + + // check for 1.) / 1. / 1.1.1 / (1). / (1) / ... + const OUString& rText = rFrame.GetText(); + sal_Int32 nPos(rPos); + int eScan = NONE; + + sal_uInt16 nStart = 0; + sal_uInt8 nDigitLvl = 0, nDigitCnt = 0; + // count number of parenthesis to assure a sensible order is found + sal_uInt16 nOpeningParentheses = 0; + sal_uInt16 nClosingParentheses = 0; + + while (nPos < rText.getLength() && nDigitLvl < MAXLEVEL - 1) + { + auto const pair = rFrame.MapViewToModel(TextFrameIndex(nPos)); + CharClass& rCC = GetCharClass(pair.first->GetSwAttrSet().GetLanguage().GetLanguage()); + const sal_Unicode cCurrentChar = rText[nPos]; + if( ('0' <= cCurrentChar && '9' >= cCurrentChar) || + (0xff10 <= cCurrentChar && 0xff19 >= cCurrentChar) ) + { + if( eScan & DELIM ) + { + if( eScan & CHG ) // not if it starts with a number + { + ++nDigitLvl; + if( pPostfix ) + *pPostfix += "\x01"; + } + + if( pNumTypes ) + *pNumTypes += OUStringChar(sal_Unicode('0' + SVX_NUM_ARABIC)); + + eScan = eScan | CHG; + } + else if( pNumTypes && !(eScan & DIGIT) ) + *pNumTypes += OUStringChar(sal_Unicode('0' + SVX_NUM_ARABIC)); + + eScan &= ~DELIM; // remove Delim + if( 0 != (eScan & ~CHG) && DIGIT != (eScan & ~CHG)) + return USHRT_MAX; + + eScan |= DIGIT; // add Digit + if( 3 == ++nDigitCnt ) // more than 2 numbers are not an enum anymore + return USHRT_MAX; + + nStart *= 10; + nStart += cCurrentChar <= '9' ? cCurrentChar - '0' : cCurrentChar - 0xff10; + } + else if( rCC.isAlpha( rText, nPos ) ) + { + bool bIsUpper = + 0 != ( i18n::KCharacterType::UPPER & + rCC.getCharacterType( rText, nPos )); + sal_Unicode cLow = rCC.lowercase(rText, nPos, 1)[0], cNumTyp; + int eTmpScan; + + // Roman numbers are "mdclxvi". Since we want to start numbering with c or d more often, + // convert first to characters and later to roman numbers if needed. + if( 256 > cLow && strchr( "mdclxvi", cLow ) ) + { + if( bIsUpper ) + { + cNumTyp = '0' + SVX_NUM_ROMAN_UPPER; + eTmpScan = UPPER_ROMAN; + } + else + { + cNumTyp = '0' + SVX_NUM_ROMAN_LOWER; + eTmpScan = LOWER_ROMAN; + } + } + else if( bIsUpper ) + { + cNumTyp = '0' + SVX_NUM_CHARS_UPPER_LETTER; + eTmpScan = UPPER_ALPHA; + } + else + { + cNumTyp = '0' + SVX_NUM_CHARS_LOWER_LETTER; + eTmpScan = LOWER_ALPHA; + } + + // Switch to roman numbers (only for c/d!) + if( 1 == nDigitCnt && ( eScan & (UPPER_ALPHA|LOWER_ALPHA) ) && + ( 3 == nStart || 4 == nStart) && 256 > cLow && + strchr( "mdclxvi", cLow ) && + (( eScan & UPPER_ALPHA ) ? (eTmpScan & (UPPER_ALPHA|UPPER_ROMAN)) + : (eTmpScan & (LOWER_ALPHA|LOWER_ROMAN))) ) + { + sal_Unicode c = '0'; + nStart = 3 == nStart ? 100 : 500; + if( UPPER_ALPHA == eTmpScan ) + { + eTmpScan = UPPER_ROMAN; + c += SVX_NUM_ROMAN_UPPER; + } + else + { + eTmpScan = LOWER_ROMAN; + c += SVX_NUM_ROMAN_LOWER; + } + + eScan = (eScan & ~(UPPER_ALPHA|LOWER_ALPHA)) | eTmpScan; + if( pNumTypes ) + (*pNumTypes) = pNumTypes->replaceAt( pNumTypes->getLength() - 1, 1, rtl::OUStringChar(c) ); + } + + if( eScan & DELIM ) + { + if( eScan & CHG ) // not if it starts with a number + { + ++nDigitLvl; + if( pPostfix ) + *pPostfix += "\x01"; + } + + if( pNumTypes ) + *pNumTypes += OUStringChar(cNumTyp); + eScan = eScan | CHG; + } + else if( pNumTypes && !(eScan & eTmpScan) ) + *pNumTypes += OUStringChar(cNumTyp); + + eScan &= ~DELIM; // remove Delim + + // if another type is set, stop here + if( 0 != ( eScan & ~CHG ) && eTmpScan != ( eScan & ~CHG )) + return USHRT_MAX; + + if( eTmpScan & (UPPER_ALPHA | LOWER_ALPHA) ) + { + // allow characters only if they appear once + return USHRT_MAX; + } + else + { + // roman numbers, check if valid characters + sal_uInt16 nVal; + bool bError = false; + switch( cLow ) + { + case 'm': nVal = 1000; goto CHECK_ROMAN_1; + case 'd': nVal = 500; goto CHECK_ROMAN_5; + case 'c': nVal = 100; goto CHECK_ROMAN_1; + case 'l': nVal = 50; goto CHECK_ROMAN_5; + case 'x': nVal = 10; goto CHECK_ROMAN_1; + case 'v': nVal = 5; goto CHECK_ROMAN_5; + +CHECK_ROMAN_1: + { + int nMod5 = nStart % (nVal * 5); + int nLast = nStart % nVal; + int n10 = nVal / 10; + + if( nMod5 == ((3 * nVal) + n10 ) || + nMod5 == ((4 * nVal) + n10 ) || + nLast == n10 ) + nStart = o3tl::narrowing<sal_uInt16>(nStart + (n10 * 8)); + else if( nMod5 == 0 || + nMod5 == (1 * nVal) || + nMod5 == (2 * nVal) ) + nStart = nStart + nVal; + else + bError = true; + } + break; + +CHECK_ROMAN_5: + { + if( ( nStart / nVal ) & 1 ) + bError = true; + else + { + int nMod = nStart % nVal; + int n10 = nVal / 5; + if( n10 == nMod ) + nStart = o3tl::narrowing<sal_uInt16>(nStart + (3 * n10)); + else if( 0 == nMod ) + nStart = nStart + nVal; + else + bError = true; + } + } + break; + + case 'i': + if( nStart % 5 >= 3 ) + bError = true; + else + nStart += 1; + break; + + default: + bError = true; + } + + if( bError ) + return USHRT_MAX; + } + eScan |= eTmpScan; // add Digit + ++nDigitCnt; + } + else if( (256 > cCurrentChar && + strchr( ".)(", cCurrentChar )) || + 0x3002 == cCurrentChar /* Chinese trad. dot */|| + 0xff0e == cCurrentChar /* Japanese dot */|| + 0xFF08 == cCurrentChar /* opening bracket Chin./Jap.*/|| + 0xFF09 == cCurrentChar )/* closing bracket Chin./Jap. */ + { + if(cCurrentChar == '(' || cCurrentChar == 0xFF09) + nOpeningParentheses++; + else if(cCurrentChar == ')'|| cCurrentChar == 0xFF08) + nClosingParentheses++; + // only if no numbers were read until here + if( pPrefix && !( eScan & ( NO_DELIM | CHG )) ) + *pPrefix += OUStringChar(rText[nPos]); + else if( pPostfix ) + *pPostfix += OUStringChar(rText[nPos]); + + if( NO_DELIM & eScan ) + { + eScan |= CHG; + if( pPrefix ) + *pPrefix += "\x01" + OUString::number( nStart ); + } + eScan &= ~NO_DELIM; // remove Delim + eScan |= DELIM; // add Digit + nDigitCnt = 0; + nStart = 0; + } + else + break; + ++nPos; + } + if (!( CHG & eScan ) || rPos == TextFrameIndex(nPos) || + nPos == rText.getLength() || !IsSpace(rText[nPos]) || + (nOpeningParentheses > nClosingParentheses)) + return USHRT_MAX; + + if( (NO_DELIM & eScan) && pPrefix ) // do not forget the last one + *pPrefix += "\x01" + OUString::number( nStart ); + + rPos = TextFrameIndex(nPos); + return nDigitLvl; // 0 .. 9 (MAXLEVEL - 1) +} + +void SwAutoFormat::SetColl( sal_uInt16 nId, bool bHdLineOrText ) +{ + m_aDelPam.DeleteMark(); + m_aDelPam.GetPoint()->Assign( *m_pCurTextFrame->GetTextNodeForParaProps() ); + + // keep hard tabs, alignment, language, hyphenation, DropCaps and nearly all frame attributes + SfxItemSetFixed< + RES_CHRATR_LANGUAGE, RES_CHRATR_LANGUAGE, + RES_PARATR_ADJUST, RES_PARATR_ADJUST, + RES_PARATR_TABSTOP, RES_PARATR_DROP, + RES_BACKGROUND, RES_SHADOW> aSet(m_pDoc->GetAttrPool()); + + if (m_aDelPam.GetPoint()->GetNode().GetTextNode()->HasSwAttrSet()) + { + aSet.Put(*m_aDelPam.GetPoint()->GetNode().GetTextNode()->GetpSwAttrSet()); + // take HeaderLine/TextBody only if centered or right aligned, otherwise only justification + if( SvxAdjustItem const * pAdj = aSet.GetItemIfSet( RES_PARATR_ADJUST, false) ) + { + SvxAdjust eAdj = pAdj->GetAdjust(); + if( bHdLineOrText ? (SvxAdjust::Right != eAdj && + SvxAdjust::Center != eAdj) + : SvxAdjust::Block != eAdj ) + aSet.ClearItem( RES_PARATR_ADJUST ); + } + } + + m_pDoc->SetTextFormatCollByAutoFormat( *m_aDelPam.GetPoint(), nId, &aSet ); +} + +static bool HasSelBlanks( + SwTextFrame const*const pStartFrame, TextFrameIndex & rStartIndex, + SwTextFrame const*const pEndFrame, TextFrameIndex & rEndIndex) +{ + if (TextFrameIndex(0) < rEndIndex + && rEndIndex < TextFrameIndex(pEndFrame->GetText().getLength()) + && ' ' == pEndFrame->GetText()[sal_Int32(rEndIndex) - 1]) + { + --rEndIndex; + return true; + } + if (rStartIndex < TextFrameIndex(pStartFrame->GetText().getLength()) + && ' ' == pStartFrame->GetText()[sal_Int32(rStartIndex)]) + { + ++rStartIndex; + return true; + } + return false; +} + +bool SwAutoFormat::HasBreakAttr(const SwTextFrame& rTextFrame) +{ + const SfxItemSet *const pSet = rTextFrame.GetTextNodeFirst()->GetpSwAttrSet(); + if( !pSet ) + return false; + + const SvxFormatBreakItem* pBreakItem = pSet->GetItemIfSet( RES_BREAK, false ); + if( pBreakItem && SvxBreak::NONE != pBreakItem->GetBreak() ) + return true; + + const SwFormatPageDesc* pItem = pSet->GetItemIfSet( RES_PAGEDESC, false ); + if( pItem && pItem->GetPageDesc() + && UseOnPage::NONE != pItem->GetPageDesc()->GetUseOn() ) + return true; + return false; +} + +/// Is there a dot at the end? +bool SwAutoFormat::IsSentenceAtEnd(const SwTextFrame & rTextFrame) +{ + const OUString& rStr = rTextFrame.GetText(); + sal_Int32 n = rStr.getLength(); + if( !n ) + return true; + + while( --n && IsSpace( rStr[ n ] ) ) + ; + return '.' == rStr[ n ]; +} + +/// Delete beginning and/or end in a node +void SwAutoFormat::DeleteLeadingTrailingBlanks(bool bStart, bool bEnd) +{ + if( !(m_aFlags.bAFormatByInput + ? m_aFlags.bAFormatByInpDelSpacesAtSttEnd + : m_aFlags.bAFormatDelSpacesAtSttEnd) ) + return; + + // delete blanks at the end of the current and at the beginning of the next one + m_aDelPam.DeleteMark(); + TextFrameIndex nPos(GetLeadingBlanks(m_pCurTextFrame->GetText())); + if (bStart && TextFrameIndex(0) != nPos) + { + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0)); + m_aDelPam.SetMark(); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + DeleteSel( m_aDelPam ); + m_aDelPam.DeleteMark(); + } + nPos = TextFrameIndex(GetTrailingBlanks(m_pCurTextFrame->GetText())); + if (bEnd && TextFrameIndex(m_pCurTextFrame->GetText().getLength()) != nPos) + { + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos( + TextFrameIndex(m_pCurTextFrame->GetText().getLength())); + m_aDelPam.SetMark(); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + DeleteSel( m_aDelPam ); + m_aDelPam.DeleteMark(); + } +} + +namespace sw { + +bool GetRanges(std::vector<std::shared_ptr<SwUnoCursor>> & rRanges, + SwDoc & rDoc, SwPaM const& rDelPam) +{ + bool isNoRedline(true); + SwRedlineTable::size_type tmp; + IDocumentRedlineAccess const& rIDRA(rDoc.getIDocumentRedlineAccess()); + if (!(rIDRA.GetRedlineFlags() & RedlineFlags::ShowDelete)) + { + return isNoRedline; + } + rIDRA.GetRedline(*rDelPam.Start(), &tmp); + SwPosition const* pCurrent(rDelPam.Start()); + for ( ; tmp < rIDRA.GetRedlineTable().size(); ++tmp) + { + SwRangeRedline const*const pRedline(rIDRA.GetRedlineTable()[tmp]); + if (*rDelPam.End() <= *pRedline->Start()) + { + break; + } + if (*pRedline->End() <= *rDelPam.Start()) + { + continue; + } + if (pRedline->GetType() == RedlineType::Delete) + { + assert(*pRedline->Start() != *pRedline->End()); + isNoRedline = false; + if (*pCurrent < *pRedline->Start()) + { + rRanges.push_back(rDoc.CreateUnoCursor(*pCurrent)); + rRanges.back()->SetMark(); + *rRanges.back()->GetPoint() = *pRedline->Start(); + } + pCurrent = pRedline->End(); + } + } + if (!isNoRedline && *pCurrent < *rDelPam.End()) + { + rRanges.push_back(rDoc.CreateUnoCursor(*pCurrent)); + rRanges.back()->SetMark(); + *rRanges.back()->GetPoint() = *rDelPam.End(); + } + return isNoRedline; +} + +} // namespace sw + +void SwAutoFormat::DeleteSel(SwPaM & rDelPam) +{ + std::vector<std::shared_ptr<SwUnoCursor>> ranges; // need correcting cursor + if (GetRanges(ranges, *m_pDoc, rDelPam)) + { + DeleteSelImpl(rDelPam, rDelPam); + } + else + { + for (auto const& pCursor : ranges) + { + DeleteSelImpl(*pCursor, rDelPam); + } + } +} + +void SwAutoFormat::DeleteSelImpl(SwPaM & rDelPam, SwPaM & rPamToCorrect) +{ + if (m_aFlags.bWithRedlining || &rDelPam != &rPamToCorrect) + { + // Add to Shell-Cursor-Ring so that DelPam will be moved as well! + SwPaM* pShCursor = m_pEditShell->GetCursor_(); + SwPaM aTmp( *m_pCurTextNd, 0, pShCursor ); + + SwPaM* pPrev = rPamToCorrect.GetPrev(); + rPamToCorrect.GetRingContainer().merge( pShCursor->GetRingContainer() ); + + m_pEditShell->DeleteSel(rDelPam, true); + + // and remove Pam again: + SwPaM* p; + SwPaM* pNext = &rPamToCorrect; + do { + p = pNext; + pNext = p->GetNext(); + p->MoveTo( &rPamToCorrect ); + } while( p != pPrev ); + + m_aNdIdx = aTmp.GetPoint()->GetNode(); + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = GetFrame(*m_pCurTextNd); // keep it up to date + } + else + m_pEditShell->DeleteSel(rDelPam, true); +} + +bool SwAutoFormat::DeleteJoinCurNextPara(SwTextFrame const*const pNextFrame, + bool const bIgnoreLeadingBlanks) +{ + // delete blanks at the end of the current and at the beginning of the next one + m_aDelPam.DeleteMark(); + TextFrameIndex nTrailingPos(GetTrailingBlanks(m_pCurTextFrame->GetText())); + + SwTextFrame const*const pEndFrame(pNextFrame ? pNextFrame : m_pCurTextFrame); + TextFrameIndex nLeadingPos(0); + if (pNextFrame) + { + nLeadingPos = TextFrameIndex( + bIgnoreLeadingBlanks ? 0 : GetLeadingBlanks(pNextFrame->GetText())); + } + else + { + nLeadingPos = TextFrameIndex(m_pCurTextFrame->GetText().getLength()); + } + + // Is there a Blank at the beginning or end? + // Do not delete it, it will be inserted again. + bool bHasBlnks = HasSelBlanks(m_pCurTextFrame, nTrailingPos, pEndFrame, nLeadingPos); + + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nTrailingPos); + m_aDelPam.SetMark(); + *m_aDelPam.GetPoint() = pEndFrame->MapViewToModelPos(nLeadingPos); + + if( *m_aDelPam.GetPoint() != *m_aDelPam.GetMark() ) + DeleteSel( m_aDelPam ); + m_aDelPam.DeleteMark(); + // note: keep m_aDelPam point at insert pos. for clients + + return !bHasBlnks; +} + +void SwAutoFormat::DelEmptyLine( bool bTstNextPara ) +{ + SetRedlineText( STR_AUTOFMTREDL_DEL_EMPTY_PARA ); + // delete blanks in empty paragraph + m_aDelPam.DeleteMark(); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos( + TextFrameIndex(0)); + m_aDelPam.SetMark(); + + m_aDelPam.GetMark()->Assign( m_pCurTextFrame->GetTextNodeFirst()->GetIndex() - 1 ); + SwTextNode* pTNd = m_aDelPam.GetMarkNode().GetTextNode(); + if( pTNd ) + // first use the previous text node + m_aDelPam.GetMark()->SetContent(pTNd->GetText().getLength()); + else if( bTstNextPara ) + { + // then try the next (at the beginning of a Doc, table cells, frames, ...) + const SwTextNode* pNext = m_pCurTextFrame->GetMergedPara() + ? m_pCurTextFrame->GetMergedPara()->pLastNode + : m_pCurTextNd; + m_aDelPam.GetMark()->Assign(pNext->GetIndex() + 1); + pTNd = m_aDelPam.GetMarkNode().GetTextNode(); + if( pTNd ) + { + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos( + TextFrameIndex(m_pCurTextFrame->GetText().getLength())); + } + } + if( pTNd ) + { // join with previous or next paragraph + DeleteSel(m_aDelPam); + } + assert(m_aDelPam.GetPointNode().IsTextNode()); + assert(!m_aDelPam.HasMark()); + m_aDelPam.SetMark(); // mark remains at join position + m_pCurTextFrame = GetFrame(*m_aDelPam.GetPointNode().GetTextNode()); + // replace until the end of the merged paragraph + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos( + TextFrameIndex(m_pCurTextFrame->GetText().getLength())); + if (*m_aDelPam.GetPoint() != *m_aDelPam.GetMark()) + { // tdf#137245 replace (not delete) to preserve any flys + m_pDoc->getIDocumentContentOperations().ReplaceRange(m_aDelPam, "", false); + } + + m_aDelPam.DeleteMark(); + ClearRedlineText(); + // note: this likely has deleted m_pCurTextFrame - update it... + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = m_pCurTextNd ? GetFrame( *m_pCurTextNd ) : nullptr; +} + +void SwAutoFormat::DelMoreLinesBlanks( bool bWithLineBreaks ) +{ + if( !(m_aFlags.bAFormatByInput + ? m_aFlags.bAFormatByInpDelSpacesBetweenLines + : m_aFlags.bAFormatDelSpacesBetweenLines) ) + return; + + // delete all blanks on the left and right of the indentation + m_aDelPam.DeleteMark(); + + SwTextFrameInfo aFInfo( m_pCurTextFrame ); + std::vector<std::pair<TextFrameIndex, TextFrameIndex>> spaces; + aFInfo.GetSpaces(spaces, !m_aFlags.bAFormatByInput || bWithLineBreaks); + + // tdf#123285 iterate backwards - delete invalidates following indexes + for (auto iter = spaces.rbegin(); iter != spaces.rend(); ++iter) + { + auto & rSpaceRange(*iter); + assert(rSpaceRange.first != rSpaceRange.second); + bool const bHasBlanks = HasSelBlanks( + m_pCurTextFrame, rSpaceRange.first, + m_pCurTextFrame, rSpaceRange.second); + if (rSpaceRange.first != rSpaceRange.second) + { + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(rSpaceRange.first); + m_aDelPam.SetMark(); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(rSpaceRange.second); + DeleteSel(m_aDelPam); + if (!bHasBlanks) + { + m_pDoc->getIDocumentContentOperations().InsertString(m_aDelPam, OUString(' ')); + } + m_aDelPam.DeleteMark(); + } + } +} + +void SwAutoFormat::JoinPrevPara() +{ + m_aDelPam.DeleteMark(); + m_aDelPam.GetPoint()->Assign( *m_pCurTextFrame->GetTextNodeFirst() ); + m_aDelPam.SetMark(); + + m_aDelPam.GetPoint()->Adjust(SwNodeOffset(-1)); + SwTextNode* pTNd = m_aDelPam.GetPointNode().GetTextNode(); + if( pTNd ) + { + // use the previous text node first + m_aDelPam.GetPoint()->SetContent(pTNd->GetText().getLength()); + DeleteSel( m_aDelPam ); + } + m_aDelPam.DeleteMark(); +} + +void SwAutoFormat::BuildIndent() +{ + SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_INDENT ); + + // read all succeeding paragraphs that belong to this indentation + bool bBreak = true; + if( m_bMoreLines ) + DelMoreLinesBlanks( true ); + else + bBreak = !IsFastFullLine(*m_pCurTextFrame) + || IsBlanksInString(*m_pCurTextFrame) + || IsSentenceAtEnd(*m_pCurTextFrame); + SetColl( RES_POOLCOLL_TEXT_IDENT ); + if( !bBreak ) + { + SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES ); + const SwTextFrame * pNextFrame = GetNextNode(); + if (pNextFrame && !m_bEnd) + { + do { + bBreak = !IsFastFullLine(*pNextFrame) + || IsBlanksInString(*pNextFrame) + || IsSentenceAtEnd(*pNextFrame); + if (DeleteJoinCurNextPara(pNextFrame)) + { + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') ); + } + if( bBreak ) + break; + pNextFrame = GetNextNode(); + } + while (CanJoin(pNextFrame) + && !CalcLevel(*pNextFrame)); + } + } + DeleteLeadingTrailingBlanks(); + AutoCorrect(); +} + +void SwAutoFormat::BuildTextIndent() +{ + SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_TEXT_INDENT); + // read all succeeding paragraphs that belong to this indentation + bool bBreak = true; + if( m_bMoreLines ) + DelMoreLinesBlanks( true ); + else + bBreak = !IsFastFullLine(*m_pCurTextFrame) + || IsBlanksInString(*m_pCurTextFrame) + || IsSentenceAtEnd(*m_pCurTextFrame); + + if( m_aFlags.bAFormatByInput ) + { + const_cast<SwTextNode*>(m_pCurTextFrame->GetTextNodeForParaProps())->SetAutoFormatLvl( + static_cast<sal_uInt8>(CalcLevel(*m_pCurTextFrame))); + } + + SetColl( RES_POOLCOLL_TEXT_MOVE ); + if( !bBreak ) + { + SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES ); + const SwTextFrame * pNextFrame = GetNextNode(); + while (CanJoin(pNextFrame) && + CalcLevel(*pNextFrame)) + { + bBreak = !IsFastFullLine(*pNextFrame) + || IsBlanksInString(*pNextFrame) + || IsSentenceAtEnd(*pNextFrame); + if (DeleteJoinCurNextPara(pNextFrame)) + { + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') ); + } + if( bBreak ) + break; + pNextFrame = GetNextNode(); + } + } + DeleteLeadingTrailingBlanks(); + AutoCorrect(); +} + +void SwAutoFormat::BuildText() +{ + SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_TEXT ); + // read all succeeding paragraphs that belong to this text without indentation + bool bBreak = true; + if( m_bMoreLines ) + DelMoreLinesBlanks(); + else + bBreak = !IsFastFullLine(*m_pCurTextFrame) + || IsBlanksInString(*m_pCurTextFrame) + || IsSentenceAtEnd(*m_pCurTextFrame); + if( !bBreak ) + { + SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES ); + const SwTextFrame * pNextFrame = GetNextNode(); + while (CanJoin(pNextFrame) && + !CalcLevel(*pNextFrame)) + { + bBreak = !IsFastFullLine(*pNextFrame) + || IsBlanksInString(*pNextFrame) + || IsSentenceAtEnd(*pNextFrame); + if (DeleteJoinCurNextPara(pNextFrame)) + { + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') ); + } + if( bBreak ) + break; + const SwTextFrame *const pCurrNode = pNextFrame; + pNextFrame = GetNextNode(); + if (!pNextFrame || pCurrNode == pNextFrame) + break; + } + } + DeleteLeadingTrailingBlanks(); + AutoCorrect(); +} + +void SwAutoFormat::BuildEnum( sal_uInt16 nLvl, sal_uInt16 nDigitLevel ) +{ + SetRedlineText( STR_AUTOFMTREDL_SET_NUMBER_BULLET ); + + bool bBreak = true; + + // first, determine current indentation and frame width + SwTwips nFrameWidth = m_pCurTextFrame->getFramePrintArea().Width(); + SwTwips nLeftTextPos; + { + TextFrameIndex nPos(0); + while (nPos < TextFrameIndex(m_pCurTextFrame->GetText().getLength()) + && IsSpace(m_pCurTextFrame->GetText()[sal_Int32(nPos)])) + { + ++nPos; + } + + SwTextFrameInfo aInfo( m_pCurTextFrame ); + nLeftTextPos = aInfo.GetCharPos(nPos); + nLeftTextPos -= m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetTextLeftMargin().GetLeft(m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent()); + } + + if( m_bMoreLines ) + DelMoreLinesBlanks(); + else + bBreak = !IsFastFullLine(*m_pCurTextFrame) + || IsBlanksInString(*m_pCurTextFrame) + || IsSentenceAtEnd(*m_pCurTextFrame); + bool bRTL = m_pEditShell->IsInRightToLeftText(); + + const OUString sStrWithTrailingBlanks = DelLeadingBlanks(m_pCurTextFrame->GetText()); + bool bIsShortBullet = sStrWithTrailingBlanks == "* " || sStrWithTrailingBlanks == "- "; + sal_uInt16 nMinLen = bIsShortBullet ? 1 : 2; + + DeleteLeadingTrailingBlanks(); + + bool bChgBullet = false, bChgEnum = false; + TextFrameIndex nAutoCorrPos(0); + + // if numbering is set, get the current one + SwNumRule aRule( m_pDoc->GetUniqueNumRuleName(), + // #i89178# + numfunc::GetDefaultPositionAndSpaceMode() ); + + const SwNumRule* pCur = nullptr; + if (m_aFlags.bSetNumRule) + { + pCur = m_pCurTextFrame->GetTextNodeForParaProps()->GetNumRule(); + if (pCur) + { + aRule = *pCur; + } + } + + // replace bullet character with defined one + const OUString& rStr = bIsShortBullet ? sStrWithTrailingBlanks : m_pCurTextFrame->GetText(); + TextFrameIndex nTextStt(0); + const sal_Unicode* pFndBulletChr = nullptr; + if (m_aFlags.bChgEnumNum && nMinLen < rStr.getLength()) + pFndBulletChr = StrChr(pBulletChar, rStr[sal_Int32(nTextStt)]); + if (nullptr != pFndBulletChr && IsSpace(rStr[sal_Int32(nTextStt) + 1])) + { + if( m_aFlags.bAFormatByInput ) + { + if( m_aFlags.bSetNumRule) + { + SwCharFormat* pCFormat = m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( + RES_POOLCHR_BULLET_LEVEL ); + bChgBullet = true; + // Was the format already somewhere adjusted? + if( !aRule.GetNumFormat( nLvl ) ) + { + int nBulletPos = pFndBulletChr - pBulletChar; + sal_UCS4 cBullChar; + const vcl::Font* pBullFnt( nullptr ); + if( nBulletPos < cnPosEnDash ) + { + cBullChar = m_aFlags.cBullet; + pBullFnt = &m_aFlags.aBulletFont; + } + else + { + cBullChar = nBulletPos < cnPosEmDash + ? cStarSymbolEnDash + : cStarSymbolEmDash; + // #i63395# + // Only apply user defined default bullet font + if ( numfunc::IsDefBulletFontUserDefined() ) + { + pBullFnt = &numfunc::GetDefBulletFont(); + } + } + + sal_Int32 nAbsPos = lBulletIndent; + SwTwips nSpaceSteps = nLvl + ? nLeftTextPos / nLvl + : lBulletIndent; + for( sal_uInt8 n = 0; n < MAXLEVEL; ++n, nAbsPos = nAbsPos + nSpaceSteps ) + { + SwNumFormat aFormat( aRule.Get( n ) ); + aFormat.SetBulletFont( pBullFnt ); + aFormat.SetBulletChar( cBullChar ); + aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL); + // #i93908# clear suffix for bullet lists + aFormat.SetListFormat("", "", n); + aFormat.SetFirstLineOffset( lBulletFirstLineOffset ); + aFormat.SetAbsLSpace( nAbsPos ); + if( !aFormat.GetCharFormat() ) + aFormat.SetCharFormat( pCFormat ); + if( bRTL ) + aFormat.SetNumAdjust( SvxAdjust::Right ); + + aRule.Set( n, aFormat ); + + if( n == nLvl && + nFrameWidth < ( nSpaceSteps * MAXLEVEL ) ) + nSpaceSteps = ( nFrameWidth - nLeftTextPos ) / + ( MAXLEVEL - nLvl ); + } + } + } + } + else + { + bChgBullet = true; + SetColl( o3tl::narrowing<sal_uInt16>(RES_POOLCOLL_BULLET_LEVEL1 + ( std::min( nLvl, cnNumBullColls ) * 4 )) ); + } + } + else + { + // Then it is a numbering + + //JP 21.11.97: The NumLevel is either the DigitLevel or, if the latter is not existent or 0, + // it is determined by the indentation level. + + OUString aPostfix, aPrefix, aNumTypes; + nDigitLevel = GetDigitLevel(*m_pCurTextFrame, nTextStt, + &aPrefix, &aPostfix, &aNumTypes); + if (USHRT_MAX != nDigitLevel) + { + bChgEnum = true; + + // Level 0 and Indentation, determine level by left indentation and default NumIndent + if( !nDigitLevel && nLeftTextPos ) + nLvl = std::min( sal_uInt16( nLeftTextPos / lNumberIndent ), + sal_uInt16( MAXLEVEL - 1 ) ); + else + nLvl = nDigitLevel; + } + + if( bChgEnum && m_aFlags.bSetNumRule ) + { + if( !pCur ) // adjust NumRule if it is new + { + SwCharFormat* pCFormat = m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool( + RES_POOLCHR_NUM_LEVEL ); + + sal_Int32 nPrefixIdx{ 0 }; + if( !nDigitLevel ) + { + SwNumFormat aFormat( aRule.Get( nLvl ) ); + const OUString sPrefix = aPrefix.getToken(0, u'\x0001', nPrefixIdx); + aFormat.SetStart( o3tl::narrowing<sal_uInt16>(o3tl::toInt32(o3tl::getToken(aPrefix, 0, u'\x0001', nPrefixIdx )))); + aFormat.SetListFormat(sPrefix, aPostfix.getToken(0, u'\x0001'), nLvl); + aFormat.SetIncludeUpperLevels( 0 ); + + if( !aFormat.GetCharFormat() ) + aFormat.SetCharFormat( pCFormat ); + + if( !aNumTypes.isEmpty() ) + aFormat.SetNumberingType(static_cast<SvxNumType>(aNumTypes[ 0 ] - '0')); + + if( bRTL ) + aFormat.SetNumAdjust( SvxAdjust::Right ); + aRule.Set( nLvl, aFormat ); + } + else + { + auto const nSpaceSteps = nLvl ? nLeftTextPos / nLvl : 0; + sal_uInt16 n; + sal_Int32 nPostfixIdx{ 0 }; + for( n = 0; n <= nLvl; ++n ) + { + SwNumFormat aFormat( aRule.Get( n ) ); + + const OUString sPrefix = n ? "" : aPrefix.getToken(0, u'\x0001', nPrefixIdx); + aFormat.SetStart( o3tl::narrowing<sal_uInt16>(o3tl::toInt32(o3tl::getToken(aPrefix, 0, u'\x0001', nPrefixIdx )) )); + aFormat.SetListFormat(sPrefix, aPostfix.getToken(0, u'\x0001', nPostfixIdx), n); + aFormat.SetIncludeUpperLevels( MAXLEVEL ); + if( n < aNumTypes.getLength() ) + aFormat.SetNumberingType(static_cast<SvxNumType>(aNumTypes[ n ] - '0')); + + aFormat.SetAbsLSpace( nSpaceSteps * n + + lNumberIndent ); + + if( !aFormat.GetCharFormat() ) + aFormat.SetCharFormat( pCFormat ); + if( bRTL ) + aFormat.SetNumAdjust( SvxAdjust::Right ); + + aRule.Set( n, aFormat ); + } + + // Does it fit completely into the frame? + bool bDefStep = nFrameWidth < (nSpaceSteps * MAXLEVEL); + for( ; n < MAXLEVEL; ++n ) + { + SwNumFormat aFormat( aRule.Get( n ) ); + aFormat.SetIncludeUpperLevels( MAXLEVEL ); + if( bDefStep ) + aFormat.SetAbsLSpace( nLeftTextPos + + SwNumRule::GetNumIndent(static_cast<sal_uInt8>(n-nLvl))); + else + aFormat.SetAbsLSpace( nSpaceSteps * n + + lNumberIndent ); + aRule.Set( n, aFormat ); + } + } + } + } + else if( !m_aFlags.bAFormatByInput ) + SetColl( o3tl::narrowing<sal_uInt16>(RES_POOLCOLL_NUM_LEVEL1 + ( std::min( nLvl, cnNumBullColls ) * 4 ) )); + else + bChgEnum = false; + } + + if ( bChgEnum || bChgBullet ) + { + m_aDelPam.DeleteMark(); + m_aDelPam.GetPoint()->Assign( *m_pCurTextFrame->GetTextNodeForParaProps() ); + + if( m_aFlags.bSetNumRule ) + { + if( m_aFlags.bAFormatByInput ) + { + m_aDelPam.SetMark(); + SwTextFrame const*const pNextFrame = GetNextNode(false); + if (pNextFrame) + { + m_aDelPam.GetMark()->Assign( *pNextFrame->GetTextNodeForParaProps() ); + m_aDelPam.GetMarkNode().GetTextNode()->SetAttrListLevel( nLvl ); + } + } + + const_cast<SwTextNode*>(m_pCurTextFrame->GetTextNodeForParaProps())->SetAttrListLevel(nLvl); + + // start new list + m_pDoc->SetNumRule(m_aDelPam, aRule, true, m_pEditShell->GetLayout()); + m_aDelPam.DeleteMark(); + + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0)); + } + else + { + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos( + bChgEnum ? nTextStt : TextFrameIndex(0)); + } + m_aDelPam.SetMark(); + + if ( bChgBullet ) + nTextStt += TextFrameIndex(bIsShortBullet ? 1 : 2); + + while (!bIsShortBullet && nTextStt < TextFrameIndex(rStr.getLength()) && IsSpace(rStr[sal_Int32(nTextStt)])) + nTextStt++; + + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nTextStt); + DeleteSel( m_aDelPam ); + + if( !m_aFlags.bSetNumRule ) + { + OUString sChgStr('\t'); + if( bChgBullet ) + sChgStr = OUString(&m_aFlags.cBullet, 1) + sChgStr; + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, sChgStr ); + + SfxItemSet aSet( m_pDoc->GetAttrPool(), aTextNodeSetRange ); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0)); + assert(&m_aDelPam.GetPoint()->GetNode() == m_pCurTextFrame->GetTextNodeForParaProps()); + if( bChgBullet ) + { + m_aDelPam.SetMark(); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(1)); + SetAllScriptItem( aSet, + SvxFontItem( m_aFlags.aBulletFont.GetFamilyType(), + m_aFlags.aBulletFont.GetFamilyName(), + m_aFlags.aBulletFont.GetStyleName(), + m_aFlags.aBulletFont.GetPitch(), + m_aFlags.aBulletFont.GetCharSet(), + RES_CHRATR_FONT ) ); + m_pDoc->SetFormatItemByAutoFormat( m_aDelPam, aSet ); + m_aDelPam.DeleteMark(); + nAutoCorrPos = TextFrameIndex(2); + aSet.ClearItem(); + } + SvxTabStopItem aTStops( RES_PARATR_TABSTOP ); + aTStops.Insert( SvxTabStop( 0 ) ); + aSet.Put( aTStops ); + assert(&m_aDelPam.GetPoint()->GetNode() == m_pCurTextFrame->GetTextNodeForParaProps()); + m_pDoc->SetFormatItemByAutoFormat( m_aDelPam, aSet ); + } + } + + if( bBreak ) + { + AutoCorrect( nAutoCorrPos ); /* Offset due to Bullet + Tab */ + return; + } + + const SwTextFrame * pNextFrame = GetNextNode(); + while (CanJoin(pNextFrame) + && nLvl == CalcLevel(*pNextFrame)) + { + SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES ); + bBreak = !IsFastFullLine(*pNextFrame) + || IsBlanksInString(*pNextFrame) + || IsSentenceAtEnd(*pNextFrame); + if (DeleteJoinCurNextPara(pNextFrame)) + { + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') ); + } + if( bBreak ) + break; + const SwTextFrame *const pCurrNode = pNextFrame; + pNextFrame = GetNextNode(); + if (!pNextFrame || pCurrNode == pNextFrame) + break; + } + DeleteLeadingTrailingBlanks( false ); + AutoCorrect( nAutoCorrPos ); +} + +void SwAutoFormat::BuildNegIndent( SwTwips nSpaces ) +{ + SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_NEG_INDENT ); + // Test of contraposition (n words, divided by spaces/tabs, with same indentation in 2nd line) + + // read all succeeding paragraphs that belong to this enumeration + bool bBreak = true; + TextFrameIndex nSpacePos(0); + const sal_Int32 nTextPos = GetBigIndent( nSpacePos ); + if( m_bMoreLines ) + DelMoreLinesBlanks( true ); + else + bBreak = !IsFastFullLine(*m_pCurTextFrame) + || (!nTextPos && IsBlanksInString(*m_pCurTextFrame)) + || IsSentenceAtEnd(*m_pCurTextFrame); + + SetColl( o3tl::narrowing<sal_uInt16>( nTextPos + ? RES_POOLCOLL_CONFRONTATION + : RES_POOLCOLL_TEXT_NEGIDENT ) ); + + if( nTextPos ) + { + const OUString& rStr = m_pCurTextFrame->GetText(); + bool bInsTab = true; + + if ('\t' == rStr[sal_Int32(nSpacePos) + 1]) // leave tab alone + { + --nSpacePos; + bInsTab = false; + } + + TextFrameIndex nSpaceStt = nSpacePos; + while (nSpaceStt && IsSpace(rStr[sal_Int32(--nSpaceStt)])) + ; + ++nSpaceStt; + + if (bInsTab && '\t' == rStr[sal_Int32(nSpaceStt)]) // leave tab alone + { + ++nSpaceStt; + bInsTab = false; + } + + m_aDelPam.DeleteMark(); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nSpacePos); + + // delete old Spaces, etc. + if( nSpaceStt < nSpacePos ) + { + m_aDelPam.SetMark(); + *m_aDelPam.GetMark() = m_pCurTextFrame->MapViewToModelPos(nSpaceStt); + DeleteSel( m_aDelPam ); + if( bInsTab ) + { + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString('\t') ); + } + } + } + + if( !bBreak ) + { + SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES ); + SwTextFrameInfo aFInfo( m_pCurTextFrame ); + const SwTextFrame * pNextFrame = GetNextNode(); + while (CanJoin(pNextFrame) && + 20 < std::abs( static_cast<tools::Long>(nSpaces - aFInfo.SetFrame( + EnsureFormatted(*pNextFrame)).GetLineStart()) ) + ) + { + bBreak = !IsFastFullLine(*pNextFrame) + || IsBlanksInString(*pNextFrame) + || IsSentenceAtEnd(*pNextFrame); + if (DeleteJoinCurNextPara(pNextFrame)) + { + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') ); + } + if( bBreak ) + break; + pNextFrame = GetNextNode(); + } + } + DeleteLeadingTrailingBlanks(); + AutoCorrect(); +} + +void SwAutoFormat::BuildHeadLine( sal_uInt16 nLvl ) +{ + if( m_aFlags.bWithRedlining ) + { + OUString sText(SwViewShell::GetShellRes()->GetAutoFormatNameLst()[ + STR_AUTOFMTREDL_SET_TMPL_HEADLINE ] ); + sText = sText.replaceAll( "$(ARG1)", OUString::number( nLvl + 1 ) ); + m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment( &sText ); + } + + SetColl( o3tl::narrowing<sal_uInt16>(RES_POOLCOLL_HEADLINE1 + nLvl ), true ); + if( m_aFlags.bAFormatByInput ) + { + SwTextFormatColl& rNxtColl = m_pCurTextFrame->GetTextNodeForParaProps()->GetTextColl()->GetNextTextFormatColl(); + + JoinPrevPara(); + + DeleteLeadingTrailingBlanks( true, false ); + const SwTextFrame* pNextFrame = GetNextNode(false); + if (pNextFrame->GetNext()) + { + (void)DeleteJoinCurNextPara(pNextFrame, true); + pNextFrame = GetNextNode(false); + } + m_aDelPam.DeleteMark(); + m_aDelPam.GetPoint()->Assign( *pNextFrame->GetTextNodeForParaProps() ); + m_pDoc->SetTextFormatColl( m_aDelPam, &rNxtColl ); + } + else + { + DeleteLeadingTrailingBlanks(); + AutoCorrect(); + } +} + +/// Start autocorrection for the current TextNode +void SwAutoFormat::AutoCorrect(TextFrameIndex nPos) +{ + SvxAutoCorrect* pATst = SvxAutoCorrCfg::Get().GetAutoCorrect(); + ACFlags aSvxFlags = pATst->GetFlags( ); + bool bReplaceQuote( aSvxFlags & ACFlags::ChgQuotes ); + bool bReplaceSglQuote( aSvxFlags & ACFlags::ChgSglQuotes ); + + if( m_aFlags.bAFormatByInput || + (!m_aFlags.bAutoCorrect && !bReplaceQuote && !bReplaceSglQuote && + !m_aFlags.bCapitalStartSentence && !m_aFlags.bCapitalStartWord && + !m_aFlags.bChgOrdinalNumber && !m_aFlags.bTransliterateRTL && + !m_aFlags.bChgToEnEmDash && !m_aFlags.bSetINetAttr && + !m_aFlags.bChgWeightUnderl && !m_aFlags.bAddNonBrkSpace) ) + return; + + const OUString* pText = &m_pCurTextFrame->GetText(); + if (TextFrameIndex(pText->getLength()) <= nPos) + return; + + bool bGetLanguage = m_aFlags.bChgOrdinalNumber || m_aFlags.bTransliterateRTL || + m_aFlags.bChgToEnEmDash || m_aFlags.bSetINetAttr || + m_aFlags.bCapitalStartWord || m_aFlags.bCapitalStartSentence || + m_aFlags.bAddNonBrkSpace; + + m_aDelPam.DeleteMark(); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0)); + + SwAutoCorrDoc aACorrDoc( *m_pEditShell, m_aDelPam ); + + SwTextFrameInfo aFInfo( nullptr ); + + TextFrameIndex nSttPos, nLastBlank = nPos; + bool bFirst = m_aFlags.bCapitalStartSentence, bFirstSent = bFirst; + sal_Unicode cChar = 0; + bool bNbspRunNext = false; + + CharClass& rAppCC = GetAppCharClass(); + + do { + while (nPos < TextFrameIndex(pText->getLength()) + && IsSpace(cChar = (*pText)[sal_Int32(nPos)])) + ++nPos; + if (nPos == TextFrameIndex(pText->getLength())) + break; // that's it + + if( ( ( bReplaceQuote && '\"' == cChar ) || + ( bReplaceSglQuote && '\'' == cChar ) ) && + (!nPos || ' ' == (*pText)[sal_Int32(nPos)-1])) + { + + // note: special case symbol fonts !!! + if( !aFInfo.GetFrame() ) + aFInfo.SetFrame( GetFrame( *m_pCurTextNd ) ); + if( !aFInfo.IsBullet( nPos )) + { + SetRedlineText( STR_AUTOFMTREDL_TYPO ); + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + bool bSetHardBlank = false; + + OUString sReplace( pATst->GetQuote( aACorrDoc, + sal_Int32(nPos), cChar, true )); + + m_aDelPam.SetMark(); + m_aDelPam.GetPoint()->SetContent( m_aDelPam.GetMark()->GetContentIndex() + 1 ); + if( 2 == sReplace.getLength() && ' ' == sReplace[ 1 ]) + { + sReplace = sReplace.copy( 0, 1 ); + bSetHardBlank = true; + } + m_pDoc->getIDocumentContentOperations().ReplaceRange( m_aDelPam, sReplace, false ); + + if( m_aFlags.bWithRedlining ) + { + m_aNdIdx = m_aDelPam.GetPoint()->GetNode(); + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = GetFrame( *m_pCurTextNd ); + pText = &m_pCurTextFrame->GetText(); + m_aDelPam.SetMark(); + aFInfo.SetFrame( nullptr ); + } + + nPos += TextFrameIndex(sReplace.getLength() - 1); + m_aDelPam.DeleteMark(); + if( bSetHardBlank ) + { + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(CHAR_HARDBLANK) ); + ++nPos; + } + } + } + + bool bCallACorr = false; + int bBreak = 0; + if (nPos && IsSpace((*pText)[sal_Int32(nPos) - 1])) + nLastBlank = nPos; + for (nSttPos = nPos; !bBreak && nPos < TextFrameIndex(pText->getLength()); ++nPos) + { + cChar = (*pText)[sal_Int32(nPos)]; + switch (cChar) + { + case '\"': + case '\'': + if( ( cChar == '\"' && bReplaceQuote ) || ( cChar == '\'' && bReplaceSglQuote ) ) + { + // consider Symbolfonts! + if( !aFInfo.GetFrame() ) + aFInfo.SetFrame( GetFrame( *m_pCurTextNd ) ); + if( !aFInfo.IsBullet( nPos )) + { + SetRedlineText( STR_AUTOFMTREDL_TYPO ); + bool bSetHardBlank = false; + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + OUString sReplace( pATst->GetQuote( aACorrDoc, + sal_Int32(nPos), cChar, false) ); + + if( 2 == sReplace.getLength() && ' ' == sReplace[ 0 ]) + { + sReplace = sReplace.copy( 1 ); + bSetHardBlank = true; + } + + m_aDelPam.SetMark(); + m_aDelPam.GetPoint()->SetContent( m_aDelPam.GetMark()->GetContentIndex() + 1 ); + m_pDoc->getIDocumentContentOperations().ReplaceRange( m_aDelPam, sReplace, false ); + + if( m_aFlags.bWithRedlining ) + { + m_aNdIdx = m_aDelPam.GetPoint()->GetNode(); + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = GetFrame( *m_pCurTextNd ); + pText = &m_pCurTextFrame->GetText(); + m_aDelPam.SetMark(); + m_aDelPam.DeleteMark(); + aFInfo.SetFrame( nullptr ); + } + + nPos += TextFrameIndex(sReplace.getLength() - 1); + m_aDelPam.DeleteMark(); + + if( bSetHardBlank ) + { + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(CHAR_HARDBLANK) ); + ++nPos; + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + } + } + } + break; + case '*': + case '_': + if( m_aFlags.bChgWeightUnderl ) + { + // consider Symbolfonts! + if( !aFInfo.GetFrame() ) + aFInfo.SetFrame( GetFrame( *m_pCurTextNd ) ); + if( !aFInfo.IsBullet( nPos )) + { + SetRedlineText( '*' == cChar + ? STR_AUTOFMTREDL_BOLD + : STR_AUTOFMTREDL_UNDER ); + + sal_Unicode cBlank = nSttPos ? (*pText)[sal_Int32(nSttPos) - 1] : 0; + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + + if (pATst->FnChgWeightUnderl(aACorrDoc, *pText, sal_Int32(nPos))) + { + if( m_aFlags.bWithRedlining ) + { + m_aNdIdx = m_aDelPam.GetPoint()->GetNode(); + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = GetFrame( *m_pCurTextNd ); + pText = &m_pCurTextFrame->GetText(); + m_aDelPam.SetMark(); + m_aDelPam.DeleteMark(); + aFInfo.SetFrame( nullptr ); + } + //#125102# in case of the mode RedlineFlags::ShowDelete the ** are still contained in pText + if (m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags() & RedlineFlags::ShowDelete) + { + nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint()) + - TextFrameIndex(1); + bBreak++; + } + // Was a character deleted before starting? + if (cBlank && cBlank != (*pText)[sal_Int32(nSttPos) - 1]) + --nSttPos; + } + } + } + break; + case '/': + if ( m_aFlags.bAddNonBrkSpace ) + { + LanguageType eLang = bGetLanguage + ? m_pCurTextFrame->GetLangOfChar(nSttPos, 0, true) + : LANGUAGE_SYSTEM; + + SetRedlineText( STR_AUTOFMTREDL_NON_BREAK_SPACE ); + if (sal_Int32 nUpdatedPos = pATst->FnAddNonBrkSpace(aACorrDoc, *pText, sal_Int32(nPos), eLang, bNbspRunNext); nUpdatedPos >= 0) + { + nPos = TextFrameIndex(nUpdatedPos); + break; + } + } + [[fallthrough]]; + case '-': + if (m_aFlags.bChgWeightUnderl) + { + // consider Symbolfonts! + if (!aFInfo.GetFrame()) + aFInfo.SetFrame(GetFrame(*m_pCurTextNd)); + if (!aFInfo.IsBullet(nPos)) + { + SetRedlineText('/' == cChar ? STR_AUTOFMTREDL_ITALIC : STR_AUTOFMTREDL_STRIKETHROUGH); + + sal_Unicode cBlank = nSttPos ? (*pText)[sal_Int32(nSttPos) - 1] : 0; + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + + if (pATst->FnChgWeightUnderl(aACorrDoc, *pText, sal_Int32(nPos))) + { + if (m_aFlags.bWithRedlining) + { + m_aNdIdx = m_aDelPam.GetPoint()->GetNode(); + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = GetFrame(*m_pCurTextNd); + pText = &m_pCurTextFrame->GetText(); + m_aDelPam.SetMark(); + m_aDelPam.DeleteMark(); + aFInfo.SetFrame(nullptr); + } + //#125102# in case of the mode RedlineFlags::ShowDelete the // or -- are still contained in pText + if (m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags() & RedlineFlags::ShowDelete) + { + nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint()) + - TextFrameIndex(1); + bBreak++; + } + // Was a character deleted before starting? + if (cBlank && cBlank != (*pText)[sal_Int32(nSttPos) - 1]) + --nSttPos; + } + } + } + break; + + case '.': + case '!': + case '?': + if( m_aFlags.bCapitalStartSentence ) + bFirstSent = true; + [[fallthrough]]; + default: + if (!(rAppCC.isBase(*pText, sal_Int32(nPos)) + || '/' == cChar )) // '/' should not be a word separator (e.g. '1/2' needs to be handled as one word for replacement) + { + --nPos; // revert ++nPos which was decremented in for loop + ++bBreak; + } + break; + } + } + + if( nPos == nSttPos ) + { + if (++nPos == TextFrameIndex(pText->getLength())) + bCallACorr = true; + } + else + bCallACorr = true; + + if( bCallACorr ) + { + *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos); + SetRedlineText( STR_AUTOFMTREDL_USE_REPLACE ); + + LanguageType eLang = bGetLanguage + ? m_pCurTextFrame->GetLangOfChar(nSttPos, 0, true) + : LANGUAGE_SYSTEM; + + if( m_bIsRightToLeft && m_aFlags.bTransliterateRTL && eLang == LANGUAGE_HUNGARIAN && + SetRedlineText( STR_AUTOFMTREDL_TRANSLITERATE_RTL ) && + aACorrDoc.TransliterateRTLWord(reinterpret_cast<sal_Int32&>(nSttPos), sal_Int32(nPos), /*bApply=*/true)) + { + nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint()); + + if( m_aFlags.bWithRedlining ) + { + m_aNdIdx = m_aDelPam.GetPoint()->GetNode(); + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = GetFrame( *m_pCurTextNd ); + pText = &m_pCurTextFrame->GetText(); + m_aDelPam.SetMark(); + m_aDelPam.DeleteMark(); + } + + continue; // do not check further + } + + if( m_aFlags.bAutoCorrect && + aACorrDoc.ChgAutoCorrWord(reinterpret_cast<sal_Int32&>(nSttPos), sal_Int32(nPos), *pATst, nullptr) ) + { + nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint()); + if( m_aFlags.bWithRedlining ) + { + m_aNdIdx = m_aDelPam.GetPoint()->GetNode(); + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = GetFrame( *m_pCurTextNd ); + pText = &m_pCurTextFrame->GetText(); + m_aDelPam.SetMark(); + m_aDelPam.DeleteMark(); + } + + continue; // do not check further + } + + if ( m_aFlags.bAddNonBrkSpace && nPos < TextFrameIndex(pText->getLength()) ) + { + SetRedlineText( STR_AUTOFMTREDL_NON_BREAK_SPACE ); + if (sal_Int32 nUpdatedPos = pATst->FnAddNonBrkSpace(aACorrDoc, *pText, sal_Int32(nPos), eLang, bNbspRunNext); nUpdatedPos >= 0) + nPos = TextFrameIndex(nUpdatedPos); + } + + if( ( m_aFlags.bChgOrdinalNumber && + SetRedlineText( STR_AUTOFMTREDL_ORDINAL ) && + pATst->FnChgOrdinalNumber(aACorrDoc, *pText, sal_Int32(nSttPos), sal_Int32(nPos), eLang)) || + ( m_aFlags.bChgToEnEmDash && + SetRedlineText( STR_AUTOFMTREDL_DASH ) && + pATst->FnChgToEnEmDash(aACorrDoc, *pText, sal_Int32(nSttPos), sal_Int32(nPos), eLang)) || + ( m_aFlags.bSetINetAttr && + (nPos == TextFrameIndex(pText->getLength()) || IsSpace((*pText)[sal_Int32(nPos)])) && + SetRedlineText( STR_AUTOFMTREDL_DETECT_URL ) && + pATst->FnSetINetAttr(aACorrDoc, *pText, sal_Int32(nLastBlank), sal_Int32(nPos), eLang)) || + ( m_aFlags.bSetDOIAttr && + (nPos == TextFrameIndex(pText->getLength()) || IsSpace((*pText)[sal_Int32(nPos)])) && + SetRedlineText( STR_AUTOFMTREDL_DETECT_DOI ) && + pATst->FnSetDOIAttr(aACorrDoc, *pText, sal_Int32(nLastBlank), sal_Int32(nPos), eLang))) + { + nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint()); + } + else + { + // two capital letters at the beginning of a word? + if( m_aFlags.bCapitalStartWord ) + { + SetRedlineText( STR_AUTOFMTREDL_CPTL_STT_WORD ); + pATst->FnCapitalStartWord(aACorrDoc, *pText, sal_Int32(nSttPos), sal_Int32(nPos), eLang); + } + // capital letter at the beginning of a sentence? + if( m_aFlags.bCapitalStartSentence && bFirst ) + { + SetRedlineText( STR_AUTOFMTREDL_CPTL_STT_SENT ); + pATst->FnCapitalStartSentence(aACorrDoc, *pText, true, sal_Int32(nSttPos), sal_Int32(nPos), eLang); + } + + bFirst = bFirstSent; + bFirstSent = false; + + if( m_aFlags.bWithRedlining ) + { + m_aNdIdx = m_aDelPam.GetPoint()->GetNode(); + m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode(); + m_pCurTextFrame = GetFrame( *m_pCurTextNd ); + pText = &m_pCurTextFrame->GetText(); + m_aDelPam.SetMark(); + m_aDelPam.DeleteMark(); + } + } + } + } + while (nPos < TextFrameIndex(pText->getLength())); + ClearRedlineText(); +} + +SwAutoFormat::SwAutoFormat( SwEditShell* pEdShell, SvxSwAutoFormatFlags aFlags, + SwNode const * pSttNd, SwNode const * pEndNd ) + : m_aFlags(std::move( aFlags )), + m_aDelPam( pEdShell->GetDoc()->GetNodes().GetEndOfExtras() ), + m_aNdIdx( pEdShell->GetDoc()->GetNodes().GetEndOfExtras(), SwNodeOffset(+1) ), + m_aEndNdIdx( pEdShell->GetDoc()->GetNodes().GetEndOfContent() ), + m_pEditShell( pEdShell ), + m_pDoc( pEdShell->GetDoc() ), + m_pCurTextNd( nullptr ), m_pCurTextFrame( nullptr ), + m_nRedlAutoFormatSeqId( 0 ) +{ + OSL_ENSURE( (pSttNd && pEndNd) || (!pSttNd && !pEndNd), + "Got no area" ); + + if( m_aFlags.bSetNumRule && !m_aFlags.bAFormatByInput ) + m_aFlags.bSetNumRule = false; + + bool bReplaceStyles = !m_aFlags.bAFormatByInput || m_aFlags.bReplaceStyles; + + const SwTextFrame * pNextFrame = nullptr; + bool bNxtEmpty = false; + bool bNxtAlpha = false; + sal_uInt16 nNxtLevel = 0; + bool bEmptyLine; + + // set area for autoformatting + if( pSttNd ) + { + m_aNdIdx = *pSttNd; + // for GoNextPara, one paragraph prior to that + sw::GotoPrevLayoutTextFrame(m_aNdIdx, m_pEditShell->GetLayout()); + m_aEndNdIdx = *pEndNd; + sw::GotoNextLayoutTextFrame(m_aEndNdIdx, m_pEditShell->GetLayout()); + + // check the previous TextNode + SwTextFrame const*const pPrevFrame = m_aNdIdx.GetNode().GetTextNode() + ? static_cast<SwTextFrame const*>(m_aNdIdx.GetNode().GetTextNode()->getLayoutFrame(m_pEditShell->GetLayout())) + : nullptr; + bEmptyLine = !pPrevFrame + || IsEmptyLine(*pPrevFrame) + || IsNoAlphaLine(*pPrevFrame); + } + else + bEmptyLine = true; // at document beginning + + m_bEnd = false; + + // set value for percentage display + m_nEndNdIdx = m_aEndNdIdx.GetIndex(); + + if( !m_aFlags.bAFormatByInput ) + { + ::StartProgress( STR_STATSTR_AUTOFORMAT, sal_Int32(m_aNdIdx.GetIndex()), + sal_Int32(m_nEndNdIdx), + m_pDoc->GetDocShell() ); + } + + RedlineFlags eRedlMode = m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags(), eOldMode = eRedlMode; + if( m_aFlags.bWithRedlining ) + { + m_pDoc->SetAutoFormatRedline( true ); + eRedlMode = RedlineFlags::On | (eOldMode & RedlineFlags::ShowMask); + } + else + eRedlMode = RedlineFlags::Ignore | (eOldMode & RedlineFlags::ShowMask); + m_pDoc->getIDocumentRedlineAccess().SetRedlineFlags( eRedlMode ); + + // save undo state (might be turned off) + bool const bUndoState = m_pDoc->GetIDocumentUndoRedo().DoesUndo(); + + // If multiple lines, then do not merge with next paragraph + m_bMoreLines = false; + + sal_uInt16 nLastCalcHeadLvl = 0; + sal_uInt16 nLastHeadLvl = USHRT_MAX; + sal_uInt16 nLevel = 0; + sal_uInt16 nDigitLvl = 0; + + // set defaults + SwTextFrameInfo aFInfo( nullptr ); + + enum Format_Status + { + READ_NEXT_PARA, // -> ISEND, TST_EMPTY_LINE + TST_EMPTY_LINE, // -> READ_NEXT_PARA, TST_ALPHA_LINE + TST_ALPHA_LINE, // -> READ_NEXT_PARA, GET_ALL_INFO, TST_ENUMERIC, IS_END + GET_ALL_INFO, // -> READ_NEXT_PARA, IS_ONE_LINE, TST_ENUMERIC, HAS_FMTCOLL + IS_ONE_LINE, // -> READ_NEXT_PARA, TST_ENUMERIC + TST_ENUMERIC, // -> READ_NEXT_PARA, TST_IDENT, TST_NEG_IDENT + TST_IDENT, // -> READ_NEXT_PARA, TST_TXT_BODY + TST_NEG_IDENT, // -> READ_NEXT_PARA, TST_TXT_BODY + TST_TXT_BODY, // -> READ_NEXT_PARA + HAS_FMTCOLL, // -> READ_NEXT_PARA + IS_END + } eStat; + + // This is the automat for autoformatting + eStat = READ_NEXT_PARA; + while( !m_bEnd ) + { + switch( eStat ) + { + case READ_NEXT_PARA: + { + GoNextPara(); + eStat = m_bEnd ? IS_END : TST_EMPTY_LINE; + } + break; + + case TST_EMPTY_LINE: + if (IsEmptyLine(*m_pCurTextFrame)) + { + if (m_aFlags.bDelEmptyNode && !HasObjects(*m_pCurTextFrame)) + { + bEmptyLine = true; + SwNodeOffset nOldCnt = m_pDoc->GetNodes().Count(); + DelEmptyLine(); + // Was there really a deletion of a node? + if( nOldCnt != m_pDoc->GetNodes().Count() ) + { + // do not skip the next paragraph + sw::GotoPrevLayoutTextFrame(m_aNdIdx, m_pEditShell->GetLayout()); + } + } + eStat = READ_NEXT_PARA; + } + else + eStat = TST_ALPHA_LINE; + break; + + case TST_ALPHA_LINE: + if (IsNoAlphaLine(*m_pCurTextFrame)) + { + // recognize a table definition +---+---+ + if( m_aFlags.bAFormatByInput && m_aFlags.bCreateTable && DoTable() ) + { + //JP 30.09.96: DoTable() builds on PopCursor and MoveCursor after AutoFormat! + pEdShell->Pop(SwCursorShell::PopMode::DeleteCurrent); + static_cast<SwPaM&>(*pEdShell->GetCursor()) = m_aDelPam; + pEdShell->Push(); + + eStat = IS_END; + break; + } + + const OUString& rStr = m_pCurTextFrame->GetText(); + SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect(); + SvxSwAutoFormatFlags& rFlags = pACorr->GetSwFlags(); + if (rFlags.bChgEnumNum && (rStr == "- " || rStr == "* ")) + { + eStat = TST_ENUMERIC; + break; + } + + // Check for 3 "---" or "===". In this case, the previous paragraph should be + // underlined and the current be deleted! + if( !DoUnderline() && bReplaceStyles ) + { + SetColl( RES_POOLCOLL_STANDARD, true ); + bEmptyLine = true; + } + eStat = READ_NEXT_PARA; + } + else + eStat = GET_ALL_INFO; + break; + + case GET_ALL_INFO: + { + if (m_pCurTextFrame->GetTextNodeForParaProps()->GetNumRule()) + { + // do nothing in numbering, go to next + bEmptyLine = false; + eStat = READ_NEXT_PARA; + // delete all blanks at beginning/end and in between + //JP 29.04.98: first only "all in between" + DelMoreLinesBlanks(); + // auto correct paragraphs that fail to enter state HAS_FMTCOLL + AutoCorrect(); + break; + } + + aFInfo.SetFrame( m_pCurTextFrame ); + + // so far: if there were templates assigned, keep these and go to next node + sal_uInt16 nPoolId = m_pCurTextFrame->GetTextNodeForParaProps()->GetTextColl()->GetPoolFormatId(); + if( IsPoolUserFormat( nPoolId ) + ? !m_aFlags.bChgUserColl + : ( RES_POOLCOLL_STANDARD != nPoolId && + ( !m_aFlags.bAFormatByInput || + (RES_POOLCOLL_TEXT_MOVE != nPoolId && + RES_POOLCOLL_TEXT != nPoolId )) )) + { + eStat = HAS_FMTCOLL; + break; + } + + // replace custom styles with text body + if ( IsPoolUserFormat( nPoolId ) && m_aFlags.bChgUserColl ) + { + SetColl( RES_POOLCOLL_TEXT, true ); + } + + // check for left margin set by the style + if( IsPoolUserFormat( nPoolId ) || + RES_POOLCOLL_STANDARD == nPoolId ) + { + SvxFirstLineIndentItem const*const pFirstLineIndent( + m_pCurTextFrame->GetTextNodeForParaProps() + ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_FIRSTLINE)); + SvxTextLeftMarginItem const*const pTextLeftMargin( + m_pCurTextFrame->GetTextNodeForParaProps() + ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_TEXTLEFT)); + short nSz(pFirstLineIndent ? pFirstLineIndent->GetTextFirstLineOffset() : 0); + if (0 != nSz || + (pTextLeftMargin && 0 != pTextLeftMargin->GetTextLeft())) + { + // exception: numbering/enumeration can have an indentation + if (IsEnumericChar(*m_pCurTextFrame)) + { + nLevel = CalcLevel(*m_pCurTextFrame, &nDigitLvl); + if( nLevel >= MAXLEVEL ) + nLevel = MAXLEVEL-1; + BuildEnum( nLevel, nDigitLvl ); + eStat = READ_NEXT_PARA; + break; + } + + // never merge (maybe only indent as exception) + m_bMoreLines = true; + + if( bReplaceStyles ) + { + // then use one of our templates + if( 0 < nSz ) // positive 1st line indentation + BuildIndent(); + else if( 0 > nSz ) // negative 1st line indentation + BuildNegIndent( aFInfo.GetLineStart() ); + else if (pTextLeftMargin && pTextLeftMargin->GetTextLeft() != 0) // is indentation + BuildTextIndent(); + } + eStat = READ_NEXT_PARA; + break; + } + } + + nLevel = CalcLevel( *m_pCurTextFrame, &nDigitLvl ); + m_bMoreLines = !IsOneLine(*m_pCurTextFrame); + // note: every use of pNextFrame in following states, until the + // next READ_NEXT_PARA, relies on this update + pNextFrame = GetNextNode(); + if (pNextFrame) + { + bNxtEmpty = IsEmptyLine(*pNextFrame); + bNxtAlpha = IsNoAlphaLine(*pNextFrame); + nNxtLevel = CalcLevel(*pNextFrame); + + if (!bEmptyLine && HasBreakAttr(*m_pCurTextFrame)) + bEmptyLine = true; + if (!bNxtEmpty && HasBreakAttr(*pNextFrame)) + bNxtEmpty = true; + + } + else + { + bNxtEmpty = false; + bNxtAlpha = false; + nNxtLevel = 0; + } + eStat = !m_bMoreLines ? IS_ONE_LINE : TST_ENUMERIC; + } + break; + + case IS_ONE_LINE: + { + eStat = TST_ENUMERIC; + if( !bReplaceStyles ) + break; + + const OUString sClrStr( DelLeadingBlanks(m_pCurTextFrame->GetText()) ); + + if( sClrStr.isEmpty() ) + { + bEmptyLine = true; + eStat = READ_NEXT_PARA; + break; // read next paragraph + } + + // check if headline + if (!bEmptyLine || !IsFirstCharCapital(*m_pCurTextFrame) + || IsBlanksInString(*m_pCurTextFrame)) + break; + + bEmptyLine = false; + const OUString sEndClrStr( DelTrailingBlanks(sClrStr) ); + const sal_Unicode cLast = sEndClrStr[sEndClrStr.getLength() - 1]; + + // not, then check if headline + if( ':' == cLast ) + { + BuildHeadLine( 2 ); + eStat = READ_NEXT_PARA; + break; + } + else if( 256 <= cLast || !strchr( ",.;", cLast ) ) + { + if( bNxtEmpty || bNxtAlpha + || (pNextFrame && IsEnumericChar(*pNextFrame))) + { + + // one level below? + if( nLevel >= MAXLEVEL ) + nLevel = MAXLEVEL-1; + + if( USHRT_MAX == nLastHeadLvl ) + nLastHeadLvl = 0; + else if( nLastCalcHeadLvl < nLevel ) + { + if( nLastHeadLvl+1 < MAXLEVEL ) + ++nLastHeadLvl; + } + // one level above? + else if( nLastCalcHeadLvl > nLevel ) + { + if( nLastHeadLvl ) + --nLastHeadLvl; + } + nLastCalcHeadLvl = nLevel; + + if( m_aFlags.bAFormatByInput ) + BuildHeadLine( nLevel ); + else + BuildHeadLine( nLastHeadLvl ); + eStat = READ_NEXT_PARA; + break; + } + } + } + break; + + case TST_ENUMERIC: + { + bEmptyLine = false; + if (IsEnumericChar(*m_pCurTextFrame)) + { + if( nLevel >= MAXLEVEL ) + nLevel = MAXLEVEL-1; + BuildEnum( nLevel, nDigitLvl ); + eStat = READ_NEXT_PARA; + } + else if( bReplaceStyles ) + eStat = nLevel ? TST_IDENT : TST_NEG_IDENT; + else + eStat = READ_NEXT_PARA; + } + break; + + case TST_IDENT: + // Spaces at the beginning, check again for indentation + if( m_bMoreLines && nLevel ) + { + SwTwips nSz = aFInfo.GetFirstIndent(); + if( 0 < nSz ) // positive 1st line indentation + BuildIndent(); + else if( 0 > nSz ) // negative 1st line indentation + BuildNegIndent( aFInfo.GetLineStart() ); + else // is indentation + BuildTextIndent(); + eStat = READ_NEXT_PARA; + } + else if (nLevel && pNextFrame && + !bNxtEmpty && !bNxtAlpha && !nNxtLevel && + !IsEnumericChar(*pNextFrame)) + { + // is an indentation + BuildIndent(); + eStat = READ_NEXT_PARA; + } + else + eStat = TST_TXT_BODY; + break; + + case TST_NEG_IDENT: + // no spaces at the beginning, check again for negative indentation + { + if( m_bMoreLines && !nLevel ) + { + SwTwips nSz = aFInfo.GetFirstIndent(); + if( 0 < nSz ) // positive 1st line indentation + BuildIndent(); + else if( 0 > nSz ) // negative 1st line indentation + BuildNegIndent( aFInfo.GetLineStart() ); + else // is _no_ indentation + BuildText(); + eStat = READ_NEXT_PARA; + } + else if (!nLevel && pNextFrame && + !bNxtEmpty && !bNxtAlpha && nNxtLevel && + !IsEnumericChar(*pNextFrame)) + { + // is a negative indentation + BuildNegIndent( aFInfo.GetLineStart() ); + eStat = READ_NEXT_PARA; + } + else + eStat = TST_TXT_BODY; + } + break; + + case TST_TXT_BODY: + { + if( m_bMoreLines ) + { + SwTwips nSz = aFInfo.GetFirstIndent(); + if( 0 < nSz ) // positive 1st line indentation + BuildIndent(); + else if( 0 > nSz ) // negative 1st line indentation + BuildNegIndent( aFInfo.GetLineStart() ); + else if( nLevel ) // is indentation + BuildTextIndent(); + else + BuildText(); + } + else if( nLevel ) + BuildTextIndent(); + else + BuildText(); + eStat = READ_NEXT_PARA; + } + break; + + case HAS_FMTCOLL: + { + // so far: if there were templates assigned, keep these and go to next node + bEmptyLine = false; + eStat = READ_NEXT_PARA; + // delete all blanks at beginning/end and in between + //JP 29.04.98: first only "all in between" + DelMoreLinesBlanks(); + + // handle hard attributes + if (m_pCurTextFrame->GetTextNodeForParaProps()->HasSwAttrSet()) + { + SvxFirstLineIndentItem const*const pFirstLineIndent( + m_pCurTextFrame->GetTextNodeForParaProps() + ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_FIRSTLINE, false)); + SvxTextLeftMarginItem const*const pTextLeftMargin( + m_pCurTextFrame->GetTextNodeForParaProps() + ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_TEXTLEFT, false)); + short nSz(pFirstLineIndent ? pFirstLineIndent->GetTextFirstLineOffset() : 0); + if( bReplaceStyles && + (0 != nSz || + (pTextLeftMargin && 0 != pTextLeftMargin->GetTextLeft()))) + { + // then use one of our templates + if( 0 < nSz ) // positive 1st line indentation + BuildIndent(); + else if( 0 > nSz ) // negative 1st line indentation + { + BuildNegIndent( aFInfo.GetLineStart() ); + } + else if (pTextLeftMargin && pTextLeftMargin->GetTextLeft()) // is indentation + BuildTextIndent(); + else + BuildText(); + } + } + // force auto correct + AutoCorrect(); + } + break; + + case IS_END: + m_bEnd = true; + break; + } + } + + if( m_aFlags.bWithRedlining ) + m_pDoc->SetAutoFormatRedline( false ); + m_pDoc->getIDocumentRedlineAccess().SetRedlineFlags( eOldMode ); + + // restore undo (in case it has been changed) + m_pDoc->GetIDocumentUndoRedo().DoUndo(bUndoState); + + // disable display of percentage again + if( !m_aFlags.bAFormatByInput ) + ::EndProgress( m_pDoc->GetDocShell() ); +} + +void SwEditShell::AutoFormat( const SvxSwAutoFormatFlags* pAFlags, bool bCurrentParagraphOnly ) +{ + std::optional<SwWait> oWait; + + CurrShell aCurr( this ); + StartAllAction(); + StartUndo( SwUndoId::AUTOFORMAT ); + + SvxSwAutoFormatFlags aAFFlags; // use default values or add params? + if( pAFlags ) + { + aAFFlags = *pAFlags; + if( !aAFFlags.bAFormatByInput ) + oWait.emplace( *GetDoc()->GetDocShell(), true ); + } + + SwPaM* pCursor = GetCursor(); + // There are more than one or a selection is open + if( pCursor->GetNext() != pCursor || pCursor->HasMark() ) + { + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( rPaM.HasMark() ) + { + SwAutoFormat( this, aAFFlags, &rPaM.Start()->GetNode(), + &rPaM.End()->GetNode() ); + } + } + } + else if (bCurrentParagraphOnly) + { + pCursor->SetMark(); + SwAutoFormat aFormat( this, std::move(aAFFlags), &pCursor->GetMark()->GetNode(), + &pCursor->GetPoint()->GetNode() ); + } + else + { + SwAutoFormat( this, std::move(aAFFlags) ); + } + + EndUndo( SwUndoId::AUTOFORMAT ); + EndAllAction(); +} + +void SwEditShell::AutoFormatBySplitNode() +{ + CurrShell aCurr( this ); + SwPaM* pCursor = GetCursor(); + if( pCursor->IsMultiSelection() || !pCursor->Move( fnMoveBackward, GoInNode ) ) + return; + + StartAllAction(); + StartUndo( SwUndoId::AUTOFORMAT ); + + bool bRange = false; + pCursor->SetMark(); + SwPosition* pMarkPos = pCursor->GetMark(); + if( pMarkPos->GetContentIndex() ) + { + pMarkPos->SetContent(0); + bRange = true; + } + else + { + // then go one node backwards + SwNodeIndex aNdIdx(pCursor->GetMark()->GetNode()); + sw::GotoPrevLayoutTextFrame(aNdIdx, GetLayout()); + SwTextNode* pTextNd = aNdIdx.GetNode().GetTextNode(); + if (pTextNd && !pTextNd->GetText().isEmpty()) + { + pCursor->GetMark()->Assign( aNdIdx ); + bRange = true; + } + } + + if( bRange ) + { + Push(); // save cursor + + SvxSwAutoFormatFlags aAFFlags = *GetAutoFormatFlags(); // use default values so far + + SwAutoFormat aFormat( this, std::move(aAFFlags), &pCursor->GetMark()->GetNode(), + &pCursor->GetPoint()->GetNode() ); + SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect(); + if( pACorr && !pACorr->IsAutoCorrFlag( ACFlags::CapitalStartSentence | ACFlags::CapitalStartWord | + ACFlags::AddNonBrkSpace | ACFlags::ChgOrdinalNumber | ACFlags::TransliterateRTL | + ACFlags::ChgToEnEmDash | ACFlags::SetINetAttr | ACFlags::Autocorrect | + ACFlags::SetDOIAttr )) + pACorr = nullptr; + + if( pACorr ) + AutoCorrect( *pACorr,false, u'\0' ); + + //JP 30.09.96: DoTable() builds on PopCursor and MoveCursor! + Pop(PopMode::DeleteCurrent); + pCursor = GetCursor(); + } + pCursor->DeleteMark(); + pCursor->Move( fnMoveForward, GoInNode ); + + EndUndo( SwUndoId::AUTOFORMAT ); + EndAllAction(); + +} + +SvxSwAutoFormatFlags* SwEditShell::GetAutoFormatFlags() +{ + if (!s_pAutoFormatFlags) + s_pAutoFormatFlags = new SvxSwAutoFormatFlags; + + return s_pAutoFormatFlags; +} + +void SwEditShell::SetAutoFormatFlags(SvxSwAutoFormatFlags const * pFlags) +{ + SvxSwAutoFormatFlags* pEditFlags = GetAutoFormatFlags(); + + pEditFlags->bSetNumRule = pFlags->bSetNumRule; + pEditFlags->bChgEnumNum = pFlags->bChgEnumNum; + pEditFlags->bSetBorder = pFlags->bSetBorder; + pEditFlags->bCreateTable = pFlags->bCreateTable; + pEditFlags->bReplaceStyles = pFlags->bReplaceStyles; + pEditFlags->bAFormatByInpDelSpacesAtSttEnd = + pFlags->bAFormatByInpDelSpacesAtSttEnd; + pEditFlags->bAFormatByInpDelSpacesBetweenLines = + pFlags->bAFormatByInpDelSpacesBetweenLines; + + //JP 15.12.98: copy BulletChar and Font into "normal" ones + // because AutoFormat can only work with the latter! + pEditFlags->cBullet = pFlags->cByInputBullet; + pEditFlags->aBulletFont = pFlags->aByInputBulletFont; + pEditFlags->cByInputBullet = pFlags->cByInputBullet; + pEditFlags->aByInputBulletFont = pFlags->aByInputBulletFont; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edatmisc.cxx b/sw/source/core/edit/edatmisc.cxx new file mode 100644 index 0000000000..cfc465fce8 --- /dev/null +++ b/sw/source/core/edit/edatmisc.cxx @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <editsh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <ndtxt.hxx> + +/* + * hard formatting (Attribute) + */ + +void SwEditShell::ResetAttr( const o3tl::sorted_vector<sal_uInt16> &attrs, SwPaM* pPaM ) +{ + CurrShell aCurr( this ); + SwPaM* pCursor = pPaM ? pPaM : GetCursor( ); + + StartAllAction(); + bool bUndoGroup = pCursor->GetNext() != pCursor; + if( bUndoGroup ) + { + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::RESETATTR, nullptr); + } + + for(const SwPaM& rCurrentCursor : pCursor->GetRingContainer()) + GetDoc()->ResetAttrs(rCurrentCursor, true, attrs, true, GetLayout()); + + if( bUndoGroup ) + { + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::RESETATTR, nullptr); + } + CallChgLnk(); + EndAllAction(); +} + +void SwEditShell::GCAttr() +{ + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if ( !rPaM.HasMark() ) + { + SwTextNode *const pTextNode = + rPaM.GetPoint()->GetNode().GetTextNode(); + if (pTextNode) + { + pTextNode->GCAttr(); + } + } + else + { + const SwNode& rEnd = rPaM.End()->GetNode(); + SwNodeIndex aIdx( rPaM.Start()->GetNode() ); + SwNode* pNd = &aIdx.GetNode(); + do { + if( pNd->IsTextNode() ) + static_cast<SwTextNode*>(pNd)->GCAttr(); + } + while( nullptr != ( pNd = GetDoc()->GetNodes().GoNext( &aIdx )) && + aIdx <= rEnd ); + } + } +} + +/// Set the attribute as new default attribute in the document. +void SwEditShell::SetDefault( const SfxPoolItem& rFormatHint ) +{ + // 7502: Action-Parenthesis + StartAllAction(); + GetDoc()->SetDefault( rFormatHint ); + EndAllAction(); +} + +/// request the default attribute in this document. +const SfxPoolItem& SwEditShell::GetDefault( sal_uInt16 nFormatHint ) const +{ + return GetDoc()->GetDefault( nFormatHint ); +} + +void SwEditShell::SetAttrItem( const SfxPoolItem& rHint, SetAttrMode nFlags, const bool /*bParagraphSetting*/ ) +{ + CurrShell aCurr( this ); + StartAllAction(); + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor ) // Ring of Cursors + { + bool bIsTableMode = IsTableMode(); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSATTR, nullptr); + + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( rPaM.HasMark() && ( bIsTableMode || + *rPaM.GetPoint() != *rPaM.GetMark() )) + { + GetDoc()->getIDocumentContentOperations().InsertPoolItem(rPaM, rHint, nFlags, GetLayout()); + } + } + + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSATTR, nullptr); + } + else + { + if( !HasSelection() ) + UpdateAttr(); + + GetDoc()->getIDocumentContentOperations().InsertPoolItem(*pCursor, rHint, nFlags, GetLayout()); + } + EndAllAction(); +} + +void SwEditShell::SetAttrSet( const SfxItemSet& rSet, SetAttrMode nFlags, SwPaM* pPaM, const bool /*bParagraphSetting*/ ) +{ + CurrShell aCurr( this ); + SwPaM* pCursor = pPaM ? pPaM : GetCursor(); + StartAllAction(); + if( pCursor->GetNext() != pCursor ) // Ring of Cursors + { + bool bIsTableMode = IsTableMode(); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::INSATTR, nullptr); + + for(SwPaM& rTmpCursor : pCursor->GetRingContainer()) + { + if( rTmpCursor.HasMark() && ( bIsTableMode || + *rTmpCursor.GetPoint() != *rTmpCursor.GetMark() )) + { + GetDoc()->getIDocumentContentOperations().InsertItemSet(rTmpCursor, rSet, nFlags, GetLayout()); + } + } + + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::INSATTR, nullptr); + } + else + { + if( !HasSelection() ) + UpdateAttr(); + + GetDoc()->getIDocumentContentOperations().InsertItemSet(*pCursor, rSet, nFlags, GetLayout()); + } + EndAllAction(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edattr.cxx b/sw/source/core/edit/edattr.cxx new file mode 100644 index 0000000000..8ff0026d03 --- /dev/null +++ b/sw/source/core/edit/edattr.cxx @@ -0,0 +1,846 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <hintids.hxx> +#include <editeng/tstpitem.hxx> +#include <editeng/lrspitem.hxx> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <osl/diagnose.h> +#include <txatbase.hxx> +#include <txtftn.hxx> +#include <fmtftn.hxx> +#include <editsh.hxx> +#include <edimp.hxx> +#include <doc.hxx> +#include <swundo.hxx> +#include <ndtxt.hxx> +#include <ftnidx.hxx> +#include <expfld.hxx> +#include <rootfrm.hxx> +#include <cntfrm.hxx> +#include <breakit.hxx> +#include <fmtfld.hxx> +#include <txtfrm.hxx> +#include <scriptinfo.hxx> +#include <svl/itemiter.hxx> +#include <svl/languageoptions.hxx> +#include <charfmt.hxx> +#include <numrule.hxx> + +/* + * hard Formatting (Attributes) + */ + +// if selection is bigger as max nodes or more than max selections +// => no attributes +static sal_uInt16 getMaxLookup() +{ + return 10000; +} + +bool SwEditShell::GetPaMAttr( SwPaM* pPaM, SfxItemSet& rSet, + const bool bMergeIndentValuesOfNumRule ) const +{ + // ??? pPaM can be different from the Cursor ??? + if( GetCursorCnt() > getMaxLookup() ) + { + rSet.InvalidateAllItems(); + return false; + } + + SfxItemSet aSet( *rSet.GetPool(), rSet.GetRanges() ); + SfxItemSet *pSet = &rSet; + + for(SwPaM& rCurrentPaM : pPaM->GetRingContainer()) + { + // #i27615# if the cursor is in front of the numbering label + // the attributes to get are those from the numbering format. + if (rCurrentPaM.IsInFrontOfLabel()) + { + SwTextNode const*const pTextNd = sw::GetParaPropsNode(*GetLayout(), + rCurrentPaM.GetPoint()->GetNode()); + + if (pTextNd) + { + SwNumRule * pNumRule = pTextNd->GetNumRule(); + + if (pNumRule) + { + int nListLevel = pTextNd->GetActualListLevel(); + + if (nListLevel < 0) + nListLevel = 0; + + if (nListLevel >= MAXLEVEL) + nListLevel = MAXLEVEL - 1; + + const OUString & aCharFormatName = + pNumRule->Get(o3tl::narrowing<sal_uInt16>(nListLevel)).GetCharFormatName(); + SwCharFormat * pCharFormat = + GetDoc()->FindCharFormatByName(aCharFormatName); + + if (pCharFormat) + rSet.Put(pCharFormat->GetAttrSet()); + } + } + + continue; + } + + SwNodeOffset nSttNd = rCurrentPaM.Start()->GetNodeIndex(), + nEndNd = rCurrentPaM.End()->GetNodeIndex(); + sal_Int32 nSttCnt = rCurrentPaM.Start()->GetContentIndex(); + sal_Int32 nEndCnt = rCurrentPaM.End()->GetContentIndex(); + + if( sal_Int32(nEndNd - nSttNd) >= getMaxLookup() ) + { + rSet.ClearItem(); + rSet.InvalidateAllItems(); + return false; + } + + // at first node the node enter his values into the GetSet (Initial) + // all additional nodes are additional merged to GetSet + for( SwNodeOffset n = nSttNd; n <= nEndNd; ++n ) + { + SwNode* pNd = GetDoc()->GetNodes()[ n ]; + switch( pNd->GetNodeType() ) + { + case SwNodeType::Text: + { + const sal_Int32 nStt = (n == nSttNd) ? nSttCnt : 0; + const sal_Int32 nEnd = (n == nEndNd) + ? nEndCnt + : pNd->GetTextNode()->GetText().getLength(); + + static_cast<SwTextNode*>(pNd)->GetParaAttr(*pSet, nStt, nEnd, + false, true, + bMergeIndentValuesOfNumRule, + GetLayout()); + } + break; + case SwNodeType::Grf: + case SwNodeType::Ole: + static_cast<SwContentNode*>(pNd)->GetAttr( *pSet ); + break; + + default: + pNd = nullptr; + } + + if( pNd ) + { + if( pSet != &rSet ) + { + if (!GetLayout()->HasMergedParas() + || pNd->GetRedlineMergeFlag() != SwNode::Merge::Hidden) + { + rSet.MergeValues( aSet ); + } + } + + if( aSet.Count() ) + aSet.ClearItem(); + } + pSet = &aSet; + } + + } + + return true; +} + +bool SwEditShell::GetCurAttr( SfxItemSet& rSet, + const bool bMergeIndentValuesOfNumRule ) const +{ + return GetPaMAttr( GetCursor(), rSet, bMergeIndentValuesOfNumRule ); +} + +void SwEditShell::GetCurParAttr( SfxItemSet& rSet) const +{ + GetPaMParAttr( GetCursor(), rSet ); +} + +void SwEditShell::GetPaMParAttr( SwPaM* pPaM, SfxItemSet& rSet ) const +{ + // number of nodes the function has explored so far + sal_uInt16 numberOfLookup = 0; + + SfxItemSet aSet( *rSet.GetPool(), rSet.GetRanges() ); + SfxItemSet* pSet = &rSet; + + for(SwPaM& rCurrentPaM : pPaM->GetRingContainer()) + { // for all the point and mark (selections) + + // get the start and the end node of the current selection + SwNodeOffset nSttNd = rCurrentPaM.GetMark()->GetNodeIndex(), + nEndNd = rCurrentPaM.GetPoint()->GetNodeIndex(); + + // reverse start and end if there number aren't sorted correctly + if( nSttNd > nEndNd ) + std::swap(nSttNd, nEndNd); + + // for all the nodes in the current selection + // get the node (paragraph) attributes + // and merge them in rSet + for( SwNodeOffset n = nSttNd; n <= nEndNd; ++n ) + { + // get the node + SwNode* pNd = GetDoc()->GetNodes()[ n ]; + + if (GetLayout()->HasMergedParas() + && pNd->GetRedlineMergeFlag() == SwNode::Merge::Hidden) + { + continue; + } + + if( pNd->IsTextNode() ) + { + // get the node (paragraph) attributes + sw::GetAttrMerged(*pSet, *pNd->GetTextNode(), GetLayout()); + + if( pSet != &rSet && aSet.Count() ) + { + rSet.MergeValues( aSet ); + aSet.ClearItem(); + } + + pSet = &aSet; + } + + ++numberOfLookup; + + // if the maximum number of node that can be inspected has been reached + if (numberOfLookup >= getMaxLookup()) + return; + } + } +} + +SwTextFormatColl* SwEditShell::GetCurTextFormatColl( ) const +{ + return GetPaMTextFormatColl( GetCursor() ); +} + +SwTextFormatColl* SwEditShell::GetPaMTextFormatColl( SwPaM* pPaM ) const +{ + // number of nodes the function have explored so far + sal_uInt16 numberOfLookup = 0; + + for(SwPaM& rCurrentPaM : pPaM->GetRingContainer()) + { // for all the point and mark (selections) + + // get the start and the end node of the current selection + SwNodeOffset nSttNd = rCurrentPaM.Start()->GetNodeIndex(), + nEndNd = rCurrentPaM.End()->GetNodeIndex(); + + // for all the nodes in the current Point and Mark + for( SwNodeOffset n = nSttNd; n <= nEndNd; ++n ) + { + // get the node + SwNode* pNd = GetDoc()->GetNodes()[ n ]; + + ++numberOfLookup; + + // if the maximum number of node that can be inspected has been reached + if (numberOfLookup >= getMaxLookup()) + return nullptr; + + if( pNd->IsTextNode() ) + { + SwTextNode *const pTextNode(sw::GetParaPropsNode(*GetLayout(), *pNd)); + // if it's a text node get its named paragraph format + SwTextFormatColl *const pFormat = pTextNode->GetTextColl(); + + // if the paragraph format exist stop here and return it + if( pFormat != nullptr ) + return pFormat; + } + } + } + + // if none of the selected node contain a named paragraph format + return nullptr; +} + +std::vector<std::pair< const SfxPoolItem*, std::unique_ptr<SwPaM> >> SwEditShell::GetItemWithPaM( sal_uInt16 nWhich ) +{ + assert(isCHRATR(nWhich)); // sw_redlinehide: only thing that works + std::vector<std::pair< const SfxPoolItem*, std::unique_ptr<SwPaM> >> vItem; + for(SwPaM& rCurrentPaM : GetCursor()->GetRingContainer()) + { // for all the point and mark (selections) + + // get the start and the end node of the current selection + SwNodeOffset nSttNd = rCurrentPaM.Start()->GetNodeIndex(), + nEndNd = rCurrentPaM.End()->GetNodeIndex(); + sal_Int32 nSttCnt = rCurrentPaM.Start()->GetContentIndex(); + sal_Int32 nEndCnt = rCurrentPaM.End()->GetContentIndex(); + + SwPaM* pNewPaM = nullptr; + const SfxPoolItem* pItem = nullptr; + + // for all the nodes in the current selection + for( SwNodeOffset n = nSttNd; n <= nEndNd; ++n ) + { + SwNode* pNd = GetDoc()->GetNodes()[ n ]; + if( pNd->IsTextNode() ) + { + SwTextNode* pTextNd = static_cast< SwTextNode* >( pNd ); + const sal_Int32 nStt = (n == nSttNd) ? nSttCnt : 0; + const sal_Int32 nEnd = (n == nEndNd) + ? nEndCnt : pTextNd->GetText().getLength(); + SwTextFrame const* pFrame; + const SwScriptInfo *const pScriptInfo = + SwScriptInfo::GetScriptInfo(*pTextNd, &pFrame); + TextFrameIndex const iStt(pScriptInfo + ? pFrame->MapModelToView(pTextNd, nStt) + : TextFrameIndex(-1/*invalid, do not use*/)); + sal_uInt8 nScript = pScriptInfo + ? pScriptInfo->ScriptType(iStt) + : css::i18n::ScriptType::WEAK; + nWhich = GetWhichOfScript( nWhich, nScript ); + + // item from attribute set + if( pTextNd->HasSwAttrSet() ) + { + pNewPaM = new SwPaM(*pNd, nStt, *pNd, nEnd); + pItem = pTextNd->GetSwAttrSet().GetItem( nWhich ); + vItem.emplace_back( pItem, std::unique_ptr<SwPaM>(pNewPaM) ); + } + + if( !pTextNd->HasHints() ) + continue; + + // items with limited range + const size_t nSize = pTextNd->GetpSwpHints()->Count(); + for( size_t m = 0; m < nSize; m++ ) + { + const SwTextAttr* pHt = pTextNd->GetpSwpHints()->Get(m); + if( pHt->Which() == RES_TXTATR_AUTOFMT || + pHt->Which() == RES_TXTATR_CHARFMT || + pHt->Which() == RES_TXTATR_INETFMT ) + { + const sal_Int32 nAttrStart = pHt->GetStart(); + const sal_Int32* pAttrEnd = pHt->End(); + + // Ignore items not in selection + if( nAttrStart > nEnd ) + break; + if( *pAttrEnd <= nStt ) + continue; + + nScript = pScriptInfo + ? pScriptInfo->ScriptType(iStt) + : css::i18n::ScriptType::WEAK; + nWhich = GetWhichOfScript( nWhich, nScript ); + const SfxItemSet* pAutoSet = CharFormat::GetItemSet( pHt->GetAttr() ); + if( pAutoSet ) + { + SfxItemIter aItemIter( *pAutoSet ); + pItem = aItemIter.GetCurItem(); + while( pItem ) + { + if( pItem->Which() == nWhich ) + { + sal_Int32 nStart = 0, nStop = 0; + if( nAttrStart < nStt ) // Attribute starts before selection + nStart = nStt; + else + nStart = nAttrStart; + if( *pAttrEnd > nEnd ) // Attribute ends after selection + nStop = nEnd; + else + nStop = *pAttrEnd; + pNewPaM = new SwPaM(*pNd, nStart, *pNd, nStop); + vItem.emplace_back( pItem, std::unique_ptr<SwPaM>(pNewPaM) ); + break; + } + pItem = aItemIter.NextItem(); + } + // default item + if( !pItem && !pTextNd->HasSwAttrSet() ) + { + pNewPaM = new SwPaM(*pNd, nStt, *pNd, nEnd); + pItem = pAutoSet->GetPool()->GetPoolDefaultItem( nWhich ); + vItem.emplace_back( pItem, std::unique_ptr<SwPaM>(pNewPaM) ); + } + } + } + } + } + } + } + return vItem; +} + +bool SwEditShell::GetCurFootnote( SwFormatFootnote* pFillFootnote ) +{ + // The cursor must be positioned on the current footnotes anchor: + SwPaM* pCursor = GetCursor(); + SwTextNode* pTextNd = pCursor->GetPointNode().GetTextNode(); + if( !pTextNd ) + return false; + + SwTextAttr *const pFootnote = pTextNd->GetTextAttrForCharAt( + pCursor->GetPoint()->GetContentIndex(), RES_TXTATR_FTN); + if( pFootnote && pFillFootnote ) + { + // Transfer data from the attribute + const SwFormatFootnote &rFootnote = static_cast<SwTextFootnote*>(pFootnote)->GetFootnote(); + pFillFootnote->SetNumber( rFootnote ); + pFillFootnote->SetEndNote( rFootnote.IsEndNote() ); + } + return nullptr != pFootnote; +} + +bool SwEditShell::SetCurFootnote( const SwFormatFootnote& rFillFootnote ) +{ + bool bChgd = false; + StartAllAction(); + + for(const SwPaM& rCursor : GetCursor()->GetRingContainer()) + { + bChgd |= + mxDoc->SetCurFootnote(rCursor, rFillFootnote.GetNumStr(), rFillFootnote.IsEndNote()); + + } + + EndAllAction(); + return bChgd; +} + +bool SwEditShell::HasFootnotes( bool bEndNotes ) const +{ + const SwFootnoteIdxs &rIdxs = mxDoc->GetFootnoteIdxs(); + for ( auto pIdx : rIdxs ) + { + const SwFormatFootnote &rFootnote = pIdx->GetFootnote(); + if ( bEndNotes == rFootnote.IsEndNote() ) + return true; + } + return false; +} + +/// Give a List of all footnotes and their beginning texts +size_t SwEditShell::GetSeqFootnoteList( SwSeqFieldList& rList, bool bEndNotes ) +{ + rList.Clear(); + + IDocumentRedlineAccess & rIDRA(mxDoc->getIDocumentRedlineAccess()); + + const size_t nFootnoteCnt = mxDoc->GetFootnoteIdxs().size(); + SwTextFootnote* pTextFootnote; + for( size_t n = 0; n < nFootnoteCnt; ++n ) + { + pTextFootnote = mxDoc->GetFootnoteIdxs()[ n ]; + const SwFormatFootnote& rFootnote = pTextFootnote->GetFootnote(); + if ( rFootnote.IsEndNote() != bEndNotes ) + continue; + + const SwNodeIndex* pIdx = pTextFootnote->GetStartNode(); + if( pIdx ) + { + SwNodeIndex aIdx( *pIdx, 1 ); + SwTextNode* pTextNd = aIdx.GetNode().GetTextNode(); + if( !pTextNd ) + pTextNd = static_cast<SwTextNode*>(mxDoc->GetNodes().GoNext( &aIdx )); + + if( pTextNd ) + { + if (GetLayout()->IsHideRedlines() + && sw::IsFootnoteDeleted(rIDRA, *pTextFootnote)) + { + continue; + } + + OUString sText(rFootnote.GetViewNumStr(*mxDoc, GetLayout())); + if( !sText.isEmpty() ) + sText += " "; + sText += pTextNd->GetExpandText(GetLayout()); + + SeqFieldLstElem aNew( sText, pTextFootnote->GetSeqRefNo() ); + while( rList.InsertSort( aNew ) ) + aNew.sDlgEntry += " "; + } + } + } + + return rList.Count(); +} + +/// Adjust left margin via object bar (similar to adjustment of numerations). +bool SwEditShell::IsMoveLeftMargin( bool bRight, bool bModulus ) const +{ + bool bRet = true; + + constexpr sal_uInt16 constTwips_2cm = o3tl::toTwips(20, o3tl::Length::mm); + constexpr tools::Long constTwips_5mm = o3tl::toTwips(5, o3tl::Length::mm); + + const SvxTabStopItem& rTabItem = GetDoc()->GetDefault( RES_PARATR_TABSTOP ); + sal_uInt16 nDefDist = o3tl::narrowing<sal_uInt16>( + rTabItem.Count() ? rTabItem[0].GetTabPos() : constTwips_2cm); + + if( !nDefDist ) + return false; + + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + SwNodeOffset nSttNd = rPaM.Start()->GetNodeIndex(), + nEndNd = rPaM.End()->GetNodeIndex(); + + SwContentNode* pCNd; + for( SwNodeOffset n = nSttNd; bRet && n <= nEndNd; ++n ) + { + pCNd = GetDoc()->GetNodes()[ n ]->GetTextNode(); + if( nullptr != pCNd ) + { + pCNd = sw::GetParaPropsNode(*GetLayout(), *pCNd); + const SvxLRSpaceItem& rLS = pCNd->GetAttr( RES_LR_SPACE ); + if( bRight ) + { + tools::Long nNext = rLS.GetTextLeft() + nDefDist; + if( bModulus ) + nNext = ( nNext / nDefDist ) * nDefDist; + SwFrame* pFrame = pCNd->getLayoutFrame( GetLayout() ); + if ( pFrame ) + { + const SwTwips nFrameWidth = pFrame->IsVertical() ? + pFrame->getFrameArea().Height() : + pFrame->getFrameArea().Width(); + bRet = nFrameWidth > (nNext + constTwips_5mm); + } + else + bRet = false; + } + } + } + + if( !bRet ) + break; + + } + return bRet; +} + +void SwEditShell::MoveLeftMargin( bool bRight, bool bModulus ) +{ + StartAllAction(); + StartUndo( SwUndoId::START ); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor ) // Multiple selection ? + { + SwPamRanges aRangeArr( *pCursor ); + SwPaM aPam( *pCursor->GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + GetDoc()->MoveLeftMargin( aRangeArr.SetPam( n, aPam ), + bRight, bModulus, GetLayout() ); + } + else + GetDoc()->MoveLeftMargin( *pCursor, bRight, bModulus, GetLayout() ); + + EndUndo( SwUndoId::END ); + EndAllAction(); +} + +static SvtScriptType lcl_SetScriptFlags( sal_uInt16 nType ) +{ + switch( nType ) + { + case css::i18n::ScriptType::LATIN: + return SvtScriptType::LATIN; + case css::i18n::ScriptType::ASIAN: + return SvtScriptType::ASIAN; + case css::i18n::ScriptType::COMPLEX: + return SvtScriptType::COMPLEX; + default: + return SvtScriptType::NONE; + } +} + +static bool lcl_IsNoEndTextAttrAtPos(SwRootFrame const& rLayout, + const SwTextNode& rTNd, sal_Int32 const nPos, + SvtScriptType &rScrpt, bool bInSelection, bool bNum ) +{ + bool bRet = false; + OUString sExp; + + // consider numbering + if ( bNum ) + { + bRet = false; + SwTextNode const*const pPropsNode(sw::GetParaPropsNode(rLayout, rTNd)); + if (pPropsNode->IsInList()) + { + OSL_ENSURE( pPropsNode->GetNumRule(), + "<lcl_IsNoEndTextAttrAtPos(..)> - no list style found at text node. Serious defect." ); + const SwNumRule* pNumRule = pPropsNode->GetNumRule(); + if(pNumRule) + { + int nListLevel = pPropsNode->GetActualListLevel(); + + if (nListLevel < 0) + nListLevel = 0; + + if (nListLevel >= MAXLEVEL) + nListLevel = MAXLEVEL - 1; + + const SwNumFormat &rNumFormat = pNumRule->Get( o3tl::narrowing<sal_uInt16>(nListLevel) ); + if( SVX_NUM_BITMAP != rNumFormat.GetNumberingType() ) + { + if ( SVX_NUM_CHAR_SPECIAL == rNumFormat.GetNumberingType() ) + { + sal_UCS4 cBullet = rNumFormat.GetBulletChar(); + sExp = OUString(&cBullet, 1); + } + else + sExp = pPropsNode->GetNumString(true, MAXLEVEL, &rLayout); + } + } + } + } + + // and fields + if (nPos < rTNd.GetText().getLength() && CH_TXTATR_BREAKWORD == rTNd.GetText()[nPos]) + { + const SwTextAttr* const pAttr = rTNd.GetTextAttrForCharAt( nPos ); + if (pAttr) + { + bRet = true; // all other than fields can be + // defined as weak-script ? + if ( RES_TXTATR_FIELD == pAttr->Which() ) + { + const SwField* const pField = pAttr->GetFormatField().GetField(); + if (pField) + { + sExp += pField->ExpandField(true, &rLayout); + } + } + } + } + + const sal_Int32 nEnd = sExp.getLength(); + if ( nEnd ) + { + if( bInSelection ) + { + sal_uInt16 nScript; + for( sal_Int32 n = 0; n < nEnd; + n = g_pBreakIt->GetBreakIter()->endOfScript( sExp, n, nScript )) + { + nScript = g_pBreakIt->GetBreakIter()->getScriptType( sExp, n ); + rScrpt |= lcl_SetScriptFlags( nScript ); + } + } + else + rScrpt |= lcl_SetScriptFlags( g_pBreakIt->GetBreakIter()-> + getScriptType( sExp, nEnd-1 )); + } + + return bRet; +} + +/// returns the script type of the selection +SvtScriptType SwEditShell::GetScriptType() const +{ + SvtScriptType nRet = SvtScriptType::NONE; + + { + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition* + if( pStt == pEnd || *pStt == *pEnd ) + { + const SwTextNode* pTNd = pStt->GetNode().GetTextNode(); + if( pTNd ) + { + // try to get SwScriptInfo + SwTextFrame const* pFrame; + const SwScriptInfo *const pScriptInfo = + SwScriptInfo::GetScriptInfo(*pTNd, &pFrame); + + sal_Int32 nPos = pStt->GetContentIndex(); + //Task 90448: we need the scripttype of the previous + // position, if no selection exist! + if( nPos ) + { + SwContentIndex aIdx( pStt->GetContentNode(), pStt->GetContentIndex() ); + if( pTNd->GoPrevious( &aIdx, SwCursorSkipMode::Chars ) ) + nPos = aIdx.GetIndex(); + } + + sal_uInt16 nScript; + + if (!pTNd->GetText().isEmpty()) + { + nScript = pScriptInfo + ? pScriptInfo->ScriptType(pFrame->MapModelToView(pTNd, nPos)) + : g_pBreakIt->GetBreakIter()->getScriptType( pTNd->GetText(), nPos ); + } + else + nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() ); + + if (!lcl_IsNoEndTextAttrAtPos(*GetLayout(), *pTNd, nPos, nRet, false, false)) + nRet |= lcl_SetScriptFlags( nScript ); + } + } + else + { + SwNodeOffset nEndIdx = pEnd->GetNodeIndex(); + SwNodeIndex aIdx( pStt->GetNode() ); + for( ; aIdx.GetIndex() <= nEndIdx; ++aIdx ) + if( aIdx.GetNode().IsTextNode() ) + { + const SwTextNode* pTNd = aIdx.GetNode().GetTextNode(); + const OUString& rText = pTNd->GetText(); + + // try to get SwScriptInfo + SwTextFrame const* pFrame; + const SwScriptInfo *const pScriptInfo = + SwScriptInfo::GetScriptInfo(*pTNd, &pFrame); + + sal_Int32 nChg = aIdx == pStt->GetNode() + ? pStt->GetContentIndex() + : 0; + sal_Int32 nEndPos = aIdx == nEndIdx + ? pEnd->GetContentIndex() + : rText.getLength(); + + OSL_ENSURE( nEndPos <= rText.getLength(), + "Index outside the range - endless loop!" ); + if (nEndPos > rText.getLength()) + nEndPos = rText.getLength(); + + bool const isUntilEnd(pScriptInfo + ? pFrame->MapViewToModelPos(TextFrameIndex(pFrame->GetText().getLength())) <= *pEnd + : rText.getLength() == nEndPos); + sal_uInt16 nScript; + while( nChg < nEndPos ) + { + TextFrameIndex iChg(pScriptInfo + ? pFrame->MapModelToView(pTNd, nChg) + : TextFrameIndex(-1/*invalid, do not use*/)); + nScript = pScriptInfo ? + pScriptInfo->ScriptType( iChg ) : + g_pBreakIt->GetBreakIter()->getScriptType( + rText, nChg ); + + if (!lcl_IsNoEndTextAttrAtPos(*GetLayout(), *pTNd, nChg, nRet, true, + TextFrameIndex(0) == iChg && isUntilEnd)) + { + nRet |= lcl_SetScriptFlags( nScript ); + } + + if( (SvtScriptType::LATIN | SvtScriptType::ASIAN | + SvtScriptType::COMPLEX) == nRet ) + break; + + sal_Int32 nFieldPos = nChg+1; + + if (pScriptInfo) + { + iChg = pScriptInfo->NextScriptChg(iChg); + if (iChg == TextFrameIndex(COMPLETE_STRING)) + { + nChg = pTNd->Len(); + } + else + { + std::pair<SwTextNode*, sal_Int32> const tmp( + pFrame->MapViewToModel(iChg)); + nChg = (tmp.first == pTNd) + ? tmp.second + : pTNd->Len(); + } + } + else + { + nChg = g_pBreakIt->GetBreakIter()->endOfScript( + rText, nChg, nScript ); + } + + nFieldPos = rText.indexOf( + CH_TXTATR_BREAKWORD, nFieldPos); + if ((-1 != nFieldPos) && (nFieldPos < nChg)) + nChg = nFieldPos; + } + if( (SvtScriptType::LATIN | SvtScriptType::ASIAN | + SvtScriptType::COMPLEX) == nRet ) + break; + } + } + if( (SvtScriptType::LATIN | SvtScriptType::ASIAN | + SvtScriptType::COMPLEX) == nRet ) + break; + + } + } + if( nRet == SvtScriptType::NONE ) + nRet = SvtLanguageOptions::GetScriptTypeOfLanguage( LANGUAGE_SYSTEM ); + return nRet; +} + +LanguageType SwEditShell::GetCurLang() const +{ + const SwPaM* pCursor = GetCursor(); + const SwPosition& rPos = *pCursor->GetPoint(); + const SwTextNode* pTNd = rPos.GetNode().GetTextNode(); + LanguageType nLang; + if( pTNd ) + { + //JP 24.9.2001: if exist no selection, then get the language before + // the current character! + sal_Int32 nPos = rPos.GetContentIndex(); + if( nPos && !pCursor->HasMark() ) + --nPos; + nLang = pTNd->GetLang( nPos ); + } + else + nLang = LANGUAGE_DONTKNOW; + return nLang; +} + +sal_uInt16 SwEditShell::GetScalingOfSelectedText() const +{ + const SwPaM* pCursor = GetCursor(); + const SwPosition* pStt = pCursor->Start(); + const SwTextNode* pTNd = pStt->GetNode().GetTextNode(); + OSL_ENSURE( pTNd, "no textnode available" ); + + sal_uInt16 nScaleWidth; + if( pTNd ) + { + SwTextFrame *const pFrame(static_cast<SwTextFrame *>( + pTNd->getLayoutFrame(GetLayout(), pStt))); + assert(pFrame); // shell cursor must be positioned in node with frame + TextFrameIndex const nStart(pFrame->MapModelToViewPos(*pStt)); + TextFrameIndex const nEnd( + sw::FrameContainsNode(*pFrame, pCursor->End()->GetNodeIndex()) + ? pFrame->MapModelToViewPos(*pCursor->End()) + : TextFrameIndex(pFrame->GetText().getLength())); + nScaleWidth = pFrame->GetScalingOfSelectedText(nStart, nEnd); + } + else + nScaleWidth = 100; // default are no scaling -> 100% + return nScaleWidth; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/eddel.cxx b/sw/source/core/edit/eddel.cxx new file mode 100644 index 0000000000..9eb51da617 --- /dev/null +++ b/sw/source/core/edit/eddel.cxx @@ -0,0 +1,427 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentContentOperations.hxx> +#include <editsh.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <undobj.hxx> +#include <SwRewriter.hxx> +#include <osl/diagnose.h> +#include <wrtsh.hxx> +#include <officecfg/Office/Writer.hxx> + +#include <strings.hrc> +#include <vector> + +void SwEditShell::DeleteSel(SwPaM& rPam, bool const isArtificialSelection, bool *const pUndo) +{ + auto const oSelectAll(StartsWith_() != SwCursorShell::StartsWith::None + ? ExtendedSelectedAll() + : ::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode *>>>{}); + // only for selections + if (!rPam.HasMark() + || (*rPam.GetPoint() == *rPam.GetMark() + && !IsFlySelectedByCursor(*GetDoc(), *rPam.Start(), *rPam.End()))) + { + return; + } + + // Is the selection in a table? Then delete only the content of the selected boxes. + // Here, there are two cases: + // 1. Point and Mark are in one box, delete selection as usual + // 2. Point and Mark are in different boxes, search all selected boxes and delete content + // 3. Point and Mark are at the document start and end, Point is in a table: delete selection as usual + if( rPam.GetPointNode().FindTableNode() && + rPam.GetPointNode().StartOfSectionNode() != + rPam.GetMarkNode().StartOfSectionNode() && !oSelectAll) + { + // group the Undo in the table + if( pUndo && !*pUndo ) + { + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + *pUndo = true; + } + SwPaM aDelPam( *rPam.Start() ); + const SwPosition* pEndSelPos = rPam.End(); + do { + aDelPam.SetMark(); + SwNode& rNd = aDelPam.GetPointNode(); + const SwNode& rEndNd = *rNd.EndOfSectionNode(); + if( pEndSelPos->GetNodeIndex() <= rEndNd.GetIndex() ) + { + *aDelPam.GetPoint() = *pEndSelPos; + pEndSelPos = nullptr; // misuse a pointer as a flag + } + else + { + // then go to the end of the selection + aDelPam.GetPoint()->Assign(rEndNd); + aDelPam.Move( fnMoveBackward, GoInContent ); + } + // skip protected boxes + if( !rNd.IsContentNode() || + !rNd.IsInProtectSect() ) + { + // delete everything + GetDoc()->getIDocumentContentOperations().DeleteAndJoin( aDelPam ); + SaveTableBoxContent( aDelPam.GetPoint() ); + } + + if( !pEndSelPos ) // at the end of a selection + break; + aDelPam.DeleteMark(); + aDelPam.Move( fnMoveForward, GoInContent ); // next box + } while( pEndSelPos ); + } + else + { + std::optional<SwPaM> pNewPam; + SwPaM * pPam = &rPam; + if (oSelectAll) + { + if (!oSelectAll->second.empty()) + { + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, SwResId(STR_MULTISEL)); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::DELETE, &aRewriter); + } + // tdf#155685 tables at the end must be deleted separately + for (SwTableNode *const pTable : oSelectAll->second) + { + GetDoc()->DelTable(pTable); + } + assert(dynamic_cast<SwShellCursor*>(&rPam)); // must be corrected pam + pNewPam.emplace(*rPam.GetMark(), *rPam.GetPoint()); + // Selection starts at the first para of the first cell, but we + // want to delete the table node before the first cell as well. + pNewPam->Start()->Assign(*oSelectAll->first); + pPam = &*pNewPam; + } + // delete everything + GetDoc()->getIDocumentContentOperations().DeleteAndJoin(*pPam, + isArtificialSelection ? SwDeleteFlags::ArtificialSelection : SwDeleteFlags::Default); + SaveTableBoxContent( pPam->GetPoint() ); + if (oSelectAll && !oSelectAll->second.empty()) + { + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); + } + } + + // Selection is not needed anymore + rPam.DeleteMark(); +} + +bool SwEditShell::Delete(bool const isArtificialSelection) +{ + CurrShell aCurr( this ); + bool bRet = false; + if ( !HasReadonlySel() || CursorInsideInputField() ) + { + if (HasHiddenSections() && + officecfg::Office::Writer::Content::Display::ShowWarningHiddenSection::get()) + { + if (!WarnHiddenSectionDialog()) + { + bRet = RemoveParagraphMetadataFieldAtCursor(); + return bRet; + } + } + + StartAllAction(); + bool bUndo = GetCursor()->GetNext() != GetCursor(); + if( bUndo ) // more than one selection? + { + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, SwResId(STR_MULTISEL)); + + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::DELETE, &aRewriter); + } + + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + DeleteSel(rPaM, isArtificialSelection, &bUndo); + } + + // If undo container then close here + if( bUndo ) + { + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); + } + EndAllAction(); + bRet = true; + } + else + { + bRet = RemoveParagraphMetadataFieldAtCursor(); + if (!bRet) + { + InfoReadOnlyDialog(false); + } + } + + return bRet; +} + +bool SwEditShell::Copy( SwEditShell& rDestShell ) +{ + CurrShell aCurr( &rDestShell ); + + // List of insert positions for smart insert of block selections + std::vector< std::shared_ptr<SwPosition> > aInsertList; + + // Fill list of insert positions + { + SwPosition * pPos = nullptr; + std::shared_ptr<SwPosition> pInsertPos; + sal_uInt16 nMove = 0; + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( !pPos ) + { + if( &rDestShell == this ) + { + // First cursor represents the target position!! + rPaM.DeleteMark(); + pPos = rPaM.GetPoint(); + continue; + } + else + pPos = rDestShell.GetCursor()->GetPoint(); + } + if( IsBlockMode() ) + { // In block mode different insert positions will be calculated + // by simulated cursor movements from the given first insert position + if( nMove ) + { + SwCursor aCursor( *pPos, nullptr); + if (aCursor.UpDown(false, nMove, nullptr, 0, *GetLayout())) + { + pInsertPos = std::make_shared<SwPosition>( *aCursor.GetPoint() ); + aInsertList.push_back( pInsertPos ); + } + } + else + pInsertPos = std::make_shared<SwPosition>( *pPos ); + ++nMove; + } + SwPosition *pTmp = IsBlockMode() ? pInsertPos.get() : pPos; + // Check if a selection would be copied into itself + if( rDestShell.GetDoc() == GetDoc() && + *rPaM.Start() <= *pTmp && *pTmp < *rPaM.End() ) + return false; + } + } + + rDestShell.StartAllAction(); + SwPosition *pPos = nullptr; + bool bRet = false; + bool bFirstMove = true; + SwNodeIndex aSttNdIdx( rDestShell.GetDoc()->GetNodes() ); + sal_Int32 nSttCntIdx = 0; + // For block selection this list is filled with the insert positions + auto pNextInsert = aInsertList.begin(); + + rDestShell.GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( !pPos ) + { + if( &rDestShell == this ) + { + // First cursor represents the target position!! + rPaM.DeleteMark(); + pPos = rPaM.GetPoint(); + continue; + } + else + pPos = rDestShell.GetCursor()->GetPoint(); + } + if( !bFirstMove ) + { + if( pNextInsert != aInsertList.end() ) + { + pPos = pNextInsert->get(); + ++pNextInsert; + } + else if( IsBlockMode() ) + GetDoc()->getIDocumentContentOperations().SplitNode( *pPos, false ); + } + + // Only for a selection (non-text nodes have selection but Point/GetMark are equal) + if( !rPaM.HasMark() || *rPaM.GetPoint() == *rPaM.GetMark() ) + continue; + + if( bFirstMove ) + { + // Store start position of the new area + aSttNdIdx = pPos->GetNodeIndex()-1; + nSttCntIdx = pPos->GetContentIndex(); + bFirstMove = false; + } + + const bool bSuccess( GetDoc()->getIDocumentContentOperations().CopyRange(rPaM, *pPos, SwCopyFlags::CheckPosInFly) ); + if (!bSuccess) + continue; + + SwPaM aInsertPaM(*pPos, SwPosition(aSttNdIdx)); + rDestShell.GetDoc()->MakeUniqueNumRules(aInsertPaM); + + bRet = true; + } + + // Maybe nothing has been moved? + if( !bFirstMove ) + { + SwPaM* pCursor = rDestShell.GetCursor(); + pCursor->SetMark(); + pCursor->GetPoint()->Assign( aSttNdIdx.GetIndex()+1, nSttCntIdx ); + pCursor->Exchange(); + } + else + { + // If the cursor moved during move process, move also its GetMark + rDestShell.GetCursor()->SetMark(); + rDestShell.GetCursor()->DeleteMark(); + } +#if OSL_DEBUG_LEVEL > 0 + // check if the indices are registered in the correct nodes + { + for(SwPaM& rCmp : rDestShell.GetCursor()->GetRingContainer()) + { + OSL_ENSURE( rCmp.GetPoint()->GetContentNode() + == rCmp.GetPointContentNode(), "Point in wrong Node" ); + OSL_ENSURE( rCmp.GetMark()->GetContentNode() + == rCmp.GetMarkContentNode(), "Mark in wrong Node" ); + } + } +#endif + + // close Undo container here + rDestShell.GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + rDestShell.EndAllAction(); + + rDestShell.SaveTableBoxContent( rDestShell.GetCursor()->GetPoint() ); + + return bRet; +} + +/** Replace a selected area in a text node with a given string. + * + * Intended for "search & replace". + * + * @param bRegExpRplc if <true> replace tabs (\\t) and replace with found string (not \&). + * E.g. [Fnd: "zzz", Repl: "xx\t\\t..&..\&"] --> "xx\t<Tab>..zzz..&" + */ +bool SwEditShell::Replace( const OUString& rNewStr, bool bRegExpRplc ) +{ + CurrShell aCurr( this ); + + bool bRet = false; + if (!HasReadonlySel(true)) + { + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( rPaM.HasMark() && *rPaM.GetMark() != *rPaM.GetPoint() ) + { + bRet = sw::ReplaceImpl(rPaM, rNewStr, bRegExpRplc, *GetDoc(), GetLayout()) + || bRet; + SaveTableBoxContent( rPaM.GetPoint() ); + } + } + + // close Undo container here + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + EndAllAction(); + } + return bRet; +} + +/** Replace a selected area in a text node with a given string. + * + * Method to replace a text selection with a new string while + * keeping possible comments (they will be moved to the end + * of the selection). + * + * @param rNewStr the new string which the selected area is to be replaced with + * @return true, if something has been replaced, false otherwise. + */ +bool SwEditShell::ReplaceKeepComments( const OUString& rNewStr) +{ + bool bRet = false; + SwPaM *pCursor = GetCursor(); + + if(pCursor != nullptr) + { + // go sure that the text selection pointed to by pCursor is valid + if(pCursor->HasMark()) + { + // preserve comments inside of the number by deleting number portions starting from the back + OUString aSelectedText = pCursor->GetText(); + sal_Int32 nCommentPos(aSelectedText.lastIndexOf(CH_TXTATR_INWORD)); + // go sure that we have a valid selection and a comment has been found + while (nCommentPos > -1) + { + // select the part of the text after the last found comment + // selection start: + pCursor->GetPoint()->AdjustContent(nCommentPos + 1); + // selection end is left where it is -> will be adjusted later on + // delete the part of the word after the last found comment + Replace(OUString(), false); + // put the selection start back to the beginning of the word + pCursor->GetPoint()->AdjustContent(-(nCommentPos + 1)); + // adjust the selection end, so that the last comment is no longer selected: + pCursor->GetMark()->AdjustContent(-1); + // search for the next possible comment + aSelectedText = pCursor->GetText(); + nCommentPos = aSelectedText.lastIndexOf(CH_TXTATR_INWORD); + } + bRet = Replace(rNewStr, false); + } + } + + return bRet; +} + +/// special method for JOE's wizards +bool SwEditShell::DelFullPara() +{ + bool bRet = false; + if( !IsTableMode() ) + { + SwPaM* pCursor = GetCursor(); + // no multi selection + if( !pCursor->IsMultiSelection() && !HasReadonlySel() ) + { + CurrShell aCurr( this ); + StartAllAction(); + bRet = GetDoc()->getIDocumentContentOperations().DelFullPara( *pCursor ); + EndAllAction(); + } + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edfcol.cxx b/sw/source/core/edit/edfcol.cxx new file mode 100644 index 0000000000..f02273e073 --- /dev/null +++ b/sw/source/core/edit/edfcol.cxx @@ -0,0 +1,2326 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <string_view> + +#include <editsh.hxx> + +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/container/XContentEnumerationAccess.hpp> +#include <com/sun/star/document/XActionLockable.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/HomogenMatrix3.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/text/RelOrientation.hpp> +#include <com/sun/star/text/TextContentAnchorType.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/WrapTextMode.hpp> +#include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/text/XTextDocument.hpp> +#include <com/sun/star/text/XTextField.hpp> +#include <com/sun/star/text/XTextRange.hpp> +#include <com/sun/star/text/XParagraphAppend.hpp> +#include <com/sun/star/text/XParagraphCursor.hpp> +#include <com/sun/star/awt/FontWeight.hpp> +#include <com/sun/star/rdf/XMetadatable.hpp> +#include <com/sun/star/security/DocumentDigitalSignatures.hpp> +#include <com/sun/star/security/XCertificate.hpp> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <comphelper/scopeguard.hxx> +#include <comphelper/string.hxx> +#include <editeng/unoprnms.hxx> +#include <sfx2/classificationhelper.hxx> +#include <svx/ClassificationCommon.hxx> +#include <svx/ClassificationField.hxx> +#include <svl/cryptosign.hxx> +#include <svl/sigstruct.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <vcl/virdev.hxx> + +#include <redline.hxx> +#include <poolfmt.hxx> +#include <hintids.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <ndtxt.hxx> +#include <paratr.hxx> +#include <viewopt.hxx> +#include <SwRewriter.hxx> +#include <numrule.hxx> +#include <swundo.hxx> +#include <docary.hxx> +#include <docsh.hxx> +#include <unoprnms.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <txtfrm.hxx> +#include <rdfhelper.hxx> +#include <sfx2/watermarkitem.hxx> + +#include <unoparagraph.hxx> +#include <strings.hrc> +#include <undobj.hxx> +#include <UndoParagraphSignature.hxx> +#include <txtatr.hxx> +#include <fmtmeta.hxx> +#include <unotxdoc.hxx> +#include <unotextbodyhf.hxx> +#include <unoport.hxx> + +#include <comphelper/diagnose_ex.hxx> +#include <IDocumentRedlineAccess.hxx> + +constexpr OUString WATERMARK_NAME = u"PowerPlusWaterMarkObject"_ustr; +#define WATERMARK_AUTO_SIZE sal_uInt32(1) + +namespace +{ +constexpr OUString MetaFilename(u"tscp/bails.rdf"_ustr); +constexpr OUString MetaNS(u"urn:bails"_ustr); +constexpr OUString ParagraphSignatureRDFNamespace = u"urn:bails:loext:paragraph:signature:"_ustr; +constexpr OUString ParagraphSignatureIdRDFName = u"urn:bails:loext:paragraph:signature:id"_ustr; +constexpr OUString ParagraphSignatureDigestRDFName = u":digest"_ustr; +constexpr OUString ParagraphSignatureDateRDFName = u":date"_ustr; +constexpr OUString ParagraphSignatureUsageRDFName = u":usage"_ustr; +constexpr OUString ParagraphSignatureLastIdRDFName = u"urn:bails:loext:paragraph:signature:lastid"_ustr; +constexpr OUString ParagraphClassificationNameRDFName = u"urn:bails:loext:paragraph:classification:name"_ustr; +constexpr OUString ParagraphClassificationValueRDFName = u"urn:bails:loext:paragraph:classification:value"_ustr; +constexpr OUString ParagraphClassificationAbbrRDFName = u"urn:bails:loext:paragraph:classification:abbreviation"_ustr; +constexpr OUString ParagraphClassificationFieldNamesRDFName = u"urn:bails:loext:paragraph:classification:fields"_ustr; +constexpr OUString MetadataFieldServiceName = u"com.sun.star.text.textfield.MetadataField"_ustr; +constexpr OUString DocInfoServiceName = u"com.sun.star.text.TextField.DocInfo.Custom"_ustr; + +/// Find all page styles which are currently used in the document. +std::vector<OUString> lcl_getUsedPageStyles(SwViewShell const * pShell) +{ + std::vector<OUString> aReturn; + + SwRootFrame* pLayout = pShell->GetLayout(); + for (SwFrame* pFrame = pLayout->GetLower(); pFrame; pFrame = pFrame->GetNext()) + { + SwPageFrame* pPage = static_cast<SwPageFrame*>(pFrame); + if (const SwPageDesc *pDesc = pPage->FindPageDesc()) + aReturn.push_back(pDesc->GetName()); + } + + return aReturn; +} + +/// Search for a field named rFieldName of type rServiceName in xText and return it. +uno::Reference<text::XTextField> lcl_findField(const uno::Reference<text::XText>& xText, const OUString& rServiceName, std::u16string_view rFieldName) +{ + uno::Reference<text::XTextField> xField; + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xText, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration(); + while (xParagraphs->hasMoreElements()) + { + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != UNO_NAME_TEXT_FIELD) + continue; + + uno::Reference<lang::XServiceInfo> xTextField; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField; + if (!xTextField->supportsService(rServiceName)) + continue; + + OUString aName; + uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY); + xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName; + if (aName == rFieldName) + { + xField = uno::Reference<text::XTextField>(xTextField, uno::UNO_QUERY); + break; + } + } + } + + return xField; +} + +/// Search for a field named rFieldName of type rServiceName in xText and return true iff found. +bool lcl_hasField(const uno::Reference<text::XText>& xText, const OUString& rServiceName, std::u16string_view rFieldName) +{ + return lcl_findField(xText, rServiceName, rFieldName).is(); +} + +/// Search for a frame with WATERMARK_NAME in name of type rServiceName in xText. Returns found name in rShapeName. +uno::Reference<drawing::XShape> lcl_getWatermark(const uno::Reference<text::XText>& xText, + const OUString& rServiceName, OUString& rShapeName, bool& bSuccess) +{ + bSuccess = false; + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xText, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration(); + while (xParagraphs->hasMoreElements()) + { + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY); + if (!xTextPortionEnumerationAccess.is()) + continue; + + bSuccess = true; + + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != "Frame") + continue; + + uno::Reference<container::XContentEnumerationAccess> xContentEnumerationAccess(xTextPortion, uno::UNO_QUERY); + if (!xContentEnumerationAccess.is()) + continue; + + uno::Reference<container::XEnumeration> xEnumeration = xContentEnumerationAccess->createContentEnumeration("com.sun.star.text.TextContent"); + if (!xEnumeration->hasMoreElements()) + continue; + + uno::Reference<lang::XServiceInfo> xWatermark(xEnumeration->nextElement(), uno::UNO_QUERY); + if (!xWatermark->supportsService(rServiceName)) + continue; + + uno::Reference<container::XNamed> xNamed(xWatermark, uno::UNO_QUERY); + + if (!xNamed->getName().match(WATERMARK_NAME)) + continue; + + rShapeName = xNamed->getName(); + + uno::Reference<drawing::XShape> xShape(xWatermark, uno::UNO_QUERY); + return xShape; + } + } + + return uno::Reference<drawing::XShape>(); +} + +/// Extract the text of the paragraph without any of the fields. +/// TODO: Consider moving to SwTextNode, or extend ModelToViewHelper. +OString lcl_getParagraphBodyText(const uno::Reference<text::XTextContent>& xText) +{ + OUStringBuffer strBuf; + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xText, uno::UNO_QUERY); + if (!xTextPortionEnumerationAccess.is()) + return OString(); + + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + while (xTextPortions->hasMoreElements()) + { + uno::Any elem = xTextPortions->nextElement(); + + //TODO: Consider including hidden and conditional texts/portions. + OUString aTextPortionType; + uno::Reference<beans::XPropertySet> xPropertySet(elem, uno::UNO_QUERY); + xPropertySet->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType == "Text") + { + uno::Reference<text::XTextRange> xTextRange(elem, uno::UNO_QUERY); + if (xTextRange.is()) + strBuf.append(xTextRange->getString()); + } + } + + // Cleanup the dummy characters added by fields (which we exclude). + comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDSTART); + comphelper::string::remove(strBuf, CH_TXT_ATR_INPUTFIELDEND); + comphelper::string::remove(strBuf, CH_TXTATR_BREAKWORD); + + return strBuf.makeStringAndClear().trim().toUtf8(); +} + +template <typename T> +std::map<OUString, OUString> lcl_getRDFStatements(const uno::Reference<frame::XModel>& xModel, + const T& xRef) +{ + try + { + const css::uno::Reference<css::rdf::XResource> xSubject(xRef, uno::UNO_QUERY); + return SwRDFHelper::getStatements(xModel, MetaNS, xSubject); + } + catch (const ::css::uno::Exception&) + { + } + + return std::map<OUString, OUString>(); +} + +/// Returns RDF (key, value) pair associated with the field, if any. +std::pair<OUString, OUString> lcl_getFieldRDFByPrefix(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextField>& xField, + std::u16string_view sPrefix) +{ + for (const auto& pair : lcl_getRDFStatements(xModel, xField)) + { + if (pair.first.startsWith(sPrefix)) + return pair; + } + + return std::make_pair(OUString(), OUString()); +} + +/// Returns RDF (key, value) pair associated with the field, if any. +template <typename T> +std::pair<OUString, OUString> lcl_getRDF(const uno::Reference<frame::XModel>& xModel, + const T& xRef, + const OUString& sRDFName) +{ + const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xRef); + const auto it = aStatements.find(sRDFName); + return (it != aStatements.end()) ? std::make_pair(it->first, it->second) : std::make_pair(OUString(), OUString()); +} + +/// Returns true iff the field in question is paragraph signature. +/// Note: must have associated RDF, since signatures are otherwise just metadata fields. +bool lcl_IsParagraphSignatureField(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextField>& xField) +{ + return (lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName).first == ParagraphSignatureIdRDFName); +} + +uno::Reference<text::XTextField> lcl_findFieldByRDF(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<text::XTextContent>& xParagraph, + const OUString& sRDFName, + std::u16string_view sRDFValue) +{ + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY); + if (!xTextPortionEnumerationAccess.is()) + return uno::Reference<text::XTextField>(); + + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + if (!xTextPortions.is()) + return uno::Reference<text::XTextField>(); + + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != UNO_NAME_TEXT_FIELD) + continue; + + uno::Reference<lang::XServiceInfo> xTextField; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField; + if (!xTextField->supportsService(MetadataFieldServiceName)) + continue; + + uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY); + const std::pair<OUString, OUString> pair = lcl_getRDF(xModel, xField, sRDFName); + if (pair.first == sRDFName && (sRDFValue.empty() || sRDFValue == pair.second)) + return xField; + } + + return uno::Reference<text::XTextField>(); +} + +struct SignatureDescr +{ + OUString msSignature; + OUString msUsage; + OUString msDate; + + bool isValid() const { return !msSignature.isEmpty(); } +}; + +SignatureDescr lcl_getSignatureDescr(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextContent>& xParagraph, + std::u16string_view sFieldId) +{ + SignatureDescr aDescr; + + const OUString prefix = ParagraphSignatureRDFNamespace + sFieldId; + const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xParagraph); + + const auto itSig = aStatements.find(prefix + ParagraphSignatureDigestRDFName); + aDescr.msSignature = (itSig != aStatements.end() ? itSig->second : OUString()); + + const auto itDate = aStatements.find(prefix + ParagraphSignatureDateRDFName); + aDescr.msDate = (itDate != aStatements.end() ? itDate->second : OUString()); + + const auto itUsage = aStatements.find(prefix + ParagraphSignatureUsageRDFName); + aDescr.msUsage = (itUsage != aStatements.end() ? itUsage->second : OUString()); + + return aDescr; +} + +SignatureDescr lcl_getSignatureDescr(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextContent>& xParagraph, + const uno::Reference<css::text::XTextField>& xField) +{ + const OUString sFieldId = lcl_getRDF(xModel, xField, ParagraphSignatureIdRDFName).second; + if (!sFieldId.isEmpty()) + return lcl_getSignatureDescr(xModel, xParagraph, sFieldId); + + return SignatureDescr(); +} + +/// Validate and create the signature field display text from the fields. +std::pair<bool, OUString> lcl_MakeParagraphSignatureFieldText(const SignatureDescr& aDescr, + const OString& utf8Text) +{ + OUString msg = SwResId(STR_INVALID_SIGNATURE); + bool valid = false; + + if (aDescr.isValid()) + { + const char* pData = utf8Text.getStr(); + const std::vector<unsigned char> data(pData, pData + utf8Text.getLength()); + + OString encSignature; + if (aDescr.msSignature.convertToString(&encSignature, RTL_TEXTENCODING_UTF8, 0)) + { + const std::vector<unsigned char> sig(svl::crypto::DecodeHexString(encSignature)); + SignatureInformation aInfo(0); + valid = svl::crypto::Signing::Verify(data, false, sig, aInfo); + valid = valid + && aInfo.nStatus == xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED; + + assert(aInfo.GetSigningCertificate()); // it was valid + msg = SwResId(STR_SIGNED_BY) + ": " + aInfo.GetSigningCertificate()->X509Subject + ", " + + aDescr.msDate; + msg += (!aDescr.msUsage.isEmpty() ? (" (" + aDescr.msUsage + "): ") : OUString(": ")); + msg += (valid ? SwResId(STR_VALID) : SwResId(STR_INVALID)); + } + } + + return std::make_pair(valid, msg); +} + +/// Validate and return validation result and signature field display text. +std::pair<bool, OUString> +lcl_MakeParagraphSignatureFieldText(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextContent>& xParagraph, + const uno::Reference<css::text::XTextField>& xField, + const OString& utf8Text) +{ + const SignatureDescr aDescr = lcl_getSignatureDescr(xModel, xParagraph, xField); + return lcl_MakeParagraphSignatureFieldText(aDescr, utf8Text); +} + +/// Generate the next valid ID for the new signature on this paragraph. +OUString lcl_getNextSignatureId(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<text::XTextContent>& xParagraph) +{ + const OUString sFieldId = lcl_getRDF(xModel, xParagraph, ParagraphSignatureLastIdRDFName).second; + return OUString::number(!sFieldId.isEmpty() ? sFieldId.toInt32() + 1 : 1); +} + +/// Creates and inserts Paragraph Signature Metadata field and creates the RDF entry +uno::Reference<text::XTextField> lcl_InsertParagraphSignature(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<text::XTextContent>& xParagraph, + const OUString& signature, + const OUString& usage) +{ + uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY); + auto xField = uno::Reference<text::XTextField>(xMultiServiceFactory->createInstance(MetadataFieldServiceName), uno::UNO_QUERY); + + // Add the signature at the end. + xField->attach(xParagraph->getAnchor()->getEnd()); + + const OUString sId = lcl_getNextSignatureId(xModel, xParagraph); + + const css::uno::Reference<css::rdf::XResource> xSubject(xField, uno::UNO_QUERY); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xSubject, ParagraphSignatureIdRDFName, sId); + + // First convert the UTC UNIX timestamp to a tools::DateTime then to local time. + DateTime aDateTime = DateTime::CreateFromUnixTime(time(nullptr)); + aDateTime.ConvertToLocalTime(); + OUStringBuffer rBuffer; + rBuffer.append(static_cast<sal_Int32>(aDateTime.GetYear())); + rBuffer.append('-'); + if (aDateTime.GetMonth() < 10) + rBuffer.append('0'); + rBuffer.append(static_cast<sal_Int32>(aDateTime.GetMonth())); + rBuffer.append('-'); + if (aDateTime.GetDay() < 10) + rBuffer.append('0'); + rBuffer.append(static_cast<sal_Int32>(aDateTime.GetDay())); + + // Now set the RDF on the paragraph, since that's what is preserved in .doc(x). + const css::uno::Reference<css::rdf::XResource> xParaSubject(xParagraph, uno::UNO_QUERY); + const OUString prefix = ParagraphSignatureRDFNamespace + sId; + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, ParagraphSignatureLastIdRDFName, sId); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureDigestRDFName, signature); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureUsageRDFName, usage); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xParaSubject, prefix + ParagraphSignatureDateRDFName, rBuffer.makeStringAndClear()); + + return xField; +} + +/// Updates the signature field text if changed and returns true only iff updated. +bool lcl_DoUpdateParagraphSignatureField(SwDoc& rDoc, + const uno::Reference<css::text::XTextField>& xField, + const OUString& sDisplayText) +{ + // Disable undo to avoid introducing noise when we edit the metadata field. + const bool isUndoEnabled = rDoc.GetIDocumentUndoRedo().DoesUndo(); + rDoc.GetIDocumentUndoRedo().DoUndo(false); + comphelper::ScopeGuard const g([&rDoc, isUndoEnabled]() { + rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled); + }); + + try + { + uno::Reference<css::text::XTextRange> xText(xField, uno::UNO_QUERY); + const OUString curText = xText->getString(); + if (curText != sDisplayText) + { + xText->setString(sDisplayText); + return true; + } + } + catch (const uno::Exception&) + { + // We failed; avoid crashing. + DBG_UNHANDLED_EXCEPTION("sw.uno", "Failed to update paragraph signature"); + } + + return false; +} + +/// Updates the signature field text if changed and returns true only iff updated. +bool lcl_UpdateParagraphSignatureField(SwDoc& rDoc, + const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextContent>& xParagraph, + const uno::Reference<css::text::XTextField>& xField, + const OString& utf8Text) +{ + const OUString sDisplayText + = lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).second; + return lcl_DoUpdateParagraphSignatureField(rDoc, xField, sDisplayText); +} + +void lcl_RemoveParagraphMetadataField(const uno::Reference<css::text::XTextField>& xField) +{ + uno::Reference<css::text::XTextRange> xParagraph(xField->getAnchor()); + xParagraph->getText()->removeTextContent(xField); +} + +/// Returns true iff the field in question is paragraph classification. +/// Note: must have associated RDF, since classifications are otherwise just metadata fields. +bool lcl_IsParagraphClassificationField(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextField>& xField, + std::u16string_view sKey) +{ + const std::pair<OUString, OUString> rdfPair = lcl_getRDF(xModel, xField, ParagraphClassificationNameRDFName); + return rdfPair.first == ParagraphClassificationNameRDFName && (sKey.empty() || rdfPair.second == sKey); +} + +uno::Reference<text::XTextField> lcl_FindParagraphClassificationField(const uno::Reference<frame::XModel>& xModel, + const rtl::Reference<SwXParagraph>& xParagraph, + std::u16string_view sKey = u"") +{ + uno::Reference<text::XTextField> xTextField; + + if (!xParagraph.is()) + return xTextField; + + // Enumerate text portions to find metadata fields. This is expensive, best to enumerate fields only. + rtl::Reference<SwXTextPortionEnumeration> xTextPortions = xParagraph->createTextFieldsEnumeration(); + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != UNO_NAME_TEXT_FIELD) + continue; + + uno::Reference<lang::XServiceInfo> xServiceInfo; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xServiceInfo; + if (!xServiceInfo->supportsService(MetadataFieldServiceName)) + continue; + + uno::Reference<text::XTextField> xField(xServiceInfo, uno::UNO_QUERY); + if (lcl_IsParagraphClassificationField(xModel, xField, sKey)) + { + xTextField = xField; + break; + } + } + + return xTextField; +} + +/// Creates and inserts Paragraph Classification Metadata field and creates the RDF entry +uno::Reference<text::XTextField> lcl_InsertParagraphClassification(const uno::Reference<frame::XModel>& xModel, + const uno::Reference<text::XTextContent>& xParent) +{ + uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY); + auto xField = uno::Reference<text::XTextField>(xMultiServiceFactory->createInstance(MetadataFieldServiceName), uno::UNO_QUERY); + + // Add the classification at the start. + xField->attach(xParent->getAnchor()->getStart()); + return xField; +} + +/// Updates the paragraph classification field text if changed and returns true only iff updated. +bool lcl_UpdateParagraphClassificationField(SwDoc* pDoc, + const uno::Reference<frame::XModel>& xModel, + const uno::Reference<css::text::XTextContent>& xTextNode, + const OUString& sKey, + const OUString& sValue, + const OUString& sDisplayText) +{ + // Disable undo to avoid introducing noise when we edit the metadata field. + const bool isUndoEnabled = pDoc->GetIDocumentUndoRedo().DoesUndo(); + pDoc->GetIDocumentUndoRedo().DoUndo(false); + comphelper::ScopeGuard const g([pDoc, isUndoEnabled] () { + pDoc->GetIDocumentUndoRedo().DoUndo(isUndoEnabled); + }); + + uno::Reference<text::XTextField> xField = lcl_InsertParagraphClassification(xModel, xTextNode); + + css::uno::Reference<css::rdf::XResource> xFieldSubject(xField, uno::UNO_QUERY); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, sKey, sValue); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphClassificationNameRDFName, sKey); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphClassificationValueRDFName, sValue); + + css::uno::Reference<css::rdf::XResource> xNodeSubject(xTextNode, uno::UNO_QUERY); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, sKey, sValue); + + return lcl_DoUpdateParagraphSignatureField(*pDoc, xField, sDisplayText); +} + +void lcl_ValidateParagraphSignatures(SwDoc& rDoc, const uno::Reference<text::XTextContent>& xParagraph, const bool updateDontRemove) +{ + SwDocShell* pDocShell = rDoc.GetDocShell(); + if (!pDocShell) + return; + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + + // Check if the paragraph is signed. + try + { + const std::pair<OUString, OUString> pair = lcl_getRDF(xModel, xParagraph, ParagraphSignatureLastIdRDFName); + if (pair.second.isEmpty()) + return; + } + catch (const ::css::uno::Exception&) + { + return; + } + + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY); + if (!xTextPortionEnumerationAccess.is()) + return; + + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + if (!xTextPortions.is()) + return; + + // Get the text (without fields). + const OString utf8Text = lcl_getParagraphBodyText(xParagraph); + if (utf8Text.isEmpty()) + return; + + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != UNO_NAME_TEXT_FIELD) + continue; + + uno::Reference<lang::XServiceInfo> xTextField; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField; + if (!xTextField->supportsService(MetadataFieldServiceName)) + continue; + + uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY); + if (!lcl_IsParagraphSignatureField(xModel, xField)) + { + continue; + } + + if (updateDontRemove) + { + lcl_UpdateParagraphSignatureField(rDoc, xModel, xParagraph, xField, utf8Text); + } + else if (!lcl_MakeParagraphSignatureFieldText(xModel, xParagraph, xField, utf8Text).first) + { + rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr); + rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoParagraphSigning>(rDoc, xField, xParagraph, false)); + lcl_RemoveParagraphMetadataField(xField); + rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::PARA_SIGN_ADD, nullptr); + } + } +} + +} // anonymous namespace + +SwTextFormatColl& SwEditShell::GetDfltTextFormatColl() const +{ + return *GetDoc()->GetDfltTextFormatColl(); +} + +sal_uInt16 SwEditShell::GetTextFormatCollCount() const +{ + return GetDoc()->GetTextFormatColls()->size(); +} + +SwTextFormatColl& SwEditShell::GetTextFormatColl(sal_uInt16 nFormatColl) const +{ + return *((*(GetDoc()->GetTextFormatColls()))[nFormatColl]); +} + +static void insertFieldToDocument(uno::Reference<lang::XMultiServiceFactory> const & rxMultiServiceFactory, + uno::Reference<text::XText> const & rxText, uno::Reference<text::XParagraphCursor> const & rxParagraphCursor, + OUString const & rsKey) +{ + uno::Reference<beans::XPropertySet> xField(rxMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY); + xField->setPropertyValue(UNO_NAME_NAME, uno::Any(rsKey)); + uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY); + + rxText->insertTextContent(rxParagraphCursor, xTextContent, false); +} + +static void removeAllClassificationFields(std::u16string_view rPolicy, uno::Reference<text::XText> const & rxText) +{ + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(rxText, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration(); + while (xParagraphs->hasMoreElements()) + { + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != UNO_NAME_TEXT_FIELD) + continue; + + uno::Reference<lang::XServiceInfo> xTextField; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField; + if (!xTextField->supportsService(DocInfoServiceName)) + continue; + + OUString aName; + uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY); + xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName; + if (aName.startsWith(rPolicy)) + { + uno::Reference<text::XTextField> xField(xTextField, uno::UNO_QUERY); + rxText->removeTextContent(xField); + } + } + } +} + +static sal_Int32 getNumberOfParagraphs(uno::Reference<text::XText> const & xText) +{ + uno::Reference<container::XEnumerationAccess> xParagraphEnumAccess(xText, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParagraphEnum = xParagraphEnumAccess->createEnumeration(); + sal_Int32 nResult = 0; + while (xParagraphEnum->hasMoreElements()) + { + xParagraphEnum->nextElement(); + nResult++; + } + return nResult; +} + +static void equaliseNumberOfParagraph(std::vector<svx::ClassificationResult> const & rResults, uno::Reference<text::XText> const & xText) +{ + sal_Int32 nNumberOfParagraphs = 0; + for (svx::ClassificationResult const & rResult : rResults) + { + if (rResult.meType == svx::ClassificationType::PARAGRAPH) + nNumberOfParagraphs++; + } + + while (getNumberOfParagraphs(xText) < nNumberOfParagraphs) + { + uno::Reference<text::XParagraphAppend> xParagraphAppend(xText, uno::UNO_QUERY); + xParagraphAppend->finishParagraph(uno::Sequence<beans::PropertyValue>()); + } +} + +void SwEditShell::ApplyAdvancedClassification(std::vector<svx::ClassificationResult> const & rResults) +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell) + return; + + const SfxObjectShell* pObjSh = SfxObjectShell::Current(); + if (!pObjSh) + return; + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY); + + uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY); + + uno::Reference<document::XDocumentProperties> xDocumentProperties = pObjSh->getDocProperties(); + + const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType()); + const std::vector<OUString> aUsedPageStyles = lcl_getUsedPageStyles(this); + for (const OUString& rPageStyleName : aUsedPageStyles) + { + uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY); + + // HEADER + bool bHeaderIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn; + uno::Reference<text::XText> xHeaderText; + if (bHeaderIsOn) + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText; + if (xHeaderText.is()) + removeAllClassificationFields(sPolicy, xHeaderText); + + // FOOTER + bool bFooterIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn; + uno::Reference<text::XText> xFooterText; + if (bFooterIsOn) + xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText; + if (xFooterText.is()) + removeAllClassificationFields(sPolicy, xFooterText); + } + + // Clear properties + uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties(); + svx::classification::removeAllProperties(xPropertyContainer); + + SfxClassificationHelper aHelper(xDocumentProperties); + + // Apply properties from the BA policy + for (svx::ClassificationResult const & rResult : rResults) + { + if (rResult.meType == svx::ClassificationType::CATEGORY) + { + aHelper.SetBACName(rResult.msName, SfxClassificationHelper::getPolicyType()); + } + } + + sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType()); + + // Insert origin document property + svx::classification::insertCreationOrigin(xPropertyContainer, aCreator, sfx::ClassificationCreationOrigin::MANUAL); + + // Insert full text as document property + svx::classification::insertFullTextualRepresentationAsDocumentProperty(xPropertyContainer, aCreator, rResults); + + for (const OUString& rPageStyleName : aUsedPageStyles) + { + uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY); + + // HEADER + bool bHeaderIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn; + if (!bHeaderIsOn) + xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(true)); + uno::Reference<text::XText> xHeaderText; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText; + equaliseNumberOfParagraph(rResults, xHeaderText); + + // FOOTER + bool bFooterIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn; + if (!bFooterIsOn) + xPageStyle->setPropertyValue(UNO_NAME_FOOTER_IS_ON, uno::Any(true)); + uno::Reference<text::XText> xFooterText; + xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText; + equaliseNumberOfParagraph(rResults, xFooterText); + + // SET/DELETE WATERMARK + SfxWatermarkItem aWatermarkItem; + aWatermarkItem.SetText(aHelper.GetDocumentWatermark()); + SetWatermark(aWatermarkItem); + + uno::Reference<text::XParagraphCursor> xHeaderParagraphCursor(xHeaderText->createTextCursor(), uno::UNO_QUERY); + uno::Reference<text::XParagraphCursor> xFooterParagraphCursor(xFooterText->createTextCursor(), uno::UNO_QUERY); + + sal_Int32 nParagraph = -1; + + for (svx::ClassificationResult const & rResult : rResults) + { + switch(rResult.meType) + { + case svx::ClassificationType::TEXT: + { + OUString sKey = aCreator.makeNumberedTextKey(); + + svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName); + insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey); + insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey); + } + break; + + case svx::ClassificationType::CATEGORY: + { + OUString sKey = aCreator.makeCategoryNameKey(); + insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey); + insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey); + } + break; + + case svx::ClassificationType::MARKING: + { + OUString sKey = aCreator.makeNumberedMarkingKey(); + svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName); + insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey); + insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey); + } + break; + + case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART: + { + OUString sKey = aCreator.makeNumberedIntellectualPropertyPartKey(); + svx::classification::addOrInsertDocumentProperty(xPropertyContainer, sKey, rResult.msName); + insertFieldToDocument(xMultiServiceFactory, xHeaderText, xHeaderParagraphCursor, sKey); + insertFieldToDocument(xMultiServiceFactory, xFooterText, xFooterParagraphCursor, sKey); + } + break; + + case svx::ClassificationType::PARAGRAPH: + { + nParagraph++; + + if (nParagraph != 0) // only jump to next paragraph, if we aren't at the first paragraph + { + xHeaderParagraphCursor->gotoNextParagraph(false); + xFooterParagraphCursor->gotoNextParagraph(false); + } + + xHeaderParagraphCursor->gotoStartOfParagraph(false); + xFooterParagraphCursor->gotoStartOfParagraph(false); + + uno::Reference<beans::XPropertySet> xHeaderPropertySet(xHeaderParagraphCursor, uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xFooterPropertySet(xFooterParagraphCursor, uno::UNO_QUERY_THROW); + if (rResult.msName == "BOLD") + { + xHeaderPropertySet->setPropertyValue("CharWeight", uno::Any(awt::FontWeight::BOLD)); + xFooterPropertySet->setPropertyValue("CharWeight", uno::Any(awt::FontWeight::BOLD)); + } + else + { + xHeaderPropertySet->setPropertyValue("CharWeight", uno::Any(awt::FontWeight::NORMAL)); + xFooterPropertySet->setPropertyValue("CharWeight", uno::Any(awt::FontWeight::NORMAL)); + } + } + break; + + default: + break; + } + } + } +} + +std::vector<svx::ClassificationResult> SwEditShell::CollectAdvancedClassification() +{ + std::vector<svx::ClassificationResult> aResult; + + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell) + return aResult; + + const SfxObjectShell* pObjSh = SfxObjectShell::Current(); + if (!pObjSh) + return aResult; + + const OUString sBlank; + + uno::Reference<document::XDocumentProperties> xDocumentProperties = pObjSh->getDocProperties(); + uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties(); + sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType()); + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY); + + std::vector<OUString> aPageStyles = lcl_getUsedPageStyles(this); + OUString aPageStyleString = aPageStyles.back(); + uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(aPageStyleString), uno::UNO_QUERY); + + bool bHeaderIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn; + if (!bHeaderIsOn) + { + const OUString aValue = svx::classification::getProperty(xPropertyContainer, aCreator.makeCategoryNameKey()); + if (!aValue.isEmpty()) + aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank }); + + return aResult; + } + + uno::Reference<text::XText> xHeaderText; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText; + + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xHeaderText, uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration(); + + // set to true if category was found in the header + bool bFoundClassificationCategory = false; + + while (xParagraphs->hasMoreElements()) + { + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraphs->nextElement(), uno::UNO_QUERY); + if (!xTextPortionEnumerationAccess.is()) + continue; + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + + // Check font weight + uno::Reference<beans::XPropertySet> xParagraphPropertySet(xTextPortionEnumerationAccess, uno::UNO_QUERY_THROW); + uno::Any aAny = xParagraphPropertySet->getPropertyValue("CharWeight"); + + OUString sWeight = (aAny.get<float>() >= awt::FontWeight::BOLD) ? OUString("BOLD") : OUString("NORMAL"); + + aResult.push_back({ svx::ClassificationType::PARAGRAPH, sWeight, sBlank, sBlank }); + + // Process portions + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != UNO_NAME_TEXT_FIELD) + continue; + + uno::Reference<lang::XServiceInfo> xTextField; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xTextField; + if (!xTextField->supportsService(DocInfoServiceName)) + continue; + + OUString aName; + uno::Reference<beans::XPropertySet> xPropertySet(xTextField, uno::UNO_QUERY); + xPropertySet->getPropertyValue(UNO_NAME_NAME) >>= aName; + + if (aCreator.isMarkingTextKey(aName)) + { + const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName); + if (!aValue.isEmpty()) + aResult.push_back({ svx::ClassificationType::TEXT, aValue, sBlank, sBlank }); + } + else if (aCreator.isCategoryNameKey(aName)) + { + const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName); + if (!aValue.isEmpty()) + aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank }); + bFoundClassificationCategory = true; + } + else if (aCreator.isCategoryIdentifierKey(aName)) + { + const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName); + if (!aValue.isEmpty()) + aResult.push_back({ svx::ClassificationType::CATEGORY, sBlank, sBlank, aValue }); + bFoundClassificationCategory = true; + } + else if (aCreator.isMarkingKey(aName)) + { + const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName); + if (!aValue.isEmpty()) + aResult.push_back({ svx::ClassificationType::MARKING, aValue, sBlank, sBlank }); + } + else if (aCreator.isIntellectualPropertyPartKey(aName)) + { + const OUString aValue = svx::classification::getProperty(xPropertyContainer, aName); + if (!aValue.isEmpty()) + aResult.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, aValue, sBlank, sBlank }); + } + } + } + + if (!bFoundClassificationCategory) + { + const OUString aValue = svx::classification::getProperty(xPropertyContainer, aCreator.makeCategoryNameKey()); + if (!aValue.isEmpty()) + aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank }); + } + + return aResult; +} + +void SwEditShell::SetClassification(const OUString& rName, SfxClassificationPolicyType eType) +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell) + return; + + SfxClassificationHelper aHelper(pDocShell->getDocProperties()); + + const bool bHadWatermark = !aHelper.GetDocumentWatermark().isEmpty(); + + // This updates the infobar as well. + aHelper.SetBACName(rName, eType); + + // Insert origin document property + uno::Reference<beans::XPropertyContainer> xPropertyContainer = pDocShell->getDocProperties()->getUserDefinedProperties(); + sfx::ClassificationKeyCreator aCreator(SfxClassificationHelper::getPolicyType()); + svx::classification::insertCreationOrigin(xPropertyContainer, aCreator, sfx::ClassificationCreationOrigin::BAF_POLICY); + + bool bHeaderIsNeeded = aHelper.HasDocumentHeader(); + bool bFooterIsNeeded = aHelper.HasDocumentFooter(); + OUString aWatermark = aHelper.GetDocumentWatermark(); + bool bWatermarkIsNeeded = !aWatermark.isEmpty(); + + if (!bHeaderIsNeeded && !bFooterIsNeeded && !bWatermarkIsNeeded && !bHadWatermark) + return; + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY); + const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames(); + + for (const OUString& rPageStyleName : aStyles) + { + uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY); + uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY); + + if (bHeaderIsNeeded || bWatermarkIsNeeded || bHadWatermark) + { + // If the header is off, turn it on. + bool bHeaderIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn; + if (!bHeaderIsOn) + xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(true)); + + // If the header already contains a document header field, no need to do anything. + uno::Reference<text::XText> xHeaderText; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText; + + if (bHeaderIsNeeded) + { + if (!lcl_hasField(xHeaderText, DocInfoServiceName, Concat2View(SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCHEADER()))) + { + // Append a field to the end of the header text. + uno::Reference<beans::XPropertySet> xField(xMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY); + xField->setPropertyValue(UNO_NAME_NAME, uno::Any(SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCHEADER())); + uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY); + xHeaderText->insertTextContent(xHeaderText->getEnd(), xTextContent, /*bAbsorb=*/false); + } + } + + SfxWatermarkItem aWatermarkItem; + aWatermarkItem.SetText(aWatermark); + SetWatermark(aWatermarkItem); + } + + if (bFooterIsNeeded) + { + // If the footer is off, turn it on. + bool bFooterIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_FOOTER_IS_ON) >>= bFooterIsOn; + if (!bFooterIsOn) + xPageStyle->setPropertyValue(UNO_NAME_FOOTER_IS_ON, uno::Any(true)); + + // If the footer already contains a document header field, no need to do anything. + uno::Reference<text::XText> xFooterText; + xPageStyle->getPropertyValue(UNO_NAME_FOOTER_TEXT) >>= xFooterText; + static OUString sFooter = SfxClassificationHelper::PROP_PREFIX_INTELLECTUALPROPERTY() + SfxClassificationHelper::PROP_DOCFOOTER(); + if (!lcl_hasField(xFooterText, DocInfoServiceName, sFooter)) + { + // Append a field to the end of the footer text. + uno::Reference<beans::XPropertySet> xField(xMultiServiceFactory->createInstance(DocInfoServiceName), uno::UNO_QUERY); + xField->setPropertyValue(UNO_NAME_NAME, uno::Any(sFooter)); + uno::Reference<text::XTextContent> xTextContent(xField, uno::UNO_QUERY); + xFooterText->insertTextContent(xFooterText->getEnd(), xTextContent, /*bAbsorb=*/false); + } + } + } +} + +// We pass xParent and xNodeSubject even though they point to the same thing because the UNO_QUERY is +// on a performance-sensitive path. +static void lcl_ApplyParagraphClassification(SwDoc* pDoc, + const uno::Reference<frame::XModel>& xModel, + const rtl::Reference<SwXParagraph>& xParent, + const css::uno::Reference<css::rdf::XResource>& xNodeSubject, + std::vector<svx::ClassificationResult> aResults) +{ + if (!xNodeSubject.is()) + return; + + // Remove all paragraph classification fields. + for (;;) + { + uno::Reference<text::XTextField> xTextField = lcl_FindParagraphClassificationField(xModel, xParent); + if (!xTextField.is()) + break; + lcl_RemoveParagraphMetadataField(xTextField); + } + + if (aResults.empty()) + return; + + // Since we always insert at the start of the paragraph, + // need to insert in reverse order. + std::reverse(aResults.begin(), aResults.end()); + // Ignore "PARAGRAPH" types + std::erase_if(aResults, + [](const svx::ClassificationResult& rResult)-> bool + { return rResult.meType == svx::ClassificationType::PARAGRAPH; }); + + sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType()); + std::vector<OUString> aFieldNames; + for (size_t nIndex = 0; nIndex < aResults.size(); ++nIndex) + { + const svx::ClassificationResult& rResult = aResults[nIndex]; + + const bool isLast = nIndex == 0; + const bool isFirst = (nIndex == aResults.size() - 1); + OUString sKey; + OUString sValue = rResult.msName; + switch (rResult.meType) + { + case svx::ClassificationType::TEXT: + { + sKey = aKeyCreator.makeNumberedTextKey(); + } + break; + + case svx::ClassificationType::CATEGORY: + { + if (rResult.msIdentifier.isEmpty()) + { + sKey = aKeyCreator.makeCategoryNameKey(); + } + else + { + sValue = rResult.msIdentifier; + sKey = aKeyCreator.makeCategoryIdentifierKey(); + } + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, ParagraphClassificationAbbrRDFName, rResult.msAbbreviatedName); + } + break; + + case svx::ClassificationType::MARKING: + { + sKey = aKeyCreator.makeNumberedMarkingKey(); + } + break; + + case svx::ClassificationType::INTELLECTUAL_PROPERTY_PART: + { + sKey = aKeyCreator.makeNumberedIntellectualPropertyPartKey(); + } + break; + + default: + break; + } + + OUString sDisplayText = (isFirst ? ("(" + rResult.msAbbreviatedName) : rResult.msAbbreviatedName); + if (isLast) + sDisplayText += ")"; + lcl_UpdateParagraphClassificationField(pDoc, xModel, xParent, sKey, sValue, sDisplayText); + aFieldNames.emplace_back(sKey); + } + + // Correct the order + std::reverse(aFieldNames.begin(), aFieldNames.end()); + OUStringBuffer sFieldNames; + bool first = true; + for (const OUString& rFieldName : aFieldNames) + { + if (!first) + sFieldNames.append("/"); + sFieldNames.append(rFieldName); + first = false; + } + + const OUString sOldFieldNames = lcl_getRDF(xModel, xNodeSubject, ParagraphClassificationFieldNamesRDFName).second; + SwRDFHelper::removeStatement(xModel, MetaNS, xNodeSubject, ParagraphClassificationFieldNamesRDFName, sOldFieldNames); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xNodeSubject, ParagraphClassificationFieldNamesRDFName, sFieldNames.makeStringAndClear()); +} + +void SwEditShell::ApplyParagraphClassification(std::vector<svx::ClassificationResult> aResults) +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell || !GetCursor() || !GetCursor()->Start()) + return; + + SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode(); + if (pNode == nullptr) + return; + + // Prevent recursive validation since this is triggered on node updates, which we do below. + const bool bOldValidationFlag = SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([this, bOldValidationFlag]() { + SetParagraphSignatureValidation(bOldValidationFlag); + }); + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + rtl::Reference<SwXParagraph> xParent = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr); + lcl_ApplyParagraphClassification(GetDoc(), xModel, xParent, css::uno::Reference<css::rdf::XResource>(xParent), std::move(aResults)); +} + +static std::vector<svx::ClassificationResult> lcl_CollectParagraphClassification(const uno::Reference<frame::XModel>& xModel, const uno::Reference<text::XTextContent>& xParagraph) +{ + std::vector<svx::ClassificationResult> aResult; + + uno::Reference<container::XEnumerationAccess> xTextPortionEnumerationAccess(xParagraph, uno::UNO_QUERY); + if (!xTextPortionEnumerationAccess.is()) + return aResult; + + uno::Reference<container::XEnumeration> xTextPortions = xTextPortionEnumerationAccess->createEnumeration(); + + const sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType()); + + while (xTextPortions->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xTextPortion(xTextPortions->nextElement(), uno::UNO_QUERY); + OUString aTextPortionType; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_PORTION_TYPE) >>= aTextPortionType; + if (aTextPortionType != UNO_NAME_TEXT_FIELD) + continue; + + uno::Reference<lang::XServiceInfo> xField; + xTextPortion->getPropertyValue(UNO_NAME_TEXT_FIELD) >>= xField; + if (!xField->supportsService(MetadataFieldServiceName)) + continue; + + uno::Reference<text::XTextField> xTextField(xField, uno::UNO_QUERY); + const OUString sPolicy = SfxClassificationHelper::policyTypeToString(SfxClassificationHelper::getPolicyType()); + const std::pair<OUString, OUString> rdfNamePair = lcl_getFieldRDFByPrefix(xModel, xTextField, sPolicy); + + uno::Reference<text::XTextRange> xTextRange(xField, uno::UNO_QUERY); + const OUString aName = rdfNamePair.first; + const OUString aValue = rdfNamePair.second; + static constexpr OUString sBlank(u""_ustr); + if (aKeyCreator.isMarkingTextKey(aName)) + { + aResult.push_back({ svx::ClassificationType::TEXT, aValue, sBlank, sBlank }); + } + else if (aKeyCreator.isCategoryNameKey(aName)) + { + aResult.push_back({ svx::ClassificationType::CATEGORY, aValue, sBlank, sBlank }); + } + else if (aKeyCreator.isCategoryIdentifierKey(aName)) + { + aResult.push_back({ svx::ClassificationType::CATEGORY, sBlank, sBlank, aValue }); + } + else if (aKeyCreator.isMarkingKey(aName)) + { + aResult.push_back({ svx::ClassificationType::MARKING, aValue, sBlank, sBlank }); + } + else if (aKeyCreator.isIntellectualPropertyPartKey(aName)) + { + aResult.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, xTextRange->getString(), sBlank, sBlank }); + } + } + + return aResult; +} + +std::vector<svx::ClassificationResult> SwEditShell::CollectParagraphClassification() +{ + std::vector<svx::ClassificationResult> aResult; + + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell || !GetCursor() || !GetCursor()->Start()) + return aResult; + + SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode(); + if (pNode == nullptr) + return aResult; + + rtl::Reference<SwXParagraph> xParent = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr); + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + return lcl_CollectParagraphClassification(xModel, xParent); +} + +static sal_Int16 lcl_GetAngle(const drawing::HomogenMatrix3& rMatrix) +{ + basegfx::B2DHomMatrix aTransformation; + basegfx::B2DTuple aScale; + basegfx::B2DTuple aTranslate; + double fRotate = 0; + double fShear = 0; + + aTransformation.set(0, 0, rMatrix.Line1.Column1); + aTransformation.set(0, 1, rMatrix.Line1.Column2); + aTransformation.set(0, 2, rMatrix.Line1.Column3); + aTransformation.set(1, 0, rMatrix.Line2.Column1); + aTransformation.set(1, 1, rMatrix.Line2.Column2); + aTransformation.set(1, 2, rMatrix.Line2.Column3); + // For this to be a valid 2D transform matrix, the last row must be [0,0,1] + assert( rMatrix.Line3.Column1 == 0 ); + assert( rMatrix.Line3.Column2 == 0 ); + assert( rMatrix.Line3.Column3 == 1 ); + + aTransformation.decompose(aScale, aTranslate, fRotate, fShear); + sal_Int16 nDeg = round(basegfx::rad2deg(fRotate)); + return nDeg < 0 ? round(nDeg) * -1 : round(360.0 - nDeg); +} + +SfxWatermarkItem SwEditShell::GetWatermark() const +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell) + return SfxWatermarkItem(); + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY); + std::vector<OUString> aUsedPageStyles = lcl_getUsedPageStyles(this); + for (const OUString& rPageStyleName : aUsedPageStyles) + { + uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY); + + bool bHeaderIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn; + if (!bHeaderIsOn) + return SfxWatermarkItem(); + + uno::Reference<text::XText> xHeaderText; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText; + + OUString sWatermark = ""; + bool bSuccess = false; + uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, "com.sun.star.drawing.CustomShape", sWatermark, bSuccess); + + if (xWatermark.is()) + { + SfxWatermarkItem aItem; + uno::Reference<text::XTextRange> xTextRange(xWatermark, uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xPropertySet(xWatermark, uno::UNO_QUERY); + Color nColor; + sal_Int16 nTransparency; + OUString aFont; + drawing::HomogenMatrix3 aMatrix; + + aItem.SetText(xTextRange->getString()); + + if (xPropertySet->getPropertyValue(UNO_NAME_CHAR_FONT_NAME) >>= aFont) + aItem.SetFont(aFont); + if (xPropertySet->getPropertyValue(UNO_NAME_FILLCOLOR) >>= nColor) + aItem.SetColor(nColor); + if (xPropertySet->getPropertyValue("Transformation") >>= aMatrix) + aItem.SetAngle(lcl_GetAngle(aMatrix)); + if (xPropertySet->getPropertyValue(UNO_NAME_FILL_TRANSPARENCE) >>= nTransparency) + aItem.SetTransparency(nTransparency); + + return aItem; + } + } + return SfxWatermarkItem(); +} + +static void lcl_placeWatermarkInHeader(const SfxWatermarkItem& rWatermark, + const uno::Reference<frame::XModel>& xModel, + const uno::Reference<beans::XPropertySet>& xPageStyle, + const uno::Reference<text::XText>& xHeaderText) +{ + if (!xHeaderText.is()) + return; + + uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY); + OUString aShapeServiceName = "com.sun.star.drawing.CustomShape"; + OUString sWatermark = WATERMARK_NAME; + bool bSuccess = false; + uno::Reference<drawing::XShape> xWatermark = lcl_getWatermark(xHeaderText, aShapeServiceName, sWatermark, bSuccess); + + bool bDeleteWatermark = rWatermark.GetText().isEmpty(); + if (xWatermark.is()) + { + drawing::HomogenMatrix3 aMatrix; + Color nColor = 0xc0c0c0; + sal_Int16 nTransparency = 50; + sal_Int16 nAngle = 45; + OUString aFont = ""; + + uno::Reference<beans::XPropertySet> xPropertySet(xWatermark, uno::UNO_QUERY); + xPropertySet->getPropertyValue(UNO_NAME_CHAR_FONT_NAME) >>= aFont; + xPropertySet->getPropertyValue(UNO_NAME_FILLCOLOR) >>= nColor; + xPropertySet->getPropertyValue(UNO_NAME_FILL_TRANSPARENCE) >>= nTransparency; + xPropertySet->getPropertyValue("Transformation") >>= aMatrix; + nAngle = lcl_GetAngle(aMatrix); + + // If the header already contains a watermark, see if it its text is up to date. + uno::Reference<text::XTextRange> xTextRange(xWatermark, uno::UNO_QUERY); + if (xTextRange->getString() != rWatermark.GetText() + || aFont != rWatermark.GetFont() + || nColor != rWatermark.GetColor() + || nAngle != rWatermark.GetAngle() + || nTransparency != rWatermark.GetTransparency() + || bDeleteWatermark) + { + // No: delete it and we'll insert a replacement. + uno::Reference<lang::XComponent> xComponent(xWatermark, uno::UNO_QUERY); + xComponent->dispose(); + xWatermark.clear(); + } + } + + if (!bSuccess || xWatermark.is() || bDeleteWatermark) + return; + + const OUString& sFont = rWatermark.GetFont(); + sal_Int16 nAngle = rWatermark.GetAngle(); + sal_Int16 nTransparency = rWatermark.GetTransparency(); + Color nColor = rWatermark.GetColor(); + + // Calc the ratio. + double fRatio = 0; + + ScopedVclPtrInstance<VirtualDevice> pDevice; + vcl::Font aFont = pDevice->GetFont(); + aFont.SetFamilyName(sFont); + aFont.SetFontSize(Size(0, 96)); + pDevice->SetFont(aFont); + + auto nTextWidth = pDevice->GetTextWidth(rWatermark.GetText()); + if (nTextWidth) + { + fRatio = pDevice->GetTextHeight(); + fRatio /= nTextWidth; + } + + // Calc the size. + sal_Int32 nWidth = 0; + awt::Size aSize; + xPageStyle->getPropertyValue(UNO_NAME_SIZE) >>= aSize; + if (aSize.Width < aSize.Height) + { + // Portrait. + sal_Int32 nLeftMargin = 0; + xPageStyle->getPropertyValue(UNO_NAME_LEFT_MARGIN) >>= nLeftMargin; + sal_Int32 nRightMargin = 0; + xPageStyle->getPropertyValue(UNO_NAME_RIGHT_MARGIN) >>= nRightMargin; + nWidth = aSize.Width - nLeftMargin - nRightMargin; + } + else + { + // Landscape. + sal_Int32 nTopMargin = 0; + xPageStyle->getPropertyValue(UNO_NAME_TOP_MARGIN) >>= nTopMargin; + sal_Int32 nBottomMargin = 0; + xPageStyle->getPropertyValue(UNO_NAME_BOTTOM_MARGIN) >>= nBottomMargin; + nWidth = aSize.Height - nTopMargin - nBottomMargin; + } + sal_Int32 nHeight = fRatio * nWidth; + + // Create and insert the shape. + uno::Reference<drawing::XShape> xShape(xMultiServiceFactory->createInstance(aShapeServiceName), uno::UNO_QUERY); + + uno::Reference<container::XNamed> xNamed(xShape, uno::UNO_QUERY); + xNamed->setName(sWatermark); + + basegfx::B2DHomMatrix aTransformation; + aTransformation.identity(); + aTransformation.scale(nWidth, nHeight); + aTransformation.rotate(-basegfx::deg2rad(nAngle)); + drawing::HomogenMatrix3 aMatrix; + aMatrix.Line1.Column1 = aTransformation.get(0, 0); + aMatrix.Line1.Column2 = aTransformation.get(0, 1); + aMatrix.Line1.Column3 = aTransformation.get(0, 2); + aMatrix.Line2.Column1 = aTransformation.get(1, 0); + aMatrix.Line2.Column2 = aTransformation.get(1, 1); + aMatrix.Line2.Column3 = aTransformation.get(1, 2); + aMatrix.Line3.Column1 = 0; + aMatrix.Line3.Column2 = 0; + aMatrix.Line3.Column3 = 1; + uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY); + xPropertySet->setPropertyValue(UNO_NAME_ANCHOR_TYPE, uno::Any(text::TextContentAnchorType_AT_CHARACTER)); + uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY); + xHeaderText->insertTextContent(xHeaderText->getEnd(), xTextContent, false); + + // The remaining properties have to be set after the shape is inserted: do that in one batch to avoid flickering. + uno::Reference<document::XActionLockable> xLockable(xShape, uno::UNO_QUERY); + xLockable->addActionLock(); + xPropertySet->setPropertyValue(UNO_NAME_FILLCOLOR, uno::Any(static_cast<sal_Int32>(nColor))); + xPropertySet->setPropertyValue(UNO_NAME_FILLSTYLE, uno::Any(drawing::FillStyle_SOLID)); + xPropertySet->setPropertyValue(UNO_NAME_FILL_TRANSPARENCE, uno::Any(nTransparency)); + xPropertySet->setPropertyValue(UNO_NAME_LINESTYLE, uno::Any(drawing::LineStyle_NONE)); + xPropertySet->setPropertyValue(UNO_NAME_OPAQUE, uno::Any(false)); + xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWHEIGHT, uno::Any(false)); + xPropertySet->setPropertyValue(UNO_NAME_TEXT_AUTOGROWWIDTH, uno::Any(false)); + xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEHEIGHT, uno::Any(nHeight)); + xPropertySet->setPropertyValue(UNO_NAME_TEXT_MINFRAMEWIDTH, uno::Any(nWidth)); + xPropertySet->setPropertyValue(UNO_NAME_TEXT_WRAP, uno::Any(text::WrapTextMode_THROUGH)); + xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT_RELATION, uno::Any(text::RelOrientation::PAGE_PRINT_AREA)); + xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT_RELATION, uno::Any(text::RelOrientation::PAGE_PRINT_AREA)); + xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME, uno::Any(sFont)); + xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME_ASIAN, uno::Any(sFont)); + xPropertySet->setPropertyValue(UNO_NAME_CHAR_FONT_NAME_COMPLEX, uno::Any(sFont)); + xPropertySet->setPropertyValue(UNO_NAME_CHAR_HEIGHT, uno::Any(WATERMARK_AUTO_SIZE)); + xPropertySet->setPropertyValue("Transformation", uno::Any(aMatrix)); + + uno::Reference<text::XTextRange> xTextRange(xShape, uno::UNO_QUERY); + xTextRange->setString(rWatermark.GetText()); + + uno::Reference<drawing::XEnhancedCustomShapeDefaulter> xDefaulter(xShape, uno::UNO_QUERY); + xDefaulter->createCustomShapeDefaults("fontwork-plain-text"); + + auto aGeomPropSeq = xPropertySet->getPropertyValue("CustomShapeGeometry").get< uno::Sequence<beans::PropertyValue> >(); + auto aGeomPropVec = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(aGeomPropSeq); + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"TextPath", uno::Any(true)}, + })); + auto it = std::find_if(aGeomPropVec.begin(), aGeomPropVec.end(), [](const beans::PropertyValue& rValue) + { + return rValue.Name == "TextPath"; + }); + if (it == aGeomPropVec.end()) + aGeomPropVec.push_back(comphelper::makePropertyValue("TextPath", aPropertyValues)); + else + it->Value <<= aPropertyValues; + xPropertySet->setPropertyValue("CustomShapeGeometry", uno::Any(comphelper::containerToSequence(aGeomPropVec))); + + // tdf#108494, tdf#109313 the header height was switched to height of a watermark + // and shape was moved to the lower part of a page, force position update + xPropertySet->getPropertyValue("Transformation") >>= aMatrix; + xPropertySet->setPropertyValue("Transformation", uno::Any(aMatrix)); + + xPropertySet->setPropertyValue(UNO_NAME_HORI_ORIENT, uno::Any(text::HoriOrientation::CENTER)); + xPropertySet->setPropertyValue(UNO_NAME_VERT_ORIENT, uno::Any(text::VertOrientation::CENTER)); + + xLockable->removeActionLock(); +} + +void SwEditShell::SetWatermark(const SfxWatermarkItem& rWatermark) +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell) + return; + const bool bNoWatermark = rWatermark.GetText().isEmpty(); + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xStyleFamilies = xStyleFamiliesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"), uno::UNO_QUERY); + const uno::Sequence<OUString> aStyles = xStyleFamily->getElementNames(); + + for (const OUString& rPageStyleName : aStyles) + { + uno::Reference<beans::XPropertySet> xPageStyle(xStyleFamily->getByName(rPageStyleName), uno::UNO_QUERY); + + // If the header is off, turn it on. + bool bHeaderIsOn = false; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_ON) >>= bHeaderIsOn; + if (!bHeaderIsOn) + { + if (bNoWatermark) + continue; // the style doesn't have any watermark - no need to do anything + + xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_ON, uno::Any(true)); + } + + // backup header height + bool bDynamicHeight = true; + sal_Int32 nOldValue; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_HEIGHT) >>= nOldValue; + xPageStyle->getPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT) >>= bDynamicHeight; + xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(false)); + + // If the header already contains a document header field, no need to do anything. + uno::Reference<text::XText> xHeaderText; + uno::Reference<text::XText> xHeaderTextFirst; + uno::Reference<text::XText> xHeaderTextLeft; + uno::Reference<text::XText> xHeaderTextRight; + + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT) >>= xHeaderText; + lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderText); + + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_FIRST) >>= xHeaderTextFirst; + lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextFirst); + + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_LEFT) >>= xHeaderTextLeft; + lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextLeft); + + xPageStyle->getPropertyValue(UNO_NAME_HEADER_TEXT_RIGHT) >>= xHeaderTextRight; + lcl_placeWatermarkInHeader(rWatermark, xModel, xPageStyle, xHeaderTextRight); + + // tdf#108494 the header height was switched to height of a watermark + // and shape was moved to the lower part of a page + xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::Any(sal_Int32(11))); + xPageStyle->setPropertyValue(UNO_NAME_HEADER_HEIGHT, uno::Any(nOldValue)); + xPageStyle->setPropertyValue(UNO_NAME_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(bDynamicHeight)); + } +} + +SwUndoParagraphSigning::SwUndoParagraphSigning(SwDoc& rDoc, + uno::Reference<text::XTextField> xField, + uno::Reference<text::XTextContent> xParent, + const bool bRemove) + : SwUndo(SwUndoId::PARA_SIGN_ADD, &rDoc), + m_rDoc(rDoc), + m_xField(std::move(xField)), + m_xParent(std::move(xParent)), + m_bRemove(bRemove) +{ + // Save the metadata and field content to undo/redo. + uno::Reference<frame::XModel> xModel = m_rDoc.GetDocShell()->GetBaseModel(); + const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, m_xField); + const auto it = aStatements.find(ParagraphSignatureIdRDFName); + if (it != aStatements.end()) + m_signature = it->second; + + const auto it2 = aStatements.find(ParagraphSignatureUsageRDFName); + if (it2 != aStatements.end()) + m_usage = it2->second; + + uno::Reference<css::text::XTextRange> xText(m_xField, uno::UNO_QUERY); + m_display = xText->getString(); +} + +void SwUndoParagraphSigning::UndoImpl(::sw::UndoRedoContext&) +{ + if (m_bRemove) + Remove(); + else + Insert(); +} + +void SwUndoParagraphSigning::RedoImpl(::sw::UndoRedoContext&) +{ + if (m_bRemove) + Insert(); + else + Remove(); +} + +void SwUndoParagraphSigning::RepeatImpl(::sw::RepeatContext&) +{ +} + +void SwUndoParagraphSigning::Insert() +{ + // Disable undo to avoid introducing noise when we edit the metadata field. + const bool isUndoEnabled = m_rDoc.GetIDocumentUndoRedo().DoesUndo(); + m_rDoc.GetIDocumentUndoRedo().DoUndo(false); + + // Prevent validation since this will trigger a premature validation + // upon inserting, but before setting the metadata. + SwEditShell* pEditSh = m_rDoc.GetEditShell(); + const bool bOldValidationFlag = pEditSh && pEditSh->SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([&] () { + if (pEditSh) + pEditSh->SetParagraphSignatureValidation(bOldValidationFlag); + m_rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled); + }); + + m_xField = lcl_InsertParagraphSignature(m_rDoc.GetDocShell()->GetBaseModel(), m_xParent, m_signature, m_usage); + lcl_DoUpdateParagraphSignatureField(m_rDoc, m_xField, m_display); +} + +void SwUndoParagraphSigning::Remove() +{ + // Disable undo to avoid introducing noise when we edit the metadata field. + const bool isUndoEnabled = m_rDoc.GetIDocumentUndoRedo().DoesUndo(); + m_rDoc.GetIDocumentUndoRedo().DoUndo(false); + + // Prevent validation since this will trigger a premature validation + // upon removing. + SwEditShell* pEditSh = m_rDoc.GetEditShell(); + const bool bOldValidationFlag = pEditSh && pEditSh->SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([&] () { + if (pEditSh) + pEditSh->SetParagraphSignatureValidation(bOldValidationFlag); + m_rDoc.GetIDocumentUndoRedo().DoUndo(isUndoEnabled); + }); + + lcl_RemoveParagraphMetadataField(m_xField); +} + +void SwEditShell::SignParagraph() +{ + SwDoc& rDoc = *GetDoc(); + SwDocShell* pDocShell = rDoc.GetDocShell(); + if (!pDocShell || !GetCursor() || !GetCursor()->Start()) + return; + const SwPosition* pPosStart = GetCursor()->Start(); + if (!pPosStart) + return; + SwTextNode* pNode = pPosStart->GetNode().GetTextNode(); + if (!pNode) + return; + + // Table text signing is not supported. + if (pNode->FindTableNode() != nullptr) + return; + + // 1. Get the text (without fields). + const rtl::Reference<SwXParagraph> xParagraph = SwXParagraph::CreateXParagraph(pNode->GetDoc(), pNode, nullptr); + const OString utf8Text = lcl_getParagraphBodyText(xParagraph); + if (utf8Text.isEmpty()) + return; + + // 2. Get certificate. + uno::Reference<security::XDocumentDigitalSignatures> xSigner( + // here none of the version-dependent methods are called + security::DocumentDigitalSignatures::createDefault( + comphelper::getProcessComponentContext())); + + uno::Sequence<css::beans::PropertyValue> aProperties; + uno::Reference<security::XCertificate> xCertificate = xSigner->chooseCertificateWithProps(aProperties); + if (!xCertificate.is()) + return; + + // 3. Sign it. + svl::crypto::Signing signing(xCertificate); + signing.AddDataRange(utf8Text.getStr(), utf8Text.getLength()); + OStringBuffer sigBuf; + if (!signing.Sign(sigBuf)) + return; + + const OUString signature = OStringToOUString(sigBuf, RTL_TEXTENCODING_UTF8, 0); + + auto it = std::find_if(std::as_const(aProperties).begin(), std::as_const(aProperties).end(), [](const beans::PropertyValue& rValue) + { + return rValue.Name == "Usage"; + }); + + OUString aUsage; + if (it != std::as_const(aProperties).end()) + it->Value >>= aUsage; + + // 4. Add metadata + // Prevent validation since this will trigger a premature validation + // upon inserting, but before setting the metadata. + const bool bOldValidationFlag = SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([this, bOldValidationFlag] () { + SetParagraphSignatureValidation(bOldValidationFlag); + }); + + rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::PARA_SIGN_ADD, nullptr); + + const uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + uno::Reference<css::text::XTextField> xField = lcl_InsertParagraphSignature(xModel, xParagraph, signature, aUsage); + + lcl_UpdateParagraphSignatureField(*GetDoc(), xModel, xParagraph, xField, utf8Text); + + rDoc.GetIDocumentUndoRedo().AppendUndo( + std::make_unique<SwUndoParagraphSigning>(rDoc, xField, xParagraph, true)); + + rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::PARA_SIGN_ADD, nullptr); +} + +void SwEditShell::ValidateParagraphSignatures(SwTextNode* pNode, bool updateDontRemove) +{ + if (!pNode || !IsParagraphSignatureValidationEnabled()) + return; + + // Table text signing is not supported. + if (pNode->FindTableNode() != nullptr) + return; + + // Prevent recursive validation since this is triggered on node updates, which we do below. + const bool bOldValidationFlag = SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([this, bOldValidationFlag] () { + SetParagraphSignatureValidation(bOldValidationFlag); + }); + + rtl::Reference<SwXParagraph> xParentText = SwXParagraph::CreateXParagraph(*GetDoc(), pNode, nullptr); + lcl_ValidateParagraphSignatures(*GetDoc(), xParentText, updateDontRemove); +} + +void SwEditShell::ValidateCurrentParagraphSignatures(bool updateDontRemove) +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell || !GetCursor() || !GetCursor()->Start() || !IsParagraphSignatureValidationEnabled()) + return; + + SwPaM* pPaM = GetCursor(); + const SwPosition* pPosStart = pPaM->Start(); + SwTextNode* pNode = pPosStart->GetNode().GetTextNode(); + ValidateParagraphSignatures(pNode, updateDontRemove); +} + +void SwEditShell::ValidateAllParagraphSignatures(bool updateDontRemove) +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell || !IsParagraphSignatureValidationEnabled()) + return; + + // Prevent recursive validation since this is triggered on node updates, which we do below. + const bool bOldValidationFlag = SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([this, bOldValidationFlag] () { + SetParagraphSignatureValidation(bOldValidationFlag); + }); + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + const uno::Reference<text::XTextDocument> xDoc(xModel, uno::UNO_QUERY); + uno::Reference<text::XText> xParent = xDoc->getText(); + uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xParent, uno::UNO_QUERY); + if (!xParagraphEnumerationAccess.is()) + return; + uno::Reference<container::XEnumeration> xParagraphs = xParagraphEnumerationAccess->createEnumeration(); + if (!xParagraphs.is()) + return; + while (xParagraphs->hasMoreElements()) + { + uno::Reference<text::XTextContent> xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY); + lcl_ValidateParagraphSignatures(*GetDoc(), xParagraph, updateDontRemove); + } +} + +static uno::Reference<text::XTextField> lcl_GetParagraphMetadataFieldAtIndex(const SwDocShell* pDocSh, SwTextNode const * pNode, const sal_uLong index) +{ + uno::Reference<text::XTextField> xTextField; + if (pNode != nullptr && pDocSh != nullptr) + { + SwTextAttr* pAttr = pNode->GetTextAttrAt(index, RES_TXTATR_METAFIELD); + SwTextMeta* pTextMeta = static_txtattr_cast<SwTextMeta*>(pAttr); + if (pTextMeta != nullptr) + { + SwFormatMeta& rFormatMeta(static_cast<SwFormatMeta&>(pTextMeta->GetAttr())); + if (::sw::Meta* pMeta = rFormatMeta.GetMeta()) + { + const css::uno::Reference<css::rdf::XResource> xSubject = pMeta->MakeUnoObject(); + uno::Reference<frame::XModel> xModel = pDocSh->GetBaseModel(); + const std::map<OUString, OUString> aStatements = lcl_getRDFStatements(xModel, xSubject); + if (aStatements.find(ParagraphSignatureIdRDFName) != aStatements.end() || + aStatements.find(ParagraphClassificationNameRDFName) != aStatements.end()) + { + xTextField = uno::Reference<text::XTextField>(xSubject, uno::UNO_QUERY); + } + } + } + } + + return xTextField; +} + +void SwEditShell::RestoreMetadataFieldsAndValidateParagraphSignatures() +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell || !IsParagraphSignatureValidationEnabled()) + return; + + // Prevent recursive validation since this is triggered on node updates, which we do below. + const bool bOldValidationFlag = SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([this, bOldValidationFlag] () { + SetParagraphSignatureValidation(bOldValidationFlag); + }); + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + const rtl::Reference<SwXTextDocument> xDoc(dynamic_cast<SwXTextDocument*>(xModel.get())); + rtl::Reference<SwXBodyText> xBodyText = xDoc->getBodyText(); + if (!xBodyText.is()) + return; + rtl::Reference<SwXParagraphEnumeration> xParagraphs = xBodyText->createParagraphEnumeration(); + + static constexpr OUString sBlank(u""_ustr); + const sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType()); + const css::uno::Sequence<css::uno::Reference<rdf::XURI>> aGraphNames = SwRDFHelper::getGraphNames(xModel, MetaNS); + + while (xParagraphs->hasMoreElements()) + { + uno::Reference<text::XTextContent> xParaOrTable(xParagraphs->nextElement(), uno::UNO_QUERY); + rtl::Reference<SwXParagraph> xParagraph(dynamic_cast<SwXParagraph*>(xParaOrTable.get())); + + try + { + const css::uno::Reference<css::rdf::XResource> xSubject(xParagraph); + const std::map<OUString, OUString> aStatements = SwRDFHelper::getStatements(xModel, aGraphNames, xSubject); + + const auto it = aStatements.find(ParagraphClassificationFieldNamesRDFName); + const OUString sFieldNames = (it != aStatements.end() ? it->second : sBlank); + std::vector<svx::ClassificationResult> aResults; + if (!sFieldNames.isEmpty()) + { + assert(it != aStatements.end() && "can only be non-empty if it was valid"); + // Order the fields + sal_Int32 nIndex = 0; + do + { + const OUString sCurFieldName = sFieldNames.getToken(0, '/', nIndex); + if (sCurFieldName.isEmpty()) + break; + + const auto it2 = aStatements.find(sCurFieldName); + bool bStatementFound = it2 != aStatements.end(); + const OUString sName = bStatementFound ? it->first : sBlank; + const OUString sValue = bStatementFound ? it->second : sBlank; + + if (aKeyCreator.isMarkingTextKey(sName)) + { + aResults.push_back({ svx::ClassificationType::TEXT, sValue, sValue, sBlank }); + } + else if (aKeyCreator.isCategoryNameKey(sName)) + { + const auto it3 = aStatements.find(ParagraphClassificationAbbrRDFName); + const OUString sAbbreviatedName = (it3 != aStatements.end() && !it3->second.isEmpty() ? it3->second : sValue); + aResults.push_back({ svx::ClassificationType::CATEGORY, sValue, sAbbreviatedName, sBlank }); + } + else if (aKeyCreator.isCategoryIdentifierKey(sName)) + { + const auto it3 = aStatements.find(ParagraphClassificationAbbrRDFName); + const OUString sAbbreviatedName = (it3 != aStatements.end() && !it3->second.isEmpty() ? it3->second : sValue); + aResults.push_back({ svx::ClassificationType::CATEGORY, sBlank, sAbbreviatedName, sValue }); + } + else if (aKeyCreator.isMarkingKey(sName)) + { + aResults.push_back({ svx::ClassificationType::MARKING, sValue, sValue, sBlank }); + } + else if (aKeyCreator.isIntellectualPropertyPartKey(sName)) + { + aResults.push_back({ svx::ClassificationType::INTELLECTUAL_PROPERTY_PART, sValue, sValue, sBlank }); + } + } + while (nIndex >= 0); + } + + // Update classification based on results. + lcl_ApplyParagraphClassification(GetDoc(), xModel, xParagraph, xSubject, aResults); + + // Get Signatures + std::map<OUString, SignatureDescr> aSignatures; + for (const auto& pair : lcl_getRDFStatements(xModel, uno::Reference<css::text::XTextContent>(xParagraph))) + { + const OUString& sName = pair.first; + if (sName.startsWith(ParagraphSignatureRDFNamespace)) + { + const OUString sSuffix = sName.copy(ParagraphSignatureRDFNamespace.getLength()); + const sal_Int32 index = sSuffix.indexOf(":"); + if (index >= 0) + { + const OUString id = sSuffix.copy(0, index); + const OUString type = sSuffix.copy(index); + const OUString& sValue = pair.second; + if (type == ParagraphSignatureDateRDFName) + aSignatures[id].msDate = sValue; + else if (type == ParagraphSignatureUsageRDFName) + aSignatures[id].msUsage = sValue; + else if (type == ParagraphSignatureDigestRDFName) + aSignatures[id].msSignature = sValue; + } + } + } + + for (const auto& pair : aSignatures) + { + uno::Reference<text::XTextField> xField = lcl_findFieldByRDF(xModel, xParagraph, ParagraphSignatureIdRDFName, pair.first); + if (!xField.is()) + { + uno::Reference<lang::XMultiServiceFactory> xMultiServiceFactory(xModel, uno::UNO_QUERY); + xField = uno::Reference<text::XTextField>(xMultiServiceFactory->createInstance(MetadataFieldServiceName), uno::UNO_QUERY); + + // Add the signature at the end. + xField->attach(xParagraph->getAnchor()->getEnd()); + + const css::uno::Reference<css::rdf::XResource> xFieldSubject(xField, uno::UNO_QUERY); + SwRDFHelper::addStatement(xModel, MetaNS, MetaFilename, xFieldSubject, ParagraphSignatureIdRDFName, pair.first); + + const OString utf8Text = lcl_getParagraphBodyText(xParagraph); + lcl_UpdateParagraphSignatureField(*GetDoc(), xModel, xParagraph, xField, utf8Text); + } + } + + lcl_ValidateParagraphSignatures(*GetDoc(), xParagraph, true); // Validate and Update signatures. + } + catch (const std::exception&) + { + } + } +} + +bool SwEditShell::IsCursorInParagraphMetadataField() const +{ + if (GetCursor() && GetCursor()->Start()) + { + SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode(); + const sal_uLong index = GetCursor()->Start()->GetContentIndex(); + uno::Reference<text::XTextField> xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index); + return xField.is(); + } + + return false; +} + +bool SwEditShell::RemoveParagraphMetadataFieldAtCursor() +{ + if (GetCursor() && GetCursor()->Start()) + { + SwTextNode* pNode = GetCursor()->Start()->GetNode().GetTextNode(); + sal_uLong index = GetCursor()->Start()->GetContentIndex(); + uno::Reference<text::XTextField> xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index); + if (!xField.is()) + { + // Try moving the cursor to see if we're _facing_ a metafield or not, + // as opposed to being within one. + index--; // Backspace moves left + + xField = lcl_GetParagraphMetadataFieldAtIndex(GetDoc()->GetDocShell(), pNode, index); + } + + if (xField.is()) + { + lcl_RemoveParagraphMetadataField(xField); + return true; + } + } + + return false; +} + +static OUString lcl_GetParagraphClassification(SfxClassificationHelper & rHelper, sfx::ClassificationKeyCreator const & rKeyCreator, + const uno::Reference<frame::XModel>& xModel, const rtl::Reference<SwXParagraph>& xParagraph) +{ + uno::Reference<text::XTextField> xTextField; + xTextField = lcl_FindParagraphClassificationField(xModel, xParagraph, rKeyCreator.makeCategoryIdentifierKey()); + if (xTextField.is()) + { + const std::pair<OUString, OUString> rdfValuePair = lcl_getRDF(xModel, xTextField, ParagraphClassificationValueRDFName); + return rHelper.GetBACNameForIdentifier(rdfValuePair.second); + } + + xTextField = lcl_FindParagraphClassificationField(xModel, xParagraph, rKeyCreator.makeCategoryNameKey()); + if (xTextField.is()) + { + return lcl_getRDF(xModel, xTextField, ParagraphClassificationNameRDFName).second; + } + + return OUString(); +} + +static OUString lcl_GetHighestClassificationParagraphClass(SwPaM* pCursor) +{ + OUString sHighestClass; + + SwTextNode* pNode = pCursor->Start()->GetNode().GetTextNode(); + if (pNode == nullptr) + return sHighestClass; + + SwDocShell* pDocShell = pNode->GetDoc().GetDocShell(); + if (!pDocShell) + return sHighestClass; + + SfxClassificationHelper aHelper(pDocShell->getDocProperties()); + sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType()); + + uno::Reference<frame::XModel> xModel = pDocShell->GetBaseModel(); + const rtl::Reference<SwXTextDocument> xDoc(dynamic_cast<SwXTextDocument*>(xModel.get())); + rtl::Reference<SwXBodyText> xBodyText = xDoc->getBodyText(); + + rtl::Reference<SwXParagraphEnumeration> xParagraphs = xBodyText->createParagraphEnumeration(); + while (xParagraphs->hasMoreElements()) + { + uno::Reference<text::XTextContent> xParaOrTable(xParagraphs->nextElement(), uno::UNO_QUERY); + rtl::Reference<SwXParagraph> xParagraph(dynamic_cast<SwXParagraph*>(xParaOrTable.get())); + const OUString sCurrentClass = lcl_GetParagraphClassification(aHelper, aKeyCreator, xModel, xParagraph); + sHighestClass = aHelper.GetHigherClass(sHighestClass, sCurrentClass); + } + + return sHighestClass; +} + +void SwEditShell::ClassifyDocPerHighestParagraphClass() +{ + SwDocShell* pDocShell = GetDoc()->GetDocShell(); + if (!pDocShell) + return; + + // Bail out as early as possible if we don't have paragraph classification. + if (!SwRDFHelper::hasMetadataGraph(pDocShell->GetBaseModel(), MetaNS)) + return; + + uno::Reference<document::XDocumentProperties> xDocumentProperties = pDocShell->getDocProperties(); + uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocumentProperties->getUserDefinedProperties(); + + sfx::ClassificationKeyCreator aKeyCreator(SfxClassificationHelper::getPolicyType()); + SfxClassificationHelper aHelper(xDocumentProperties); + + OUString sHighestClass = lcl_GetHighestClassificationParagraphClass(GetCursor()); + + const OUString aClassificationCategory = svx::classification::getProperty(xPropertyContainer, aKeyCreator.makeCategoryNameKey()); + + if (!aClassificationCategory.isEmpty()) + { + sHighestClass = aHelper.GetHigherClass(sHighestClass, aClassificationCategory); + } + + if (aClassificationCategory != sHighestClass) + { + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Question, VclButtonsType::Ok, + SwResId(STR_CLASSIFICATION_LEVEL_CHANGED))); + xQueryBox->run(); + } + + const SfxClassificationPolicyType eHighestClassType = SfxClassificationHelper::stringToPolicyType(sHighestClass); + + // Prevent paragraph signature validation since the below changes (f.e. watermarking) are benign. + const bool bOldValidationFlag = SetParagraphSignatureValidation(false); + comphelper::ScopeGuard const g([this, bOldValidationFlag]() { + SetParagraphSignatureValidation(bOldValidationFlag); + }); + + // Check the origin, if "manual" (created via advanced classification dialog), + // then we just need to set the category name. + if (sfx::getCreationOriginProperty(xPropertyContainer, aKeyCreator) == sfx::ClassificationCreationOrigin::MANUAL) + { + aHelper.SetBACName(sHighestClass, eHighestClassType); + ApplyAdvancedClassification(CollectAdvancedClassification()); + } + else + { + SetClassification(sHighestClass, eHighestClassType); + } +} + +// #i62675# +void SwEditShell::SetTextFormatColl(SwTextFormatColl *pFormat, + const bool bResetListAttrs) +{ + SwTextFormatColl *pLocal = pFormat? pFormat: (*GetDoc()->GetTextFormatColls())[0]; + StartAllAction(); + + SwRewriter aRewriter; + + aRewriter.AddRule(UndoArg1, pLocal->GetName()); + + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::SETFMTCOLL, &aRewriter); + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if (!rPaM.HasReadonlySel( GetViewOptions()->IsFormView(), true)) + { + // store previous paragraph style for track changes + OUString sParaStyleName; + sal_uInt16 nPoolId = USHRT_MAX; + SwContentNode * pCnt = rPaM.Start()->GetNode().GetContentNode(); + if ( pCnt && pCnt->GetTextNode() && GetDoc()->getIDocumentRedlineAccess().IsRedlineOn() ) + { + const SwTextFormatColl* pTextFormatColl = pCnt->GetTextNode()->GetTextColl(); + sal_uInt16 nStylePoolId = pTextFormatColl->GetPoolFormatId(); + // default paragraph style + if ( nStylePoolId == RES_POOLCOLL_STANDARD ) + nPoolId = nStylePoolId; + else + sParaStyleName = pTextFormatColl->GetName(); + } + + // Change the paragraph style to pLocal and remove all direct paragraph formatting. + GetDoc()->SetTextFormatColl(rPaM, pLocal, true, bResetListAttrs, GetLayout()); + + // If there are hints on the nodes which cover the whole node, then remove those, too. + SwPaM aPaM(*rPaM.Start(), *rPaM.End()); + if (SwTextNode* pEndTextNode = aPaM.End()->GetNode().GetTextNode()) + { + aPaM.Start()->SetContent(0); + aPaM.End()->SetContent(pEndTextNode->GetText().getLength()); + } + GetDoc()->RstTextAttrs(aPaM, /*bInclRefToxMark=*/false, /*bExactRange=*/true, GetLayout()); + + // add redline tracking the previous paragraph style + if ( GetDoc()->getIDocumentRedlineAccess().IsRedlineOn() && + // multi-paragraph ParagraphFormat redline ranges + // haven't supported by AppendRedline(), yet + // TODO handle multi-paragraph selections, too, + // e.g. by breaking them to single paragraphs + aPaM.Start()->GetNode() == aPaM.End()->GetNode() ) + { + SwRangeRedline * pRedline = new SwRangeRedline( RedlineType::ParagraphFormat, aPaM ); + auto const result(GetDoc()->getIDocumentRedlineAccess().AppendRedline( pRedline, true)); + // store original paragraph style to reject formatting change + if ( IDocumentRedlineAccess::AppendResult::IGNORED != result && + ( nPoolId == RES_POOLCOLL_STANDARD || !sParaStyleName.isEmpty() ) ) + { + std::unique_ptr<SwRedlineExtraData_FormatColl> xExtra; + xExtra.reset(new SwRedlineExtraData_FormatColl(sParaStyleName, nPoolId, nullptr)); + if (xExtra) + pRedline->SetExtraData( xExtra.get() ); + } + } + } + + } + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::SETFMTCOLL, &aRewriter); + EndAllAction(); +} + +SwTextFormatColl* SwEditShell::MakeTextFormatColl(const OUString& rFormatCollName, + SwTextFormatColl* pParent) +{ + SwTextFormatColl *pColl; + if ( pParent == nullptr ) + pParent = &GetTextFormatColl(0); + pColl = GetDoc()->MakeTextFormatColl(rFormatCollName, pParent); + if ( pColl == nullptr ) + { + OSL_FAIL( "MakeTextFormatColl failed" ); + } + return pColl; + +} + +void SwEditShell::FillByEx(SwTextFormatColl* pColl) +{ + SwPaM * pCursor = GetCursor(); + SwContentNode * pCnt = pCursor->GetPointContentNode(); + if (pCnt->IsTextNode()) // uhm... what nonsense would happen if not? + { // only need properties-node because BREAK/PAGEDESC filtered anyway! + pCnt = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode()); + } + const SfxItemSet* pSet = pCnt->GetpSwAttrSet(); + if( !pSet ) + return; + + // JP 05.10.98: Special treatment if one of the attributes Break/PageDesc/NumRule(auto) is + // in the ItemSet. Otherwise there will be too much or wrong processing (NumRules!) + // Bug 57568 + + // Do NOT copy AutoNumRules into the template + const SwNumRuleItem* pItem; + const SwNumRule* pRule = nullptr; + if (SfxItemState::SET == pSet->GetItemState(RES_BREAK, false) + || SfxItemState::SET == pSet->GetItemState(RES_PAGEDESC, false) + || ((pItem = pSet->GetItemIfSet(RES_PARATR_NUMRULE, false)) + && nullptr != (pRule = GetDoc()->FindNumRulePtr(pItem->GetValue())) + && pRule->IsAutoRule())) + { + SfxItemSet aSet( *pSet ); + aSet.ClearItem( RES_BREAK ); + aSet.ClearItem( RES_PAGEDESC ); + + if (pRule + || ((pItem = pSet->GetItemIfSet(RES_PARATR_NUMRULE, false)) + && nullptr != (pRule = GetDoc()->FindNumRulePtr(pItem->GetValue())) + && pRule->IsAutoRule())) + aSet.ClearItem( RES_PARATR_NUMRULE ); + + if( aSet.Count() ) + GetDoc()->ChgFormat(*pColl, aSet ); + } + else + GetDoc()->ChgFormat(*pColl, *pSet ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edfld.cxx b/sw/source/core/edit/edfld.cxx new file mode 100644 index 0000000000..f77ae8d97e --- /dev/null +++ b/sw/source/core/edit/edfld.cxx @@ -0,0 +1,411 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_features.h> +#include <config_fuzzers.h> + +#include <osl/diagnose.h> +#include <unotools/charclass.hxx> +#include <editsh.hxx> +#include <fldbas.hxx> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <docary.hxx> +#include <fmtfld.hxx> +#include <txtfld.hxx> +#include <pamtyp.hxx> +#include <expfld.hxx> +#include <swundo.hxx> +#include <dbmgr.hxx> +#include <hints.hxx> +#include <fieldhint.hxx> +#include <DocumentSettingManager.hxx> +#include <IDocumentContentOperations.hxx> + +/// count field types with a ResId, if SwFieldIds::Unknown count all +size_t SwEditShell::GetFieldTypeCount(SwFieldIds nResId ) const +{ + const SwFieldTypes* pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes(); + + if(nResId == SwFieldIds::Unknown) + { + return o3tl::narrowing<sal_uInt16>(pFieldTypes->size()); + } + + // all types with the same ResId + size_t nIdx = 0; + for(const auto & pFieldType : *pFieldTypes) + { + // same ResId -> increment index + if(pFieldType->Which() == nResId) + nIdx++; + } + return nIdx; +} + +/// get field types with a ResId, if 0 get all +SwFieldType* SwEditShell::GetFieldType(size_t nField, SwFieldIds nResId ) const +{ + const SwFieldTypes* pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes(); + + if(nResId == SwFieldIds::Unknown && nField < pFieldTypes->size()) + { + return (*pFieldTypes)[nField].get(); + } + + size_t nIdx = 0; + for(const auto & pFieldType : *pFieldTypes) + { + // same ResId -> increment index + if(pFieldType->Which() == nResId) + { + if(nIdx == nField) + return pFieldType.get(); + nIdx++; + } + } + return nullptr; +} + +/// get first type with given ResId and name +SwFieldType* SwEditShell::GetFieldType(SwFieldIds nResId, const OUString& rName) const +{ + return GetDoc()->getIDocumentFieldsAccess().GetFieldType( nResId, rName, false ); +} + +/// delete field type +void SwEditShell::RemoveFieldType(size_t nField) +{ + GetDoc()->getIDocumentFieldsAccess().RemoveFieldType(nField); +} + +/// delete field type based on its name +void SwEditShell::RemoveFieldType(SwFieldIds nResId, const OUString& rStr) +{ + const SwFieldTypes* pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes(); + const SwFieldTypes::size_type nSize = pFieldTypes->size(); + const CharClass& rCC = GetAppCharClass(); + + OUString aTmp( rCC.lowercase( rStr )); + + for(SwFieldTypes::size_type i = 0; i < nSize; ++i) + { + // same ResId -> increment index + SwFieldType* pFieldType = (*pFieldTypes)[i].get(); + if( pFieldType->Which() == nResId ) + { + if( aTmp == rCC.lowercase( pFieldType->GetName() ) ) + { + GetDoc()->getIDocumentFieldsAccess().RemoveFieldType(i); + return; + } + } + } +} + +void SwEditShell::FieldToText( SwFieldType const * pType ) +{ + if( !pType->HasWriterListeners() ) + return; + + CurrShell aCurr( this ); + StartAllAction(); + StartUndo( SwUndoId::DELETE ); + Push(); + SwPaM* pPaM = GetCursor(); + const SwFieldHint aHint(pPaM, GetLayout()); + pType->CallSwClientNotify(aHint); + + Pop(PopMode::DeleteCurrent); + EndAllAction(); + EndUndo( SwUndoId::DELETE ); +} + +/// add a field at the cursor position +bool SwEditShell::InsertField(SwField const & rField, const bool bForceExpandHints) +{ + CurrShell aCurr( this ); + StartAllAction(); + SwFormatField aField( rField ); + + const SetAttrMode nInsertFlags = bForceExpandHints + ? SetAttrMode::FORCEHINTEXPAND + : SetAttrMode::DEFAULT; + + bool bSuccess(false); + for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) // for each PaM + { + bSuccess |= GetDoc()->getIDocumentContentOperations().InsertPoolItem(rPaM, aField, nInsertFlags); + OSL_ENSURE( bSuccess, "Doc->Insert(Field) failed"); + } + + EndAllAction(); + return bSuccess; +} + +/// Are the PaMs positioned on fields? +static SwTextField* lcl_FindInputField( SwDoc* pDoc, const SwField& rField ) +{ + // Search field via its address. For input fields this needs to be done in protected fields. + SwTextField* pTField = nullptr; + if (SwFieldIds::Input == rField.Which() + || (SwFieldIds::SetExp == rField.Which() + && static_cast<const SwSetExpField&>(rField).GetInputFlag() + && (static_cast<SwSetExpFieldType*>(rField.GetTyp())->GetType() + & nsSwGetSetExpType::GSE_STRING))) + { + for (const SfxPoolItem* pItem : pDoc->GetAttrPool().GetItemSurrogates(RES_TXTATR_INPUTFIELD)) + { + auto pFormatField = dynamic_cast<const SwFormatField*>(pItem); + if( pFormatField && pFormatField->GetField() == &rField ) + { + pTField = const_cast<SwFormatField*>(pFormatField)->GetTextField(); + break; + } + } + } + else if( SwFieldIds::SetExp == rField.Which() + && static_cast<const SwSetExpField&>(rField).GetInputFlag() ) + { + for (const SfxPoolItem* pItem : pDoc->GetAttrPool().GetItemSurrogates(RES_TXTATR_FIELD)) + { + auto pFormatField = dynamic_cast<const SwFormatField*>(pItem); + if( pFormatField && pFormatField->GetField() == &rField ) + { + pTField = const_cast<SwFormatField*>(pFormatField)->GetTextField(); + break; + } + } + } + return pTField; +} + +void SwEditShell::UpdateOneField(SwField &rField) +{ + CurrShell aCurr( this ); + StartAllAction(); + { + // If there are no selections so take the value of the current cursor position. + SwPaM* pCursor = GetCursor(); + SwTextField *pTextField; + SwFormatField *pFormatField; + + if ( !pCursor->IsMultiSelection() && !pCursor->HasMark()) + { + pTextField = GetTextFieldAtPos(pCursor->Start(), ::sw::GetTextAttrMode::Default); + + if (!pTextField) // #i30221# + pTextField = lcl_FindInputField( GetDoc(), rField); + + if (pTextField != nullptr) + { + GetDoc()->getIDocumentFieldsAccess().UpdateField( + pTextField, + rField, + true); + } + } + + // bOkay (instead of return because of EndAllAction) becomes false, + // 1) if only one PaM has more than one field or + // 2) if there are mixed field types + bool bOkay = true; + bool bTableSelBreak = false; + + SwMsgPoolItem aFieldHint( RES_TXTATR_FIELD ); // Search-Hint + SwMsgPoolItem aAnnotationFieldHint( RES_TXTATR_ANNOTATION ); + SwMsgPoolItem aInputFieldHint( RES_TXTATR_INPUTFIELD ); + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) // for each PaM + { + if( rPaM.HasMark() && bOkay ) // ... with selection + { + // copy of the PaM + SwPaM aCurPam( *rPaM.GetMark(), *rPaM.GetPoint() ); + SwPaM aPam( *rPaM.GetPoint() ); + + SwPosition *pCurStt = aCurPam.Start(), *pCurEnd = + aCurPam.End(); + /* + * In case that there are two contiguous fields in a PaM, the aPam goes step by step + * to the end. aCurPam is reduced in each loop. If aCurPam was searched completely, + * the loop terminates because Start = End. + */ + + // Search for SwTextField ... + while( bOkay + && pCurStt->GetContentIndex() != pCurEnd->GetContentIndex() + && (sw::FindAttrImpl(aPam, aFieldHint, fnMoveForward, aCurPam, true, GetLayout()) + || sw::FindAttrImpl(aPam, aAnnotationFieldHint, fnMoveForward, aCurPam, false, GetLayout()) + || sw::FindAttrImpl(aPam, aInputFieldHint, fnMoveForward, aCurPam, false, GetLayout()))) + { + // if only one PaM has more than one field ... + if( aPam.Start()->GetContentIndex() != pCurStt->GetContentIndex() ) + bOkay = false; + + pTextField = GetTextFieldAtPos(pCurStt, ::sw::GetTextAttrMode::Default); + if( nullptr != pTextField ) + { + pFormatField = const_cast<SwFormatField*>(&pTextField->GetFormatField()); + SwField *pCurField = pFormatField->GetField(); + + // if there are mixed field types + if( pCurField->GetTyp()->Which() != + rField.GetTyp()->Which() ) + bOkay = false; + + bTableSelBreak = GetDoc()->getIDocumentFieldsAccess().UpdateField( + pTextField, + rField, + false); + } + // The search area is reduced by the found area: + pCurStt->AdjustContent(+1); + } + } + + if( bTableSelBreak ) // If table section and table formula are updated -> finish + break; + + } + } + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); +} + +SwDBData const & SwEditShell::GetDBData() const +{ + return GetDoc()->GetDBData(); +} + +void SwEditShell::ChgDBData(const SwDBData& rNewData) +{ + GetDoc()->ChgDBData(rNewData); +} + +void SwEditShell::GetAllUsedDB( std::vector<OUString>& rDBNameList, + std::vector<OUString> const * pAllDBNames ) +{ + GetDoc()->GetAllUsedDB( rDBNameList, pAllDBNames ); +} + +void SwEditShell::ChangeDBFields( const std::vector<OUString>& rOldNames, + const OUString& rNewName ) +{ + GetDoc()->ChangeDBFields( rOldNames, rNewName ); +} + +/// Update all expression fields +void SwEditShell::UpdateExpFields(bool bCloseDB) +{ + CurrShell aCurr( this ); + StartAllAction(); + GetDoc()->getIDocumentFieldsAccess().UpdateExpFields(nullptr, true); + if (bCloseDB) + { +#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS + GetDoc()->GetDBManager()->CloseAll(); // close all database connections +#endif + } + EndAllAction(); +} + +SwDBManager* SwEditShell::GetDBManager() const +{ +#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS + return GetDoc()->GetDBManager(); +#else + return NULL; +#endif +} + +/// insert field type +SwFieldType* SwEditShell::InsertFieldType(const SwFieldType& rFieldType) +{ + return GetDoc()->getIDocumentFieldsAccess().InsertFieldType(rFieldType); +} + +void SwEditShell::LockExpFields() +{ + GetDoc()->getIDocumentFieldsAccess().LockExpFields(); +} + +void SwEditShell::UnlockExpFields() +{ + GetDoc()->getIDocumentFieldsAccess().UnlockExpFields(); +} + +bool SwEditShell::IsExpFieldsLocked() const +{ + return GetDoc()->getIDocumentFieldsAccess().IsExpFieldsLocked(); +} + +void SwEditShell::SetFieldUpdateFlags( SwFieldUpdateFlags eFlags ) +{ + getIDocumentSettingAccess().setFieldUpdateFlags( eFlags ); +} + +SwFieldUpdateFlags SwEditShell::GetFieldUpdateFlags() const +{ + return getIDocumentSettingAccess().getFieldUpdateFlags( false ); +} + +void SwEditShell::SetLabelDoc( bool bFlag ) +{ + GetDoc()->GetDocumentSettingManager().set(DocumentSettingId::LABEL_DOCUMENT, bFlag ); +} + +bool SwEditShell::IsLabelDoc() const +{ + return getIDocumentSettingAccess().get(DocumentSettingId::LABEL_DOCUMENT); +} + +void SwEditShell::ChangeAuthorityData(const SwAuthEntry* pNewData) +{ + GetDoc()->ChangeAuthorityData(pNewData); +} + +bool SwEditShell::IsAnyDatabaseFieldInDoc()const +{ + // Similar to: SwDoc::GetDBDesc + const SwFieldTypes * pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes(); + for(const auto & pFieldType : *pFieldTypes) + { + if(IsUsed(*pFieldType)) + { + switch(pFieldType->Which()) + { + case SwFieldIds::Database: + case SwFieldIds::DbNextSet: + case SwFieldIds::DbNumSet: + case SwFieldIds::DbSetNumber: + { + std::vector<SwFormatField*> vFields; + pFieldType->GatherFields(vFields); + return vFields.size(); + } + break; + default: break; + } + } + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edfldexp.cxx b/sw/source/core/edit/edfldexp.cxx new file mode 100644 index 0000000000..3247d05005 --- /dev/null +++ b/sw/source/core/edit/edfldexp.cxx @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <editsh.hxx> +#include <dbfld.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/sdb/DatabaseContext.hpp> +#include <doc.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <docary.hxx> +#include <fmtfld.hxx> + +using namespace com::sun::star; + +bool SwEditShell::IsFieldDataSourceAvailable(OUString& rUsedDataSource) const +{ + const SwFieldTypes* pFieldTypes = GetDoc()->getIDocumentFieldsAccess().GetFieldTypes(); + uno::Reference<uno::XComponentContext> xContext(::comphelper::getProcessComponentContext()); + uno::Reference<sdb::XDatabaseContext> xDBContext = sdb::DatabaseContext::create(xContext); + std::vector<SwFormatField*> vFields; + for (const auto& pFieldType : *pFieldTypes) + { + if (IsUsed(*pFieldType) && pFieldType->Which() == SwFieldIds::Database) + pFieldType->GatherFields(vFields); + } + if (!vFields.size()) + return true; + + const SwDBData& rData + = static_cast<SwDBFieldType*>(vFields.front()->GetField()->GetTyp())->GetDBData(); + try + { + return xDBContext->getByName(rData.sDataSource).hasValue(); + } + catch (uno::Exception const&) + { + rUsedDataSource = rData.sDataSource; + return false; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edfmt.cxx b/sw/source/core/edit/edfmt.cxx new file mode 100644 index 0000000000..ee291e82e8 --- /dev/null +++ b/sw/source/core/edit/edfmt.cxx @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <doc.hxx> +#include <IDocumentStylePoolAccess.hxx> +#include <editsh.hxx> +#include <pam.hxx> +#include <fchrfmt.hxx> +#include <frmfmt.hxx> +#include <charfmt.hxx> +#include <ndtxt.hxx> + +sal_uInt16 SwEditShell::GetCharFormatCount() const +{ + return GetDoc()->GetCharFormats()->size(); +} + +SwCharFormat& SwEditShell::GetCharFormat(sal_uInt16 nFormat) const +{ + return *((*(GetDoc()->GetCharFormats()))[nFormat]); +} + +SwCharFormat* SwEditShell::GetCurCharFormat() const +{ + SwCharFormat *pFormat = nullptr; + SfxItemSetFixed<RES_TXTATR_CHARFMT, RES_TXTATR_CHARFMT> aSet( GetDoc()->GetAttrPool() ); + const SwFormatCharFormat* pItem; + if( GetCurAttr( aSet ) && + (pItem = aSet.GetItemIfSet( RES_TXTATR_CHARFMT, false ) ) ) + pFormat = pItem->GetCharFormat(); + + return pFormat; +} + +void SwEditShell::FillByEx(SwCharFormat* pCharFormat) +{ + SwPaM* pPam = GetCursor(); + const SwContentNode* pCNd = pPam->GetPointContentNode(); + if( pCNd->IsTextNode() ) + { + SwTextNode const*const pTextNode(pCNd->GetTextNode()); + sal_Int32 nStt; + sal_Int32 nEnd; + if( pPam->HasMark() ) + { + const SwPosition* pPtPos = pPam->GetPoint(); + const SwPosition* pMkPos = pPam->GetMark(); + if( pPtPos->GetNode() == pMkPos->GetNode() ) // in the same node? + { + nStt = pPtPos->GetContentIndex(); + if( nStt < pMkPos->GetContentIndex() ) + nEnd = pMkPos->GetContentIndex(); + else + { + nEnd = nStt; + nStt = pMkPos->GetContentIndex(); + } + } + else + { + nStt = pMkPos->GetContentIndex(); + if( pPtPos->GetNode() < pMkPos->GetNode() ) + { + nEnd = nStt; + nStt = 0; + } + else + nEnd = pTextNode->GetText().getLength(); + } + } + else + nStt = nEnd = pPam->GetPoint()->GetContentIndex(); + + SfxItemSet aSet( mxDoc->GetAttrPool(), + pCharFormat->GetAttrSet().GetRanges() ); + pTextNode->GetParaAttr(aSet, nStt, nEnd, false, true, false, GetLayout()); + pCharFormat->SetFormatAttr( aSet ); + } + else if( pCNd->HasSwAttrSet() ) + pCharFormat->SetFormatAttr( *pCNd->GetpSwAttrSet() ); +} + +size_t SwEditShell::GetTableFrameFormatCount(bool bUsed) const +{ + return GetDoc()->GetTableFrameFormatCount(bUsed); +} + +SwFrameFormat& SwEditShell::GetTableFrameFormat(size_t nFormat, bool bUsed ) const +{ + return GetDoc()->GetTableFrameFormat(nFormat, bUsed ); +} + +OUString SwEditShell::GetUniqueTableName() const +{ + return GetDoc()->GetUniqueTableName(); +} + +SwCharFormat* SwEditShell::MakeCharFormat( const OUString& rName ) +{ + SwCharFormat* pDerivedFrom = GetDoc()->GetDfltCharFormat(); + + return GetDoc()->MakeCharFormat( rName, pDerivedFrom ); +} + +SwTextFormatColl* SwEditShell::GetTextCollFromPool( sal_uInt16 nId ) +{ + return GetDoc()->getIDocumentStylePoolAccess().GetTextCollFromPool( nId ); +} + +/// return the requested automatic format - base-class ! +SwFormat* SwEditShell::GetFormatFromPool( sal_uInt16 nId ) +{ + return GetDoc()->getIDocumentStylePoolAccess().GetFormatFromPool( nId ); +} + +SwPageDesc* SwEditShell::GetPageDescFromPool( sal_uInt16 nId ) +{ + return GetDoc()->getIDocumentStylePoolAccess().GetPageDescFromPool( nId ); +} + +bool SwEditShell::IsUsed( const sw::BroadcastingModify& rModify ) const +{ + return mxDoc->IsUsed( rModify ); +} + +const SwFlyFrameFormat* SwEditShell::FindFlyByName( const OUString& rName ) const +{ + return mxDoc->FindFlyByName(rName); +} + +SwCharFormat* SwEditShell::FindCharFormatByName( const OUString& rName ) const +{ + return mxDoc->FindCharFormatByName( rName ); +} + +SwTextFormatColl* SwEditShell::FindTextFormatCollByName( const OUString& rName ) const +{ + return mxDoc->FindTextFormatCollByName( rName ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edglbldc.cxx b/sw/source/core/edit/edglbldc.cxx new file mode 100644 index 0000000000..9e71db2fd6 --- /dev/null +++ b/sw/source/core/edit/edglbldc.cxx @@ -0,0 +1,374 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentState.hxx> +#include <editsh.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <docary.hxx> +#include <swundo.hxx> +#include <section.hxx> +#include <doctxm.hxx> +#include <edglbldc.hxx> + +bool SwEditShell::IsGlobalDoc() const +{ + return getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT); +} + +void SwEditShell::SetGlblDocSaveLinks( bool bFlag ) +{ + getIDocumentSettingAccess().set(DocumentSettingId::GLOBAL_DOCUMENT_SAVE_LINKS, bFlag); + if( !GetDoc()->getIDocumentState().IsModified() ) // Bug 57028 + { + GetDoc()->GetIDocumentUndoRedo().SetUndoNoResetModified(); + } + GetDoc()->getIDocumentState().SetModified(); +} + +bool SwEditShell::IsGlblDocSaveLinks() const +{ + return getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT_SAVE_LINKS); +} + +void SwEditShell::GetGlobalDocContent( SwGlblDocContents& rArr ) const +{ + rArr.clear(); + + if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) ) + return; + + // then all linked areas on the topmost level + SwDoc* pMyDoc = GetDoc(); + const SwSectionFormats& rSectFormats = pMyDoc->GetSections(); + + for( auto n = rSectFormats.size(); n; ) + { + const SwSection* pSect = rSectFormats[ --n ]->GetGlobalDocSection(); + if( pSect ) + { + std::unique_ptr<SwGlblDocContent> pNew; + switch( pSect->GetType() ) + { + case SectionType::ToxHeader: + break; // ignore + case SectionType::ToxContent: + assert( dynamic_cast<const SwTOXBaseSection*>( pSect) && "no TOXBaseSection!" ); + pNew.reset(new SwGlblDocContent( static_cast<const SwTOXBaseSection*>(pSect) )); + break; + + default: + pNew.reset(new SwGlblDocContent( pSect )); + break; + } + if (pNew) + rArr.insert( std::move(pNew) ); + } + } + + // and finally add the dummies (other text) + SwNode* pNd; + SwNodeOffset nSttIdx = pMyDoc->GetNodes().GetEndOfExtras().GetIndex() + 2; + for( SwGlblDocContents::size_type n = 0; n < rArr.size(); ++n ) + { + const SwGlblDocContent& rNew = *rArr[ n ]; + // Search from StartPos until rNew.DocPos for a content node. + // If one exists then a dummy entry is needed. + for( ; nSttIdx < rNew.GetDocPos(); ++nSttIdx ) + if( ( pNd = pMyDoc->GetNodes()[ nSttIdx ])->IsContentNode() + || pNd->IsSectionNode() || pNd->IsTableNode() ) + { + std::unique_ptr<SwGlblDocContent> pNew(new SwGlblDocContent( nSttIdx )); + if( rArr.insert( std::move(pNew) ).second ) + ++n; // to the next position + break; + } + + // set StartPosition to the end + nSttIdx = pMyDoc->GetNodes()[ rNew.GetDocPos() ]->EndOfSectionIndex(); + ++nSttIdx; + } + + // Should the end also be set? + if( !rArr.empty() ) + { + SwNodeOffset nNdEnd = pMyDoc->GetNodes().GetEndOfContent().GetIndex(); + for( ; nSttIdx < nNdEnd; ++nSttIdx ) + if( ( pNd = pMyDoc->GetNodes()[ nSttIdx ])->IsContentNode() + || pNd->IsSectionNode() || pNd->IsTableNode() ) + { + rArr.insert( std::make_unique<SwGlblDocContent>( nSttIdx ) ); + break; + } + } + else + { + std::unique_ptr<SwGlblDocContent> pNew(new SwGlblDocContent( + pMyDoc->GetNodes().GetEndOfExtras().GetIndex() + 2 )); + rArr.insert( std::move(pNew) ); + } +} + +void SwEditShell::InsertGlobalDocContent( const SwGlblDocContent& rInsPos, + SwSectionData & rNew) +{ + if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) ) + return; + + CurrShell aCurr( this ); + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor || IsTableMode() ) + ClearMark(); + + SwPosition& rPos = *pCursor->GetPoint(); + rPos.Assign( rInsPos.GetDocPos() ); + + bool bEndUndo = false; + SwDoc* pMyDoc = GetDoc(); + SwTextNode *const pTextNd = rPos.GetNode().GetTextNode(); + if( !pTextNd ) + { + bEndUndo = true; + pMyDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + rPos.Adjust(SwNodeOffset(-1)); + pMyDoc->getIDocumentContentOperations().AppendTextNode( rPos ); + } + + InsertSection( rNew ); + + if( bEndUndo ) + { + pMyDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + EndAllAction(); +} + +bool SwEditShell::InsertGlobalDocContent( const SwGlblDocContent& rInsPos, + const SwTOXBase& rTOX ) +{ + if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) ) + return false; + + CurrShell aCurr( this ); + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor || IsTableMode() ) + ClearMark(); + + SwPosition& rPos = *pCursor->GetPoint(); + rPos.Assign(rInsPos.GetDocPos()); + + bool bEndUndo = false; + SwDoc* pMyDoc = GetDoc(); + SwTextNode* pTextNd = rPos.GetNode().GetTextNode(); + if (!pTextNd || !pTextNd->GetText().getLength() || rPos.GetNodeIndex() + 1 == + pMyDoc->GetNodes().GetEndOfContent().GetIndex() ) + { + bEndUndo = true; + pMyDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + rPos.Adjust(SwNodeOffset(-1)); + pMyDoc->getIDocumentContentOperations().AppendTextNode( rPos ); + } + + InsertTableOf( rTOX ); + + if( bEndUndo ) + { + pMyDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + EndAllAction(); + + return true; +} + +bool SwEditShell::InsertGlobalDocContent( const SwGlblDocContent& rInsPos ) +{ + if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) ) + return false; + + CurrShell aCurr( this ); + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor || IsTableMode() ) + ClearMark(); + + SwPosition& rPos = *pCursor->GetPoint(); + rPos.Assign(rInsPos.GetDocPos() - 1); + + SwDoc* pMyDoc = GetDoc(); + pMyDoc->getIDocumentContentOperations().AppendTextNode( rPos ); + EndAllAction(); + return true; +} + +void SwEditShell::DeleteGlobalDocContent( const SwGlblDocContents& rArr , + size_t nDelPos ) +{ + if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) ) + return; + + CurrShell aCurr( this ); + StartAllAction(); + StartUndo( SwUndoId::START ); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor || IsTableMode() ) + ClearMark(); + + SwPosition& rPos = *pCursor->GetPoint(); + + SwDoc* pMyDoc = GetDoc(); + const SwGlblDocContent& rDelPos = *rArr[ nDelPos ]; + SwNodeOffset nDelIdx = rDelPos.GetDocPos(); + if( 1 == rArr.size() ) + { + // we need at least one node! + rPos.Assign(nDelIdx - 1); + + pMyDoc->getIDocumentContentOperations().AppendTextNode( rPos ); + ++nDelIdx; + } + + switch( rDelPos.GetType() ) + { + case GLBLDOC_UNKNOWN: + { + rPos.Assign(nDelIdx); + pCursor->SetMark(); + if( ++nDelPos < rArr.size() ) + rPos.Assign(rArr[ nDelPos ]->GetDocPos(), -1); + else + rPos.Assign(pMyDoc->GetNodes().GetEndOfContent(), -1); + if( !pMyDoc->getIDocumentContentOperations().DelFullPara( *pCursor ) ) + Delete(false); + } + break; + + case GLBLDOC_TOXBASE: + { + const SwTOXBaseSection* pTOX = static_cast<const SwTOXBaseSection*>(rDelPos.GetTOX()); + pMyDoc->DeleteTOX( *pTOX, true ); + } + break; + + case GLBLDOC_SECTION: + { + SwSectionFormat* pSectFormat = const_cast<SwSectionFormat*>(rDelPos.GetSection()->GetFormat()); + pMyDoc->DelSectionFormat( pSectFormat, true ); + } + break; + } + + EndUndo( SwUndoId::END ); + EndAllAction(); +} + +bool SwEditShell::MoveGlobalDocContent( const SwGlblDocContents& rArr , + size_t nFromPos, size_t nToPos, + size_t nInsPos ) +{ + if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) || + nFromPos >= rArr.size() || nToPos > rArr.size() || + nInsPos > rArr.size() || nFromPos >= nToPos || + ( nFromPos <= nInsPos && nInsPos <= nToPos ) ) + return false; + + CurrShell aCurr( this ); + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor || IsTableMode() ) + ClearMark(); + + SwDoc* pMyDoc = GetDoc(); + SwNodeRange aRg( pMyDoc->GetNodes(), rArr[ nFromPos ]->GetDocPos() ); + if( nToPos < rArr.size() ) + aRg.aEnd = rArr[ nToPos ]->GetDocPos(); + else + aRg.aEnd = pMyDoc->GetNodes().GetEndOfContent(); + + SwNodeIndex aInsPos( pMyDoc->GetNodes() ); + if( nInsPos < rArr.size() ) + aInsPos = rArr[ nInsPos ]->GetDocPos(); + else + aInsPos = pMyDoc->GetNodes().GetEndOfContent(); + + bool bRet = pMyDoc->getIDocumentContentOperations().MoveNodeRange( aRg, aInsPos.GetNode(), + SwMoveFlags::CREATEUNDOOBJ ); + + EndAllAction(); + return bRet; +} + +void SwEditShell::GotoGlobalDocContent( const SwGlblDocContent& rPos ) +{ + if( !getIDocumentSettingAccess().get(DocumentSettingId::GLOBAL_DOCUMENT) ) + return; + + CurrShell aCurr( this ); + SttCursorMove(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor || IsTableMode() ) + ClearMark(); + + SwPosition& rCursorPos = *pCursor->GetPoint(); + rCursorPos.Assign(rPos.GetDocPos()); + + SwDoc* pMyDoc = GetDoc(); + SwContentNode * pCNd = rCursorPos.GetNode().GetContentNode(); + if( !pCNd ) + pCNd = pMyDoc->GetNodes().GoNext( &rCursorPos ); + + EndCursorMove(); +} + +SwGlblDocContent::SwGlblDocContent( SwNodeOffset nPos ) +{ + m_eType = GLBLDOC_UNKNOWN; + m_PTR.pTOX = nullptr; + m_nDocPos = nPos; +} + +SwGlblDocContent::SwGlblDocContent( const SwTOXBaseSection* pTOX ) +{ + m_eType = GLBLDOC_TOXBASE; + m_PTR.pTOX = pTOX; + + const SwSectionNode* pSectNd = pTOX->GetFormat()->GetSectionNode(); + m_nDocPos = pSectNd ? pSectNd->GetIndex() : SwNodeOffset(0); +} + +SwGlblDocContent::SwGlblDocContent( const SwSection* pSect ) +{ + m_eType = GLBLDOC_SECTION; + m_PTR.pSect = pSect; + + const SwSectionNode* pSectNd = pSect->GetFormat()->GetSectionNode(); + m_nDocPos = pSectNd ? pSectNd->GetIndex() : SwNodeOffset(0); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edglss.cxx b/sw/source/core/edit/edglss.cxx new file mode 100644 index 0000000000..80310bab4f --- /dev/null +++ b/sw/source/core/edit/edglss.cxx @@ -0,0 +1,324 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> +#include <osl/endian.h> +#include <tools/urlobj.hxx> +#include <doc.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <pam.hxx> +#include <editsh.hxx> +#include <frmfmt.hxx> +#include <rootfrm.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <shellio.hxx> +#include <iodetect.hxx> +#include <frameformats.hxx> + +void SwEditShell::InsertGlossary( SwTextBlocks& rGlossary, const OUString& rStr ) +{ + StartAllAction(); + GetDoc()->InsertGlossary( rGlossary, rStr, *GetCursor(), this ); + EndAllAction(); +} + +/// convert current selection into text block and add to the text block document, incl. templates +sal_uInt16 SwEditShell::MakeGlossary( SwTextBlocks& rBlks, const OUString& rName, const OUString& rShortName, + bool bSaveRelFile, const OUString* pOnlyText ) +{ + SwDoc* pGDoc = rBlks.GetDoc(); + + OUString sBase; + if(bSaveRelFile) + { + INetURLObject aURL( rBlks.GetFileName() ); + sBase = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + rBlks.SetBaseURL( sBase ); + + if( pOnlyText ) + return rBlks.PutText( rShortName, rName, *pOnlyText ); + + rBlks.ClearDoc(); + if( rBlks.BeginPutDoc( rShortName, rName ) ) + { + rBlks.GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags_intern( RedlineFlags::DeleteRedlines ); + CopySelToDoc(*pGDoc); + rBlks.GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags_intern( RedlineFlags::NONE ); + return rBlks.PutDoc(); + } + + return USHRT_MAX; +} + +sal_uInt16 SwEditShell::SaveGlossaryDoc( SwTextBlocks& rBlock, + const OUString& rName, + const OUString& rShortName, + bool bSaveRelFile, + bool bOnlyText ) +{ + StartAllAction(); + + SwDoc* pGDoc = rBlock.GetDoc(); + SwDoc* pMyDoc = GetDoc(); + + OUString sBase; + if(bSaveRelFile) + { + INetURLObject aURL( rBlock.GetFileName() ); + sBase = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + rBlock.SetBaseURL( sBase ); + sal_uInt16 nRet = USHRT_MAX; + + if( bOnlyText ) + { + KillPams(); + + SwPaM* pCursor = GetCursor(); + + SwNodeIndex aStt( pMyDoc->GetNodes().GetEndOfExtras(), 1 ); + SwContentNode* pContentNd = pMyDoc->GetNodes().GoNext( &aStt ); + const SwNode* pNd = pContentNd->FindTableNode(); + if( !pNd ) + pNd = pContentNd; + + pCursor->GetPoint()->Assign(*pNd); + pCursor->SetMark(); + + // then until the end of the Node array + pCursor->GetPoint()->Assign(pMyDoc->GetNodes().GetEndOfContent().GetIndex()-1); + pContentNd = pCursor->GetPointContentNode(); + if( pContentNd ) + pCursor->GetPoint()->SetContent( pContentNd->Len() ); + + OUString sBuf; + GetSelectedText( sBuf, ParaBreakType::ToOnlyCR ); + if( !sBuf.isEmpty() ) + nRet = rBlock.PutText( rShortName, rName, sBuf ); + } + else + { + rBlock.ClearDoc(); + if( rBlock.BeginPutDoc( rShortName, rName ) ) + { + SwNodeIndex aStt( pMyDoc->GetNodes().GetEndOfExtras(), 1 ); + SwContentNode* pContentNd = pMyDoc->GetNodes().GoNext( &aStt ); + const SwNode* pNd = pContentNd->FindTableNode(); + if( !pNd ) pNd = pContentNd; + SwPaM aCpyPam( *pNd ); + aCpyPam.SetMark(); + + // then until the end of the nodes array + aCpyPam.GetPoint()->Assign(pMyDoc->GetNodes().GetEndOfContent().GetIndex()-1); + pContentNd = aCpyPam.GetPointContentNode(); + if(pContentNd) + aCpyPam.GetPoint()->SetContent( pContentNd->Len() ); + + aStt = pGDoc->GetNodes().GetEndOfExtras(); + pContentNd = pGDoc->GetNodes().GoNext( &aStt ); + SwPosition aInsPos( aStt ); + pMyDoc->getIDocumentContentOperations().CopyRange(aCpyPam, aInsPos, SwCopyFlags::CheckPosInFly); + + nRet = rBlock.PutDoc(); + } + } + EndAllAction(); + return nRet; +} + +/// copy all selections to the doc +bool SwEditShell::CopySelToDoc( SwDoc& rInsDoc ) +{ + SwNodes& rNds = rInsDoc.GetNodes(); + + SwNodeIndex aIdx( rNds.GetEndOfContent(), -1 ); + SwContentNode *const pContentNode = aIdx.GetNode().GetContentNode(); + SwPosition aPos( aIdx, pContentNode, pContentNode ? pContentNode->Len() : 0); + + bool bRet = false; + CurrShell aCurr( this ); + + rInsDoc.getIDocumentFieldsAccess().LockExpFields(); + + if( IsTableMode() ) + { + // Copy parts of a table: create a table with the width of the original one and copy the + // selected boxes. The sizes are corrected on a percentage basis. + + // search boxes using the layout + SwTableNode* pTableNd; + SwSelBoxes aBoxes; + GetTableSel( *this, aBoxes ); + if( !aBoxes.empty() && nullptr != (pTableNd = const_cast<SwTableNode*>(aBoxes[0] + ->GetSttNd()->FindTableNode()) )) + { + // check if the table name can be copied + bool bCpyTableNm = aBoxes.size() == pTableNd->GetTable().GetTabSortBoxes().size(); + if( bCpyTableNm ) + { + const OUString rTableName = pTableNd->GetTable().GetFrameFormat()->GetName(); + const sw::TableFrameFormats& rTableFormats = *rInsDoc.GetTableFrameFormats(); + for( auto n = rTableFormats.size(); n; ) + if( rTableFormats[ --n ]->GetName() == rTableName ) + { + bCpyTableNm = false; + break; + } + } + bRet = rInsDoc.InsCopyOfTable( aPos, aBoxes, nullptr, bCpyTableNm, false, pTableNd->GetTable().GetTableStyleName() ); + } + else + bRet = false; + } + else + { + bool bColSel = GetCursor_()->IsColumnSelection(); + if( bColSel && rInsDoc.IsClipBoard() ) + rInsDoc.SetColumnSelection( true ); + auto const oSelectAll(StartsWith_() != SwCursorShell::StartsWith::None + ? ExtendedSelectedAll() + : ::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode*>>>{}); + { + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( !rPaM.HasMark() ) + { + SwContentNode *const pNd = rPaM.GetPointContentNode(); + if (nullptr != pNd && + ( bColSel || !pNd->GetTextNode() ) ) + { + rPaM.SetMark(); + rPaM.Move( fnMoveForward, GoInContent ); + bRet = GetDoc()->getIDocumentContentOperations().CopyRange(rPaM, aPos, SwCopyFlags::CheckPosInFly) + || bRet; + rPaM.Exchange(); + rPaM.DeleteMark(); + } + } + else + { + // Make a copy, so that in case we need to adjust the selection + // for the purpose of copying, our shell cursor is not touched. + // (Otherwise we would have to restore it.) + SwPaM aPaM(*rPaM.GetMark(), *rPaM.GetPoint()); + if (oSelectAll) + { + // Selection starts at the first para of the first cell, + // but we want to copy the table and the start node before + // the first cell as well. + aPaM.Start()->Assign(*oSelectAll->first); + } + bRet = GetDoc()->getIDocumentContentOperations().CopyRange( aPaM, aPos, SwCopyFlags::CheckPosInFly) + || bRet; + } + } + } + } + + rInsDoc.getIDocumentFieldsAccess().UnlockExpFields(); + if( !rInsDoc.getIDocumentFieldsAccess().IsExpFieldsLocked() ) + rInsDoc.getIDocumentFieldsAccess().UpdateExpFields(nullptr, true); + + return bRet; +} + +/** Get text in a Selection + */ +void SwEditShell::GetSelectedText( OUString &rBuf, ParaBreakType nHndlParaBrk ) +{ + GetCursor(); // creates all cursors if needed + if( IsSelOnePara() ) + { + rBuf = GetSelText(); + if( ParaBreakType::ToBlank == nHndlParaBrk ) + { + rBuf = rBuf.replaceAll("\x0a", " "); + } + else if( IsSelFullPara() && + ParaBreakType::ToOnlyCR != nHndlParaBrk ) + { + rBuf += SAL_NEWLINE_STRING; + } + } + else if( IsSelection() ) + { + SvMemoryStream aStream; +#ifdef OSL_BIGENDIAN + aStream.SetEndian( SvStreamEndian::BIG ); +#else + aStream.SetEndian( SvStreamEndian::LITTLE ); +#endif + WriterRef xWrt; + SwReaderWriter::GetWriter( FILTER_TEXT, OUString(), xWrt ); + if( xWrt.is() ) + { + // write selected areas into an ASCII document + SwWriter aWriter( aStream, *this); + xWrt->SetShowProgress(false); + + switch( nHndlParaBrk ) + { + case ParaBreakType::ToBlank: + xWrt->m_bASCII_ParaAsBlank = true; + xWrt->m_bASCII_NoLastLineEnd = true; + break; + + case ParaBreakType::ToOnlyCR: + xWrt->m_bASCII_ParaAsCR = true; + xWrt->m_bASCII_NoLastLineEnd = true; + break; + } + + //JP 09.05.00: write as UNICODE ! (and not as ANSI) + SwAsciiOptions aAsciiOpt( xWrt->GetAsciiOptions() ); + aAsciiOpt.SetCharSet( RTL_TEXTENCODING_UCS2 ); + xWrt->SetAsciiOptions( aAsciiOpt ); + xWrt->m_bUCS2_WithStartChar = false; + xWrt->m_bHideDeleteRedlines = GetLayout()->IsHideRedlines(); + + if ( ! aWriter.Write(xWrt).IsError() ) + { + aStream.WriteUInt16( '\0' ); + + const sal_Unicode *p = static_cast<sal_Unicode const *>(aStream.GetData()); + if (p) + rBuf = OUString(p); + else + { + const sal_uInt64 nLen = aStream.GetSize(); + OSL_ENSURE( nLen/sizeof( sal_Unicode )<o3tl::make_unsigned(SAL_MAX_INT32), "Stream can't fit in OUString" ); + rtl_uString *pStr = rtl_uString_alloc(static_cast<sal_Int32>(nLen / sizeof( sal_Unicode ))); + aStream.Seek( 0 ); + aStream.ResetError(); + //endian specific?, yipes! + aStream.ReadBytes(pStr->buffer, nLen); + rBuf = OUString(pStr, SAL_NO_ACQUIRE); + } + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/editsh.cxx b/sw/source/core/edit/editsh.cxx new file mode 100644 index 0000000000..333eecd352 --- /dev/null +++ b/sw/source/core/edit/editsh.cxx @@ -0,0 +1,1098 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <hintids.hxx> +#include <osl/diagnose.h> +#include <vcl/commandevent.hxx> +#include <unotools/charclass.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <fmtsrnd.hxx> +#include <fmtinfmt.hxx> +#include <txtinet.hxx> +#include <frmfmt.hxx> +#include <charfmt.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentSettingAccess.hxx> +#include <IDocumentLinksAdministration.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentStatistics.hxx> +#include <IDocumentState.hxx> +#include <editsh.hxx> +#include <frame.hxx> +#include <cntfrm.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <flyfrm.hxx> +#include <swundo.hxx> +#include <calc.hxx> +#include <ndgrf.hxx> +#include <ndole.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <extinput.hxx> +#include <scriptinfo.hxx> +#include <unocrsrhelper.hxx> +#include <section.hxx> +#include <numrule.hxx> +#include <SwNodeNum.hxx> +#include <unocrsr.hxx> +#include <calbck.hxx> + +using namespace com::sun::star; + +void SwEditShell::Insert( sal_Unicode c, bool bOnlyCurrCursor ) +{ + StartAllAction(); + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + const bool bSuccess = GetDoc()->getIDocumentContentOperations().InsertString(rPaM, OUString(c)); + OSL_ENSURE( bSuccess, "Doc->Insert() failed." ); + + SaveTableBoxContent( rPaM.GetPoint() ); + if( bOnlyCurrCursor ) + break; + + } + + EndAllAction(); +} + +void SwEditShell::Insert2(const OUString &rStr, const bool bForceExpandHints ) +{ + StartAllAction(); + { + const SwInsertFlags nInsertFlags = + bForceExpandHints + ? (SwInsertFlags::FORCEHINTEXPAND | SwInsertFlags::EMPTYEXPAND) + : SwInsertFlags::EMPTYEXPAND; + + for(SwPaM& rCurrentCursor : getShellCursor( true )->GetRingContainer()) + { + //OPT: GetSystemCharSet + GetDoc()->getIDocumentContentOperations().SetIME(false); + const bool bSuccess = + GetDoc()->getIDocumentContentOperations().InsertString(rCurrentCursor, rStr, nInsertFlags); + OSL_ENSURE( bSuccess, "Doc->Insert() failed." ); + + if (bSuccess) + { + GetDoc()->UpdateRsid( rCurrentCursor, rStr.getLength() ); + + // Set paragraph rsid if beginning of paragraph + SwTextNode *const pTextNode = + rCurrentCursor.GetPoint()->GetNode().GetTextNode(); + if( pTextNode && pTextNode->Len() == 1) + GetDoc()->UpdateParRsid( pTextNode ); + } + + SaveTableBoxContent( rCurrentCursor.GetPoint() ); + + } + } + + // calculate cursor bidi level + SwCursor* pTmpCursor = GetCursor_(); + const bool bDoNotSetBidiLevel = ! pTmpCursor || + ( dynamic_cast<SwUnoCursor*>(pTmpCursor) != nullptr ); + + if ( ! bDoNotSetBidiLevel ) + { + SwNode& rNode = pTmpCursor->GetPoint()->GetNode(); + if ( rNode.IsTextNode() ) + { + sal_Int32 nPrevPos = pTmpCursor->GetPoint()->GetContentIndex(); + if ( nPrevPos ) + --nPrevPos; + + SwTextFrame const* pFrame; + SwScriptInfo *const pSI = SwScriptInfo::GetScriptInfo( + static_cast<SwTextNode&>(rNode), &pFrame, true); + + sal_uInt8 nLevel = 0; + if ( ! pSI ) + { + // seems to be an empty paragraph. + Point aPt; + std::pair<Point, bool> const tmp(aPt, false); + pFrame = static_cast<SwTextFrame*>( + static_cast<SwTextNode&>(rNode).getLayoutFrame( + GetLayout(), pTmpCursor->GetPoint(), &tmp)); + + SwScriptInfo aScriptInfo; + aScriptInfo.InitScriptInfo(static_cast<SwTextNode&>(rNode), + pFrame->GetMergedPara(), pFrame->IsRightToLeft()); + TextFrameIndex const iPrevPos(pFrame->MapModelToView( + &static_cast<SwTextNode&>(rNode), nPrevPos)); + nLevel = aScriptInfo.DirType( iPrevPos ); + } + else + { + if (TextFrameIndex(COMPLETE_STRING) != pSI->GetInvalidityA()) + { + // mystery why this doesn't use the other overload? + pSI->InitScriptInfo(static_cast<SwTextNode&>(rNode), pFrame->GetMergedPara()); + } + TextFrameIndex const iPrevPos(pFrame->MapModelToView( + &static_cast<SwTextNode&>(rNode), nPrevPos)); + nLevel = pSI->DirType(iPrevPos); + } + + pTmpCursor->SetCursorBidiLevel( nLevel ); + } + } + + SetInFrontOfLabel( false ); // #i27615# + + EndAllAction(); +} + +void SwEditShell::Overwrite(const OUString &rStr) +{ + StartAllAction(); + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( !GetDoc()->getIDocumentContentOperations().Overwrite(rPaM, rStr ) ) + { + OSL_FAIL( "Doc->getIDocumentContentOperations().Overwrite(Str) failed." ); + } + SaveTableBoxContent( rPaM.GetPoint() ); + } + EndAllAction(); +} + +void SwEditShell::SplitNode( bool bAutoFormat, bool bCheckTableStart ) +{ + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + // Here, a table cell becomes a normal text cell. + GetDoc()->ClearBoxNumAttrs( rPaM.GetPoint()->GetNode() ); + GetDoc()->getIDocumentContentOperations().SplitNode( *rPaM.GetPoint(), bCheckTableStart ); + } + + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + + if( bAutoFormat ) + AutoFormatBySplitNode(); + + ClearTableBoxContent(); + + EndAllAction(); +} + +bool SwEditShell::AppendTextNode() +{ + bool bRet = false; + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + GetDoc()->ClearBoxNumAttrs( rPaM.GetPoint()->GetNode() ); + bRet = GetDoc()->getIDocumentContentOperations().AppendTextNode( *rPaM.GetPoint()) || bRet; + } + + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + + ClearTableBoxContent(); + + EndAllAction(); + return bRet; +} + +// the returned SwGrfNode pointer is used in GetGraphic() and GetGraphicSize() +SwGrfNode * SwEditShell::GetGrfNode_() const +{ + SwGrfNode *pGrfNode = nullptr; + SwPaM* pCursor = GetCursor(); + if( !pCursor->HasMark() || + pCursor->GetPoint()->GetNode() == pCursor->GetMark()->GetNode() ) + pGrfNode = pCursor->GetPoint()->GetNode().GetGrfNode(); + + return pGrfNode; +} + +// returns a Graphic pointer if CurrentCursor->GetPoint() points to a SwGrfNode and +// GetMark is not set or points to the same Graphic +const Graphic* SwEditShell::GetGraphic( bool bWait ) const +{ + SwGrfNode* pGrfNode = GetGrfNode_(); + const Graphic* pGrf( nullptr ); + if ( pGrfNode ) + { + pGrf = &(pGrfNode->GetGrf(bWait && GraphicType::Default == pGrfNode->GetGrf().GetType())); + } + return pGrf; +} + +bool SwEditShell::IsLinkedGrfSwapOut() const +{ + SwGrfNode *pGrfNode = GetGrfNode_(); + return pGrfNode && + pGrfNode->IsLinkedFile() && + GraphicType::Default == pGrfNode->GetGrfObj().GetType(); +} + +const GraphicObject* SwEditShell::GetGraphicObj() const +{ + SwGrfNode* pGrfNode = GetGrfNode_(); + return pGrfNode ? &(pGrfNode->GetGrfObj()) : nullptr; +} + +const GraphicAttr* SwEditShell::GetGraphicAttr( GraphicAttr& rGA ) const +{ + SwGrfNode* pGrfNode = GetGrfNode_(); + const SwFrame* pFrame = GetCurrFrame(false); + return pGrfNode ? &(pGrfNode->GetGraphicAttr( rGA, pFrame )) : nullptr; +} + +GraphicType SwEditShell::GetGraphicType() const +{ + SwGrfNode *pGrfNode = GetGrfNode_(); + return pGrfNode ? pGrfNode->GetGrfObj().GetType() : GraphicType::NONE; +} + +// returns the size of a graphic in <rSz> if CurrentCursor->GetPoint() points to a SwGrfNode and +// GetMark is not set or points to the same graphic +bool SwEditShell::GetGrfSize(Size& rSz) const +{ + SwNoTextNode* pNoTextNd; + SwPaM* pCurrentCursor = GetCursor(); + if( ( !pCurrentCursor->HasMark() + || pCurrentCursor->GetPoint()->GetNode() == pCurrentCursor->GetMark()->GetNode() ) + && nullptr != ( pNoTextNd = pCurrentCursor->GetPointNode().GetNoTextNode() ) ) + { + rSz = pNoTextNd->GetTwipSize(); + return true; + } + return false; + +} + +/// Read again if graphic is not OK and replace old one +void SwEditShell::ReRead( const OUString& rGrfName, const OUString& rFltName, + const Graphic* pGraphic ) +{ + StartAllAction(); + mxDoc->getIDocumentContentOperations().ReRead( *GetCursor(), rGrfName, rFltName, pGraphic ); + EndAllAction(); +} + +/// Returns the name and the filter name of a graphic if the pointer is on a graphic. +/// If a String-pointer is != 0 then return corresponding name. +void SwEditShell::GetGrfNms( OUString* pGrfName, OUString* pFltName, + const SwFlyFrameFormat* pFormat ) const +{ + OSL_ENSURE( pGrfName || pFltName, "No parameters" ); + if( pFormat ) + SwDoc::GetGrfNms( *pFormat, pGrfName, pFltName ); + else + { + SwGrfNode *pGrfNode = GetGrfNode_(); + if( pGrfNode && pGrfNode->IsLinkedFile() ) + pGrfNode->GetFileFilterNms( pGrfName, pFltName ); + } +} + +const tools::PolyPolygon *SwEditShell::GetGraphicPolygon() const +{ + SwNoTextNode *pNd = GetCursor()->GetPointNode().GetNoTextNode(); + return pNd->HasContour(); +} + +void SwEditShell::SetGraphicPolygon( const tools::PolyPolygon *pPoly ) +{ + SwNoTextNode *pNd = GetCursor()->GetPointNode().GetNoTextNode(); + StartAllAction(); + pNd->SetContour( pPoly ); + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pNd->getLayoutFrame(GetLayout())->GetUpper()); + const SwFormatSurround &rSur = pFly->GetFormat()->GetSurround(); + pFly->GetFormat()->CallSwClientNotify(sw::LegacyModifyHint(&rSur, &rSur)); + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); +} + +void SwEditShell::ClearAutomaticContour() +{ + SwNoTextNode *pNd = GetCursor()->GetPointNode().GetNoTextNode(); + OSL_ENSURE( pNd, "is no NoTextNode!" ); + if( pNd->HasAutomaticContour() ) + { + StartAllAction(); + pNd->SetContour( nullptr ); + SwFlyFrame *pFly = static_cast<SwFlyFrame*>(pNd->getLayoutFrame(GetLayout())->GetUpper()); + const SwFormatSurround &rSur = pFly->GetFormat()->GetSurround(); + pFly->GetFormat()->CallSwClientNotify(sw::LegacyModifyHint(&rSur, &rSur)); + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); + } +} + +/** Get OLE object at pointer. + * + * Returns a pointer to a SvInPlaceObjectRef if CurrentCursor->GetPoint() points to a SwOLENode and + * GetMark is not set or points to the same object reference. Gets this pointer from the Doc + * if the object should be searched by name. + */ +svt::EmbeddedObjectRef& SwEditShell::GetOLEObject() const +{ + OSL_ENSURE( CNT_OLE == GetCntType(), "GetOLEObj: no OLENode." ); + OSL_ENSURE( !GetCursor()->HasMark() || + (GetCursor()->HasMark() && + GetCursor()->GetPoint()->GetNode() == GetCursor()->GetMark()->GetNode()), + "GetOLEObj: no OLENode." ); + + SwOLENode *pOLENode = GetCursor()->GetPointNode().GetOLENode(); + OSL_ENSURE( pOLENode, "GetOLEObj: no OLENode." ); + SwOLEObj& rOObj = pOLENode->GetOLEObj(); + return rOObj.GetObject(); +} + +bool SwEditShell::HasOLEObj( std::u16string_view rName ) const +{ + SwStartNode *pStNd; + SwNodeIndex aIdx( *GetNodes().GetEndOfAutotext().StartOfSectionNode(), 1 ); + while ( nullptr != (pStNd = aIdx.GetNode().GetStartNode()) ) + { + ++aIdx; + SwNode& rNd = aIdx.GetNode(); + if( rNd.IsOLENode() && + rName == static_cast<SwOLENode&>(rNd).GetChartTableName() && + static_cast<SwOLENode&>(rNd).getLayoutFrame( GetLayout() ) ) + return true; + + aIdx.Assign( *pStNd->EndOfSectionNode(), + 1 ); + } + return false; +} + +void SwEditShell::SetChartName( const OUString &rName ) +{ + SwOLENode *pONd = GetCursor()->GetPointNode().GetOLENode(); + OSL_ENSURE( pONd, "ChartNode not found" ); + pONd->SetChartTableName( rName ); +} + +void SwEditShell::UpdateCharts( const OUString& rName ) +{ + GetDoc()->UpdateCharts( rName ); +} + +/// change table name +void SwEditShell::SetTableName( SwFrameFormat& rTableFormat, const OUString &rNewName ) +{ + GetDoc()->SetTableName( rTableFormat, rNewName ); +} + +/// request current word +OUString SwEditShell::GetCurWord() const +{ + const SwPaM& rPaM = *GetCursor(); + const SwTextNode* pNd = rPaM.GetPointNode().GetTextNode(); + if (!pNd) + { + return OUString(); + } + SwTextFrame const*const pFrame(static_cast<SwTextFrame*>(pNd->getLayoutFrame(GetLayout()))); + if (pFrame) + { + return pFrame->GetCurWord(*rPaM.GetPoint()); + } + return OUString(); +} + +void SwEditShell::UpdateDocStat( ) +{ + StartAllAction(); + GetDoc()->getIDocumentStatistics().UpdateDocStat( false, true ); + EndAllAction(); +} + +const SwDocStat& SwEditShell::GetUpdatedDocStat() +{ + StartAllAction(); + const SwDocStat &rRet = GetDoc()->getIDocumentStatistics().GetUpdatedDocStat( false, true ); + EndAllAction(); + return rRet; +} + +/// get the reference of a given name in the Doc +const SwFormatRefMark* SwEditShell::GetRefMark( std::u16string_view rName ) const +{ + return GetDoc()->GetRefMark( rName ); +} + +/// get the names of all references in a Doc +sal_uInt16 SwEditShell::GetRefMarks( std::vector<OUString>* pStrings ) const +{ + return GetDoc()->GetRefMarks( pStrings ); +} + +OUString SwEditShell::GetDropText( const sal_Int32 nChars ) const +{ + /* + * pb: made changes for #i74939# + * + * always return a string even though there is a selection + */ + + OUString aText; + SwPaM* pCursor = GetCursor(); + if ( IsMultiSelection() ) + { + // if a multi selection exists, search for the first line + // -> it is the cursor with the lowest index + SwNodeOffset nIndex = pCursor->GetMark()->GetNodeIndex(); + bool bPrev = true; + SwPaM* pLast = pCursor; + SwPaM* pTemp = pCursor; + while ( bPrev ) + { + SwPaM* pPrev2 = pTemp->GetPrev(); + bPrev = ( pPrev2 && pPrev2 != pLast ); + if ( bPrev ) + { + pTemp = pPrev2; + SwNodeOffset nTemp = pPrev2->GetMark()->GetNodeIndex(); + if ( nTemp < nIndex ) + { + nIndex = nTemp; + pCursor = pPrev2; + } + } + } + } + + SwTextNode const*const pTextNd = pCursor->GetMarkNode().GetTextNode(); + if( pTextNd ) + { + SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>( + pTextNd->getLayoutFrame(GetLayout()))); + SAL_WARN_IF(!pTextFrame, "sw.core", "GetDropText cursor has no frame?"); + if (pTextFrame) + { + TextFrameIndex const nDropLen(pTextFrame->GetDropLen(TextFrameIndex(nChars))); + aText = pTextFrame->GetText().copy(0, sal_Int32(nDropLen)); + } + } + + return aText; +} + +void SwEditShell::ReplaceDropText( const OUString &rStr, SwPaM* pPaM ) +{ + SwPaM* pCursor = pPaM ? pPaM : GetCursor(); + if( !(pCursor->GetPoint()->GetNode() == pCursor->GetMark()->GetNode() && + pCursor->GetPointNode().GetTextNode()->IsTextNode()) ) + return; + + StartAllAction(); + + const SwNode& rNd = pCursor->GetPoint()->GetNode(); + SwPaM aPam( rNd, rStr.getLength(), rNd, 0 ); + SwTextFrame const*const pTextFrame(static_cast<SwTextFrame const*>( + rNd.GetTextNode()->getLayoutFrame(GetLayout()))); + if (pTextFrame) + { + *aPam.GetPoint() = pTextFrame->MapViewToModelPos(TextFrameIndex(0)); + *aPam.GetMark() = pTextFrame->MapViewToModelPos(TextFrameIndex( + std::min(rStr.getLength(), pTextFrame->GetText().getLength()))); + } + if( !GetDoc()->getIDocumentContentOperations().Overwrite( aPam, rStr ) ) + { + OSL_FAIL( "Doc->getIDocumentContentOperations().Overwrite(Str) failed." ); + } + + EndAllAction(); +} + +OUString SwEditShell::Calculate() +{ + OUStringBuffer aFormel; // the final formula + SwCalc aCalc( *GetDoc() ); + const CharClass& rCC = GetAppCharClass(); + + for(SwPaM& rCurrentPaM : GetCursor()->GetNext()->GetRingContainer()) + { + SwTextNode* pTextNd = rCurrentPaM.GetPointNode().GetTextNode(); + if(pTextNd) + { + const SwPosition *pStart = rCurrentPaM.Start(), *pEnd = rCurrentPaM.End(); + const sal_Int32 nStt = pStart->GetContentIndex(); + OUString aStr = pTextNd->GetExpandText(GetLayout(), + nStt, pEnd->GetContentIndex() - nStt); + + aStr = rCC.lowercase( aStr ); + + bool bValidFields = false; + sal_Int32 nPos = 0; + + while( nPos < aStr.getLength() ) + { + sal_Unicode ch = aStr[ nPos++ ]; + if( rCC.isLetter( aStr, nPos-1 ) || ch == '_' ) + { + sal_Int32 nTmpStt = nPos-1; + while( nPos < aStr.getLength() && + 0 != ( ch = aStr[ nPos++ ]) && + (rCC.isLetterNumeric( aStr, nPos - 1 ) || + ch == '_'|| ch == '.' )) + ; + + if( nPos < aStr.getLength() ) + --nPos; + + OUString sVar = aStr.copy( nTmpStt, nPos - nTmpStt ); + if( !::FindOperator( sVar ) && + (aCalc.GetVarTable().find(sVar) != aCalc.GetVarTable().end() || + aCalc.VarLook( sVar )) ) + { + if( !bValidFields ) + { + GetDoc()->getIDocumentFieldsAccess().FieldsToCalc( aCalc, + pStart->GetNodeIndex(), + pStart->GetContentIndex() ); + bValidFields = true; + } + aFormel.append("(" + aCalc.GetStrResult( aCalc.VarLook( sVar )->nValue ) + ")"); + } + else + aFormel.append(sVar); + } + else + aFormel.append(ch); + } + } + } + + return aCalc.GetStrResult( aCalc.Calculate(aFormel.makeStringAndClear()) ); +} + +sfx2::LinkManager& SwEditShell::GetLinkManager() +{ + return mxDoc->getIDocumentLinksAdministration().GetLinkManager(); +} + +void *SwEditShell::GetIMapInventor() const +{ + // The node on which the cursor points should be sufficient as a unique identifier + return static_cast<void*>(&(GetCursor()->GetPointNode())); +} + +// #i73788# +Graphic SwEditShell::GetIMapGraphic() const +{ + // returns always a graphic if the cursor is in a Fly + CurrShell aCurr( const_cast<SwEditShell*>(this) ); + Graphic aRet; + SwPaM* pCursor = GetCursor(); + if ( !pCursor->HasMark() ) + { + SwNode& rNd =pCursor->GetPointNode(); + if( rNd.IsGrfNode() ) + { + SwGrfNode & rGrfNode(static_cast<SwGrfNode&>(rNd)); + aRet = rGrfNode.GetGrf(GraphicType::Default == rGrfNode.GetGrf().GetType()); + } + else if ( rNd.IsOLENode() ) + { + if (const Graphic* pGraphic = static_cast<SwOLENode&>(rNd).GetGraphic()) + aRet = *pGraphic; + } + else + { + SwFlyFrame* pFlyFrame = rNd.GetContentNode()->getLayoutFrame( GetLayout() )->FindFlyFrame(); + if(pFlyFrame) + aRet = pFlyFrame->GetFormat()->MakeGraphic(); + } + } + return aRet; +} + +bool SwEditShell::InsertURL( const SwFormatINetFormat& rFormat, const OUString& rStr, bool bKeepSelection ) +{ + // URL and hint text (directly or via selection) necessary + if( rFormat.GetValue().isEmpty() || ( rStr.isEmpty() && !HasSelection() ) ) + return false; + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_INSERT_URLTXT, nullptr); + bool bInsText = true; + + if( !rStr.isEmpty() ) + { + SwPaM* pCursor = GetCursor(); + if( pCursor->HasMark() && *pCursor->GetPoint() != *pCursor->GetMark() ) + { + // Selection existent, multi selection? + bool bDelText = true; + if( !pCursor->IsMultiSelection() ) + { + // simple selection -> check the text + const OUString sText(comphelper::string::stripEnd(GetSelText(), ' ')); + if( sText == rStr ) + bDelText = bInsText = false; + } + else if( rFormat.GetValue() == rStr ) // Are Name and URL equal? + bDelText = bInsText = false; + + if( bDelText ) + Delete(true); + } + else if( pCursor->IsMultiSelection() && rFormat.GetValue() == rStr ) + bInsText = false; + + if( bInsText ) + { + Insert2( rStr ); + SetMark(); + ExtendSelection( false, rStr.getLength() ); + } + } + else + bInsText = false; + + SetAttrItem( rFormat ); + if (bInsText && !IsCursorPtAtEnd()) + SwapPam(); + if(!bKeepSelection) + ClearMark(); + if( bInsText ) + DontExpandFormat(); + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::UI_INSERT_URLTXT, nullptr ); + EndAllAction(); + return true; +} + +void SwEditShell::GetINetAttrs(SwGetINetAttrs& rArr, bool bIncludeInToxContent) +{ + rArr.clear(); + + const SwCharFormats* pFormats = GetDoc()->GetCharFormats(); + for( auto n = pFormats->size(); 1 < n; ) + { + SwIterator<SwTextINetFormat,SwCharFormat> aIter(*(*pFormats)[--n]); + for( SwTextINetFormat* pFnd = aIter.First(); pFnd; pFnd = aIter.Next() ) + { + SwTextNode const*const pTextNd(pFnd->GetpTextNode()); + SwTextFrame const*const pFrame(pTextNd + ? static_cast<SwTextFrame const*>(pTextNd->getLayoutFrame(GetLayout())) + : nullptr); + if (nullptr != pTextNd && nullptr != pFrame + && pTextNd->GetNodes().IsDocNodes() + // check it's not fully deleted + && pFrame->MapModelToView(pTextNd, pFnd->GetStart()) + != pFrame->MapModelToView(pTextNd, *pFnd->GetEnd())) + { + // tdf#52113, tdf#148312 Don't include table of contents hyperlinks in the + // Navigator content tree Hyperlinks entries + if (!bIncludeInToxContent) + { + if(const SwSectionNode* pSectNd = pTextNd->FindSectionNode()) + { + SectionType eType = pSectNd->GetSection().GetType(); + if(SectionType::ToxContent == eType) + continue; + } + } + + SwTextINetFormat& rAttr = *pFnd; + OUString sText( pTextNd->GetExpandText(GetLayout(), + rAttr.GetStart(), *rAttr.GetEnd() - rAttr.GetStart()) ); + + sText = sText.replaceAll("\x0a", ""); + sText = comphelper::string::strip(sText, ' '); + + if( !sText.isEmpty() ) + { + rArr.emplace_back(sText, rAttr); + } + } + } + } +} + +/// If the cursor is in an INetAttribute then it will be deleted completely (incl. hint text, the +/// latter is needed for drag & drop) +void SwEditShell::DelINetAttrWithText() +{ + bool bRet = SelectTextAttr( RES_TXTATR_INETFMT, false ); + if( bRet ) + DeleteSel(*GetCursor(), true); +} + +/// Set the DontExpand flag at the text character attributes +bool SwEditShell::DontExpandFormat() +{ + bool bRet = false; + if( !IsTableMode() && GetDoc()->DontExpandFormat( *GetCursor()->GetPoint() )) + { + bRet = true; + CallChgLnk(); + } + return bRet; +} + +SvNumberFormatter* SwEditShell::GetNumberFormatter() +{ + return GetDoc()->GetNumberFormatter(); +} + +bool SwEditShell::ConvertFieldsToText() +{ + StartAllAction(); + bool bRet = GetDoc()->ConvertFieldsToText(*GetLayout()); + EndAllAction(); + return bRet; +} + +void SwEditShell::SetNumberingRestart() +{ + StartAllAction(); + Push(); + // iterate over all text contents - body, frames, header, footer, footnote text + SwPaM* pCursor = GetCursor(); + for(int i = 0; i < 2; i++) + { + if(!i) + MakeFindRange(SwDocPositions::Start, SwDocPositions::End, pCursor); // body content + else + MakeFindRange(SwDocPositions::OtherStart, SwDocPositions::OtherEnd, pCursor); // extra content + SwPosition* pSttPos = pCursor->Start(), *pEndPos = pCursor->End(); + SwNodeOffset nCurrNd = pSttPos->GetNodeIndex(); + SwNodeOffset nEndNd = pEndPos->GetNodeIndex(); + if( nCurrNd <= nEndNd ) + { + SwContentFrame* pContentFrame; + bool bGoOn = true; + // iterate over all paragraphs + while( bGoOn ) + { + SwNode* pNd = GetDoc()->GetNodes()[ nCurrNd ]; + switch( pNd->GetNodeType() ) + { + case SwNodeType::Text: + pContentFrame = static_cast<SwTextNode*>(pNd)->getLayoutFrame( GetLayout() ); + if( nullptr != pContentFrame ) + { + // skip hidden frames - ignore protection! + if( !static_cast<SwTextFrame*>(pContentFrame)->IsHiddenNow() ) + { + // if the node is numbered and the starting value of the numbering equals the + // start value of the numbering rule then set this value as hard starting value + + // get the node num + // OD 2005-11-09 + SwTextNode* pTextNd( pNd->GetTextNode() ); + SwNumRule* pNumRule( pTextNd->GetNumRule() ); + + // sw_redlinehide: not sure what this should do, only called from mail-merge + bool bIsNodeNum = + ( pNumRule && pTextNd->GetNum() && + ( pTextNd->HasNumber() || pTextNd->HasBullet() ) && + pTextNd->IsCountedInList() && + !pTextNd->IsListRestart() ); + if (bIsNodeNum) + { + int nListLevel = pTextNd->GetActualListLevel(); + + if (nListLevel < 0) + nListLevel = 0; + + if (nListLevel >= MAXLEVEL) + nListLevel = MAXLEVEL - 1; + + bIsNodeNum = pTextNd->GetNum()->GetNumber() == + pNumRule->Get( o3tl::narrowing<sal_uInt16>(nListLevel) ).GetStart(); + } + if (bIsNodeNum) + { + // now set the start value as attribute + SwPosition aCurrentNode(*pNd); + GetDoc()->SetNumRuleStart( aCurrentNode ); + } + } + } + break; + case SwNodeType::Section: + // skip hidden sections - ignore protection! + if(static_cast<SwSectionNode*>(pNd)->GetSection().IsHidden() ) + nCurrNd = pNd->EndOfSectionIndex(); + break; + default: break; + } + + bGoOn = nCurrNd < nEndNd; + ++nCurrNd; + } + } + } + + Pop(PopMode::DeleteCurrent); + EndAllAction(); +} + +sal_Int32 SwEditShell::GetLineCount() +{ + sal_Int32 nRet = 0; + CalcLayout(); + SwPaM* pPam = GetCursor(); + SwNodeIndex aStart( pPam->GetPoint()->GetNode() ); + SwContentNode* pCNd; + SwContentFrame *pContentFrame = nullptr; + + aStart = SwNodeOffset(0); + + while( nullptr != ( pCNd = GetDoc()->GetNodes().GoNextSection( + &aStart, true, false )) ) + { + if( nullptr != ( pContentFrame = pCNd->getLayoutFrame( GetLayout() ) ) && pContentFrame->IsTextFrame() ) + { + SwTextFrame *const pFrame(static_cast<SwTextFrame*>(pContentFrame)); + nRet = nRet + pFrame->GetLineCount(TextFrameIndex(COMPLETE_STRING)); + if (GetLayout()->HasMergedParas()) + { + if (auto const*const pMerged = pFrame->GetMergedPara()) + { + aStart = *pMerged->pLastNode; + } + } + } + } + return nRet; +} + +tools::Long SwEditShell::CompareDoc( const SwDoc& rDoc ) +{ + StartAllAction(); + tools::Long nRet = GetDoc()->CompareDoc( rDoc ); + EndAllAction(); + return nRet; +} + +tools::Long SwEditShell::MergeDoc( const SwDoc& rDoc ) +{ + StartAllAction(); + tools::Long nRet = GetDoc()->MergeDoc( rDoc ); + EndAllAction(); + return nRet; +} + +const SwFootnoteInfo& SwEditShell::GetFootnoteInfo() const +{ + return GetDoc()->GetFootnoteInfo(); +} + +void SwEditShell::SetFootnoteInfo(const SwFootnoteInfo& rInfo) +{ + StartAllAction(); + CurrShell aCurr( this ); + GetDoc()->SetFootnoteInfo(rInfo); + CallChgLnk(); + EndAllAction(); +} + +const SwEndNoteInfo& SwEditShell::GetEndNoteInfo() const +{ + return GetDoc()->GetEndNoteInfo(); +} + +void SwEditShell::SetEndNoteInfo(const SwEndNoteInfo& rInfo) +{ + StartAllAction(); + CurrShell aCurr( this ); + GetDoc()->SetEndNoteInfo(rInfo); + EndAllAction(); +} + +const SwLineNumberInfo& SwEditShell::GetLineNumberInfo() const +{ + return GetDoc()->GetLineNumberInfo(); +} + +void SwEditShell::SetLineNumberInfo(const SwLineNumberInfo& rInfo) +{ + StartAllAction(); + CurrShell aCurr( this ); + GetDoc()->SetLineNumberInfo(rInfo); + AddPaintRect( GetLayout()->getFrameArea() ); + EndAllAction(); +} + +sal_uInt16 SwEditShell::GetLinkUpdMode() const +{ + return getIDocumentSettingAccess().getLinkUpdateMode( false ); +} + +void SwEditShell::SetLinkUpdMode( sal_uInt16 nMode ) +{ + getIDocumentSettingAccess().setLinkUpdateMode( nMode ); +} + +// Interface for TextInputData - (for text input of japanese/chinese characters) +void SwEditShell::CreateExtTextInput(LanguageType eInputLanguage) +{ + SwExtTextInput* pRet = GetDoc()->CreateExtTextInput( *GetCursor() ); + pRet->SetLanguage(eInputLanguage); + pRet->SetOverwriteCursor( SwCursorShell::IsOverwriteCursor() ); +} + +OUString SwEditShell::DeleteExtTextInput( bool bInsText ) +{ + const SwPosition& rPos = *GetCursor()->GetPoint(); + SwExtTextInput* pDel = GetDoc()->GetExtTextInput( rPos.GetNode(), + rPos.GetContentIndex() ); + if( !pDel ) + { + //JP 25.10.2001: under UNIX the cursor is moved before the Input- + // Engine event comes in. So take any - normally there + // exist only one at the time. -- Task 92016 + pDel = GetDoc()->GetExtTextInput(); + } + OUString sRet; + if( pDel ) + { + OUString sTmp; + SwUnoCursorHelper::GetTextFromPam(*pDel, sTmp); + sRet = sTmp; + CurrShell aCurr( this ); + StartAllAction(); + pDel->SetInsText( bInsText ); + SetOverwriteCursor( pDel->IsOverwriteCursor() ); + const SwPosition aPos( *pDel->GetPoint() ); + GetDoc()->DeleteExtTextInput( pDel ); + + // In this case, the "replace" function did not set the cursor + // to the original position. Therefore we have to do this manually. + if ( ! bInsText && IsOverwriteCursor() ) + *GetCursor()->GetPoint() = aPos; + + EndAllAction(); + } + return sRet; +} + +void SwEditShell::SetExtTextInputData( const CommandExtTextInputData& rData ) +{ + SwPaM* pCurrentCursor = GetCursor(); + const SwPosition& rPos = *pCurrentCursor->GetPoint(); + SwExtTextInput* pInput = GetDoc()->GetExtTextInput( rPos.GetNode() ); + if( !pInput ) + return; + + StartAllAction(); + CurrShell aCurr( this ); + + if( !rData.IsOnlyCursorChanged() ) + pInput->SetInputData( rData ); + // position cursor + const SwPosition& rStt = *pInput->Start(); + const sal_Int32 nNewCursorPos = rStt.GetContentIndex() + rData.GetCursorPos(); + + // ugly but works + ShowCursor(); + const sal_Int32 nDiff = nNewCursorPos - rPos.GetContentIndex(); + if( nDiff != 0) + { + bool bLeft = nDiff < 0; + sal_Int32 nMaxGuard = std::abs(nDiff); + while (true) + { + auto nOldPos = pCurrentCursor->GetPoint()->GetContentIndex(); + if (bLeft) + Left(1, SwCursorSkipMode::Chars); + else + Right(1, SwCursorSkipMode::Chars); + auto nNewPos = pCurrentCursor->GetPoint()->GetContentIndex(); + + // expected success + if (nNewPos == nNewCursorPos) + break; + + if (nNewPos == nOldPos) + { + // if there was no movement, we have failed for some reason + SAL_WARN("sw.core", "IM cursor move failed"); + break; + } + + if (--nMaxGuard == 0) + { + // if it takes more cursor moves than there are utf-16 chars to move past + // something has probably gone wrong + SAL_WARN("sw.core", "IM abandoning cursor positioning"); + break; + } + } + } + + SetOverwriteCursor( rData.IsCursorOverwrite() ); + + EndAllAction(); + + if( !rData.IsCursorVisible() ) // must be called after the EndAction + HideCursor(); + +} + +void SwEditShell::TransliterateText( TransliterationFlags nType ) +{ + utl::TransliterationWrapper aTrans( ::comphelper::getProcessComponentContext(), nType ); + StartAllAction(); + CurrShell aCurr( this ); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor ) + { + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( rPaM.HasMark() ) + GetDoc()->getIDocumentContentOperations().TransliterateText( rPaM, aTrans ); + } + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + } + else + GetDoc()->getIDocumentContentOperations().TransliterateText( *pCursor, aTrans ); + + EndAllAction(); +} + +void SwEditShell::CountWords( SwDocStat& rStat ) const +{ + for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( rPaM.HasMark() ) + SwDoc::CountWords( rPaM, rStat ); + + } +} + +void SwEditShell::ApplyViewOptions( const SwViewOption &rOpt ) +{ + SwCursorShell::StartAction(); + SwViewShell::ApplyViewOptions( rOpt ); + SwEditShell::EndAction(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edlingu.cxx b/sw/source/core/edit/edlingu.cxx new file mode 100644 index 0000000000..5731d2b2be --- /dev/null +++ b/sw/source/core/edit/edlingu.cxx @@ -0,0 +1,1731 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/linguistic2/ProofreadingResult.hpp> +#include <com/sun/star/linguistic2/XProofreadingIterator.hpp> +#include <com/sun/star/linguistic2/XHyphenatedWord.hpp> +#include <com/sun/star/linguistic2/XLinguProperties.hpp> +#include <com/sun/star/text/XFlatParagraph.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <o3tl/any.hxx> + +#include <unoflatpara.hxx> + +#include <strings.hrc> +#include <hintids.hxx> +#include <osl/diagnose.h> +#include <unotools/linguprops.hxx> +#include <linguistic/lngprops.hxx> +#include <editeng/langitem.hxx> +#include <editeng/SpellPortions.hxx> +#include <svl/languageoptions.hxx> +#include <editsh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <rootfrm.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <ndtxt.hxx> +#include <viewopt.hxx> +#include <SwGrammarMarkUp.hxx> +#include <mdiexp.hxx> +#include <cntfrm.hxx> +#include <splargs.hxx> +#include <redline.hxx> +#include <docary.hxx> +#include <docsh.hxx> +#include <txatbase.hxx> +#include <txtfrm.hxx> +#include <comphelper/propertyvalue.hxx> + +using namespace ::svx; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::linguistic2; + +namespace { + +class SwLinguIter +{ + SwEditShell* m_pSh; + std::unique_ptr<SwPosition> m_pStart; + std::unique_ptr<SwPosition> m_pEnd; + std::unique_ptr<SwPosition> m_pCurr; + std::unique_ptr<SwPosition> m_pCurrX; + sal_uInt16 m_nCursorCount; + +public: + SwLinguIter(); + + SwEditShell* GetSh() { return m_pSh; } + + const SwPosition *GetEnd() const { return m_pEnd.get(); } + void SetEnd(SwPosition* pNew) { m_pEnd.reset(pNew); } + + const SwPosition *GetStart() const { return m_pStart.get(); } + void SetStart(SwPosition* pNew) { m_pStart.reset(pNew); } + + const SwPosition *GetCurr() const { return m_pCurr.get(); } + void SetCurr(SwPosition* pNew) { m_pCurr.reset(pNew); } + + const SwPosition *GetCurrX() const { return m_pCurrX.get(); } + void SetCurrX(SwPosition* pNew) { m_pCurrX.reset(pNew); } + + sal_uInt16& GetCursorCnt() { return m_nCursorCount; } + + // for the UI: + void Start_( SwEditShell *pSh, SwDocPositions eStart, + SwDocPositions eEnd ); + void End_(bool bRestoreSelection = true); +}; + +// #i18881# to be able to identify the positions of the changed words +// the content positions of each portion need to be saved +struct SpellContentPosition +{ + sal_Int32 nLeft; + sal_Int32 nRight; +}; + +} + +typedef std::vector<SpellContentPosition> SpellContentPositions; + +namespace { + +class SwSpellIter : public SwLinguIter +{ + uno::Reference<XSpellChecker1> m_xSpeller; + svx::SpellPortions m_aLastPortions; + + SpellContentPositions m_aLastPositions; + bool m_bBackToStartOfSentence; + + void CreatePortion(uno::Reference< XSpellAlternatives > const & xAlt, + const linguistic2::ProofreadingResult* pGrammarResult, + bool bIsField, bool bIsHidden); + + void AddPortion(uno::Reference< XSpellAlternatives > const & xAlt, + const linguistic2::ProofreadingResult* pGrammarResult, + const SpellContentPositions& rDeletedRedlines); +public: + SwSpellIter() + : m_bBackToStartOfSentence(false) + { + } + + void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd ); + + uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt ); + + bool SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck); + void ToSentenceStart(); + const svx::SpellPortions& GetLastPortions() const { return m_aLastPortions; } + const SpellContentPositions& GetLastPositions() const { return m_aLastPositions; } +}; + +/// used for text conversion +class SwConvIter : public SwLinguIter +{ + SwConversionArgs& m_rArgs; + +public: + explicit SwConvIter(SwConversionArgs& rConvArgs) + : m_rArgs(rConvArgs) + { + } + + void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd ); + + uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt ); +}; + +class SwHyphIter : public SwLinguIter +{ + // With that we save a GetFrame() in Hyphenate //TODO: does it actually matter? + const SwTextNode *m_pLastNode; + SwTextFrame *m_pLastFrame; + friend SwTextFrame * sw::SwHyphIterCacheLastTextFrame(SwTextNode const * pNode, const sw::Creator& rCreator); + + bool m_bOldIdle; + static void DelSoftHyph( SwPaM &rPam ); + +public: + SwHyphIter() + : m_pLastNode(nullptr) + , m_pLastFrame(nullptr) + , m_bOldIdle(false) + { + } + + void Start( SwEditShell *pSh, SwDocPositions eStart, SwDocPositions eEnd ); + void End(); + + void Ignore(); + + uno::Any Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt ); + + static bool IsAuto(); + void InsertSoftHyph( const sal_Int32 nHyphPos ); + void ShowSelection(); +}; + +} + +static SwSpellIter* g_pSpellIter = nullptr; +static SwConvIter* g_pConvIter = nullptr; +static SwHyphIter* g_pHyphIter = nullptr; + +SwLinguIter::SwLinguIter() + : m_pSh(nullptr) + , m_nCursorCount(0) +{ + // TODO missing: ensurance of re-entrance, OSL_ENSURE( etc. +} + +void SwLinguIter::Start_( SwEditShell *pShell, SwDocPositions eStart, + SwDocPositions eEnd ) +{ + // TODO missing: ensurance of re-entrance, locking + if (m_pSh) + return; + + bool bSetCurr; + + m_pSh = pShell; + + CurrShell aCurr(m_pSh); + + OSL_ENSURE(!m_pEnd, "SwLinguIter::Start_ without End?"); + + SwPaM* pCursor = m_pSh->GetCursor(); + + if( pShell->HasSelection() || pCursor != pCursor->GetNext() ) + { + bSetCurr = nullptr != GetCurr(); + m_nCursorCount = m_pSh->GetCursorCnt(); + if (m_pSh->IsTableMode()) + m_pSh->TableCursorToCursor(); + + m_pSh->Push(); + sal_uInt16 n; + for (n = 0; n < m_nCursorCount; ++n) + { + m_pSh->Push(); + m_pSh->DestroyCursor(); + } + m_pSh->Pop(SwCursorShell::PopMode::DeleteCurrent); + } + else + { + bSetCurr = false; + m_nCursorCount = 1; + m_pSh->Push(); + m_pSh->SetLinguRange(eStart, eEnd); + } + + pCursor = m_pSh->GetCursor(); + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + + m_pStart.reset(new SwPosition(*pCursor->GetPoint())); + m_pEnd.reset(new SwPosition(*pCursor->GetMark())); + if( bSetCurr ) + { + SwPosition* pNew = new SwPosition( *GetStart() ); + SetCurr( pNew ); + pNew = new SwPosition( *pNew ); + SetCurrX( pNew ); + } + + pCursor->SetMark(); +} + +void SwLinguIter::End_(bool bRestoreSelection) +{ + if (!m_pSh) + return; + + OSL_ENSURE(m_pEnd, "SwLinguIter::End_ without end?"); + if(bRestoreSelection) + { + while (m_nCursorCount--) + m_pSh->Pop(SwCursorShell::PopMode::DeleteCurrent); + + m_pSh->KillPams(); + m_pSh->ClearMark(); + } + m_pStart.reset(); + m_pEnd.reset(); + m_pCurr.reset(); + m_pCurrX.reset(); + + m_pSh = nullptr; +} + +void SwSpellIter::Start( SwEditShell *pShell, SwDocPositions eStart, + SwDocPositions eEnd ) +{ + if( GetSh() ) + return; + + m_xSpeller = ::GetSpellChecker(); + if (m_xSpeller.is()) + Start_( pShell, eStart, eEnd ); + m_aLastPortions.clear(); + m_aLastPositions.clear(); +} + +// This method is the origin of SwEditShell::SpellContinue() +uno::Any SwSpellIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt ) +{ + //!! + //!! Please check SwConvIter also when modifying this + //!! + + uno::Any aSpellRet; + SwEditShell *pMySh = GetSh(); + if( !pMySh ) + return aSpellRet; + + OSL_ENSURE( GetEnd(), "SwSpellIter::Continue without start?"); + + uno::Reference< uno::XInterface > xSpellRet; + bool bGoOn = true; + do { + SwPaM *pCursor = pMySh->GetCursor(); + if ( !pCursor->HasMark() ) + pCursor->SetMark(); + + *pMySh->GetCursor()->GetPoint() = *GetCurr(); + *pMySh->GetCursor()->GetMark() = *GetEnd(); + pMySh->GetDoc()->Spell(*pMySh->GetCursor(), m_xSpeller, pPageCnt, pPageSt, false, + pMySh->GetLayout()) + >>= xSpellRet; + bGoOn = GetCursorCnt() > 1; + if( xSpellRet.is() ) + { + bGoOn = false; + SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() ); + SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() ); + SetCurr( pNewPoint ); + SetCurrX( pNewMark ); + } + if( bGoOn ) + { + pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent); + pCursor = pMySh->GetCursor(); + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + SwPosition* pNew = new SwPosition( *pCursor->GetPoint() ); + SetStart( pNew ); + pNew = new SwPosition( *pCursor->GetMark() ); + SetEnd( pNew ); + pNew = new SwPosition( *GetStart() ); + SetCurr( pNew ); + pNew = new SwPosition( *pNew ); + SetCurrX( pNew ); + pCursor->SetMark(); + --GetCursorCnt(); + } + }while ( bGoOn ); + aSpellRet <<= xSpellRet; + return aSpellRet; +} + +void SwConvIter::Start( SwEditShell *pShell, SwDocPositions eStart, + SwDocPositions eEnd ) +{ + if( GetSh() ) + return; + Start_( pShell, eStart, eEnd ); +} + +uno::Any SwConvIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt ) +{ + //!! + //!! Please check SwSpellIter also when modifying this + //!! + + uno::Any aConvRet{ OUString() }; + SwEditShell *pMySh = GetSh(); + if( !pMySh ) + return aConvRet; + + OSL_ENSURE( GetEnd(), "SwConvIter::Continue() without Start?"); + + OUString aConvText; + bool bGoOn = true; + do { + SwPaM *pCursor = pMySh->GetCursor(); + if ( !pCursor->HasMark() ) + pCursor->SetMark(); + + *pMySh->GetCursor()->GetPoint() = *GetCurr(); + *pMySh->GetCursor()->GetMark() = *GetEnd(); + + // call function to find next text portion to be converted + uno::Reference< linguistic2::XSpellChecker1 > xEmpty; + pMySh->GetDoc()->Spell(*pMySh->GetCursor(), xEmpty, pPageCnt, pPageSt, false, + pMySh->GetLayout(), &m_rArgs) + >>= aConvText; + + bGoOn = GetCursorCnt() > 1; + if( !aConvText.isEmpty() ) + { + bGoOn = false; + SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() ); + SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() ); + + SetCurr( pNewPoint ); + SetCurrX( pNewMark ); + } + if( bGoOn ) + { + pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent); + pCursor = pMySh->GetCursor(); + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + SwPosition* pNew = new SwPosition( *pCursor->GetPoint() ); + SetStart( pNew ); + pNew = new SwPosition( *pCursor->GetMark() ); + SetEnd( pNew ); + pNew = new SwPosition( *GetStart() ); + SetCurr( pNew ); + pNew = new SwPosition( *pNew ); + SetCurrX( pNew ); + pCursor->SetMark(); + --GetCursorCnt(); + } + }while ( bGoOn ); + return Any( aConvText ); +} + +bool SwHyphIter::IsAuto() +{ + uno::Reference< beans::XPropertySet > xProp( ::GetLinguPropertySet() ); + return xProp.is() && *o3tl::doAccess<bool>(xProp->getPropertyValue( + UPN_IS_HYPH_AUTO )); +} + +void SwHyphIter::ShowSelection() +{ + SwEditShell *pMySh = GetSh(); + if( pMySh ) + { + pMySh->StartAction(); + // Caution! Due to EndAction() formatting is started which can lead to the fact that new + // words are added to/set in the Hyphenator. Thus: save! + pMySh->EndAction(); + } +} + +void SwHyphIter::Start( SwEditShell *pShell, SwDocPositions eStart, SwDocPositions eEnd ) +{ + // robust + if( GetSh() || GetEnd() ) + { + OSL_ENSURE( !GetSh(), "SwHyphIter::Start: missing HyphEnd()" ); + return; + } + + // nothing to do (at least not in the way as in the "else" part) + m_bOldIdle = pShell->GetViewOptions()->IsIdle(); + pShell->GetViewOptions()->SetIdle( false ); + Start_( pShell, eStart, eEnd ); +} + +// restore selections +void SwHyphIter::End() +{ + if( !GetSh() ) + return; + GetSh()->GetViewOptions()->SetIdle(m_bOldIdle); + End_(); +} + +uno::Any SwHyphIter::Continue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt ) +{ + uno::Any aHyphRet; + SwEditShell *pMySh = GetSh(); + if( !pMySh ) + return aHyphRet; + + const bool bAuto = IsAuto(); + uno::Reference< XHyphenatedWord > xHyphWord; + bool bGoOn = false; + do { + SwPaM *pCursor; + do { + OSL_ENSURE( GetEnd(), "SwHyphIter::Continue without Start?" ); + pCursor = pMySh->GetCursor(); + if ( !pCursor->HasMark() ) + pCursor->SetMark(); + if ( *pCursor->GetPoint() < *pCursor->GetMark() ) + { + pCursor->Exchange(); + pCursor->SetMark(); + } + + if ( *pCursor->End() <= *GetEnd() ) + { + *pCursor->GetMark() = *GetEnd(); + + // Do we need to break the word at the current cursor position? + const Point aCursorPos( pMySh->GetCharRect().Pos() ); + xHyphWord = pMySh->GetDoc()->Hyphenate( pCursor, aCursorPos, + pPageCnt, pPageSt ); + } + + if( bAuto && xHyphWord.is() ) + { + SwEditShell::InsertSoftHyph( xHyphWord->getHyphenationPos() + 1); + } + } while( bAuto && xHyphWord.is() ); //end of do-while + bGoOn = !xHyphWord.is() && GetCursorCnt() > 1; + + if( bGoOn ) + { + pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent); + pCursor = pMySh->GetCursor(); + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + SwPosition* pNew = new SwPosition(*pCursor->End()); + SetEnd( pNew ); + pCursor->SetMark(); + --GetCursorCnt(); + } + } while ( bGoOn ); + aHyphRet <<= xHyphWord; + return aHyphRet; +} + +/// ignore hyphenation +void SwHyphIter::Ignore() +{ + SwEditShell *pMySh = GetSh(); + SwPaM *pCursor = pMySh->GetCursor(); + + // delete old SoftHyphen + DelSoftHyph( *pCursor ); + + // and continue + pCursor->Start()->SetContent( pCursor->End()->GetContentIndex() ); + pCursor->SetMark(); +} + +void SwHyphIter::DelSoftHyph( SwPaM &rPam ) +{ + const SwPosition* pStt = rPam.Start(); + const sal_Int32 nStart = pStt->GetContentIndex(); + const sal_Int32 nEnd = rPam.End()->GetContentIndex(); + SwTextNode *pNode = pStt->GetNode().GetTextNode(); + pNode->DelSoftHyph( nStart, nEnd ); +} + +void SwHyphIter::InsertSoftHyph( const sal_Int32 nHyphPos ) +{ + SwEditShell *pMySh = GetSh(); + OSL_ENSURE( pMySh, "SwHyphIter::InsertSoftHyph: missing HyphStart()"); + if( !pMySh ) + return; + + SwPaM *pCursor = pMySh->GetCursor(); + auto [pSttPos, pEndPos] = pCursor->StartEnd(); // SwPosition* + + const sal_Int32 nLastHyphLen = GetEnd()->GetContentIndex() - + pSttPos->GetContentIndex(); + + if( pSttPos->GetNode() != pEndPos->GetNode() || !nLastHyphLen ) + { + OSL_ENSURE( pSttPos->GetNode() == pEndPos->GetNode(), + "SwHyphIter::InsertSoftHyph: node warp during hyphenation" ); + OSL_ENSURE(nLastHyphLen, "SwHyphIter::InsertSoftHyph: missing HyphContinue()"); + *pSttPos = *pEndPos; + return; + } + + pMySh->StartAction(); + { + SwDoc *pDoc = pMySh->GetDoc(); + DelSoftHyph( *pCursor ); + pSttPos->AdjustContent( +nHyphPos ); + SwPaM aRg( *pSttPos ); + pDoc->getIDocumentContentOperations().InsertString( aRg, OUString(CHAR_SOFTHYPHEN) ); + } + // revoke selection + pCursor->DeleteMark(); + pMySh->EndAction(); + pCursor->SetMark(); +} + +namespace sw { + +SwTextFrame * +SwHyphIterCacheLastTextFrame(SwTextNode const * pNode, const sw::Creator& create) +{ + assert(g_pHyphIter); + if (pNode != g_pHyphIter->m_pLastNode || !g_pHyphIter->m_pLastFrame) + { + g_pHyphIter->m_pLastNode = pNode; + g_pHyphIter->m_pLastFrame = create(); + } + return g_pHyphIter->m_pLastFrame; +} + +} + +bool SwEditShell::HasLastSentenceGotGrammarChecked() +{ + bool bTextWasGrammarChecked = false; + if (g_pSpellIter) + { + svx::SpellPortions aLastPortions( g_pSpellIter->GetLastPortions() ); + for (size_t i = 0; i < aLastPortions.size() && !bTextWasGrammarChecked; ++i) + { + // bIsGrammarError is also true if the text was only checked but no + // grammar error was found. (That is if a ProofreadingResult was obtained in + // SwDoc::Spell and in turn bIsGrammarError was set in SwSpellIter::CreatePortion) + if (aLastPortions[i].bIsGrammarError) + bTextWasGrammarChecked = true; + } + } + return bTextWasGrammarChecked; +} + +bool SwEditShell::HasConvIter() +{ + return nullptr != g_pConvIter; +} + +bool SwEditShell::HasHyphIter() +{ + return nullptr != g_pHyphIter; +} + +void SwEditShell::SetLinguRange( SwDocPositions eStart, SwDocPositions eEnd ) +{ + SwPaM *pCursor = GetCursor(); + MakeFindRange( eStart, eEnd, pCursor ); + if( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); +} + +void SwEditShell::SpellStart( + SwDocPositions eStart, SwDocPositions eEnd, SwDocPositions eCurr, + SwConversionArgs *pConvArgs ) +{ + SwLinguIter *pLinguIter = nullptr; + + // do not spell if interactive spelling is active elsewhere + if (!pConvArgs && !g_pSpellIter) + { + g_pSpellIter = new SwSpellIter; + pLinguIter = g_pSpellIter; + } + // do not do text conversion if it is active elsewhere + if (pConvArgs && !g_pConvIter) + { + g_pConvIter = new SwConvIter( *pConvArgs ); + pLinguIter = g_pConvIter; + } + + if (pLinguIter) + { + SwCursor* pSwCursor = GetCursor(); + + SwPosition *pTmp = new SwPosition( *pSwCursor->GetPoint() ); + pSwCursor->FillFindPos( eCurr, *pTmp ); + pLinguIter->SetCurr( pTmp ); + + pTmp = new SwPosition( *pTmp ); + pLinguIter->SetCurrX( pTmp ); + } + + if (!pConvArgs && g_pSpellIter) + g_pSpellIter->Start( this, eStart, eEnd ); + if (pConvArgs && g_pConvIter) + g_pConvIter->Start( this, eStart, eEnd ); +} + +void SwEditShell::SpellEnd( SwConversionArgs const *pConvArgs, bool bRestoreSelection ) +{ + if (!pConvArgs && g_pSpellIter && g_pSpellIter->GetSh() == this) + { + g_pSpellIter->End_(bRestoreSelection); + delete g_pSpellIter; + g_pSpellIter = nullptr; + } + if (pConvArgs && g_pConvIter && g_pConvIter->GetSh() == this) + { + g_pConvIter->End_(); + delete g_pConvIter; + g_pConvIter = nullptr; + } +} + +/// @returns SPL_ return values as in splchk.hxx +uno::Any SwEditShell::SpellContinue( + sal_uInt16* pPageCnt, sal_uInt16* pPageSt, + SwConversionArgs const *pConvArgs ) +{ + uno::Any aRes; + + if ((!pConvArgs && g_pSpellIter->GetSh() != this) || + ( pConvArgs && g_pConvIter->GetSh() != this)) + return aRes; + + if( pPageCnt && !*pPageCnt ) + { + sal_uInt16 nEndPage = GetLayout()->GetPageNum(); + nEndPage += nEndPage * 10 / 100; + *pPageCnt = nEndPage; + if( nEndPage ) + ::StartProgress( STR_STATSTR_SPELL, 0, nEndPage, GetDoc()->GetDocShell() ); + } + + OSL_ENSURE( pConvArgs || g_pSpellIter, "SpellIter missing" ); + OSL_ENSURE( !pConvArgs || g_pConvIter, "ConvIter missing" ); + //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all + // Paints are also disabled. + ++mnStartAction; + OUString aRet; + uno::Reference< uno::XInterface > xRet; + if (pConvArgs) + { + g_pConvIter->Continue( pPageCnt, pPageSt ) >>= aRet; + aRes <<= aRet; + } + else + { + g_pSpellIter->Continue( pPageCnt, pPageSt ) >>= xRet; + aRes <<= xRet; + } + --mnStartAction; + + if( !aRet.isEmpty() || xRet.is() ) + { + // then make awt::Selection again visible + StartAction(); + EndAction(); + } + return aRes; +} + +/* Interactive Hyphenation (BP 10.03.93) + * + * 1) HyphStart + * - Revoke all Selections + * - Save current Cursor + * - if no selections existent: + * - create new selection reaching until document end + * 2) HyphContinue + * - add nLastHyphLen onto SelectionStart + * - iterate over all selected areas + * - pDoc->Hyphenate() iterates over all Nodes of a selection + * - pTextNode->Hyphenate() calls SwTextFrame::Hyphenate of the EditShell + * - SwTextFrame:Hyphenate() iterates over all rows of the Pam + * - LineIter::Hyphenate() sets the Hyphenator and the Pam based on + * the to be separated word. + * - Returns true if there is a hyphenation and false if the Pam is processed. + * - If true, show the selected word and set nLastHyphLen. + * - If false, delete current selection and select next one. Returns HYPH_OK if no more. + * 3) InsertSoftHyph (might be called by UI if needed) + * - Place current cursor and add attribute. + * 4) HyphEnd + * - Restore old cursor, EndAction + */ +void SwEditShell::HyphStart( SwDocPositions eStart, SwDocPositions eEnd ) +{ + // do not hyphenate if interactive hyphenation is active elsewhere + if (!g_pHyphIter) + { + g_pHyphIter = new SwHyphIter; + g_pHyphIter->Start( this, eStart, eEnd ); + } +} + +/// restore selections +void SwEditShell::HyphEnd() +{ + assert(g_pHyphIter); + if (g_pHyphIter->GetSh() == this) + { + g_pHyphIter->End(); + delete g_pHyphIter; + g_pHyphIter = nullptr; + } +} + +/// @returns HYPH_CONTINUE if hyphenation, HYPH_OK if selected area was processed. +uno::Reference< uno::XInterface > + SwEditShell::HyphContinue( sal_uInt16* pPageCnt, sal_uInt16* pPageSt ) +{ + assert(g_pHyphIter); + if (g_pHyphIter->GetSh() != this) + return nullptr; + + if( pPageCnt && !*pPageCnt && !*pPageSt ) + { + sal_uInt16 nEndPage = GetLayout()->GetPageNum(); + nEndPage += nEndPage * 10 / 100; + if( nEndPage > 14 ) + { + *pPageCnt = nEndPage; + ::StartProgress( STR_STATSTR_HYPHEN, 0, nEndPage, GetDoc()->GetDocShell()); + } + else // here we once and for all suppress StatLineStartPercent + *pPageSt = 1; + } + + //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all + // Paints are also disabled. + ++mnStartAction; + uno::Reference< uno::XInterface > xRet; + g_pHyphIter->Continue( pPageCnt, pPageSt ) >>= xRet; + --mnStartAction; + + if( xRet.is() ) + g_pHyphIter->ShowSelection(); + + return xRet; +} + +/** Insert soft hyphen + * + * @param nHyphPos Offset in the to be separated word + */ +void SwEditShell::InsertSoftHyph( const sal_Int32 nHyphPos ) +{ + assert(g_pHyphIter); + g_pHyphIter->InsertSoftHyph( nHyphPos ); +} + +/// ignore hyphenation +void SwEditShell::HyphIgnore() +{ + assert(g_pHyphIter); + //JP 18.07.95: prevent displaying selection on error messages. NO StartAction so that all + // Paints are also disabled. + ++mnStartAction; + g_pHyphIter->Ignore(); + --mnStartAction; + + g_pHyphIter->ShowSelection(); +} + +void SwEditShell::HandleCorrectionError(const OUString& aText, SwPosition aPos, sal_Int32 nBegin, + sal_Int32 nLen, const Point* pPt, + SwRect& rSelectRect) +{ + // save the start and end positions of the line and the starting point + SwNode const& rNode(GetCursor()->GetPoint()->GetNode()); + Push(); + LeftMargin(); + const sal_Int32 nLineStart = &rNode == &GetCursor()->GetPoint()->GetNode() + ? GetCursor()->GetPoint()->GetContentIndex() + : 0; + RightMargin(); + const sal_Int32 nLineEnd = &rNode == &GetCursor()->GetPoint()->GetNode() + ? GetCursor()->GetPoint()->GetContentIndex() + : rNode.GetTextNode()->Len(); + Pop(PopMode::DeleteCurrent); + + // make sure the selection build later from the data below does + // not "in word" character to the left and right in order to + // preserve those. Therefore count those "in words" in order to + // modify the selection accordingly. + const sal_Unicode* pChar = aText.getStr(); + sal_Int32 nLeft = 0; + while (*pChar++ == CH_TXTATR_INWORD) + ++nLeft; + pChar = aText.getLength() ? aText.getStr() + aText.getLength() - 1 : nullptr; + sal_Int32 nRight = 0; + while (pChar && *pChar-- == CH_TXTATR_INWORD) + ++nRight; + + aPos.SetContent( nBegin + nLeft ); + SwPaM* pCursor = GetCursor(); + *pCursor->GetPoint() = aPos; + pCursor->SetMark(); + ExtendSelection( true, nLen - nLeft - nRight ); + // don't determine the rectangle in the current line + const sal_Int32 nWordStart = (nBegin + nLeft) < nLineStart ? nLineStart : nBegin + nLeft; + // take one less than the line end - otherwise the next line would be calculated + const sal_Int32 nWordEnd = (nBegin + nLen - nLeft - nRight) > nLineEnd + ? nLineEnd : (nBegin + nLen - nLeft - nRight); + Push(); + pCursor->DeleteMark(); + SwPosition& rPtPos = *GetCursor()->GetPoint(); + rPtPos.SetContent(nWordStart); + SwRect aStartRect; + SwCursorMoveState aState; + aState.m_bRealWidth = true; + SwContentNode* pContentNode = pCursor->GetPointContentNode(); + std::pair<Point, bool> tmp; + if (pPt) + { + tmp.first = *pPt; + tmp.second = false; + } + SwContentFrame *const pContentFrame = pContentNode->getLayoutFrame(GetLayout(), pCursor->GetPoint(), pPt ? &tmp : nullptr); + + pContentFrame->GetCharRect( aStartRect, *pCursor->GetPoint(), &aState ); + rPtPos.SetContent(nWordEnd - 1); + SwRect aEndRect; + pContentFrame->GetCharRect( aEndRect, *pCursor->GetPoint(),&aState ); + rSelectRect = aStartRect.Union( aEndRect ); + Pop(PopMode::DeleteCurrent); +} + +/** Get a list of potential corrections for misspelled word. + * + * If empty, word is unknown but there are no corrections available. + * If NULL then the word is not misspelled but correct. + * + * @brief SwEditShell::GetCorrection + * @return list or NULL pointer + */ +uno::Reference< XSpellAlternatives > + SwEditShell::GetCorrection( const Point* pPt, SwRect& rSelectRect ) +{ + uno::Reference< XSpellAlternatives > xSpellAlt; + + if( IsTableMode() ) + return nullptr; + SwPaM* pCursor = GetCursor(); + SwPosition aPos( *pCursor->GetPoint() ); + SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText ); + SwTextNode *pNode = nullptr; + SwWrongList *pWrong = nullptr; + if (pPt && GetLayout()->GetModelPositionForViewPoint( &aPos, *const_cast<Point*>(pPt), &eTmpState )) + pNode = aPos.GetNode().GetTextNode(); + if (nullptr == pNode) + pNode = pCursor->GetPointNode().GetTextNode(); + if (nullptr != pNode) + pWrong = pNode->GetWrong(); + if (nullptr != pWrong && !pNode->IsInProtectSect()) + { + sal_Int32 nBegin = aPos.GetContentIndex(); + sal_Int32 nLen = 1; + if (pWrong->InWrongWord(nBegin, nLen) && !pNode->IsSymbolAt(nBegin)) + { + const OUString aText(pNode->GetText().copy(nBegin, nLen)); + // TODO: this doesn't handle fieldmarks properly + ModelToViewHelper const aConversionMap(*pNode, GetLayout(), + ExpandMode::ExpandFields | ExpandMode::ExpandFootnote | ExpandMode::ReplaceMode + | ExpandMode::HideFieldmarkCommands + | (GetLayout()->IsHideRedlines() ? ExpandMode::HideDeletions : ExpandMode(0)) + | (GetViewOptions()->IsShowHiddenChar() ? ExpandMode(0) : ExpandMode::HideInvisible)); + auto const nBeginView(aConversionMap.ConvertToViewPosition(nBegin)); + OUString const aWord(aConversionMap.getViewText().copy(nBeginView, + aConversionMap.ConvertToViewPosition(nBegin+nLen) - nBeginView)); + + uno::Reference< XSpellChecker1 > xSpell( ::GetSpellChecker() ); + if( xSpell.is() ) + { + LanguageType eActLang = pNode->GetLang( nBegin, nLen ); + if( xSpell->hasLanguage( static_cast<sal_uInt16>(eActLang) )) + { + // restrict the maximal number of suggestions displayed + // in the context menu. + // Note: That could of course be done by clipping the + // resulting sequence but the current third party + // implementations result differs greatly if the number of + // suggestions to be returned gets changed. Statistically + // it gets much better if told to return e.g. only 7 strings + // than returning e.g. 16 suggestions and using only the + // first 7. Thus we hand down the value to use to that + // implementation here by providing an additional parameter. + Sequence< PropertyValue > aPropVals ( { comphelper::makePropertyValue( UPN_MAX_NUMBER_OF_SUGGESTIONS, sal_Int16(7)) } ); + + xSpellAlt = xSpell->spell( aWord, static_cast<sal_uInt16>(eActLang), aPropVals ); + } + } + + if ( xSpellAlt.is() ) // error found? + { + HandleCorrectionError( aText, std::move(aPos), nBegin, nLen, pPt, rSelectRect ); + } + } + } + return xSpellAlt; +} + +bool SwEditShell::GetGrammarCorrection( + linguistic2::ProofreadingResult /*out*/ &rResult, // the complete result + sal_Int32 /*out*/ &rErrorPosInText, // offset of error position in string that was grammar checked... + sal_Int32 /*out*/ &rErrorIndexInResult, // index of error in rResult.aGrammarErrors + uno::Sequence< OUString > /*out*/ &rSuggestions, // suggestions to be used for the error found + const Point *pPt, SwRect &rSelectRect ) +{ + bool bRes = false; + + if( IsTableMode() ) + return bRes; + + SwPaM* pCursor = GetCursor(); + SwPosition aPos( *pCursor->GetPoint() ); + SwCursorMoveState eTmpState( CursorMoveState::SetOnlyText ); + SwTextNode *pNode = nullptr; + SwGrammarMarkUp *pWrong = nullptr; + if (pPt && GetLayout()->GetModelPositionForViewPoint( &aPos, *const_cast<Point*>(pPt), &eTmpState )) + pNode = aPos.GetNode().GetTextNode(); + if (nullptr == pNode) + pNode = pCursor->GetPointNode().GetTextNode(); + if (nullptr != pNode) + pWrong = pNode->GetGrammarCheck(); + if (nullptr != pWrong && !pNode->IsInProtectSect()) + { + sal_Int32 nBegin = aPos.GetContentIndex(); + sal_Int32 nLen = 1; + if (pWrong->InWrongWord(nBegin, nLen)) + { + const OUString aText(pNode->GetText().copy(nBegin, nLen)); + + uno::Reference< linguistic2::XProofreadingIterator > xGCIterator( mxDoc->GetGCIterator() ); + if (xGCIterator.is()) + { + uno::Reference< lang::XComponent > xDoc = mxDoc->GetDocShell()->GetBaseModel(); + + // Expand the string: + const ModelToViewHelper aConversionMap(*pNode, GetLayout()); + const OUString& aExpandText = aConversionMap.getViewText(); + // get XFlatParagraph to use... + uno::Reference< text::XFlatParagraph > xFlatPara = new SwXFlatParagraph( *pNode, aExpandText, aConversionMap ); + + // get error position of cursor in XFlatParagraph + rErrorPosInText = aConversionMap.ConvertToViewPosition( nBegin ); + + const sal_Int32 nStartOfSentence = aConversionMap.ConvertToViewPosition( pWrong->getSentenceStart( nBegin ) ); + const sal_Int32 nEndOfSentence = aConversionMap.ConvertToViewPosition( pWrong->getSentenceEnd( nBegin ) ); + + rResult = xGCIterator->checkSentenceAtPosition( + xDoc, xFlatPara, aExpandText, lang::Locale(), nStartOfSentence, + nEndOfSentence == COMPLETE_STRING ? aExpandText.getLength() : nEndOfSentence, + rErrorPosInText ); + bRes = true; + + // get suggestions to use for the specific error position + rSuggestions.realloc( 0 ); + // return suggestions for first error that includes the given error position + auto pError = std::find_if(std::cbegin(rResult.aErrors), std::cend(rResult.aErrors), + [rErrorPosInText, nLen](const linguistic2::SingleProofreadingError &rError) { + return rError.nErrorStart <= rErrorPosInText + && rErrorPosInText + nLen <= rError.nErrorStart + rError.nErrorLength + && rError.aSuggestions.size() > 0; }); + if (pError != std::cend(rResult.aErrors)) + { + rSuggestions = pError->aSuggestions; + rErrorIndexInResult = static_cast<sal_Int32>(std::distance(std::cbegin(rResult.aErrors), pError)); + } + } + + if (rResult.aErrors.hasElements()) // error found? + { + HandleCorrectionError( aText, std::move(aPos), nBegin, nLen, pPt, rSelectRect ); + } + } + } + + return bRes; +} + +bool SwEditShell::SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck) +{ + OSL_ENSURE( g_pSpellIter, "SpellIter missing" ); + if (!g_pSpellIter) + return false; + bool bRet = g_pSpellIter->SpellSentence(rPortions, bIsGrammarCheck); + + // make Selection visible - this should simply move the + // cursor to the end of the sentence + StartAction(); + EndAction(); + return bRet; +} + +///make SpellIter start with the current sentence when called next time +void SwEditShell::PutSpellingToSentenceStart() +{ + OSL_ENSURE( g_pSpellIter, "SpellIter missing" ); + if (!g_pSpellIter) + return; + g_pSpellIter->ToSentenceStart(); +} + +static sal_uInt32 lcl_CountRedlines(const svx::SpellPortions& rLastPortions) +{ + return static_cast<sal_uInt32>(std::count_if(rLastPortions.begin(), rLastPortions.end(), + [](const svx::SpellPortion& rPortion) { return rPortion.bIsHidden; })); +} + +void SwEditShell::MoveContinuationPosToEndOfCheckedSentence() +{ + // give hint that continuation position for spell/grammar checking is + // at the end of this sentence + if (g_pSpellIter) + { + g_pSpellIter->SetCurr( new SwPosition( *g_pSpellIter->GetCurrX() ) ); + } +} + +void SwEditShell::ApplyChangedSentence(const svx::SpellPortions& rNewPortions, bool bRecheck) +{ + // Note: rNewPortions.size() == 0 is valid and happens when the whole + // sentence got removed in the dialog + + OSL_ENSURE( g_pSpellIter, "SpellIter missing" ); + if (!g_pSpellIter || + g_pSpellIter->GetLastPortions().empty()) // no portions -> no text to be changed + return; + + const SpellPortions& rLastPortions = g_pSpellIter->GetLastPortions(); + const SpellContentPositions rLastPositions = g_pSpellIter->GetLastPositions(); + OSL_ENSURE(!rLastPortions.empty() && + rLastPortions.size() == rLastPositions.size(), + "last vectors of spelling results are not set or not equal"); + + // iterate over the new portions, beginning at the end to take advantage of the previously + // saved content positions + + mxDoc->GetIDocumentUndoRedo().StartUndo( SwUndoId::UI_TEXT_CORRECTION, nullptr ); + StartAction(); + + SwPaM *pCursor = GetCursor(); + // save cursor position (which should be at the end of the current sentence) + // for later restoration + Push(); + + sal_uInt32 nRedlinePortions = lcl_CountRedlines(rLastPortions); + if((rLastPortions.size() - nRedlinePortions) == rNewPortions.size()) + { + OSL_ENSURE( !rNewPortions.empty(), "rNewPortions should not be empty here" ); + OSL_ENSURE( !rLastPortions.empty(), "rLastPortions should not be empty here" ); + OSL_ENSURE( !rLastPositions.empty(), "rLastPositions should not be empty here" ); + + // the simple case: the same number of elements on both sides + // each changed element has to be applied to the corresponding source element + svx::SpellPortions::const_iterator aCurrentNewPortion = rNewPortions.end(); + SpellPortions::const_iterator aCurrentOldPortion = rLastPortions.end(); + SpellContentPositions::const_iterator aCurrentOldPosition = rLastPositions.end(); + do + { + --aCurrentNewPortion; + --aCurrentOldPortion; + --aCurrentOldPosition; + //jump over redline portions + while(aCurrentOldPortion->bIsHidden) + { + if (aCurrentOldPortion != rLastPortions.begin() && + aCurrentOldPosition != rLastPositions.begin()) + { + --aCurrentOldPortion; + --aCurrentOldPosition; + } + else + { + OSL_FAIL("ApplyChangedSentence: iterator positions broken" ); + break; + } + } + if ( !pCursor->HasMark() ) + pCursor->SetMark(); + pCursor->GetPoint()->SetContent( aCurrentOldPosition->nLeft ); + pCursor->GetMark()->SetContent( aCurrentOldPosition->nRight ); + sal_uInt16 nScriptType = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( aCurrentNewPortion->eLanguage ); + sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE; + switch(nScriptType) + { + case css::i18n::ScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break; + case css::i18n::ScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break; + } + if(aCurrentNewPortion->sText != aCurrentOldPortion->sText) + { + // change text ... + // ... and apply language if necessary + if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage) + SetAttrItem( SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId) ); + + // if there is a comment inside the original word, don't delete it: + // but keep it at the end of the replacement + ReplaceKeepComments(aCurrentNewPortion->sText); + } + else if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage) + { + // apply language + SetAttrItem( SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId) ); + } + else if( aCurrentNewPortion->bIgnoreThisError ) + { + // add the 'ignore' markup to the TextNode's grammar ignore markup list + IgnoreGrammarErrorAt( *pCursor ); + OSL_FAIL("TODO: add ignore mark to text node"); + } + } + while(aCurrentNewPortion != rNewPortions.begin()); + } + else + { + OSL_ENSURE( !rLastPositions.empty(), "rLastPositions should not be empty here" ); + + // select the complete sentence + SpellContentPositions::const_iterator aCurrentEndPosition = rLastPositions.end(); + --aCurrentEndPosition; + SpellContentPositions::const_iterator aCurrentStartPosition = rLastPositions.begin(); + pCursor->GetPoint()->SetContent( aCurrentStartPosition->nLeft ); + pCursor->GetMark()->SetContent( aCurrentEndPosition->nRight ); + + // delete the sentence completely + mxDoc->getIDocumentContentOperations().DeleteAndJoin(*pCursor); + for(const auto& rCurrentNewPortion : rNewPortions) + { + // set the language attribute + SvtScriptType nScriptType = GetScriptType(); + sal_uInt16 nLangWhichId = RES_CHRATR_LANGUAGE; + switch(nScriptType) + { + case SvtScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break; + case SvtScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break; + default: break; + } + SfxItemSet aSet(GetAttrPool(), nLangWhichId, nLangWhichId); + GetCurAttr( aSet ); + const SvxLanguageItem& rLang = static_cast<const SvxLanguageItem& >(aSet.Get(nLangWhichId)); + if(rLang.GetLanguage() != rCurrentNewPortion.eLanguage) + SetAttrItem( SvxLanguageItem(rCurrentNewPortion.eLanguage, nLangWhichId) ); + // insert the new string + mxDoc->getIDocumentContentOperations().InsertString(*pCursor, rCurrentNewPortion.sText); + + // set the cursor to the end of the inserted string + *pCursor->Start() = *pCursor->End(); + } + } + + // restore cursor to the end of the sentence + // (will work also if the sentence length has changed, + // since cursors get updated automatically!) + Pop(PopMode::DeleteCurrent); + + // collapse cursor to the end of the modified sentence + *pCursor->Start() = *pCursor->End(); + if (bRecheck) + { + // in grammar check the current sentence has to be checked again + GoStartSentence(); + } + // set continuation position for spell/grammar checking to the end of this sentence + g_pSpellIter->SetCurr( new SwPosition(*pCursor->Start()) ); + + mxDoc->GetIDocumentUndoRedo().EndUndo( SwUndoId::UI_TEXT_CORRECTION, nullptr ); + EndAction(); + +} +/** Collect all deleted redlines of the current text node + * beginning at the start of the cursor position + */ +static SpellContentPositions lcl_CollectDeletedRedlines(SwEditShell const * pSh) +{ + SpellContentPositions aRedlines; + SwDoc* pDoc = pSh->GetDoc(); + const bool bShowChg = IDocumentRedlineAccess::IsShowChanges( pDoc->getIDocumentRedlineAccess().GetRedlineFlags() ); + if ( bShowChg ) + { + SwPaM *pCursor = pSh->GetCursor(); + const SwPosition* pStartPos = pCursor->Start(); + const SwTextNode* pTextNode = pCursor->GetPointNode().GetTextNode(); + + SwRedlineTable::size_type nAct = pDoc->getIDocumentRedlineAccess().GetRedlinePos( *pTextNode, RedlineType::Any ); + const sal_Int32 nStartIndex = pStartPos->GetContentIndex(); + for ( ; nAct < pDoc->getIDocumentRedlineAccess().GetRedlineTable().size(); nAct++ ) + { + const SwRangeRedline* pRed = pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nAct ]; + + if ( pRed->Start()->GetNode() > *pTextNode ) + break; + + if( RedlineType::Delete == pRed->GetType() ) + { + sal_Int32 nStart_, nEnd_; + pRed->CalcStartEnd( pTextNode->GetIndex(), nStart_, nEnd_ ); + sal_Int32 nStart = nStart_; + sal_Int32 nEnd = nEnd_; + if(nStart >= nStartIndex || nEnd >= nStartIndex) + { + SpellContentPosition aAdd; + aAdd.nLeft = nStart; + aAdd.nRight = nEnd; + aRedlines.push_back(aAdd); + } + } + } + } + return aRedlines; +} + +/// remove the redline positions after the current selection +static void lcl_CutRedlines( SpellContentPositions& aDeletedRedlines, SwEditShell const * pSh ) +{ + if(!aDeletedRedlines.empty()) + { + SwPaM *pCursor = pSh->GetCursor(); + const SwPosition* pEndPos = pCursor->End(); + const sal_Int32 nEnd = pEndPos->GetContentIndex(); + while(!aDeletedRedlines.empty() && + aDeletedRedlines.back().nLeft > nEnd) + { + aDeletedRedlines.pop_back(); + } + } +} + +static SpellContentPosition lcl_FindNextDeletedRedline( + const SpellContentPositions& rDeletedRedlines, + sal_Int32 nSearchFrom ) +{ + SpellContentPosition aRet; + aRet.nLeft = aRet.nRight = SAL_MAX_INT32; + if(!rDeletedRedlines.empty()) + { + auto aIter = std::find_if_not(rDeletedRedlines.begin(), rDeletedRedlines.end(), + [nSearchFrom](const SpellContentPosition& rPos) { return rPos.nLeft < nSearchFrom; }); + if (aIter != rDeletedRedlines.end()) + aRet = *aIter; + } + return aRet; +} + +bool SwSpellIter::SpellSentence(svx::SpellPortions& rPortions, bool bIsGrammarCheck) +{ + bool bRet = false; + m_aLastPortions.clear(); + m_aLastPositions.clear(); + + SwEditShell *pMySh = GetSh(); + if( !pMySh ) + return false; + + OSL_ENSURE( GetEnd(), "SwSpellIter::SpellSentence without Start?"); + + uno::Reference< XSpellAlternatives > xSpellRet; + linguistic2::ProofreadingResult aGrammarResult; + bool bGoOn = true; + bool bGrammarErrorFound = false; + do { + SwPaM *pCursor = pMySh->GetCursor(); + if ( !pCursor->HasMark() ) + pCursor->SetMark(); + + *pCursor->GetPoint() = *GetCurr(); + *pCursor->GetMark() = *GetEnd(); + + if (m_bBackToStartOfSentence) + { + pMySh->GoStartSentence(); + m_bBackToStartOfSentence = false; + } + uno::Any aSpellRet = pMySh->GetDoc()->Spell(*pCursor, m_xSpeller, nullptr, nullptr, + bIsGrammarCheck, pMySh->GetLayout()); + aSpellRet >>= xSpellRet; + aSpellRet >>= aGrammarResult; + bGoOn = GetCursorCnt() > 1; + bGrammarErrorFound = aGrammarResult.aErrors.hasElements(); + if( xSpellRet.is() || bGrammarErrorFound ) + { + bGoOn = false; + SwPosition* pNewPoint = new SwPosition( *pCursor->GetPoint() ); + SwPosition* pNewMark = new SwPosition( *pCursor->GetMark() ); + + SetCurr( pNewPoint ); + SetCurrX( pNewMark ); + } + if( bGoOn ) + { + pMySh->Pop(SwCursorShell::PopMode::DeleteCurrent); + pCursor = pMySh->GetCursor(); + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + SwPosition* pNew = new SwPosition( *pCursor->GetPoint() ); + SetStart( pNew ); + pNew = new SwPosition( *pCursor->GetMark() ); + SetEnd( pNew ); + pNew = new SwPosition( *GetStart() ); + SetCurr( pNew ); + pNew = new SwPosition( *pNew ); + SetCurrX( pNew ); + pCursor->SetMark(); + --GetCursorCnt(); + } + } while ( bGoOn ); + + if(xSpellRet.is() || bGrammarErrorFound) + { + // an error has been found + // To fill the spell portions the beginning of the sentence has to be found + SwPaM *pCursor = pMySh->GetCursor(); + // set the mark to the right if necessary + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + // the cursor has to be collapsed on the left to go to the start of the sentence - if sentence ends inside of the error + pCursor->DeleteMark(); + pCursor->SetMark(); + bool bStartSent = pMySh->GoStartSentence(); + SpellContentPositions aDeletedRedlines = lcl_CollectDeletedRedlines(pMySh); + if(bStartSent) + { + // create a portion from the start part + AddPortion(nullptr, nullptr, aDeletedRedlines); + } + // Set the cursor to the error already found + *pCursor->GetPoint() = *GetCurrX(); + *pCursor->GetMark() = *GetCurr(); + AddPortion(xSpellRet, &aGrammarResult, aDeletedRedlines); + + // save the end position of the error to continue from here + SwPosition aSaveStartPos = *pCursor->End(); + // determine the end of the current sentence + if ( *pCursor->GetPoint() < *pCursor->GetMark() ) + pCursor->Exchange(); + // again collapse to start marking after the end of the error + pCursor->DeleteMark(); + pCursor->SetMark(); + + pMySh->GoEndSentence(); + if( bGrammarErrorFound ) + { + const ModelToViewHelper aConversionMap(static_cast<SwTextNode&>(pCursor->GetPointNode()), pMySh->GetLayout()); + const OUString& aExpandText = aConversionMap.getViewText(); + sal_Int32 nSentenceEnd = + aConversionMap.ConvertToViewPosition( aGrammarResult.nBehindEndOfSentencePosition ); + // remove trailing space + if( aExpandText[nSentenceEnd - 1] == ' ' ) + --nSentenceEnd; + if( pCursor->End()->GetContentIndex() < nSentenceEnd ) + { + pCursor->End()->SetContent(nSentenceEnd); + } + } + + lcl_CutRedlines( aDeletedRedlines, pMySh ); + // save the 'global' end of the spellchecking + const SwPosition aSaveEndPos = *GetEnd(); + // set the sentence end as 'local' end + SetEnd( new SwPosition( *pCursor->End() )); + + *pCursor->GetPoint() = aSaveStartPos; + *pCursor->GetMark() = *GetEnd(); + // now the rest of the sentence has to be searched for errors + // for each error the non-error text between the current and the last error has + // to be added to the portions - if necessary broken into same-language-portions + if( !bGrammarErrorFound ) //in grammar check there's only one error returned + { + do + { + xSpellRet = nullptr; + // don't search for grammar errors here anymore! + pMySh->GetDoc()->Spell(*pCursor, m_xSpeller, nullptr, nullptr, false, + pMySh->GetLayout()) + >>= xSpellRet; + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + SetCurr( new SwPosition( *pCursor->GetPoint() )); + SetCurrX( new SwPosition( *pCursor->GetMark() )); + + // if an error has been found go back to the text preceding the error + if(xSpellRet.is()) + { + *pCursor->GetPoint() = aSaveStartPos; + *pCursor->GetMark() = *GetCurr(); + } + // add the portion + AddPortion(nullptr, nullptr, aDeletedRedlines); + + if(xSpellRet.is()) + { + *pCursor->GetPoint() = *GetCurr(); + *pCursor->GetMark() = *GetCurrX(); + AddPortion(xSpellRet, nullptr, aDeletedRedlines); + // move the cursor to the end of the error string + *pCursor->GetPoint() = *GetCurrX(); + // and save the end of the error as new start position + aSaveStartPos = *GetCurrX(); + // and the end of the sentence + *pCursor->GetMark() = *GetEnd(); + } + // if the end of the sentence has already been reached then break here + if(*GetCurrX() >= *GetEnd()) + break; + } + while(xSpellRet.is()); + } + else + { + // go to the end of sentence as the grammar check returned it + // at this time the Point is behind the grammar error + // and the mark points to the sentence end as + if ( *pCursor->GetPoint() < *pCursor->GetMark() ) + pCursor->Exchange(); + } + + // the part between the last error and the end of the sentence has to be added + *pMySh->GetCursor()->GetPoint() = *GetEnd(); + if(*GetCurrX() < *GetEnd()) + { + AddPortion(nullptr, nullptr, aDeletedRedlines); + } + // set the shell cursor to the end of the sentence to prevent a visible selection + *pCursor->GetMark() = *GetEnd(); + if( !bIsGrammarCheck ) + { + // set the current position to the end of the sentence + SetCurr( new SwPosition(*GetEnd()) ); + } + // restore the 'global' end + SetEnd( new SwPosition(aSaveEndPos) ); + rPortions = m_aLastPortions; + bRet = true; + } + else + { + // if no error could be found the selection has to be corrected - at least if it's not in the body + *pMySh->GetCursor()->GetPoint() = *GetEnd(); + pMySh->GetCursor()->DeleteMark(); + } + + return bRet; +} + +void SwSpellIter::ToSentenceStart() { m_bBackToStartOfSentence = true; } + +static LanguageType lcl_GetLanguage(SwEditShell& rSh) +{ + SvtScriptType nScriptType = rSh.GetScriptType(); + TypedWhichId<SvxLanguageItem> nLangWhichId = RES_CHRATR_LANGUAGE; + + switch(nScriptType) + { + case SvtScriptType::ASIAN : nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break; + case SvtScriptType::COMPLEX : nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break; + default: break; + } + SfxItemSet aSet(rSh.GetAttrPool(), nLangWhichId, nLangWhichId); + rSh.GetCurAttr( aSet ); + const SvxLanguageItem& rLang = aSet.Get(nLangWhichId); + return rLang.GetLanguage(); +} + +/// create a text portion at the given position +void SwSpellIter::CreatePortion(uno::Reference< XSpellAlternatives > const & xAlt, + const linguistic2::ProofreadingResult* pGrammarResult, + bool bIsField, bool bIsHidden) +{ + svx::SpellPortion aPortion; + OUString sText; + GetSh()->GetSelectedText( sText ); + if(sText.isEmpty()) + return; + + // in case of redlined deletions the selection of an error is not the same as the _real_ word + if(xAlt.is()) + aPortion.sText = xAlt->getWord(); + else if(pGrammarResult) + { + aPortion.bIsGrammarError = true; + if(pGrammarResult->aErrors.hasElements()) + { + aPortion.aGrammarError = pGrammarResult->aErrors[0]; + aPortion.sText = pGrammarResult->aText.copy( aPortion.aGrammarError.nErrorStart, aPortion.aGrammarError.nErrorLength ); + aPortion.xGrammarChecker = pGrammarResult->xProofreader; + auto pProperty = std::find_if(std::cbegin(pGrammarResult->aProperties), std::cend(pGrammarResult->aProperties), + [](const beans::PropertyValue& rProperty) { return rProperty.Name == "DialogTitle"; }); + if (pProperty != std::cend(pGrammarResult->aProperties)) + pProperty->Value >>= aPortion.sDialogTitle; + } + } + else + aPortion.sText = sText; + aPortion.eLanguage = lcl_GetLanguage(*GetSh()); + aPortion.bIsField = bIsField; + aPortion.bIsHidden = bIsHidden; + aPortion.xAlternatives = xAlt; + SpellContentPosition aPosition; + SwPaM *pCursor = GetSh()->GetCursor(); + aPosition.nLeft = pCursor->Start()->GetContentIndex(); + aPosition.nRight = pCursor->End()->GetContentIndex(); + m_aLastPortions.push_back(aPortion); + m_aLastPositions.push_back(aPosition); +} + +void SwSpellIter::AddPortion(uno::Reference< XSpellAlternatives > const & xAlt, + const linguistic2::ProofreadingResult* pGrammarResult, + const SpellContentPositions& rDeletedRedlines) +{ + SwEditShell *pMySh = GetSh(); + OUString sText; + pMySh->GetSelectedText( sText ); + if(sText.isEmpty()) + return; + + if(xAlt.is() || pGrammarResult != nullptr) + { + CreatePortion(xAlt, pGrammarResult, false, false); + } + else + { + SwPaM *pCursor = GetSh()->GetCursor(); + if ( *pCursor->GetPoint() > *pCursor->GetMark() ) + pCursor->Exchange(); + // save the start and end positions + SwPosition aStart(*pCursor->GetPoint()); + SwPosition aEnd(*pCursor->GetMark()); + // iterate over the text to find changes in language + // set the mark equal to the point + *pCursor->GetMark() = aStart; + SwTextNode* pTextNode = pCursor->GetPointNode().GetTextNode(); + LanguageType eStartLanguage = lcl_GetLanguage(*GetSh()); + SpellContentPosition aNextRedline = lcl_FindNextDeletedRedline( + rDeletedRedlines, aStart.GetContentIndex() ); + if( aNextRedline.nLeft == aStart.GetContentIndex() ) + { + // select until the end of the current redline + const sal_Int32 nEnd = aEnd.GetContentIndex() < aNextRedline.nRight ? + aEnd.GetContentIndex() : aNextRedline.nRight; + pCursor->GetPoint()->SetContent( nEnd ); + CreatePortion(xAlt, pGrammarResult, false, true); + aStart = *pCursor->End(); + // search for next redline + aNextRedline = lcl_FindNextDeletedRedline( + rDeletedRedlines, aStart.GetContentIndex() ); + } + while(*pCursor->GetPoint() < aEnd) + { + // #125786 in table cell with fixed row height the cursor might not move forward + if(!GetSh()->Right(1, SwCursorSkipMode::Cells)) + break; + + bool bField = false; + // read the character at the current position to check if it's a field + sal_Unicode const cChar = + pTextNode->GetText()[pCursor->GetMark()->GetContentIndex()]; + if( CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar) + { + const SwTextAttr* pTextAttr = pTextNode->GetTextAttrForCharAt( + pCursor->GetMark()->GetContentIndex() ); + const sal_uInt16 nWhich = pTextAttr + ? pTextAttr->Which() + : RES_TXTATR_END; + switch (nWhich) + { + case RES_TXTATR_FIELD: + case RES_TXTATR_ANNOTATION: + case RES_TXTATR_FTN: + case RES_TXTATR_FLYCNT: + bField = true; + break; + } + } + else if (cChar == CH_TXT_ATR_FORMELEMENT) + { + SwPosition aPos(*pCursor->GetMark()); + bField = pMySh->GetDoc()->getIDocumentMarkAccess()->getDropDownFor(aPos); + } + + LanguageType eCurLanguage = lcl_GetLanguage(*GetSh()); + bool bRedline = aNextRedline.nLeft == pCursor->GetPoint()->GetContentIndex(); + // create a portion if the next character + // - is a field, + // - is at the beginning of a deleted redline + // - has a different language + if(bField || bRedline || eCurLanguage != eStartLanguage) + { + eStartLanguage = eCurLanguage; + // go one step back - the cursor currently selects the first character + // with a different language + // in the case of redlining it's different + if(eCurLanguage != eStartLanguage || bField) + *pCursor->GetPoint() = *pCursor->GetMark(); + // set to the last start + *pCursor->GetMark() = aStart; + // create portion should only be called if a selection exists + // there's no selection if there's a field at the beginning + if(*pCursor->Start() != *pCursor->End()) + CreatePortion(xAlt, pGrammarResult, false, false); + aStart = *pCursor->End(); + // now export the field - if there is any + if(bField) + { + *pCursor->GetMark() = *pCursor->GetPoint(); + GetSh()->Right(1, SwCursorSkipMode::Cells); + CreatePortion(xAlt, pGrammarResult, true, false); + aStart = *pCursor->End(); + } + } + // if a redline start then create a portion for it + if(bRedline) + { + *pCursor->GetMark() = *pCursor->GetPoint(); + // select until the end of the current redline + const sal_Int32 nEnd = aEnd.GetContentIndex() < aNextRedline.nRight ? + aEnd.GetContentIndex() : aNextRedline.nRight; + pCursor->GetPoint()->SetContent( nEnd ); + CreatePortion(xAlt, pGrammarResult, false, true); + aStart = *pCursor->End(); + // search for next redline + aNextRedline = lcl_FindNextDeletedRedline( + rDeletedRedlines, aStart.GetContentIndex() ); + } + *pCursor->GetMark() = *pCursor->GetPoint(); + } + pCursor->SetMark(); + *pCursor->GetMark() = aStart; + CreatePortion(xAlt, pGrammarResult, false, false); + } +} + +void SwEditShell::IgnoreGrammarErrorAt( SwPaM& rErrorPosition ) +{ + SwTextNode *pNode; + SwWrongList *pWrong; + SwNodeIndex aIdx(rErrorPosition.Start()->GetNode()); + SwNodeIndex aEndIdx(rErrorPosition.Start()->GetNode()); + sal_Int32 nStart = rErrorPosition.Start()->GetContentIndex(); + sal_Int32 nEnd = COMPLETE_STRING; + while( aIdx <= aEndIdx ) + { + pNode = aIdx.GetNode().GetTextNode(); + if( pNode ) { + if( aIdx == aEndIdx ) + nEnd = rErrorPosition.End()->GetContentIndex(); + pWrong = pNode->GetGrammarCheck(); + if( pWrong ) + pWrong->RemoveEntry( nStart, nEnd ); + pWrong = pNode->GetWrong(); + if( pWrong ) + pWrong->RemoveEntry( nStart, nEnd ); + SwTextFrame::repaintTextFrames( *pNode ); + } + ++aIdx; + nStart = 0; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/ednumber.cxx b/sw/source/core/edit/ednumber.cxx new file mode 100644 index 0000000000..c272aa5f8e --- /dev/null +++ b/sw/source/core/edit/ednumber.cxx @@ -0,0 +1,880 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <editsh.hxx> +#include <edimp.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentState.hxx> +#include <ndtxt.hxx> +#include <txtfrm.hxx> +#include <swundo.hxx> +#include <numrule.hxx> +#include <osl/diagnose.h> + +SwPamRanges::SwPamRanges( const SwPaM& rRing ) +{ + for(SwPaM& rTmp : const_cast<SwPaM*>(&rRing)->GetRingContainer()) + Insert( rTmp.GetMark()->GetNode(), rTmp.GetPoint()->GetNode() ); +} + +void SwPamRanges::Insert( const SwNode& rIdx1, const SwNode& rIdx2 ) +{ + SwPamRange aRg( rIdx1.GetIndex(), rIdx2.GetIndex() ); + if( aRg.nEnd < aRg.nStart ) + { aRg.nStart = aRg.nEnd; aRg.nEnd = rIdx1.GetIndex(); } + + o3tl::sorted_vector<SwPamRange>::const_iterator it = maVector.lower_bound(aRg); //search Insert Position + size_t nPos = it - maVector.begin(); + if (!maVector.empty() && (it != maVector.end()) && (*it) == aRg) + { + // is the one in the Array smaller? + SwPamRange const& rTmp = maVector[nPos]; + if( rTmp.nEnd < aRg.nEnd ) + { + aRg.nEnd = rTmp.nEnd; + maVector.erase(maVector.begin() + nPos); // combine + } + else + return; // done, because by precondition everything is combined + } + + bool bEnd; + do { + bEnd = true; + + // combine with predecessor? + if( nPos > 0 ) + { + SwPamRange const& rTmp = maVector[nPos-1]; + if( rTmp.nEnd == aRg.nStart + || rTmp.nEnd+1 == aRg.nStart ) + { + aRg.nStart = rTmp.nStart; + bEnd = false; + maVector.erase( maVector.begin() + --nPos ); // combine + } + // range contained in rTmp? + else if( rTmp.nStart <= aRg.nStart && aRg.nEnd <= rTmp.nEnd ) + return; + } + // combine with successor? + if( nPos < maVector.size() ) + { + SwPamRange const& rTmp = maVector[nPos]; + if( rTmp.nStart == aRg.nEnd || + rTmp.nStart == aRg.nEnd+1 ) + { + aRg.nEnd = rTmp.nEnd; + bEnd = false; + maVector.erase( maVector.begin() + nPos ); // combine + } + + // range contained in rTmp? + else if( rTmp.nStart <= aRg.nStart && aRg.nEnd <= rTmp.nEnd ) + return; + } + } while( !bEnd ); + + maVector.insert( aRg ); +} + +SwPaM& SwPamRanges::SetPam( size_t nArrPos, SwPaM& rPam ) +{ + assert( nArrPos < Count() ); + const SwPamRange& rTmp = maVector[ nArrPos ]; + rPam.GetPoint()->Assign(rTmp.nStart); + rPam.SetMark(); + rPam.GetPoint()->Assign(rTmp.nEnd); + return rPam; +} + +// Rule book for outline numbering + +void SwEditShell::SetOutlineNumRule(const SwNumRule& rRule) +{ + StartAllAction(); // bracketing for updating! + GetDoc()->SetOutlineNumRule(rRule); + EndAllAction(); +} + +const SwNumRule* SwEditShell::GetOutlineNumRule() const +{ + return GetDoc()->GetOutlineNumRule(); +} + +// Set if there is no numbering yet, else update. +// Works with old and new rules. Update only differences. + +// paragraphs without numbering, with indentations +void SwEditShell::NoNum() +{ + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor ) // Multiple selection? + { + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + SwPamRanges aRangeArr( *pCursor ); + SwPaM aPam( *pCursor->GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + GetDoc()->NoNum( aRangeArr.SetPam( n, aPam )); + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + else + // sw_redlinehide: leave cursor as is, will be split at Point & apply to new node + GetDoc()->NoNum( *pCursor ); + + EndAllAction(); +} + +// The entire selection is numbered (ignoring unnumbered empty lines) +bool SwEditShell::SelectionHasNumber() const +{ + bool bResult = false; + for (SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + SwNodeOffset nStt = rPaM.Start()->GetNodeIndex(); + SwNodeOffset nEnd = rPaM.End()->GetNodeIndex(); + for (SwNodeOffset nPos = nStt; nPos<=nEnd; nPos++) + { + SwTextNode* pTextNd = mxDoc->GetNodes()[nPos]->GetTextNode(); + if (pTextNd) + { + pTextNd = sw::GetParaPropsNode(*GetLayout(), *pTextNd); + } + if (pTextNd && (!bResult || pTextNd->Len()!=0)) + { + bResult = pTextNd->HasNumber(); + + // #b6340308# special case: outline numbered, not counted paragraph + if (bResult && + pTextNd->GetNumRule() == GetDoc()->GetOutlineNumRule() && + !pTextNd->IsCountedInList()) + { + bResult = false; + } + if (!bResult && pTextNd->Len()) + break; + } + } + } + + return bResult; +} + +// add a new function to determine number on/off status +bool SwEditShell::SelectionHasBullet() const +{ + bool bResult = false; + for (SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + SwNodeOffset nStt = rPaM.Start()->GetNodeIndex(); + SwNodeOffset nEnd = rPaM.End()->GetNodeIndex(); + for (SwNodeOffset nPos = nStt; nPos<=nEnd; nPos++) + { + SwTextNode* pTextNd = mxDoc->GetNodes()[nPos]->GetTextNode(); + if (pTextNd) + { + pTextNd = sw::GetParaPropsNode(*GetLayout(), *pTextNd); + } + if (pTextNd && (!bResult || pTextNd->Len()!=0)) + { + bResult = pTextNd->HasBullet(); + + if (!bResult && pTextNd->Len()) + break; + } + } + } + + return bResult; +} + +// -> #i29560# +bool SwEditShell::HasNumber() const +{ + bool bResult = false; + + const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), GetCursor()->GetPoint()->GetNode()); + + if (pTextNd) + { + bResult = pTextNd->HasNumber(); + + // special case: outline numbered, not counted paragraph + if ( bResult && + pTextNd->GetNumRule() == GetDoc()->GetOutlineNumRule() && + !pTextNd->IsCountedInList() ) + { + bResult = false; + } + } + + return bResult; +} + +bool SwEditShell::HasBullet() const +{ + bool bResult = false; + + const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), GetCursor()->GetPoint()->GetNode()); + + if (pTextNd) + { + bResult = pTextNd->HasBullet(); + } + + return bResult; +} +// <- #i29560# + +// delete, split list +void SwEditShell::DelNumRules() +{ + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->IsMultiSelection() ) + { + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + for (SwPaM& rPaM : pCursor->GetRingContainer()) + { + GetDoc()->DelNumRules(rPaM, GetLayout()); + } + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + else + GetDoc()->DelNumRules(*pCursor, GetLayout()); + + // Call AttrChangeNotify on the UI-side. Should actually be redundant but there was a bug once. + CallChgLnk(); + + // Cursor cannot be in front of a label anymore, because numbering/bullet is deleted. + SetInFrontOfLabel( false ); + + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); +} + +// up- & downgrading +void SwEditShell::NumUpDown( bool bDown ) +{ + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( !pCursor->IsMultiSelection() ) + GetDoc()->NumUpDown(*pCursor, bDown, GetLayout()); + else + { + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + SwPamRanges aRangeArr( *pCursor ); + SwPaM aPam( *pCursor->GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + GetDoc()->NumUpDown(aRangeArr.SetPam( n, aPam ), bDown, GetLayout()); + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + GetDoc()->getIDocumentState().SetModified(); + + // #i54693# Update marked numbering levels + if ( IsInFrontOfLabel() ) + UpdateMarkedListLevel(); + + CallChgLnk(); + + EndAllAction(); +} + +bool SwEditShell::IsFirstOfNumRuleAtCursorPos() const +{ + return SwDoc::IsFirstOfNumRuleAtPos(*GetCursor()->GetPoint(), *GetLayout()); +} + +// -> #i23725#, #i90078# +void SwEditShell::ChangeIndentOfAllListLevels( const sal_Int32 nDiff ) +{ + StartAllAction(); + + const SwNumRule *pCurNumRule = GetNumRuleAtCurrCursorPos(); + if ( pCurNumRule != nullptr ) + { + SwNumRule aRule(*pCurNumRule); + const SwNumFormat& aRootNumFormat(aRule.Get(0)); + if( nDiff > 0 || aRootNumFormat.GetIndentAt() + nDiff > 0) // fdo#42708 + { + // #i90078# + aRule.ChangeIndent( nDiff ); + } + // no start of new list + SetCurNumRule( aRule, false ); + } + + EndAllAction(); +} + +// #i90078# +void SwEditShell::SetIndent(short nIndent, const SwPosition & rPos) +{ + StartAllAction(); + + SwPosition pos(rPos); + SwNumRule *pCurNumRule = SwDoc::GetNumRuleAtPos(pos, GetLayout()); + + if (pCurNumRule) + { + SwNumRule aRule(*pCurNumRule); + if ( !IsMultiSelection() && IsFirstOfNumRuleAtCursorPos() ) + { + aRule.SetIndentOfFirstListLevelAndChangeOthers( nIndent ); + } + else + { + const SwTextNode* pTextNode = pos.GetNode().GetTextNode(); + if ( pTextNode != nullptr + && pTextNode->GetActualListLevel() >= 0 ) + { + aRule.SetIndent( nIndent, static_cast< sal_uInt16 >( pTextNode->GetActualListLevel() ) ); + } + } + + // change numbering rule - changed numbering rule is not applied at <aPaM> + SwPaM aPaM(pos); + GetDoc()->SetNumRule(aPaM, aRule, false, GetLayout(), OUString(), false); + } + + EndAllAction(); +} + +bool SwEditShell::MoveParagraph( SwNodeOffset nOffset ) +{ + StartAllAction(); + + SwPaM *pCursor = GetCursor(); + + bool bRet = GetDoc()->MoveParagraph( *pCursor, nOffset ); + + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); + return bRet; +} + +int SwEditShell::GetCurrentParaOutlineLevel( ) const +{ + int nLevel = 0; + + SwPaM* pCursor = GetCursor(); + const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode()); + if (pTextNd) + nLevel = pTextNd->GetAttrOutlineLevel(); + return nLevel; +} + +void SwEditShell::GetCurrentOutlineLevels( sal_uInt8& rUpper, sal_uInt8& rLower ) +{ + SwPaM* pCursor = GetCursor(); + SwPaM aCursor( *pCursor->Start() ); + aCursor.SetMark(); + if( pCursor->HasMark() ) + *aCursor.GetPoint() = *pCursor->End(); + SwDoc::GotoNextNum(*aCursor.GetPoint(), GetLayout(), false, &rUpper, &rLower); +} + +bool SwEditShell::MoveNumParas( bool bUpperLower, bool bUpperLeft ) +{ + StartAllAction(); + + // On all selections? + SwPaM* pCursor = GetCursor(); + SwPaM aCursor( *pCursor->Start() ); + aCursor.SetMark(); + + if( pCursor->HasMark() ) + *aCursor.GetPoint() = *pCursor->End(); + + bool bRet = false; + sal_uInt8 nUpperLevel, nLowerLevel; + if (SwDoc::GotoNextNum( *aCursor.GetPoint(), GetLayout(), false, + &nUpperLevel, &nLowerLevel )) + { + if( bUpperLower ) + { + // on top of the next numbering + SwNodeOffset nOffset(0); + const SwNode* pNd; + + if( bUpperLeft ) // move up + { + SwPosition aPos( *aCursor.GetMark() ); + if (SwDoc::GotoPrevNum( aPos, GetLayout(), false )) + nOffset = aPos.GetNodeIndex() - + aCursor.GetMark()->GetNodeIndex(); + else + { + SwNodeOffset nStt = aPos.GetNodeIndex(), nIdx = nStt - 1; + + if (SwTextNode const*const pStt = aPos.GetNode().GetTextNode()) + { + std::pair<SwTextNode *, SwTextNode *> nodes( + sw::GetFirstAndLastNode(*GetLayout(), *pStt)); + nIdx = nodes.first->GetIndex() - 1; + } + while( nIdx && ( + ( pNd = GetDoc()->GetNodes()[ nIdx ])->IsSectionNode() || + ( pNd->IsEndNode() && pNd->StartOfSectionNode()->IsSectionNode()))) + --nIdx; + if( GetDoc()->GetNodes()[ nIdx ]->IsTextNode() ) + nOffset = nIdx - nStt; + } + } + else // move down + { + assert(!aCursor.GetPointNode().IsTextNode() + || sw::IsParaPropsNode(*GetLayout(), *aCursor.GetPointNode().GetTextNode())); + const SwNumRule* pOrig = sw::GetParaPropsNode(*GetLayout(), *aCursor.GetMarkNode().GetTextNode())->GetNumRule(); + if( aCursor.GetPointNode().IsTextNode() && + pOrig == aCursor.GetPointNode().GetTextNode()->GetNumRule() ) + { + SwNodeOffset nStt = aCursor.GetPoint()->GetNodeIndex(), nIdx = nStt+1; + if (SwTextNode const*const pStt = aCursor.GetPoint()->GetNode().GetTextNode()) + { + std::pair<SwTextNode *, SwTextNode *> nodes( + sw::GetFirstAndLastNode(*GetLayout(), *pStt)); + nIdx = nodes.second->GetIndex() + 1; + } + + while (nIdx < GetDoc()->GetNodes().Count()-1) + { + pNd = GetDoc()->GetNodes()[ nIdx ]; + + if (pNd->IsSectionNode() || + (pNd->IsEndNode() && pNd->StartOfSectionNode()->IsSectionNode())) + { + ++nIdx; + } + else if (pNd->IsTextNode()) + { + SwTextNode const*const pTextNode = + sw::GetParaPropsNode(*GetLayout(), *pNd); + if (pOrig == pTextNode->GetNumRule() + && pTextNode->GetActualListLevel() > nUpperLevel) + { + std::pair<SwTextNode *, SwTextNode *> nodes( + sw::GetFirstAndLastNode(*GetLayout(), *pTextNode)); + nIdx = nodes.second->GetIndex() + 1; + } + else + { + break; + } + } + // #i57856# + else + { + break; + } + } + + if( nStt == nIdx || !GetDoc()->GetNodes()[ nIdx ]->IsTextNode() ) + nOffset = SwNodeOffset(1); + else + nOffset = nIdx - nStt; + } + else + nOffset = SwNodeOffset(1); + } + + if( nOffset ) + { + aCursor.Move( fnMoveBackward, GoInNode ); + bRet = GetDoc()->MoveParagraph( aCursor, nOffset ); + } + } + else if( (bUpperLeft ? nUpperLevel : nLowerLevel+1) < MAXLEVEL ) + { + aCursor.Move( fnMoveBackward, GoInNode ); + bRet = GetDoc()->NumUpDown(aCursor, !bUpperLeft, GetLayout()); + } + } + + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); + return bRet; +} + +bool SwEditShell::OutlineUpDown( short nOffset ) +{ + StartAllAction(); + + bool bRet = true; + SwPaM* pCursor = GetCursor(); + if( !pCursor->IsMultiSelection() ) + bRet = GetDoc()->OutlineUpDown(*pCursor, nOffset, GetLayout()); + else + { + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + SwPamRanges aRangeArr( *pCursor ); + SwPaM aPam( *pCursor->GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + bRet = bRet && GetDoc()->OutlineUpDown( + aRangeArr.SetPam(n, aPam), nOffset, GetLayout()); + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + GetDoc()->getIDocumentState().SetModified(); + EndAllAction(); + return bRet; +} + +bool SwEditShell::MoveOutlinePara( SwOutlineNodes::difference_type nOffset ) +{ + StartAllAction(); + bool bRet = GetDoc()->MoveOutlinePara( *GetCursor(), nOffset ); + EndAllAction(); + return bRet; +} + +// Outlines and SubOutline are ReadOnly? +bool SwEditShell::IsProtectedOutlinePara() const +{ + bool bRet = false; + const SwNode& rNd = GetCursor()->Start()->GetNode(); + if( rNd.IsTextNode() ) + { + const SwOutlineNodes& rOutlNd = GetDoc()->GetNodes().GetOutLineNds(); + SwNode* pNd = const_cast<SwNode*>(&rNd); + bool bFirst = true; + SwOutlineNodes::size_type nPos; + int nLvl(0); + if( !rOutlNd.Seek_Entry( pNd, &nPos ) && nPos ) + --nPos; + + for( ; nPos < rOutlNd.size(); ++nPos ) + { + SwNode* pTmpNd = rOutlNd[ nPos ]; + + if (!sw::IsParaPropsNode(*GetLayout(), *pTmpNd->GetTextNode())) + { + continue; + } + + int nTmpLvl = pTmpNd->GetTextNode()->GetAttrOutlineLevel(); + + OSL_ENSURE( nTmpLvl >= 0 && nTmpLvl <= MAXLEVEL, + "<SwEditShell::IsProtectedOutlinePara()>" ); + + if( bFirst ) + { + nLvl = nTmpLvl; + bFirst = false; + } + else if( nLvl >= nTmpLvl ) + break; + + if( pTmpNd->IsProtect() ) + { + bRet = true; + break; + } + } + } +#if OSL_DEBUG_LEVEL > 0 + else + { + OSL_FAIL("Cursor not on an outline node"); + } +#endif + return bRet; +} + +/** Test whether outline may be moved (bCopy == false) + * or copied (bCopy == true) + * Verify these conditions: + * 1) outline must be within main body (and not in redline) + * 2) outline must not be within table + * 3) if bCopy is set, outline must not be write protected + */ +static bool lcl_IsOutlineMoveAndCopyable(SwEditShell const& rShell, + SwOutlineNodes::size_type const nIdx, bool const bCopy) +{ + const SwNodes& rNds = rShell.GetDoc()->GetNodes(); + const SwNode* pNd = rNds.GetOutLineNds()[ nIdx ]; + return pNd->GetIndex() >= rNds.GetEndOfExtras().GetIndex() && // 1) body + !pNd->FindTableNode() && // 2) table + sw::IsParaPropsNode(*rShell.GetLayout(), *pNd->GetTextNode()) && + ( bCopy || !pNd->IsProtect() ); // 3) write +} + +bool SwEditShell::IsOutlineMovable( SwOutlineNodes::size_type nIdx ) const +{ + return lcl_IsOutlineMoveAndCopyable( *this, nIdx, false ); +} + +bool SwEditShell::IsOutlineCopyable( SwOutlineNodes::size_type nIdx ) const +{ + return lcl_IsOutlineMoveAndCopyable( *this, nIdx, true ); +} + +bool SwEditShell::NumOrNoNum( + bool bNumOn, + bool bChkStart ) +{ + bool bRet = false; + + if ( !IsMultiSelection() + && !HasSelection() + && ( !bChkStart || IsSttPara() ) ) + { + StartAllAction(); + SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *GetCursor()->GetPoint())); + bRet = GetDoc()->NumOrNoNum(pos.GetNode(), !bNumOn); + EndAllAction(); + } + return bRet; +} + +bool SwEditShell::IsNoNum( bool bChkStart ) const +{ + // a Backspace in the paragraph without number becomes a Delete + bool bResult = false; + + if ( !IsMultiSelection() + && !HasSelection() + && ( !bChkStart || IsSttPara() ) ) + { + const SwTextNode* pTextNd = sw::GetParaPropsNode(*GetLayout(), GetCursor()->GetPoint()->GetNode()); + if ( pTextNd != nullptr ) + { + bResult = !pTextNd->IsCountedInList(); + } + } + + return bResult; +} + +sal_uInt8 SwEditShell::GetNumLevel() const +{ + // return current level where the point of the cursor is + sal_uInt8 nLevel = MAXLEVEL; + + SwPaM* pCursor = GetCursor(); + const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode()); + + OSL_ENSURE( pTextNd, "GetNumLevel() without text node" ); + if ( pTextNd == nullptr ) + return nLevel; + + const SwNumRule* pRule = pTextNd->GetNumRule(); + if ( pRule != nullptr ) + { + const int nListLevelOfTextNode( pTextNd->GetActualListLevel() ); + if ( nListLevelOfTextNode >= 0 ) + { + nLevel = static_cast<sal_uInt8>( nListLevelOfTextNode ); + } + } + + return nLevel; +} + +const SwNumRule* SwEditShell::GetNumRuleAtCurrCursorPos() const +{ + SwPosition pos(*GetCursor()->GetPoint()); + return SwDoc::GetNumRuleAtPos( pos, GetLayout() ); +} + +const SwNumRule* SwEditShell::GetNumRuleAtCurrentSelection() const +{ + const SwNumRule* pNumRuleAtCurrentSelection = nullptr; + + bool bDifferentNumRuleFound = false; + for(const SwPaM& rCurrentCursor : GetCursor()->GetRingContainer()) + { + const SwNode& rEndNode(rCurrentCursor.End()->GetNode()); + + for ( SwNodeIndex aNode(rCurrentCursor.Start()->GetNode()); aNode <= rEndNode; ++aNode ) + { + SwPosition pos(aNode); + const SwNumRule* pNumRule = SwDoc::GetNumRuleAtPos(pos, GetLayout()); + if ( pNumRule == nullptr ) + { + continue; + } + else if ( pNumRule != pNumRuleAtCurrentSelection ) + { + if ( pNumRuleAtCurrentSelection == nullptr ) + { + pNumRuleAtCurrentSelection = pNumRule; + } + else + { + pNumRuleAtCurrentSelection = nullptr; + bDifferentNumRuleFound = true; + break; + } + } + } + if(bDifferentNumRuleFound) + break; + } + + return pNumRuleAtCurrentSelection; +} + +void SwEditShell::SetCurNumRule( const SwNumRule& rRule, + bool bCreateNewList, + const OUString& rContinuedListId, + const bool bResetIndentAttrs ) +{ + StartAllAction(); + + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::INSATTR, nullptr ); + + SwPaM* pCursor = GetCursor(); + if( IsMultiSelection() ) + { + OUString sContinuedListId(rContinuedListId); + for (SwPaM& rPaM : pCursor->GetRingContainer()) + { + OUString sListId = GetDoc()->SetNumRule(rPaM, rRule, + bCreateNewList, GetLayout(), sContinuedListId, + true, bResetIndentAttrs ); + + //tdf#87548 On creating a new list for a multi-selection only + //create a single new list for the multi-selection, not one per selection + if (bCreateNewList) + { + sContinuedListId = sListId; + bCreateNewList = false; + } + + GetDoc()->SetCounted(rPaM, true, GetLayout()); + } + } + else + { + GetDoc()->SetNumRule( *pCursor, rRule, + bCreateNewList, GetLayout(), rContinuedListId, + true, bResetIndentAttrs ); + GetDoc()->SetCounted( *pCursor, true, GetLayout() ); + } + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::INSATTR, nullptr ); + + EndAllAction(); +} + +OUString SwEditShell::GetUniqueNumRuleName() const +{ + return GetDoc()->GetUniqueNumRuleName(); +} + +void SwEditShell::ChgNumRuleFormats( const SwNumRule& rRule ) +{ + StartAllAction(); + GetDoc()->ChgNumRuleFormats( rRule ); + EndAllAction(); +} + +void SwEditShell::ReplaceNumRule( const OUString& rOldRule, const OUString& rNewRule ) +{ + StartAllAction(); + SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *GetCursor()->GetPoint())); + GetDoc()->ReplaceNumRule( pos, rOldRule, rNewRule ); + EndAllAction(); +} + +void SwEditShell::SetNumRuleStart( bool bFlag, SwPaM* pPaM ) +{ + StartAllAction(); + SwPaM* pCursor = pPaM ? pPaM : GetCursor(); + if( pCursor->IsMultiSelection() ) // multiple selection ? + { + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + SwPamRanges aRangeArr( *pCursor ); + SwPaM aPam( *pCursor->GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + { + SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *aRangeArr.SetPam( n, aPam ).GetPoint())); + GetDoc()->SetNumRuleStart( pos, bFlag ); + } + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + else + { + SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *GetCursor()->GetPoint())); + GetDoc()->SetNumRuleStart(pos, bFlag); + } + + EndAllAction(); +} + +bool SwEditShell::IsNumRuleStart( SwPaM* pPaM ) const +{ + SwPaM* pCursor = pPaM ? pPaM : GetCursor( ); + const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode()); + return pTextNd && pTextNd->IsListRestart(); +} + +void SwEditShell::SetNodeNumStart( sal_uInt16 nStt ) +{ + StartAllAction(); + + SwPaM* pCursor = GetCursor(); + if( pCursor->IsMultiSelection() ) // multiple selection ? + { + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + SwPamRanges aRangeArr( *pCursor ); + SwPaM aPam( *pCursor->GetPoint() ); + for( size_t n = 0; n < aRangeArr.Count(); ++n ) + { + SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *aRangeArr.SetPam( n, aPam ).GetPoint())); + GetDoc()->SetNodeNumStart( pos, nStt ); + } + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + } + else + { + SwPosition const pos(sw::GetParaPropsPos(*GetLayout(), *pCursor->GetPoint())); + GetDoc()->SetNodeNumStart( pos, nStt ); + } + + EndAllAction(); +} + +sal_uInt16 SwEditShell::GetNodeNumStart( SwPaM* pPaM ) const +{ + SwPaM* pCursor = pPaM ? pPaM : GetCursor(); + const SwTextNode *const pTextNd = sw::GetParaPropsNode(*GetLayout(), pCursor->GetPoint()->GetNode()); + // correction: check, if list restart value is set at text node and + // use new method <SwTextNode::GetAttrListRestartValue()>. + // return USHRT_MAX, if no list restart value is found. + if ( pTextNd && pTextNd->HasAttrListRestartValue() ) + { + return o3tl::narrowing<sal_uInt16>(pTextNd->GetAttrListRestartValue()); + } + return USHRT_MAX; +} + +const SwNumRule * SwEditShell::SearchNumRule( const bool bNum, + OUString& sListId ) +{ + return GetDoc()->SearchNumRule( *(GetCursor()->Start()), + false/*bForward*/, bNum, false/*bOutline*/, -1/*nNonEmptyAllowe*/, + sListId, GetLayout() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edredln.cxx b/sw/source/core/edit/edredln.cxx new file mode 100644 index 0000000000..1778745f57 --- /dev/null +++ b/sw/source/core/edit/edredln.cxx @@ -0,0 +1,196 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <IDocumentRedlineAccess.hxx> +#include <docary.hxx> +#include <redline.hxx> +#include <doc.hxx> +#include <editsh.hxx> +#include <frmtool.hxx> + +RedlineFlags SwEditShell::GetRedlineFlags() const +{ + return GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags(); +} + +void SwEditShell::SetRedlineFlags( RedlineFlags eMode ) +{ + if( eMode != GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags() ) + { + CurrShell aCurr( this ); + StartAllAction(); + GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eMode ); + EndAllAction(); + } +} + +bool SwEditShell::IsRedlineOn() const +{ + return GetDoc()->getIDocumentRedlineAccess().IsRedlineOn(); +} + +SwRedlineTable::size_type SwEditShell::GetRedlineCount() const +{ + return GetDoc()->getIDocumentRedlineAccess().GetRedlineTable().size(); +} + +const SwRangeRedline& SwEditShell::GetRedline( SwRedlineTable::size_type nPos ) const +{ + return *GetDoc()->getIDocumentRedlineAccess().GetRedlineTable()[ nPos ]; +} + +static void lcl_InvalidateAll( SwViewShell* pSh ) +{ + for(SwViewShell& rCurrentShell : pSh->GetRingContainer()) + { + if ( rCurrentShell.GetWin() ) + rCurrentShell.GetWin()->Invalidate(); + } +} + +bool SwEditShell::AcceptRedline( SwRedlineTable::size_type nPos ) +{ + CurrShell aCurr( this ); + StartAllAction(); + bool bRet = GetDoc()->getIDocumentRedlineAccess().AcceptRedline( nPos, true, true ); + if( !nPos && !::IsExtraData( GetDoc() ) ) + lcl_InvalidateAll( this ); + EndAllAction(); + return bRet; +} + +bool SwEditShell::RejectRedline( SwRedlineTable::size_type nPos ) +{ + CurrShell aCurr( this ); + StartAllAction(); + bool bRet = GetDoc()->getIDocumentRedlineAccess().RejectRedline( nPos, true, true ); + if( !nPos && !::IsExtraData( GetDoc() ) ) + lcl_InvalidateAll( this ); + EndAllAction(); + return bRet; +} + +bool SwEditShell::AcceptRedlinesInSelection() +{ + CurrShell aCurr( this ); + StartAllAction(); + // in table selection mode, process the selected boxes in reverse order + // to allow accepting their text changes and the tracked row deletions + bool bRet = false; + if ( IsTableMode() ) + { + const SwSelBoxes& rBoxes = GetTableCursor()->GetSelectedBoxes(); + std::vector<std::unique_ptr<SwPaM>> vBoxes; + for(auto pBox : rBoxes) + { + if ( !pBox->IsEmpty() ) + { + const SwStartNode *pSttNd = pBox->GetSttNd(); + SwNode* pEndNode = pSttNd->GetNodes()[pSttNd->EndOfSectionIndex()]; + vBoxes.push_back(std::unique_ptr<SwPaM>(new SwPaM(*pEndNode, 0, *pSttNd, 0))); + } + } + + for (size_t i = 0; i < vBoxes.size(); ++i) + bRet |= GetDoc()->getIDocumentRedlineAccess().AcceptRedline( *vBoxes[vBoxes.size()-i-1], true ); + } + else + bRet = GetDoc()->getIDocumentRedlineAccess().AcceptRedline( *GetCursor(), true ); + EndAllAction(); + return bRet; +} + +bool SwEditShell::RejectRedlinesInSelection() +{ + CurrShell aCurr( this ); + StartAllAction(); + bool bRet = false; + // in table selection mode, process the selected boxes in reverse order + // to allow rejecting their text changes and the tracked row insertions + if ( IsTableMode() ) + { + const SwSelBoxes& rBoxes = GetTableCursor()->GetSelectedBoxes(); + std::vector<std::unique_ptr<SwPaM>> vBoxes; + for(auto pBox : rBoxes) + { + if ( !pBox->IsEmpty() ) + { + const SwStartNode *pSttNd = pBox->GetSttNd(); + SwNode* pEndNode = pSttNd->GetNodes()[pSttNd->EndOfSectionIndex()]; + vBoxes.push_back(std::unique_ptr<SwPaM>(new SwPaM(*pEndNode, 0, *pSttNd, 0))); + } + } + + for (size_t i = 0; i < vBoxes.size(); ++i) + bRet |= GetDoc()->getIDocumentRedlineAccess().RejectRedline( *vBoxes[vBoxes.size()-i-1], true ); + } + else + bRet = GetDoc()->getIDocumentRedlineAccess().RejectRedline( *GetCursor(), true ); + EndAllAction(); + return bRet; +} + +// Set the comment at the Redline +bool SwEditShell::SetRedlineComment( const OUString& rS ) +{ + bool bRet = false; + for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + bRet = bRet || GetDoc()->getIDocumentRedlineAccess().SetRedlineComment( rPaM, rS ); + } + + return bRet; +} + +const SwRangeRedline* SwEditShell::GetCurrRedline() const +{ + if (const SwRangeRedline* pRed = GetDoc()->getIDocumentRedlineAccess().GetRedline( *GetCursor()->GetPoint(), nullptr )) + return pRed; + // check the other side of the selection to handle completely selected changes, where the Point is at the end + return GetDoc()->getIDocumentRedlineAccess().GetRedline( *GetCursor()->GetMark(), nullptr ); +} + +void SwEditShell::UpdateRedlineAttr() +{ + if( IDocumentRedlineAccess::IsShowChanges(GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags()) ) + { + CurrShell aCurr( this ); + StartAllAction(); + + GetDoc()->getIDocumentRedlineAccess().UpdateRedlineAttr(); + + EndAllAction(); + } +} + +/** Search the Redline of the data given + * + * @return Returns the Pos of the Array, or SwRedlineTable::npos if not present + */ +SwRedlineTable::size_type SwEditShell::FindRedlineOfData( const SwRedlineData& rData ) const +{ + const SwRedlineTable& rTable = GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + + for( SwRedlineTable::size_type i = 0, nCnt = rTable.size(); i < nCnt; ++i ) + if( &rTable[ i ]->GetRedlineData() == &rData ) + return i; + return SwRedlineTable::npos; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edsect.cxx b/sw/source/core/edit/edsect.cxx new file mode 100644 index 0000000000..a7e652aea9 --- /dev/null +++ b/sw/source/core/edit/edsect.cxx @@ -0,0 +1,439 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <editsh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentContentOperations.hxx> +#include <pam.hxx> +#include <docary.hxx> +#include <swundo.hxx> +#include <section.hxx> +#include <sectfrm.hxx> +#include <cntfrm.hxx> +#include <tabfrm.hxx> +#include <rootfrm.hxx> +#include <osl/diagnose.h> + +SwSection const* +SwEditShell::InsertSection( + SwSectionData & rNewData, SfxItemSet const*const pAttr) +{ + const SwSection* pRet = nullptr; + if( !IsTableMode() ) + { + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::INSSECTION, nullptr ); + + for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + SwSection const*const pNew = + GetDoc()->InsertSwSection( rPaM, rNewData, nullptr, pAttr ); + if( !pRet ) + pRet = pNew; + } + + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::INSSECTION, nullptr ); + EndAllAction(); + } + return pRet; +} + +bool SwEditShell::IsInsRegionAvailable() const +{ + if( IsTableMode() ) + return false; + SwPaM* pCursor = GetCursor(); + if( pCursor->GetNext() != pCursor ) + return false; + if( pCursor->HasMark() ) + return 0 != SwDoc::IsInsRegionAvailable( *pCursor ); + + return true; +} + +const SwSection* SwEditShell::GetCurrSection() const +{ + if( IsTableMode() ) + return nullptr; + + return SwDoc::GetCurrSection( *GetCursor()->GetPoint() ); +} + +/** Deliver the responsible area of the columns. + * + * In footnotes it may not be the area within the footnote. + */ +SwSection* SwEditShell::GetAnySection( bool bOutOfTab, const Point* pPt ) +{ + SwFrame *pFrame; + if ( pPt ) + { + SwPosition aPos( *GetCursor()->GetPoint() ); + Point aPt( *pPt ); + GetLayout()->GetModelPositionForViewPoint( &aPos, aPt ); + SwContentNode *pNd = aPos.GetNode().GetContentNode(); + std::pair<Point, bool> const tmp(*pPt, true); + pFrame = pNd->getLayoutFrame(GetLayout(), nullptr, &tmp); + } + else + pFrame = GetCurrFrame( false ); + + if( bOutOfTab && pFrame ) + pFrame = pFrame->FindTabFrame(); + if( pFrame && pFrame->IsInSct() ) + { + SwSectionFrame* pSect = pFrame->FindSctFrame(); + OSL_ENSURE( pSect, "GetAnySection: Where's my Sect?" ); + if( pSect->IsInFootnote() && pSect->GetUpper()->IsInSct() ) + { + pSect = pSect->GetUpper()->FindSctFrame(); + OSL_ENSURE( pSect, "GetAnySection: Where's my SectFrame?" ); + } + return pSect->GetSection(); + } + return nullptr; +} + +size_t SwEditShell::GetSectionFormatCount() const +{ + return GetDoc()->GetSections().size(); +} + +bool SwEditShell::IsAnySectionInDoc() const +{ + const SwSectionFormats& rFormats = GetDoc()->GetSections(); + + for( const SwSectionFormat* pFormat : rFormats ) + { + SectionType eTmpType; + if( pFormat->IsInNodesArr() && + ( (eTmpType = pFormat->GetSection()->GetType()) != SectionType::ToxContent + && SectionType::ToxHeader != eTmpType ) ) + { + return true; + } + } + return false; +} + +size_t SwEditShell::GetSectionFormatPos( const SwSectionFormat& rFormat ) const +{ + SwSectionFormat* pFormat = const_cast<SwSectionFormat*>(&rFormat); + return GetDoc()->GetSections().GetPos( pFormat ); +} + +const SwSectionFormat& SwEditShell::GetSectionFormat(size_t nFormat) const +{ + return *GetDoc()->GetSections()[ nFormat ]; +} + +void SwEditShell::DelSectionFormat(size_t nFormat) +{ + StartAllAction(); + GetDoc()->DelSectionFormat( GetDoc()->GetSections()[ nFormat ] ); + // Call the AttrChangeNotify on the UI page. + CallChgLnk(); + EndAllAction(); +} + +void SwEditShell::UpdateSection(size_t const nSect, + SwSectionData & rNewData, SfxItemSet const*const pAttr) +{ + StartAllAction(); + GetDoc()->UpdateSection( nSect, rNewData, pAttr ); + // Call the AttrChangeNotify on the UI page. + CallChgLnk(); + EndAllAction(); +} + +OUString SwEditShell::GetUniqueSectionName( const OUString* pChkStr ) const +{ + return GetDoc()->GetUniqueSectionName( pChkStr ); +} + +void SwEditShell::SetSectionAttr( const SfxItemSet& rSet, + SwSectionFormat* pSectFormat ) +{ + if( pSectFormat ) + SetSectionAttr_( *pSectFormat, rSet ); + else + { + // for all section in the selection + + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition* + + SwSectionNode* pSttSectNd = pStt->GetNode().FindSectionNode(), + * pEndSectNd = pEnd->GetNode().FindSectionNode(); + + if( pSttSectNd || pEndSectNd ) + { + if( pSttSectNd ) + SetSectionAttr_( *pSttSectNd->GetSection().GetFormat(), + rSet ); + if( pEndSectNd && pSttSectNd != pEndSectNd ) + SetSectionAttr_( *pEndSectNd->GetSection().GetFormat(), + rSet ); + + if( pSttSectNd && pEndSectNd ) + { + SwNodeIndex aSIdx( pStt->GetNode() ); + SwNodeIndex aEIdx( pEnd->GetNode() ); + if( pSttSectNd->EndOfSectionIndex() < + pEndSectNd->GetIndex() ) + { + aSIdx = pSttSectNd->EndOfSectionIndex() + 1; + aEIdx = *pEndSectNd; + } + + while( aSIdx < aEIdx ) + { + if( nullptr != (pSttSectNd = aSIdx.GetNode().GetSectionNode()) + || ( aSIdx.GetNode().IsEndNode() && + nullptr != ( pSttSectNd = aSIdx.GetNode(). + StartOfSectionNode()->GetSectionNode())) ) + SetSectionAttr_( *pSttSectNd->GetSection().GetFormat(), + rSet ); + ++aSIdx; + } + } + } + + } + } +} + +void SwEditShell::SetSectionAttr_( SwSectionFormat& rSectFormat, + const SfxItemSet& rSet ) +{ + StartAllAction(); + if(SfxItemState::SET == rSet.GetItemState(RES_CNTNT, false)) + { + SfxItemSet aSet(rSet); + aSet.ClearItem(RES_CNTNT); + GetDoc()->SetAttr( aSet, rSectFormat ); + } + else + GetDoc()->SetAttr( rSet, rSectFormat ); + + // Call the AttrChangeNotify on the UI page. + CallChgLnk(); + EndAllAction(); +} + +/** Search inside the cursor selection for full selected sections. + * + * @return If any part of section in the selection return 0, if more than one return the count. + */ +sal_uInt16 SwEditShell::GetFullSelectedSectionCount() const +{ + sal_uInt16 nRet = 0; + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + + auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition* + const SwContentNode* pCNd; + // check the selection, if Start at Node begin and End at Node end + if( pStt->GetContentIndex() || + ( nullptr == ( pCNd = pEnd->GetNode().GetContentNode() )) || + pCNd->Len() != pEnd->GetContentIndex() ) + { + nRet = 0; + break; + } + +// !!! +// what about table at start or end ? +// There is no selection possible! +// What about only a table inside the section ? +// There is only a table selection possible! + + SwNodeIndex aSIdx( pStt->GetNode(), -1 ), aEIdx( pEnd->GetNode(), +1 ); + if( !aSIdx.GetNode().IsSectionNode() || + !aEIdx.GetNode().IsEndNode() || + !aEIdx.GetNode().StartOfSectionNode()->IsSectionNode() ) + { + nRet = 0; + break; + } + + ++nRet; + if( &aSIdx.GetNode() != aEIdx.GetNode().StartOfSectionNode() ) + ++nRet; + + } + return nRet; +} + +/** Find the suitable node for a special insert (alt-enter). + * + * This should enable inserting text before/after sections and tables. + * + * A node is found if: + * 1) the innermost table/section is not in a write-protected area + * 2) pCurrentPos is at or just before an end node + * (or at or just after a start node) + * 3) there are only start/end nodes between pCurrentPos and the innermost + * table/section + * + * If a suitable node is found, an SwNode* is returned; else it is NULL. + */ +static const SwNode* lcl_SpecialInsertNode(const SwPosition* pCurrentPos) +{ + const SwNode* pReturn = nullptr; + + // the current position + OSL_ENSURE( pCurrentPos != nullptr, "Strange, we have no position!" ); + const SwNode& rCurrentNode = pCurrentPos->GetNode(); + + // find innermost section or table. At the end of this scope, + // pInnermostNode contains the section/table before/after which we should + // insert our empty paragraph, or it will be NULL if none is found. + const SwNode* pInnermostNode = nullptr; + { + const SwNode* pTableNode = rCurrentNode.FindTableNode(); + const SwNode* pSectionNode = rCurrentNode.FindSectionNode(); + + // find the table/section which is close + if( pTableNode == nullptr ) + pInnermostNode = pSectionNode; + else if ( pSectionNode == nullptr ) + pInnermostNode = pTableNode; + else + { + // compare and choose the larger one + pInnermostNode = + ( pSectionNode->GetIndex() > pTableNode->GetIndex() ) + ? pSectionNode : pTableNode; + } + } + + // The previous version had a check to skip empty read-only sections. Those + // shouldn't occur, so we only need to check whether our pInnermostNode is + // inside a protected area. + + // Now, pInnermostNode is NULL or the innermost section or table node. + if( (pInnermostNode != nullptr) && !pInnermostNode->IsProtect() ) + { + OSL_ENSURE( pInnermostNode->IsTableNode() || + pInnermostNode->IsSectionNode(), "wrong node found" ); + OSL_ENSURE( ( pInnermostNode->GetIndex() <= rCurrentNode.GetIndex() )&& + ( pInnermostNode->EndOfSectionNode()->GetIndex() >= + rCurrentNode.GetIndex() ), "wrong node found" ); + + // we now need to find the possible start/end positions + + // we found a start if + // - we're at or just before a start node + // - there are only start nodes between the current and pInnermostNode + SwNodeIndex aBegin( pCurrentPos->GetNode() ); + if( rCurrentNode.IsContentNode() && + (pCurrentPos->GetContentIndex() == 0)) + --aBegin; + while( (aBegin != pInnermostNode->GetIndex()) && + aBegin.GetNode().IsStartNode() ) + --aBegin; + bool bStart = ( aBegin == pInnermostNode->GetIndex() ); + + // we found an end if + // - we're at or just before an end node + // - there are only end nodes between the current node and + // pInnermostNode's end node or + // - there are only end nodes between the last table cell merged with + // the current cell and pInnermostNode's end node + SwNodeIndex aEnd( pCurrentPos->GetNode() ); + if( rCurrentNode.IsContentNode() && + ( pCurrentPos->GetContentIndex() == + rCurrentNode.GetContentNode()->Len() ) ) + { + ++aEnd; + + // tdf#156492 handle cells merged vertically in the bottom right corner + if ( pInnermostNode->IsTableNode() ) + { + const SwNode* pTableBoxStartNode = pCurrentPos->GetNode().FindTableBoxStartNode(); + const SwTableBox* pTableBox = pTableBoxStartNode->GetTableBox(); + if ( pTableBox && pTableBox->getRowSpan() > 1 ) + { + const SwTableNode* pTableNd = pInnermostNode->FindTableNode(); + pTableBox = & pTableBox->FindEndOfRowSpan( pTableNd->GetTable(), + pTableBox->getRowSpan() ); + pTableBoxStartNode = pTableBox->GetSttNd(); + aEnd = pTableBoxStartNode->GetIndex() + 2; + } + } + } + while( (aEnd != pInnermostNode->EndOfSectionNode()->GetIndex()) && + aEnd.GetNode().IsEndNode() ) + ++aEnd; + bool bEnd = ( aEnd == pInnermostNode->EndOfSectionNode()->GetIndex() ); + + // evaluate result: if both start + end, end is preferred + if( bEnd ) + pReturn = pInnermostNode->EndOfSectionNode(); + else if ( bStart ) + pReturn = pInnermostNode; + } + + OSL_ENSURE( ( pReturn == nullptr ) || pReturn->IsStartNode() || + pReturn->IsEndNode(), + "SpecialInsertNode failed" ); + return pReturn; +} + +/** a node can be special-inserted (alt-Enter) whenever lcl_SpecialInsertNode + finds a suitable position +*/ +bool SwEditShell::CanSpecialInsert() const +{ + return nullptr != lcl_SpecialInsertNode( GetCursor()->GetPoint() ); +} + +/** check whether a node can be special-inserted (alt-Enter), and do so. Return + whether insertion was possible. + */ +void SwEditShell::DoSpecialInsert() +{ + // get current node + SwPosition* pCursorPos = GetCursor()->GetPoint(); + const SwNode* pInsertNode = lcl_SpecialInsertNode( pCursorPos ); + if( pInsertNode == nullptr ) + return; + + StartAllAction(); + + // adjust insert position to insert before start nodes and after end + // nodes + SwNodeIndex aInsertIndex( *pInsertNode, + SwNodeOffset(pInsertNode->IsStartNode() ? -1 : 0) ); + SwPosition aInsertPos( aInsertIndex ); + + // insert a new text node, and set the cursor + GetDoc()->getIDocumentContentOperations().AppendTextNode( aInsertPos ); + *pCursorPos = aInsertPos; + + // call AttrChangeNotify for the UI + CallChgLnk(); + + EndAllAction(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edtab.cxx b/sw/source/core/edit/edtab.cxx new file mode 100644 index 0000000000..5e318ea748 --- /dev/null +++ b/sw/source/core/edit/edtab.cxx @@ -0,0 +1,527 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <fesh.hxx> +#include <hintids.hxx> +#include <hints.hxx> + +#include <swwait.hxx> +#include <editsh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentChartDataProviderAccess.hxx> +#include <IDocumentFieldsAccess.hxx> +#include <IDocumentState.hxx> +#include <cntfrm.hxx> +#include <pam.hxx> +#include <ndtxt.hxx> +#include <swtable.hxx> +#include <swundo.hxx> +#include <tblsel.hxx> +#include <cellfrm.hxx> +#include <cellatr.hxx> +#include <swtblfmt.hxx> +#include <swddetbl.hxx> +#include <mdiexp.hxx> +#include <itabenum.hxx> +#include <svl/numformat.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/uitest/eventdescription.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +namespace { + +void collectUIInformation(const OUString& rAction, const OUString& aParameters) +{ + EventDescription aDescription; + aDescription.aAction = rAction; + aDescription.aParameters = {{"parameters", aParameters}}; + aDescription.aID = "writer_edit"; + aDescription.aKeyWord = "SwEditWinUIObject"; + aDescription.aParent = "MainWindow"; + UITestLogger::getInstance().logEvent(aDescription); +} + +} + +//Added for bug #i119954# Application crashed if undo/redo convert nest table to text +static bool ConvertTableToText( const SwTableNode *pTableNode, sal_Unicode cCh ); + +static void ConvertNestedTablesToText( const SwTableLines &rTableLines, sal_Unicode cCh ) +{ + for (size_t n = 0; n < rTableLines.size(); ++n) + { + SwTableLine* pTableLine = rTableLines[ n ]; + for (size_t i = 0; i < pTableLine->GetTabBoxes().size(); ++i) + { + SwTableBox* pTableBox = pTableLine->GetTabBoxes()[ i ]; + if (pTableBox->GetTabLines().empty()) + { + SwNodeIndex nodeIndex( *pTableBox->GetSttNd(), 1 ); + SwNodeIndex endNodeIndex( *pTableBox->GetSttNd()->EndOfSectionNode() ); + for( ; nodeIndex < endNodeIndex ; ++nodeIndex ) + { + if ( SwTableNode* pTableNode = nodeIndex.GetNode().GetTableNode() ) + ConvertTableToText( pTableNode, cCh ); + } + } + else + { + ConvertNestedTablesToText( pTableBox->GetTabLines(), cCh ); + } + } + } +} + +bool ConvertTableToText( const SwTableNode *pConstTableNode, sal_Unicode cCh ) +{ + SwTableNode *pTableNode = const_cast< SwTableNode* >( pConstTableNode ); + ConvertNestedTablesToText( pTableNode->GetTable().GetTabLines(), cCh ); + return pTableNode->GetDoc().TableToText( pTableNode, cCh ); +} +//End for bug #i119954# + +const SwTable& SwEditShell::InsertTable( const SwInsertTableOptions& rInsTableOpts, + sal_uInt16 nRows, sal_uInt16 nCols, + const SwTableAutoFormat* pTAFormat ) +{ + StartAllAction(); + SwPosition* pPos = GetCursor()->GetPoint(); + + bool bEndUndo = 0 != pPos->GetContentIndex(); + if( bEndUndo ) + { + StartUndo( SwUndoId::START ); + GetDoc()->getIDocumentContentOperations().SplitNode( *pPos, false ); + } + + // If called from a shell the adjust item is propagated + // from pPos to the new content nodes in the table. + const SwTable *pTable = GetDoc()->InsertTable( rInsTableOpts, *pPos, + nRows, nCols, + css::text::HoriOrientation::FULL, pTAFormat, + nullptr, true ); + if( bEndUndo ) + EndUndo( SwUndoId::END ); + + EndAllAction(); + + OUString parameter = " Columns : " + OUString::number( nCols ) + " , Rows : " + OUString::number( nRows ) + " "; + collectUIInformation("CREATE_TABLE", parameter); + + return *pTable; +} + +bool SwEditShell::TextToTable( const SwInsertTableOptions& rInsTableOpts, + sal_Unicode cCh, + const SwTableAutoFormat* pTAFormat ) +{ + SwWait aWait( *GetDoc()->GetDocShell(), true ); + bool bRet = false; + StartAllAction(); + for(const SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( rPaM.HasMark() ) + bRet |= nullptr != GetDoc()->TextToTable( rInsTableOpts, rPaM, cCh, + css::text::HoriOrientation::FULL, pTAFormat ); + } + EndAllAction(); + return bRet; +} + +bool SwEditShell::TableToText( sal_Unicode cCh ) +{ + SwWait aWait( *GetDoc()->GetDocShell(), true ); + SwPaM* pCursor = GetCursor(); + const SwTableNode* pTableNd = + SwDoc::IsInTable( pCursor->GetPoint()->GetNode() ); + if (!pTableNd) + return false; + + if( IsTableMode() ) + { + ClearMark(); + pCursor = GetCursor(); + } + else if (pCursor->GetNext() != pCursor) + return false; + + // TL_CHART2: + // tell the charts about the table to be deleted and have them use their own data + GetDoc()->getIDocumentChartDataProviderAccess().CreateChartInternalDataProviders( &pTableNd->GetTable() ); + + StartAllAction(); + + // move current Cursor out of the listing area + SwNodeIndex aTabIdx( *pTableNd ); + pCursor->DeleteMark(); + pCursor->GetPoint()->Assign(*pTableNd->EndOfSectionNode()); + // move sPoint and Mark out of the area! + pCursor->SetMark(); + pCursor->DeleteMark(); + + //Modified for bug #i119954# Application crashed if undo/redo convert nest table to text + StartUndo(); + bool bRet = ConvertTableToText( pTableNd, cCh ); + EndUndo(); + //End for bug #i119954# + pCursor->GetPoint()->Assign(aTabIdx); + + SwContentNode* pCNd = pCursor->GetPointContentNode(); + if( !pCNd ) + pCursor->Move( fnMoveForward, GoInContent ); + + EndAllAction(); + return bRet; +} + +bool SwEditShell::IsTextToTableAvailable() const +{ + bool bOnlyText = false; + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + if( rPaM.HasMark() && *rPaM.GetPoint() != *rPaM.GetMark() ) + { + bOnlyText = true; + + // check if selection is in listing + SwNodeOffset nStt = rPaM.Start()->GetNodeIndex(), + nEnd = rPaM.End()->GetNodeIndex(); + + for( ; nStt <= nEnd; ++nStt ) + if( !GetDoc()->GetNodes()[ nStt ]->IsTextNode() ) + { + bOnlyText = false; + break; + } + + if( !bOnlyText ) + break; + } + } + + return bOnlyText; +} + +void SwEditShell::InsertDDETable( const SwInsertTableOptions& rInsTableOpts, + SwDDEFieldType* pDDEType, + sal_uInt16 nRows, sal_uInt16 nCols ) +{ + SwPosition* pPos = GetCursor()->GetPoint(); + // Do not try to insert table into Footnotes/Endnotes! tdf#76007 prevents that. + if (pPos->GetNode() < pPos->GetNodes().GetEndOfInserts() + && pPos->GetNode().GetIndex() >= pPos->GetNodes().GetEndOfInserts().StartOfSectionIndex()) + return; + + StartAllAction(); + + bool bEndUndo = 0 != pPos->GetContentIndex(); + if( bEndUndo ) + { + StartUndo( SwUndoId::START ); + GetDoc()->getIDocumentContentOperations().SplitNode( *pPos, false ); + } + + const SwInsertTableOptions aInsTableOpts( rInsTableOpts.mnInsMode | SwInsertTableFlags::DefaultBorder, + rInsTableOpts.mnRowsToRepeat ); + SwTable* pTable = const_cast<SwTable*>(GetDoc()->InsertTable( aInsTableOpts, *pPos, + nRows, nCols, css::text::HoriOrientation::FULL )); + + SwTableNode* pTableNode = const_cast<SwTableNode*>(pTable->GetTabSortBoxes()[ 0 ]-> + GetSttNd()->FindTableNode()); + std::unique_ptr<SwDDETable> pDDETable(new SwDDETable( *pTable, pDDEType )); + pTableNode->SetNewTable( std::move(pDDETable) ); // set the DDE table + + if( bEndUndo ) + EndUndo( SwUndoId::END ); + + EndAllAction(); +} + +/** update fields of a listing */ +void SwEditShell::UpdateTable() +{ + const SwTableNode* pTableNd = IsCursorInTable(); + + if( pTableNd ) + { + StartAllAction(); + if( DoesUndo() ) + StartUndo(); + EndAllTableBoxEdit(); + GetDoc()->getIDocumentFieldsAccess().UpdateTableFields(&pTableNd->GetTable()); + if( DoesUndo() ) + EndUndo(); + EndAllAction(); + } +} + +// get/set Change Mode + +TableChgMode SwEditShell::GetTableChgMode() const +{ + TableChgMode eMode; + const SwTableNode* pTableNd = IsCursorInTable(); + if( pTableNd ) + eMode = pTableNd->GetTable().GetTableChgMode(); + else + eMode = GetTableChgDefaultMode(); + return eMode; +} + +void SwEditShell::SetTableChgMode( TableChgMode eMode ) +{ + const SwTableNode* pTableNd = IsCursorInTable(); + + if( pTableNd ) + { + const_cast<SwTable&>(pTableNd->GetTable()).SetTableChgMode( eMode ); + if( !GetDoc()->getIDocumentState().IsModified() ) // Bug 57028 + { + GetDoc()->GetIDocumentUndoRedo().SetUndoNoResetModified(); + } + GetDoc()->getIDocumentState().SetModified(); + } +} + +bool SwEditShell::GetTableBoxFormulaAttrs( SfxItemSet& rSet ) const +{ + SwSelBoxes aBoxes; + if( IsTableMode() ) + ::GetTableSelCrs( *this, aBoxes ); + else + { + SwFrame* pFrame = GetCurrFrame()->GetUpper(); + while (pFrame && !pFrame->IsCellFrame()) + pFrame = pFrame->GetUpper(); + + if (pFrame) + { + auto pBox = const_cast<SwTableBox*>(static_cast<SwCellFrame*>(pFrame)->GetTabBox()); + aBoxes.insert(pBox); + } + } + + for (size_t n = 0; n < aBoxes.size(); ++n) + { + const SwTableBox* pSelBox = aBoxes[ n ]; + const SwTableBoxFormat* pTableFormat = static_cast<SwTableBoxFormat*>(pSelBox->GetFrameFormat()); + if( !n ) + { + // Convert formulae into external presentation + const SwTable& rTable = pSelBox->GetSttNd()->FindTableNode()->GetTable(); + + const_cast<SwTable*>(&rTable)->SwitchFormulasToExternalRepresentation(); + rSet.Put( pTableFormat->GetAttrSet() ); + } + else + rSet.MergeValues( pTableFormat->GetAttrSet() ); + } + return 0 != rSet.Count(); +} + +void SwEditShell::SetTableBoxFormulaAttrs( const SfxItemSet& rSet ) +{ + CurrShell aCurr( this ); + SwSelBoxes aBoxes; + if( IsTableMode() ) + ::GetTableSelCrs( *this, aBoxes ); + else + { + do { + SwFrame *pFrame = GetCurrFrame(); + do { + pFrame = pFrame->GetUpper(); + } while ( pFrame && !pFrame->IsCellFrame() ); + if ( pFrame ) + { + SwTableBox *pBox = const_cast<SwTableBox*>(static_cast<SwCellFrame*>(pFrame)->GetTabBox()); + aBoxes.insert( pBox ); + } + } while( false ); + } + + // When setting a formula, do not check further! + if( SfxItemState::SET == rSet.GetItemState( RES_BOXATR_FORMULA )) + ClearTableBoxContent(); + + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo( SwUndoId::START, nullptr ); + for (size_t n = 0; n < aBoxes.size(); ++n) + { + GetDoc()->SetTableBoxFormulaAttrs( *aBoxes[ n ], rSet ); + } + GetDoc()->GetIDocumentUndoRedo().EndUndo( SwUndoId::END, nullptr ); + EndAllAction(); +} + +bool SwEditShell::IsTableBoxTextFormat() const +{ + if( IsTableMode() ) + return false; + + const SwTableBox *pBox = nullptr; + { + SwFrame *pFrame = GetCurrFrame(); + do { + pFrame = pFrame->GetUpper(); + } while ( pFrame && !pFrame->IsCellFrame() ); + if ( pFrame ) + pBox = static_cast<SwCellFrame*>(pFrame)->GetTabBox(); + } + + if( !pBox ) + return false; + + sal_uInt32 nFormat = 0; + if( const SwTableBoxNumFormat* pItem = pBox->GetFrameFormat()->GetAttrSet().GetItemIfSet( + RES_BOXATR_FORMAT )) + { + nFormat = pItem->GetValue(); + return GetDoc()->GetNumberFormatter()->IsTextFormat( nFormat ); + } + + SwNodeOffset nNd = pBox->IsValidNumTextNd(); + if( NODE_OFFSET_MAX == nNd ) + return true; + + const OUString& rText = GetDoc()->GetNodes()[ nNd ]->GetTextNode()->GetText(); + if( rText.isEmpty() ) + return false; + + double fVal; + return !GetDoc()->IsNumberFormat( rText, nFormat, fVal ); +} + +OUString SwEditShell::GetTableBoxText() const +{ + OUString sRet; + if( !IsTableMode() ) + { + const SwTableBox *pBox = nullptr; + { + SwFrame *pFrame = GetCurrFrame(); + do { + pFrame = pFrame->GetUpper(); + } while ( pFrame && !pFrame->IsCellFrame() ); + if ( pFrame ) + pBox = static_cast<SwCellFrame*>(pFrame)->GetTabBox(); + } + + SwNodeOffset nNd; + if( pBox && NODE_OFFSET_MAX != ( nNd = pBox->IsValidNumTextNd() ) ) + sRet = GetDoc()->GetNodes()[ nNd ]->GetTextNode()->GetText(); + } + return sRet; +} + +void SwEditShell::SplitTable( SplitTable_HeadlineOption eMode ) +{ + SwPaM *pCursor = GetCursor(); + if( pCursor->GetPointNode().FindTableNode() ) + { + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + GetDoc()->SplitTable( *pCursor->GetPoint(), eMode, true ); + + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + ClearFEShellTabCols(*GetDoc(), nullptr); + EndAllAction(); + } +} + +bool SwEditShell::MergeTable( bool bWithPrev ) +{ + bool bRet = false; + SwPaM *pCursor = GetCursor(); + if( pCursor->GetPointNode().FindTableNode() ) + { + StartAllAction(); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::EMPTY, nullptr); + + bRet = GetDoc()->MergeTable( *pCursor->GetPoint(), bWithPrev ); + + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::EMPTY, nullptr); + ClearFEShellTabCols(*GetDoc(), nullptr); + EndAllAction(); + } + return bRet; +} + +bool SwEditShell::CanMergeTable( bool bWithPrev, bool* pChkNxtPrv ) const +{ + bool bRet = false; + const SwPaM *pCursor = GetCursor(); + const SwTableNode* pTableNd = pCursor->GetPointNode().FindTableNode(); + if( pTableNd && dynamic_cast< const SwDDETable* >(&pTableNd->GetTable()) == nullptr) + { + bool bNew = pTableNd->GetTable().IsNewModel(); + const SwNodes& rNds = GetDoc()->GetNodes(); + if( pChkNxtPrv ) + { + const SwTableNode* pChkNd = rNds[ pTableNd->GetIndex() - 1 ]->FindTableNode(); + if( pChkNd && dynamic_cast< const SwDDETable* >(&pChkNd->GetTable()) == nullptr && + bNew == pChkNd->GetTable().IsNewModel() && + // Consider table in table case + pChkNd->EndOfSectionIndex() == pTableNd->GetIndex() - 1 ) + { + *pChkNxtPrv = true; + bRet = true; // using Prev is possible + } + else + { + pChkNd = rNds[ pTableNd->EndOfSectionIndex() + 1 ]->GetTableNode(); + if( pChkNd && dynamic_cast< const SwDDETable* >(&pChkNd->GetTable()) == nullptr && + bNew == pChkNd->GetTable().IsNewModel() ) + { + *pChkNxtPrv = false; + bRet = true; // using Next is possible + } + } + } + else + { + const SwTableNode* pTmpTableNd = nullptr; + + if( bWithPrev ) + { + pTmpTableNd = rNds[ pTableNd->GetIndex() - 1 ]->FindTableNode(); + // Consider table in table case + if ( pTmpTableNd && pTmpTableNd->EndOfSectionIndex() != pTableNd->GetIndex() - 1 ) + pTmpTableNd = nullptr; + } + else + pTmpTableNd = rNds[ pTableNd->EndOfSectionIndex() + 1 ]->GetTableNode(); + + bRet = pTmpTableNd && dynamic_cast< const SwDDETable* >(&pTmpTableNd->GetTable()) == nullptr && + bNew == pTmpTableNd->GetTable().IsNewModel(); + } + } + return bRet; +} + +/** create InsertDB as table Undo */ +void SwEditShell::AppendUndoForInsertFromDB( bool bIsTable ) +{ + GetDoc()->AppendUndoForInsertFromDB( *GetCursor(), bIsTable ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edtox.cxx b/sw/source/core/edit/edtox.cxx new file mode 100644 index 0000000000..602224c80e --- /dev/null +++ b/sw/source/core/edit/edtox.cxx @@ -0,0 +1,394 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/util/SearchAlgorithms2.hpp> +#include <com/sun/star/util/SearchFlags.hpp> +#include <i18nlangtag/languagetag.hxx> +#include <i18nutil/transliteration.hxx> +#include <i18nutil/searchopt.hxx> +#include <svl/fstathelper.hxx> +#include <osl/diagnose.h> +#include <osl/thread.h> +#include <unotools/syslocale.hxx> + +#include <sfx2/docfile.hxx> + +#include <swtypes.hxx> +#include <rootfrm.hxx> +#include <editsh.hxx> +#include <doc.hxx> +#include <IDocumentContentOperations.hxx> +#include <IDocumentUndoRedo.hxx> +#include <pam.hxx> +#include <swundo.hxx> +#include <tox.hxx> +#include <doctxm.hxx> +#include <docary.hxx> +#include <mdiexp.hxx> +#include <strings.hrc> +#include <iodetect.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::util; + +// Add/delete listing markers to a document + +void SwEditShell::Insert(const SwTOXMark& rMark) +{ + bool bInsAtPos = rMark.IsAlternativeText(); + StartAllAction(); + for(SwPaM& rPaM : GetCursor()->GetRingContainer()) + { + auto [pStt, pEnd] = rPaM.StartEnd(); // SwPosition* + if( bInsAtPos ) + { + SwPaM aTmp( *pStt ); + GetDoc()->getIDocumentContentOperations().InsertPoolItem( aTmp, rMark ); + } + else if( *pEnd != *pStt ) + { + GetDoc()->getIDocumentContentOperations().InsertPoolItem( + rPaM, rMark, SetAttrMode::DONTEXPAND ); + } + + } + EndAllAction(); +} + +void SwEditShell::DeleteTOXMark( SwTOXMark const * pMark ) +{ + CurrShell aCurr( this ); + StartAllAction(); + + mxDoc->DeleteTOXMark( pMark ); + + EndAllAction(); +} + +/// Collect all listing markers +void SwEditShell::GetCurTOXMarks(SwTOXMarks& rMarks) const +{ + SwDoc::GetCurTOXMark( *GetCursor()->Start(), rMarks ); +} + +bool SwEditShell::IsTOXBaseReadonly(const SwTOXBase& rTOXBase) +{ + assert( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) && "no TOXBaseSection!" ); + const SwTOXBaseSection& rTOXSect = static_cast<const SwTOXBaseSection&>(rTOXBase); + return rTOXSect.IsProtect(); +} + +void SwEditShell::SetTOXBaseReadonly(const SwTOXBase& rTOXBase, bool bReadonly) +{ + assert( dynamic_cast<const SwTOXBaseSection*>( &rTOXBase) && "no TOXBaseSection!" ); + const SwTOXBaseSection& rTOXSect = static_cast<const SwTOXBaseSection&>(rTOXBase); + const_cast<SwTOXBase&>(rTOXBase).SetProtected(bReadonly); + OSL_ENSURE( rTOXSect.SwSection::GetType() == SectionType::ToxContent, "not a TOXContentSection" ); + + SwSectionData aSectionData(rTOXSect); + aSectionData.SetProtectFlag(bReadonly); + UpdateSection( GetSectionFormatPos( *rTOXSect.GetFormat() ), aSectionData ); +} + +const SwTOXBase* SwEditShell::GetDefaultTOXBase( TOXTypes eTyp, bool bCreate ) +{ + return GetDoc()->GetDefaultTOXBase( eTyp, bCreate ); +} + +void SwEditShell::SetDefaultTOXBase(const SwTOXBase& rBase) +{ + GetDoc()->SetDefaultTOXBase(rBase); +} + +/// Insert listing and create content +void SwEditShell::InsertTableOf( const SwTOXBase& rTOX, const SfxItemSet* pSet ) +{ + CurrShell aCurr( this ); + StartAllAction(); + + SwDocShell* pDocSh = GetDoc()->GetDocShell(); + ::StartProgress( STR_STATSTR_TOX_INSERT, 0, 0, pDocSh ); + + // Insert listing + const SwTOXBaseSection* pTOX = mxDoc->InsertTableOf( + *GetCursor()->GetPoint(), rTOX, pSet, true, GetLayout() ); + OSL_ENSURE(pTOX, "No current TOX"); + + // start formatting + CalcLayout(); + + // insert page numbering + const_cast<SwTOXBaseSection*>(pTOX)->UpdatePageNum(); + + pTOX->SetPosAtStartEnd( *GetCursor()->GetPoint() ); + + // Fix for empty listing + InvalidateWindows( maVisArea ); + ::EndProgress( pDocSh ); + EndAllAction(); +} + +/// update tables of content +void SwEditShell::UpdateTableOf(const SwTOXBase& rTOX, const SfxItemSet* pSet) +{ + assert(dynamic_cast<const SwTOXBaseSection*>(&rTOX) && "no TOXBaseSection!"); + SwTOXBaseSection& rTOXSect = static_cast<SwTOXBaseSection&>(const_cast<SwTOXBase&>(rTOX)); + if (!rTOXSect.GetFormat()->GetSectionNode()) + return; + + SwDoc* pMyDoc = GetDoc(); + SwDocShell* pDocSh = pMyDoc->GetDocShell(); + + bool bInIndex = &rTOX == GetCurTOX(); + CurrShell aCurr( this ); + StartAllAction(); + + ::StartProgress( STR_STATSTR_TOX_UPDATE, 0, 0, pDocSh ); + + pMyDoc->GetIDocumentUndoRedo().StartUndo(SwUndoId::TOXCHANGE, nullptr); + + // create listing stub + rTOXSect.Update(pSet, GetLayout()); + + // correct Cursor + if( bInIndex ) + rTOXSect.SetPosAtStartEnd(*GetCursor()->GetPoint()); + + // start formatting + // tdf#139426 ...but allow suppression of AssertFlyPages + GetLayout()->SetTableUpdateInProgress(true); + CalcLayout(); + GetLayout()->SetTableUpdateInProgress(false); + + // insert page numbering + rTOXSect.UpdatePageNum(); + + pMyDoc->GetIDocumentUndoRedo().EndUndo(SwUndoId::TOXCHANGE, nullptr); + + ::EndProgress( pDocSh ); + EndAllAction(); +} + +/// Get current listing before or at the Cursor +const SwTOXBase* SwEditShell::GetCurTOX() const +{ + return SwDoc::GetCurTOX( *GetCursor()->GetPoint() ); +} + +bool SwEditShell::DeleteTOX( const SwTOXBase& rTOXBase, bool bDelNodes ) +{ + return GetDoc()->DeleteTOX( rTOXBase, bDelNodes ); +} + +// manage types of listings + +const SwTOXType* SwEditShell::GetTOXType(TOXTypes eTyp, sal_uInt16 nId) const +{ + return mxDoc->GetTOXType(eTyp, nId); +} + +// manage keys for the alphabetical index + +void SwEditShell::GetTOIKeys( SwTOIKeyType eTyp, std::vector<OUString>& rArr ) const +{ + GetDoc()->GetTOIKeys( eTyp, rArr, *GetLayout() ); +} + +sal_uInt16 SwEditShell::GetTOXCount() const +{ + const SwSectionFormats& rFormats = GetDoc()->GetSections(); + sal_uInt16 nRet = 0; + for( auto n = rFormats.size(); n; ) + { + const SwSection* pSect = rFormats[ --n ]->GetSection(); + if( SectionType::ToxContent == pSect->GetType() && + pSect->GetFormat()->GetSectionNode() ) + ++nRet; + } + return nRet; +} + +const SwTOXBase* SwEditShell::GetTOX( sal_uInt16 nPos ) const +{ + const SwSectionFormats& rFormats = GetDoc()->GetSections(); + sal_uInt16 nCnt {0}; + for( const SwSectionFormat *pFormat : rFormats ) + { + const SwSection* pSect = pFormat->GetSection(); + if( SectionType::ToxContent == pSect->GetType() && + pSect->GetFormat()->GetSectionNode() && + nCnt++ == nPos ) + { + assert( dynamic_cast<const SwTOXBaseSection*>( pSect) && "no TOXBaseSection!" ); + return static_cast<const SwTOXBaseSection*>(pSect); + } + } + return nullptr; +} + +/** Update of all listings after reading-in a file */ +void SwEditShell::SetUpdateTOX( bool bFlag ) +{ + GetDoc()->SetUpdateTOX( bFlag ); +} + +bool SwEditShell::IsUpdateTOX() const +{ + return GetDoc()->IsUpdateTOX(); +} + +OUString const & SwEditShell::GetTOIAutoMarkURL() const +{ + return GetDoc()->GetTOIAutoMarkURL(); +} + +void SwEditShell::SetTOIAutoMarkURL(const OUString& rSet) +{ + GetDoc()->SetTOIAutoMarkURL(rSet); +} + +void SwEditShell::ApplyAutoMark() +{ + StartAllAction(); + bool bDoesUndo = DoesUndo(); + DoUndo(false); + //1. remove all automatic generated index entries if AutoMarkURL has a + // length and the file exists + //2. load file + //3. select all occurrences of the searched words + //4. apply index entries + + OUString sAutoMarkURL(GetDoc()->GetTOIAutoMarkURL()); + if( !sAutoMarkURL.isEmpty() && FStatHelper::IsDocument( sAutoMarkURL )) + { + //1. + const SwTOXType* pTOXType = GetTOXType(TOX_INDEX, 0); + + SwTOXMarks aMarks; + pTOXType->CollectTextMarks(aMarks); + for( SwTOXMark* pMark : aMarks ) + { + if(pMark->IsAutoGenerated() && pMark->GetTextTOXMark()) + // mba: test iteration; objects are deleted in iteration + DeleteTOXMark(pMark); + } + + //2. + SfxMedium aMedium( sAutoMarkURL, StreamMode::STD_READ ); + SvStream& rStrm = *aMedium.GetInStream(); + Push(); + // tdf#106899 - import tox concordance file using the appropriate character set + rtl_TextEncoding eChrSet = SwIoSystem::GetTextEncoding(rStrm); + if (eChrSet == RTL_TEXTENCODING_DONTKNOW) + eChrSet = ::osl_getThreadTextEncoding(); + + // SearchOptions to be used in loop below + sal_Int32 const nLEV_Other = 2; // -> changedChars; + sal_Int32 const nLEV_Longer = 3; //! -> deletedChars; + sal_Int32 const nLEV_Shorter = 1; //! -> insertedChars; + + i18nutil::SearchOptions2 aSearchOpt( + SearchFlags::LEV_RELAXED, + "", "", + SvtSysLocale().GetLanguageTag().getLocale(), + nLEV_Other, nLEV_Longer, nLEV_Shorter, + TransliterationFlags::NONE, + SearchAlgorithms2::ABSOLUTE, + '\\' ); + + OStringBuffer aRdLine; + while (rStrm.good()) + { + rStrm.ReadLine( aRdLine ); + + // # -> comment + // ; -> delimiter between entries -> + // Format: TextToSearchFor;AlternativeString;PrimaryKey;SecondaryKey;CaseSensitive;WordOnly + // Leading and trailing blanks are ignored + if( !aRdLine.isEmpty() && '#' != aRdLine[0] ) + { + OUString sLine(OStringToOUString(aRdLine, eChrSet)); + + sal_Int32 nTokenPos = 0; + OUString sToSelect( sLine.getToken(0, ';', nTokenPos ) ); + if( !sToSelect.isEmpty() ) + { + OUString sAlternative = sLine.getToken(0, ';', nTokenPos); + OUString sPrimary = sLine.getToken(0, ';', nTokenPos); + OUString sSecondary = sLine.getToken(0, ';', nTokenPos); + OUString sCase = sLine.getToken(0, ';', nTokenPos); + OUString sWordOnly = sLine.getToken(0, ';', nTokenPos); + + //3. + bool bCaseSensitive = !sCase.isEmpty() && sCase != "0"; + bool bWordOnly = !sWordOnly.isEmpty() && sWordOnly != "0"; + + if (!bCaseSensitive) + { + aSearchOpt.transliterateFlags |= + TransliterationFlags::IGNORE_CASE; + } + else + { + aSearchOpt.transliterateFlags &= + ~TransliterationFlags::IGNORE_CASE; + } + if ( bWordOnly) + aSearchOpt.searchFlag |= SearchFlags::NORM_WORD_ONLY; + else + aSearchOpt.searchFlag &= ~SearchFlags::NORM_WORD_ONLY; + + aSearchOpt.searchString = sToSelect; + + KillPams(); + bool bCancel; + + // todo/mba: assuming that notes shouldn't be searched + sal_Int32 nRet = Find_Text(aSearchOpt, false/*bSearchInNotes*/, SwDocPositions::Start, SwDocPositions::End, bCancel, + FindRanges::InSelAll ); + + if(nRet) + { + SwTOXMark* pTmpMark = new SwTOXMark(pTOXType); + if( !sPrimary.isEmpty() ) + { + pTmpMark->SetPrimaryKey( sPrimary ); + if( !sSecondary.isEmpty() ) + pTmpMark->SetSecondaryKey( sSecondary ); + } + if( !sAlternative.isEmpty() ) + pTmpMark->SetAlternativeText(sAlternative); + pTmpMark->SetMainEntry(false); + pTmpMark->SetAutoGenerated(true); + //4. + SwEditShell::Insert(*pTmpMark); + } + } + } + } + KillPams(); + Pop(PopMode::DeleteCurrent); + } + DoUndo(bDoesUndo); + EndAllAction(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edundo.cxx b/sw/source/core/edit/edundo.cxx new file mode 100644 index 0000000000..576b092214 --- /dev/null +++ b/sw/source/core/edit/edundo.cxx @@ -0,0 +1,257 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <svx/svdmark.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <editsh.hxx> +#include <fesh.hxx> +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <UndoCore.hxx> +#include <swundo.hxx> +#include <flyfrm.hxx> +#include <frmfmt.hxx> +#include <docsh.hxx> +#include <pagefrm.hxx> +#include <textboxhelper.hxx> +#include <fmtanchr.hxx> + +#include <wrtsh.hxx> + +/** helper function to select all objects in an SdrMarkList; + * implementation: see below */ +static void lcl_SelectSdrMarkList( SwEditShell* pShell, + const SdrMarkList* pSdrMarkList ); + +bool SwEditShell::CursorsLocked() const +{ + return GetDoc()->GetDocShell()->GetModel()->hasControllersLocked(); +} + +void SwEditShell::HandleUndoRedoContext(::sw::UndoRedoContext & rContext) +{ + // do nothing if somebody has locked controllers! + if (CursorsLocked()) + { + return; + } + + SwFrameFormat * pSelFormat(nullptr); + SdrMarkList * pMarkList(nullptr); + rContext.GetSelections(pSelFormat, pMarkList); + + if (pSelFormat) // select frame + { + if (RES_DRAWFRMFMT == pSelFormat->Which()) + { + SdrObject* pSObj = pSelFormat->FindSdrObject(); + + // Before layout calc, inline anchored textboxes have to be synced unless crash. + if (pSelFormat->GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR + && pSelFormat->GetOtherTextBoxFormats()) + SwTextBoxHelper::synchronizeGroupTextBoxProperty(SwTextBoxHelper::changeAnchor, + pSelFormat, pSObj); + + static_cast<SwFEShell*>(this)->SelectObj( + pSObj->GetCurrentBoundRect().Center() ); + } + else + { + Point aPt; + SwFlyFrame *const pFly = + static_cast<SwFlyFrameFormat*>(pSelFormat)->GetFrame(& aPt); + if (pFly) + { + // fdo#36681: Invalidate the content and layout to refresh + // the picture anchoring properly + SwPageFrame* pPageFrame = pFly->FindPageFrameOfAnchor(); + pPageFrame->InvalidateFlyLayout(); + pPageFrame->InvalidateContent(); + + static_cast<SwFEShell*>(this)->SelectFlyFrame(*pFly); + } + } + } + else if (pMarkList) + { + lcl_SelectSdrMarkList( this, pMarkList ); + } + else if (GetCursor()->GetNext() != GetCursor()) + { + // current cursor is the last one: + // go around the ring, to the first cursor + GoNextCursor(); + } +} + +void SwEditShell::Undo(sal_uInt16 const nCount, sal_uInt16 nOffset) +{ + MakeAllOutlineContentTemporarilyVisible a(GetDoc(), true); + + CurrShell aCurr( this ); + + // current undo state was not saved + ::sw::UndoGuard const undoGuard(GetDoc()->GetIDocumentUndoRedo()); + bool bRet = false; + + StartAllAction(); + { + // Actually it should be enough to just work on the current Cursor, i.e. if there is a cycle + // cancel the latter temporarily, so that an insert during Undo is not done in all areas. + KillPams(); + SetMark(); // Bound1 and Bound2 in the same Node + ClearMark(); + + // Keep Cursor - so that we're able to set it at + // the same position for autoformat or autocorrection + SwUndoId nLastUndoId(SwUndoId::EMPTY); + GetLastUndoInfo(nullptr, & nLastUndoId); + const bool bRestoreCursor = nCount == 1 + && ( SwUndoId::AUTOFORMAT == nLastUndoId + || SwUndoId::AUTOCORRECT == nLastUndoId + || SwUndoId::SETDEFTATTR == nLastUndoId ); + Push(); + + // Destroy stored TableBoxPtr. A detection is only permitted for the new "Box"! + ClearTableBoxContent(); + + const RedlineFlags eOld = GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags(); + + try { + for (sal_uInt16 i = 0; i < nCount; ++i) + { + bRet = GetDoc()->GetIDocumentUndoRedo().UndoWithOffset(nOffset) || bRet; + } + } catch (const css::uno::Exception &) { + TOOLS_WARN_EXCEPTION("sw.core", "SwEditShell::Undo()"); + } + + if (bRestoreCursor) + { // fdo#39003 Pop does not touch the rest of the cursor ring + KillPams(); // so call this first to get rid of unwanted cursors + } + Pop(bRestoreCursor ? PopMode::DeleteCurrent : PopMode::DeleteStack); + + GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + GetDoc()->getIDocumentRedlineAccess().CompressRedlines(); + + // automatic detection of the new "Box" + SaveTableBoxContent(); + } + EndAllAction(); +} + +void SwEditShell::Redo(sal_uInt16 const nCount) +{ + MakeAllOutlineContentTemporarilyVisible a(GetDoc(), true); + + CurrShell aCurr( this ); + + bool bRet = false; + + // undo state was not saved + ::sw::UndoGuard const undoGuard(GetDoc()->GetIDocumentUndoRedo()); + + StartAllAction(); + + { + // Actually it should be enough to just work on the current Cursor, i.e. if there is a cycle + // cancel the latter temporarily, so that an insert during Undo is not done in all areas. + KillPams(); + SetMark(); // Bound1 and Bound2 in the same Node + ClearMark(); + + SwUndoId nFirstRedoId(SwUndoId::EMPTY); + GetDoc()->GetIDocumentUndoRedo().GetFirstRedoInfo(nullptr, & nFirstRedoId); + const bool bRestoreCursor = nCount == 1 && SwUndoId::SETDEFTATTR == nFirstRedoId; + Push(); + + // Destroy stored TableBoxPtr. A detection is only permitted for the new "Box"! + ClearTableBoxContent(); + + RedlineFlags eOld = GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags(); + + try { + for (sal_uInt16 i = 0; i < nCount; ++i) + { + bRet = GetDoc()->GetIDocumentUndoRedo().Redo() + || bRet; + } + } catch (const css::uno::Exception &) { + TOOLS_WARN_EXCEPTION("sw.core", "SwEditShell::Redo()"); + } + + Pop(bRestoreCursor ? PopMode::DeleteCurrent : PopMode::DeleteStack); + + GetDoc()->getIDocumentRedlineAccess().SetRedlineFlags( eOld ); + GetDoc()->getIDocumentRedlineAccess().CompressRedlines(); + + // automatic detection of the new "Box" + SaveTableBoxContent(); + } + + EndAllAction(); +} + +void SwEditShell::Repeat(sal_uInt16 const nCount) +{ + CurrShell aCurr( this ); + + StartAllAction(); + + try { + ::sw::RepeatContext context(*GetDoc(), *GetCursor()); + GetDoc()->GetIDocumentUndoRedo().Repeat( context, nCount ); + } catch (const css::uno::Exception &) { + TOOLS_WARN_EXCEPTION("sw.core", "SwEditShell::Repeat()"); + } + + EndAllAction(); +} + +static void lcl_SelectSdrMarkList( SwEditShell* pShell, + const SdrMarkList* pSdrMarkList ) +{ + OSL_ENSURE( pShell != nullptr, "need shell!" ); + OSL_ENSURE( pSdrMarkList != nullptr, "need mark list" ); + + SwFEShell* pFEShell = dynamic_cast<SwFEShell*>( pShell ); + if( !pFEShell ) + return; + + bool bFirst = true; + for( size_t i = 0; i < pSdrMarkList->GetMarkCount(); ++i ) + { + SdrObject *pObj = pSdrMarkList->GetMark( i )->GetMarkedSdrObj(); + if( pObj ) + { + pFEShell->SelectObj( Point(), bFirst ? 0 : SW_ADD_SELECT, pObj ); + bFirst = false; + } + } + + // the old implementation would always unselect + // objects, even if no new ones were selected. If this + // is a problem, we need to re-work this a little. + OSL_ENSURE( pSdrMarkList->GetMarkCount() != 0, "empty mark list" ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/edit/edws.cxx b/sw/source/core/edit/edws.cxx new file mode 100644 index 0000000000..4e46ae2daf --- /dev/null +++ b/sw/source/core/edit/edws.cxx @@ -0,0 +1,331 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <editsh.hxx> + +#include <officecfg/Office/Common.hxx> +#include <osl/diagnose.h> +#include <unotools/configmgr.hxx> +#include <vcl/window.hxx> + +#include <doc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <IDocumentState.hxx> +#include <pam.hxx> +#include <acorrect.hxx> +#include <swtable.hxx> +#include <ndtxt.hxx> +#include <txtfrm.hxx> +#include <swundo.hxx> +#include <SwRewriter.hxx> +#include <frameformats.hxx> + +// masqueraded copy constructor +SwEditShell::SwEditShell( SwEditShell& rEdSH, vcl::Window *pWindow ) + : SwCursorShell( rEdSH, pWindow ) + , m_bNbspRunNext(false) // TODO: would copying that make sense? only if editing continues + , m_bDoParagraphSignatureValidation(true) +{ +} + +SwEditShell::SwEditShell( SwDoc& rDoc, vcl::Window *pWindow, const SwViewOption *pOptions ) + : SwCursorShell( rDoc, pWindow, pOptions ) + , m_bNbspRunNext(false) + , m_bDoParagraphSignatureValidation(true) +{ + if (!utl::ConfigManager::IsFuzzing() && 0 < officecfg::Office::Common::Undo::Steps::get()) + { + GetDoc()->GetIDocumentUndoRedo().DoUndo(true); + } + + // Restore the paragraph metadata fields and validate signatures. + RestoreMetadataFieldsAndValidateParagraphSignatures(); +} + +SwEditShell::~SwEditShell() // USED +{ +} + +bool SwEditShell::IsModified() const +{ + return GetDoc()->getIDocumentState().IsModified(); +} + +void SwEditShell::SetModified() +{ + GetDoc()->getIDocumentState().SetModified(); +} + +void SwEditShell::ResetModified() +{ + GetDoc()->getIDocumentState().ResetModified(); +} + +void SwEditShell::SetUndoNoResetModified() +{ + GetDoc()->getIDocumentState().SetModified(); + GetDoc()->GetIDocumentUndoRedo().SetUndoNoResetModified(); +} + +void SwEditShell::StartAllAction() +{ + for(SwViewShell& rCurrentShell : GetRingContainer()) + { + if (SwEditShell* pEditShell = dynamic_cast<SwEditShell*>(&rCurrentShell)) + pEditShell->StartAction(); + else + rCurrentShell.StartAction(); + } +} + +void SwEditShell::EndAllAction() +{ + for(SwViewShell& rCurrentShell : GetRingContainer()) + { + if( auto pEditShell = dynamic_cast<SwEditShell *>(&rCurrentShell)) + pEditShell->EndAction(); + else + rCurrentShell.EndAction(); + } +} + +void SwEditShell::CalcLayout() +{ + StartAllAction(); + SwViewShell::CalcLayout(); + + for(SwViewShell& rCurrentShell : GetRingContainer()) + { + if ( rCurrentShell.GetWin() ) + rCurrentShell.GetWin()->Invalidate(); + } + + EndAllAction(); +} + +/** Get the content type of a shell + * + * @todo Is this called for every attribute? + */ +sal_uInt16 SwEditShell::GetCntType() const +{ + sal_uInt16 nRet = 0; + if( IsTableMode() ) + nRet = CNT_TXT; + else + switch( GetCursor()->GetPointNode().GetNodeType() ) + { + case SwNodeType::Text: nRet = CNT_TXT; break; + case SwNodeType::Grf: nRet = CNT_GRF; break; + case SwNodeType::Ole: nRet = CNT_OLE; break; + default: break; + } + + OSL_ASSERT( nRet ); + return nRet; +} + +bool SwEditShell::HasOtherCnt() const + +{ + if ( !GetDoc()->GetSpzFrameFormats()->empty() ) + return true; + + const SwNodes &rNds = GetDoc()->GetNodes(); + const SwNode *pNd; + + pNd = &rNds.GetEndOfInserts(); + if ( SwNodeOffset(1) != (pNd->GetIndex() - pNd->StartOfSectionIndex()) ) + return true; + + pNd = &rNds.GetEndOfAutotext(); + return SwNodeOffset(1) != (pNd->GetIndex() - pNd->StartOfSectionIndex()); +} + +SwActContext::SwActContext(SwEditShell *pShell) + : m_rShell(*pShell) +{ + m_rShell.StartAction(); +} + +SwActContext::~SwActContext() COVERITY_NOEXCEPT_FALSE +{ + m_rShell.EndAction(); +} + +SwMvContext::SwMvContext(SwEditShell *pShell) + : m_rShell(*pShell) +{ + m_rShell.SttCursorMove(); +} + +SwMvContext::~SwMvContext() COVERITY_NOEXCEPT_FALSE +{ + m_rShell.EndCursorMove(); +} + +SwFrameFormat *SwEditShell::GetTableFormat() // fastest test on a table +{ + const SwTableNode* pTableNd = IsCursorInTable(); + return pTableNd ? static_cast<SwFrameFormat*>(pTableNd->GetTable().GetFrameFormat()) : nullptr; +} + +// TODO: Why is this called 3x for a new document? +sal_uInt16 SwEditShell::GetTOXTypeCount(TOXTypes eTyp) const +{ + return mxDoc->GetTOXTypeCount(eTyp); +} + +void SwEditShell::InsertTOXType(const SwTOXType& rTyp) +{ + mxDoc->InsertTOXType(rTyp); +} + +void SwEditShell::DoUndo( bool bOn ) +{ GetDoc()->GetIDocumentUndoRedo().DoUndo( bOn ); } + +bool SwEditShell::DoesUndo() const +{ return GetDoc()->GetIDocumentUndoRedo().DoesUndo(); } + +void SwEditShell::DoGroupUndo( bool bOn ) +{ GetDoc()->GetIDocumentUndoRedo().DoGroupUndo( bOn ); } + +bool SwEditShell::DoesGroupUndo() const +{ return GetDoc()->GetIDocumentUndoRedo().DoesGroupUndo(); } + +void SwEditShell::DelAllUndoObj() +{ + GetDoc()->GetIDocumentUndoRedo().DelAllUndoObj(); +} + +// Combine continuous calls of Insert/Delete/Overwrite on characters. Default: sdbcx::Group-Undo. + +/** open undo container + * + * @return nUndoId ID of the container + */ +SwUndoId SwEditShell::StartUndo( SwUndoId eUndoId, + const SwRewriter *pRewriter ) +{ return GetDoc()->GetIDocumentUndoRedo().StartUndo( eUndoId, pRewriter ); } + +/** close undo container + * + * not used by UI + * + * @param eUndoId ID of the undo container + * @param pRewriter ? +*/ +SwUndoId SwEditShell::EndUndo(SwUndoId eUndoId, const SwRewriter *pRewriter) +{ return GetDoc()->GetIDocumentUndoRedo().EndUndo(eUndoId, pRewriter); } + +bool SwEditShell::GetLastUndoInfo(OUString *const o_pStr, + SwUndoId *const o_pId, + const SwView* pView) const +{ + return GetDoc()->GetIDocumentUndoRedo().GetLastUndoInfo(o_pStr, o_pId, pView); +} + +bool SwEditShell::GetFirstRedoInfo(OUString *const o_pStr, + SwUndoId *const o_pId, + const SwView* pView) const +{ + return GetDoc()->GetIDocumentUndoRedo().GetFirstRedoInfo(o_pStr, o_pId, pView); +} + +SwUndoId SwEditShell::GetRepeatInfo(OUString *const o_pStr) const +{ return GetDoc()->GetIDocumentUndoRedo().GetRepeatInfo(o_pStr); } + +/** Auto correction */ +void SwEditShell::AutoCorrect( SvxAutoCorrect& rACorr, bool bInsert, + sal_Unicode cChar ) +{ + CurrShell aCurr( this ); + + StartAllAction(); + + SwPaM* pCursor = getShellCursor( true ); + SwTextNode* pTNd = pCursor->GetPointNode().GetTextNode(); + + SwAutoCorrDoc aSwAutoCorrDoc( *this, *pCursor, cChar ); + // FIXME: this _must_ be called with reference to the actual node text! + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTNd->getLayoutFrame(GetLayout()))); + TextFrameIndex const nPos(pFrame->MapModelToViewPos(*pCursor->GetPoint())); + // tdf#147414 sw_redlinehide: if cursor moved backward, it may be at the + // start of a delete redline - but MapViewToModelPos() always returns end + // of redline and it will be called when AutoCorrect actually inserts + // something - so first normalize cursor point to end of redline so that + // point will then be moved forward when something is inserted. + *pCursor->GetPoint() = pFrame->MapViewToModelPos(nPos); + // The hope is that the AutoCorrect never deletes nodes, hence never + // deletes SwTextFrames, hence we can pass in the SwTextFrame::GetText() + // result and it will be updated via the SwTextFrame::SwClientNotify() + // on editing operations. + OUString const& rMergedText(pFrame->GetText()); + rACorr.DoAutoCorrect( aSwAutoCorrDoc, + rMergedText, sal_Int32(nPos), + cChar, bInsert, m_bNbspRunNext, GetWin() ); + if( cChar ) + SaveTableBoxContent( pCursor->GetPoint() ); + EndAllAction(); +} + +void SwEditShell::SetNewDoc() +{ + GetDoc()->getIDocumentState().SetNewDoc(true); +} + +OUString SwEditShell::GetPrevAutoCorrWord(SvxAutoCorrect& rACorr) +{ + CurrShell aCurr( this ); + + OUString sRet; + SwPaM* pCursor = getShellCursor( true ); + SwTextNode* pTNd = pCursor->GetPointNode().GetTextNode(); + if (pTNd) + { + SwAutoCorrDoc aSwAutoCorrDoc( *this, *pCursor, 0 ); + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTNd->getLayoutFrame(GetLayout()))); + TextFrameIndex const nPos(pFrame->MapModelToViewPos(*pCursor->GetPoint())); + sRet = rACorr.GetPrevAutoCorrWord(aSwAutoCorrDoc, pFrame->GetText(), sal_Int32(nPos)); + } + return sRet; +} + +std::vector<OUString> SwEditShell::GetChunkForAutoText() +{ + CurrShell aCurr(this); + + std::vector<OUString> aRet; + SwPaM* pCursor = getShellCursor(true); + SwTextNode* pTNd = pCursor->GetPointNode().GetTextNode(); + if (pTNd) + { + const auto pFrame = static_cast<SwTextFrame const*>(pTNd->getLayoutFrame(GetLayout())); + TextFrameIndex const nPos(pFrame->MapModelToViewPos(*pCursor->GetPoint())); + aRet = SvxAutoCorrect::GetChunkForAutoText(pFrame->GetText(), sal_Int32(nPos)); + } + return aRet; +} + +SwAutoCompleteWord& SwEditShell::GetAutoCompleteWords() +{ + return SwDoc::GetAutoCompleteWords(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |