/* -*- 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 "impedit.hxx"
#include <sal/log.hxx>
#include <o3tl/safeint.hxx>
#include <osl/diagnose.h>
#include <editeng/editview.hxx>
#include <editeng/editeng.hxx>
#include <edtspell.hxx>
#include <editeng/flditem.hxx>
#include <svl/intitem.hxx>
#include <svl/eitem.hxx>
#include <editeng/unolingu.hxx>
#include <com/sun/star/linguistic2/XDictionary.hpp>
using namespace com::sun::star::uno;
using namespace com::sun::star::linguistic2;
EditSpellWrapper::EditSpellWrapper(weld::Widget* pWindow,
bool bIsStart, EditView* pView )
: SvxSpellWrapper(pWindow, bIsStart, false/*bIsAllRight*/)
{
SAL_WARN_IF( !pView, "editeng", "One view has to be abandoned!" );
// Keep IgnoreList, delete ReplaceList...
if (LinguMgr::GetChangeAllList().is())
LinguMgr::GetChangeAllList()->clear();
pEditView = pView;
}
void EditSpellWrapper::SpellStart( SvxSpellArea eArea )
{
EditEngine& rEditEngine = pEditView->getEditEngine();
ImpEditEngine& rImpEditEngine = pEditView->getImpEditEngine();
SpellInfo* pSpellInfo = rImpEditEngine.GetSpellInfo();
if ( eArea == SvxSpellArea::BodyStart )
{
// Is called when
// a) Spell-Forward has arrived at the end and should restart at the top
// IsEndDone() returns also true, when backward-spelling is started at the end!
if ( IsEndDone() )
{
pSpellInfo->bSpellToEnd = false;
pSpellInfo->aSpellTo = pSpellInfo->aSpellStart;
pEditView->getImpl().SetEditSelection(rEditEngine.GetEditDoc().GetStartPaM());
}
else
{
pSpellInfo->bSpellToEnd = true;
pSpellInfo->aSpellTo = rImpEditEngine.CreateEPaM(
rEditEngine.GetEditDoc().GetStartPaM() );
}
}
else if ( eArea == SvxSpellArea::BodyEnd )
{
// Is called when
// a) Spell-Forward is launched
// IsStartDone() return also true, when forward-spelling is started at the beginning!
if ( !IsStartDone() )
{
pSpellInfo->bSpellToEnd = true;
pSpellInfo->aSpellTo = rImpEditEngine.CreateEPaM(
rEditEngine.GetEditDoc().GetEndPaM() );
}
else
{
pSpellInfo->bSpellToEnd = false;
pSpellInfo->aSpellTo = pSpellInfo->aSpellStart;
pEditView->getImpl().SetEditSelection(rEditEngine.GetEditDoc().GetEndPaM());
}
}
else if ( eArea == SvxSpellArea::Body )
{
; // Is handled by the App through SpellNextDocument
}
else
{
OSL_FAIL( "SpellStart: Unknown Area!" );
}
}
void EditSpellWrapper::SpellContinue()
{
SetLast(pEditView->getImpEditEngine().ImpSpell(pEditView));
}
bool EditSpellWrapper::SpellMore()
{
EditEngine& rEditEngine = pEditView->getEditEngine();
ImpEditEngine& rImpEditEngine = pEditView->getImpEditEngine();
SpellInfo* pSpellInfo = rImpEditEngine.GetSpellInfo();
bool bMore = false;
if ( pSpellInfo->bMultipleDoc )
{
bMore = rEditEngine.SpellNextDocument();
if ( bMore )
{
// The text has been entered into the engine, when backwards then
// it must be behind the selection.
pEditView->getImpl().SetEditSelection(rEditEngine.GetEditDoc().GetStartPaM());
}
}
return bMore;
}
void EditSpellWrapper::ReplaceAll( const OUString &rNewText )
{
// Is called when the word is in ReplaceList of the spell checker
pEditView->InsertText( rNewText );
CheckSpellTo();
}
void EditSpellWrapper::CheckSpellTo()
{
ImpEditEngine& rImpEditEngine = pEditView->getImpEditEngine();
SpellInfo* pSpellInfo = rImpEditEngine.GetSpellInfo();
EditPaM aPaM( pEditView->getImpl().GetEditSelection().Max() );
EPaM aEPaM = rImpEditEngine.CreateEPaM( aPaM );
if ( aEPaM.nPara == pSpellInfo->aSpellTo.nPara )
{
// Check if SpellToEnd still has a valid Index, if replace has been
// performed in the paragraph.
if ( pSpellInfo->aSpellTo.nIndex > aPaM.GetNode()->Len() )
pSpellInfo->aSpellTo.nIndex = aPaM.GetNode()->Len();
}
}
WrongList::WrongList() : mnInvalidStart(0), mnInvalidEnd(Valid) {}
void WrongList::SetRanges( std::vector<editeng::MisspellRange>&&rRanges )
{
maRanges = std::move(rRanges);
}
bool WrongList::IsValid() const
{
return mnInvalidStart == Valid;
}
void WrongList::SetValid()
{
mnInvalidStart = Valid;
mnInvalidEnd = 0;
}
void WrongList::SetInvalidRange( size_t nStart, size_t nEnd )
{
if (mnInvalidStart == Valid || nStart < mnInvalidStart)
mnInvalidStart = nStart;
if (mnInvalidEnd < nEnd)
mnInvalidEnd = nEnd;
}
void WrongList::ResetInvalidRange( size_t nStart, size_t nEnd )
{
mnInvalidStart = nStart;
mnInvalidEnd = nEnd;
}
void WrongList::TextInserted( size_t nPos, size_t nLength, bool bPosIsSep )
{
if (IsValid())
{
mnInvalidStart = nPos;
mnInvalidEnd = nPos + nLength;
}
else
{
if ( mnInvalidStart > nPos )
mnInvalidStart = nPos;
if ( mnInvalidEnd >= nPos )
mnInvalidEnd = mnInvalidEnd + nLength;
else
mnInvalidEnd = nPos + nLength;
}
for (size_t i = 0, n = maRanges.size(); i < n; ++i)
{
editeng::MisspellRange& rWrong = maRanges[i];
bool bRefIsValid = true;
if (rWrong.mnEnd >= nPos)
{
// Move all Wrongs after the insert position...
if (rWrong.mnStart > nPos)
{
rWrong.mnStart += nLength;
rWrong.mnEnd += nLength;
}
// 1: Starts before and goes until nPos...
else if (rWrong.mnEnd == nPos)
{
// Should be halted at a blank!
if ( !bPosIsSep )
rWrong.mnEnd += nLength;
}
// 2: Starts before and goes until after nPos...
else if ((rWrong.mnStart < nPos) && (rWrong.mnEnd > nPos))
{
rWrong.mnEnd += nLength;
// When a separator remove and re-examine the Wrong
if ( bPosIsSep )
{
// Split Wrong...
editeng::MisspellRange aNewWrong(rWrong.mnStart, nPos);
rWrong.mnStart = nPos + 1;
maRanges.insert(maRanges.begin() + i, aNewWrong);
// Reference no longer valid after Insert, the other
// was inserted in front of this position
bRefIsValid = false;
++i; // Not this again...
}
}
// 3: Attribute starts at position ..
else if (rWrong.mnStart == nPos)
{
rWrong.mnEnd += nLength;
if ( bPosIsSep )
++(rWrong.mnStart);
}
}
SAL_WARN_IF(bRefIsValid && rWrong.mnStart >= rWrong.mnEnd, "editeng",
"TextInserted, editeng::MisspellRange: Start >= End?!");
}
SAL_WARN_IF(DbgIsBuggy(), "editeng", "InsertWrong: WrongList broken!");
}
void WrongList::TextDeleted( size_t nPos, size_t nLength )
{
size_t nEndPos = nPos + nLength;
if (IsValid())
{
const size_t nNewInvalidStart = nPos ? nPos - 1 : 0;
mnInvalidStart = nNewInvalidStart;
mnInvalidEnd = nNewInvalidStart + 1;
}
else
{
if ( mnInvalidStart > nPos )
mnInvalidStart = nPos;
if ( mnInvalidEnd > nPos )
{
if (mnInvalidEnd > nEndPos)
mnInvalidEnd = mnInvalidEnd - nLength;
else
mnInvalidEnd = nPos+1;
}
}
for (WrongList::iterator i = maRanges.begin(); i != maRanges.end(); )
{
bool bDelWrong = false;
if (i->mnEnd >= nPos)
{
// Move all Wrongs after the insert position...
if (i->mnStart >= nEndPos)
{
i->mnStart -= nLength;
i->mnEnd -= nLength;
}
// 1. Delete Internal Wrongs ...
else if (i->mnStart >= nPos && i->mnEnd <= nEndPos)
{
bDelWrong = true;
}
// 2. Wrong begins before, ends inside or behind it ...
else if (i->mnStart <= nPos && i->mnEnd > nPos)
{
if (i->mnEnd <= nEndPos) // ends inside
i->mnEnd = nPos;
else
i->mnEnd -= nLength; // ends after
}
// 3. Wrong begins inside, ending after ...
else if (i->mnStart >= nPos && i->mnEnd > nEndPos)
{
i->mnStart = nEndPos - nLength;
i->mnEnd -= nLength;
}
}
SAL_WARN_IF(i->mnStart >= i->mnEnd, "editeng",
"TextDeleted, editeng::MisspellRange: Start >= End?!");
if ( bDelWrong )
{
i = maRanges.erase(i);
}
else
{
++i;
}
}
SAL_WARN_IF(DbgIsBuggy(), "editeng", "TextDeleted: WrongList broken!");
}
bool WrongList::NextWrong( size_t& rnStart, size_t& rnEnd ) const
{
/*
rnStart get the start position, is possibly adjusted wrt. Wrong start
rnEnd does not have to be initialized.
*/
for (auto const& range : maRanges)
{
if (range.mnEnd > rnStart)
{
rnStart = range.mnStart;
rnEnd = range.mnEnd;
return true;
}
}
return false;
}
bool WrongList::HasWrong( size_t nStart, size_t nEnd ) const
{
for (auto const& range : maRanges)
{
if (range.mnStart == nStart && range.mnEnd == nEnd)
return true;
else if (range.mnStart >= nStart)
break;
}
return false;
}
bool WrongList::HasAnyWrong( size_t nStart, size_t nEnd ) const
{
for (auto const& range : maRanges)
{
if (range.mnEnd >= nStart && range.mnStart < nEnd)
return true;
else if (range.mnStart >= nEnd)
break;
}
return false;
}
void WrongList::ClearWrongs( size_t nStart, size_t nEnd,
const ContentNode* pNode )
{
for (WrongList::iterator i = maRanges.begin(); i != maRanges.end(); )
{
if (i->mnEnd > nStart && i->mnStart < nEnd)
{
if (i->mnEnd > nEnd) // Runs out
{
i->mnStart = nEnd;
// Blanks?
while (i->mnStart < o3tl::make_unsigned(pNode->Len()) &&
(pNode->GetChar(i->mnStart) == ' ' ||
pNode->IsFeature(i->mnStart)))
{
++i->mnStart;
}
++i;
}
else
{
i = maRanges.erase(i);
// no increment here
}
}
else
{
++i;
}
}
SAL_WARN_IF(DbgIsBuggy(), "editeng", "ClearWrongs: WrongList broken!");
}
void WrongList::InsertWrong( size_t nStart, size_t nEnd )
{
WrongList::iterator nPos = std::find_if(maRanges.begin(), maRanges.end(),
[&nStart](const editeng::MisspellRange& rRange) { return rRange.mnStart >= nStart; });
if (nPos != maRanges.end())
{
{
// It can really only happen that the Wrong starts exactly here
// and runs along, but not that there are several ranges ...
// Exactly in the range is no one allowed to be, otherwise this
// Method can not be called!
SAL_WARN_IF((nPos->mnStart != nStart || nPos->mnEnd <= nEnd) && nPos->mnStart <= nEnd, "editeng", "InsertWrong: RangeMismatch!");
if (nPos->mnStart == nStart && nPos->mnEnd > nEnd)
nPos->mnStart = nEnd + 1;
}
maRanges.insert(nPos, editeng::MisspellRange(nStart, nEnd));
}
else
maRanges.emplace_back(nStart, nEnd);
SAL_WARN_IF(DbgIsBuggy(), "editeng", "InsertWrong: WrongList broken!");
}
void WrongList::MarkWrongsInvalid()
{
if (!maRanges.empty())
SetInvalidRange(maRanges.front().mnStart, maRanges.back().mnEnd);
}
WrongList* WrongList::Clone() const
{
return new WrongList(*this);
}
// #i102062#
bool WrongList::operator==(const WrongList& rCompare) const
{
// check direct members
if(GetInvalidStart() != rCompare.GetInvalidStart()
|| GetInvalidEnd() != rCompare.GetInvalidEnd())
return false;
return std::equal(maRanges.begin(), maRanges.end(), rCompare.maRanges.begin(), rCompare.maRanges.end(),
[](const editeng::MisspellRange& a, const editeng::MisspellRange& b) {
return a.mnStart == b.mnStart && a.mnEnd == b.mnEnd; });
}
bool WrongList::empty() const
{
return maRanges.empty();
}
void WrongList::push_back(const editeng::MisspellRange& rRange)
{
maRanges.push_back(rRange);
}
editeng::MisspellRange& WrongList::back()
{
return maRanges.back();
}
const editeng::MisspellRange& WrongList::back() const
{
return maRanges.back();
}
WrongList::iterator WrongList::begin()
{
return maRanges.begin();
}
WrongList::iterator WrongList::end()
{
return maRanges.end();
}
WrongList::const_iterator WrongList::begin() const
{
return maRanges.begin();
}
WrongList::const_iterator WrongList::end() const
{
return maRanges.end();
}
bool WrongList::DbgIsBuggy() const
{
// Check if the ranges overlap.
bool bError = false;
for (WrongList::const_iterator i = maRanges.begin(); !bError && (i != maRanges.end()); ++i)
{
bError = std::any_of(i + 1, maRanges.end(), [&i](const editeng::MisspellRange& rRange) {
return i->mnStart <= rRange.mnEnd && rRange.mnStart <= i->mnEnd; });
}
return bError;
}
EdtAutoCorrDoc::EdtAutoCorrDoc(
EditEngine* pE, ContentNode* pN, sal_Int32 nCrsr, sal_Unicode cIns) :
mpEditEngine(pE),
pCurNode(pN),
nCursor(nCrsr),
bAllowUndoAction(cIns != 0),
bUndoAction(false) {}
EdtAutoCorrDoc::~EdtAutoCorrDoc()
{
if ( bUndoAction )
mpEditEngine->UndoActionEnd();
}
bool EdtAutoCorrDoc::Delete(sal_Int32 nStt, sal_Int32 nEnd)
{
EditSelection aSel( EditPaM( pCurNode, nStt ), EditPaM( pCurNode, nEnd ) );
mpEditEngine->DeleteSelection(aSel);
SAL_WARN_IF(nCursor < nEnd, "editeng",
"Cursor in the heart of the action?!");
nCursor -= ( nEnd-nStt );
bAllowUndoAction = false;
return true;
}
bool EdtAutoCorrDoc::Insert(sal_Int32 nPos, const OUString& rTxt)
{
EditSelection aSel(EditPaM( pCurNode, nPos ));
mpEditEngine->InsertText(aSel, rTxt);
SAL_WARN_IF(nCursor < nPos, "editeng",
"Cursor in the heart of the action?!");
nCursor = nCursor + rTxt.getLength();
if ( bAllowUndoAction && ( rTxt.getLength() == 1 ) )
ImplStartUndoAction();
bAllowUndoAction = false;
return true;
}
bool EdtAutoCorrDoc::Replace(sal_Int32 nPos, const OUString& rTxt)
{
return ReplaceRange( nPos, rTxt.getLength(), rTxt );
}
bool EdtAutoCorrDoc::ReplaceRange(sal_Int32 nPos, sal_Int32 nSourceLength, const OUString& rTxt)
{
// Actually a Replace introduce => corresponds to UNDO
sal_Int32 nEnd = nPos+nSourceLength;
if ( nEnd > pCurNode->Len() )
nEnd = pCurNode->Len();
// #i5925# First insert new text behind to be deleted text, for keeping attributes.
mpEditEngine->InsertText(EditSelection(EditPaM(pCurNode, nEnd)), rTxt);
mpEditEngine->DeleteSelection(
EditSelection(EditPaM(pCurNode, nPos), EditPaM(pCurNode, nEnd)));
if ( nPos == nCursor )
nCursor = nCursor + rTxt.getLength();
if ( bAllowUndoAction && ( rTxt.getLength() == 1 ) )
ImplStartUndoAction();
bAllowUndoAction = false;
return true;
}
void EdtAutoCorrDoc::SetAttr(sal_Int32 nStt, sal_Int32 nEnd,
sal_uInt16 nSlotId, SfxPoolItem& rItem)
{
SfxItemPool* pPool = &mpEditEngine->GetEditDoc().GetItemPool();
while ( pPool->GetSecondaryPool() &&
pPool->GetName() != "EditEngineItemPool" )
{
pPool = pPool->GetSecondaryPool();
}
sal_uInt16 nWhich = pPool->GetWhichIDFromSlotID( nSlotId );
if ( nWhich )
{
rItem.SetWhich( nWhich );
SfxItemSet aSet = mpEditEngine->GetEmptyItemSet();
aSet.Put( rItem );
EditSelection aSel( EditPaM( pCurNode, nStt ), EditPaM( pCurNode, nEnd ) );
aSel.Max().SetIndex( nEnd ); // ???
mpEditEngine->SetAttribs( aSel, aSet, SetAttribsMode::Edge );
bAllowUndoAction = false;
}
}
bool EdtAutoCorrDoc::SetINetAttr(sal_Int32 nStt, sal_Int32 nEnd,
const OUString& rURL)
{
// Turn the Text into a command field ...
EditSelection aSel( EditPaM( pCurNode, nStt ), EditPaM( pCurNode, nEnd ) );
OUString aText = mpEditEngine->GetSelected(aSel);
aSel = mpEditEngine->DeleteSelection(aSel);
SAL_WARN_IF(nCursor < nEnd, "editeng",
"Cursor in the heart of the action?!");
nCursor -= ( nEnd-nStt );
SvxFieldItem aField( SvxURLField( rURL, aText, SvxURLFormat::Repr ),
EE_FEATURE_FIELD );
mpEditEngine->InsertField(aSel, aField);
nCursor++;
mpEditEngine->UpdateFieldsOnly();
bAllowUndoAction = false;
return true;
}
OUString const* EdtAutoCorrDoc::GetPrevPara(bool const)
{
// Return previous paragraph, so that it can be determined,
// whether the current word is at the beginning of a sentence.
bAllowUndoAction = false; // Not anymore ...
EditDoc& rEditDoc = mpEditEngine->GetEditDoc();
sal_Int32 nPos = rEditDoc.GetPos( pCurNode );
// Special case: Bullet => Paragraph start => simply return NULL...
const SfxBoolItem& rBulletState = mpEditEngine->GetParaAttrib( nPos, EE_PARA_BULLETSTATE );
bool bBullet = rBulletState.GetValue();
if ( !bBullet && (mpEditEngine->GetControlWord() & EEControlBits::OUTLINER) )
{
// The Outliner has still a Bullet at Level 0.
const SfxInt16Item& rLevel = mpEditEngine->GetParaAttrib( nPos, EE_PARA_OUTLLEVEL );
if ( rLevel.GetValue() == 0 )
bBullet = true;
}
if ( bBullet )
return nullptr;
for ( sal_Int32 n = nPos; n; )
{
n--;
ContentNode* pNode = rEditDoc.GetObject(n);
if ( pNode->Len() )
return & pNode->GetString();
}
return nullptr;
}
bool EdtAutoCorrDoc::ChgAutoCorrWord( sal_Int32& rSttPos,
sal_Int32 nEndPos, SvxAutoCorrect& rACorrect,
OUString* pPara )
{
// Paragraph-start or a blank found, search for the word
// shortcut in Auto
bAllowUndoAction = false; // Not anymore ...
OUString aShort( pCurNode->Copy( rSttPos, nEndPos - rSttPos ) );
bool bRet = false;
if( aShort.isEmpty() ) {
return bRet;
}
LanguageTag aLanguageTag( mpEditEngine->GetLanguage( EditPaM( pCurNode, rSttPos+1 ) ).nLang );
sal_Int32 sttPos = rSttPos;
auto pStatus = rACorrect.SearchWordsInList(pCurNode->GetString(),
sttPos, nEndPos,
*this, aLanguageTag);
if( !pStatus ) {
return bRet;
}
sal_Int32 minSttPos = sttPos;
do {
const SvxAutocorrWord* pFnd = pStatus->GetAutocorrWord();
if( pFnd && pFnd->IsTextOnly() )
{
// replace also last colon of keywords surrounded by colons
// (for example, ":name:")
bool replaceLastChar = pFnd->GetShort()[0] == ':'
&& pFnd->GetShort().endsWith(":");
// then replace
EditSelection aSel( EditPaM( pCurNode, sttPos ),
EditPaM( pCurNode, nEndPos + (replaceLastChar ? 1 : 0) ));
aSel = mpEditEngine->DeleteSelection(aSel);
SAL_WARN_IF(nCursor < nEndPos, "editeng",
"Cursor in the heart of the action?!");
nCursor -= ( nEndPos-sttPos );
mpEditEngine->InsertText(aSel, pFnd->GetLong());
nCursor = nCursor + pFnd->GetLong().getLength();
nEndPos = sttPos + pFnd->GetLong().getLength();
if( pPara ) {
*pPara = pCurNode->GetString();
}
bRet = true;
if( sttPos < minSttPos ) {
minSttPos = sttPos;
}
}
sttPos = rSttPos;
} while( SvxAutoCorrect::SearchWordsNext(pCurNode->GetString(),
sttPos, nEndPos, *pStatus) );
rSttPos = minSttPos;
return bRet;
}
bool EdtAutoCorrDoc::TransliterateRTLWord( sal_Int32& /*rSttPos*/,
sal_Int32 /*nEndPos*/, bool /*bApply*/ )
{
// Paragraph-start or a blank found, search for the word
// shortcut in Auto
bool bRet = false;
return bRet;
}
LanguageType EdtAutoCorrDoc::GetLanguage( sal_Int32 nPos ) const
{
return mpEditEngine->GetLanguage( EditPaM( pCurNode, nPos+1 ) ).nLang;
}
void EdtAutoCorrDoc::ImplStartUndoAction()
{
sal_Int32 nPara = mpEditEngine->GetEditDoc().GetPos( pCurNode );
ESelection aSel( nPara, nCursor, nPara, nCursor );
mpEditEngine->UndoActionStart( EDITUNDO_INSERT, aSel );
bUndoAction = true;
bAllowUndoAction = false;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V560 A part of conditional expression is always true: pFnd.