/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <hintids.hxx>
#include <unotools/charclass.hxx>
#include <editeng/boxitem.hxx>
#include <editeng/lrspitem.hxx>
#include <editeng/formatbreakitem.hxx>
#include <editeng/adjustitem.hxx>
#include <editeng/tstpitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/langitem.hxx>
#include <editeng/unolingu.hxx>
#include <editeng/acorrcfg.hxx>
#include <swwait.hxx>
#include <fmtpdsc.hxx>
#include <fmtanchr.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <DocumentRedlineManager.hxx>
#include <IDocumentStylePoolAccess.hxx>
#include <docary.hxx>
#include <editsh.hxx>
#include <index.hxx>
#include <pam.hxx>
#include <edimp.hxx>
#include <fesh.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 <swtable.hxx>
#include <acorrect.hxx>
#include <shellres.hxx>
#include <section.hxx>
#include <frmatr.hxx>
#include <charatr.hxx>
#include <mdiexp.hxx>
#include <strings.hrc>
#include <comcore.hxx>
#include <numrule.hxx>
#include <itabenum.hxx>
#include <memory>
using namespace ::com::sun::star;
//JP 16.12.99: definition:
// from pos cPosEnDash to cPosEmDash all chars changed to em dashes,
// from pos cPosEmDash to cPosEnd all chars changed to em dashes
// all other chars are changed to the user configuration
const sal_Unicode pBulletChar[6] = { '+', '*', '-', 0x2013, 0x2014, 0 };
const int cnPosEnDash = 2, cnPosEmDash = 4;
const sal_Unicode cStarSymbolEnDash = 0x2013;
const sal_Unicode cStarSymbolEmDash = 0x2014;
SvxSwAutoFormatFlags* SwEditShell::s_pAutoFormatFlags = nullptr;
// Number of num-/bullet-paragraph templates. MAXLEVEL will soon be raised
// to x, but not the number of templates. (Artifact from <= 4.0)
const sal_uInt16 cnNumBullColls = 4;
class SwAutoFormat
{
SvxSwAutoFormatFlags m_aFlags;
SwPaM m_aDelPam; // a Pam that can be used
SwNodeIndex m_aNdIdx; // the index on the current TextNode
SwNodeIndex m_aEndNdIdx; // index on the end of the area
SwEditShell* m_pEditShell;
SwDoc* m_pDoc;
SwTextNode* m_pCurTextNd; // the current TextNode
SwTextFrame* m_pCurTextFrame; // frame of the current TextNode
sal_uLong m_nEndNdIdx; // for the percentage-display
mutable std::unique_ptr<CharClass> m_pCharClass; // Character classification
mutable LanguageType m_eCharClassLang;
sal_uInt16 m_nLastHeadLvl, m_nLastCalcHeadLvl;
sal_uInt16 m_nRedlAutoFormatSeqId;
enum
{
NONE = 0,
DELIM = 1,
DIGIT = 2,
CHG = 4,
LOWER_ALPHA = 8,
UPPER_ALPHA = 16,
LOWER_ROMAN = 32,
UPPER_ROMAN = 64,
NO_DELIM = (DIGIT|LOWER_ALPHA|UPPER_ALPHA|LOWER_ROMAN|UPPER_ROMAN)
};
bool m_bEnd : 1;
bool m_bMoreLines : 1;
CharClass& GetCharClass( LanguageType eLang ) const
{
if( !m_pCharClass || eLang != m_eCharClassLang )
{
m_pCharClass.reset( new CharClass( LanguageTag( eLang ) ) );
m_eCharClassLang = eLang;
}
return *m_pCharClass;
}
static bool IsSpace( const sal_Unicode c )
{ return (' ' == c || '\t' == c || 0x0a == c|| 0x3000 == c /* Jap. space */); }
void SetColl( sal_uInt16 nId, bool bHdLineOrText = false );
OUString GoNextPara();
bool HasObjects( const SwNode& rNd );
// TextNode methods
const SwTextNode* GetNextNode() const;
static bool IsEmptyLine( const SwTextNode& rNd )
{ return rNd.GetText().isEmpty() ||
rNd.GetText().getLength() == GetLeadingBlanks( rNd.GetText() ); }
bool IsOneLine( const SwTextNode& ) const;
bool IsFastFullLine( const SwTextNode& ) const;
bool IsNoAlphaLine( const SwTextNode&) const;
bool IsEnumericChar( const SwTextNode&) const;
static bool IsBlanksInString( const SwTextNode&);
sal_uInt16 CalcLevel( const SwTextNode&, sal_uInt16 *pDigitLvl = nullptr ) const;
sal_Int32 GetBigIndent( sal_Int32& rCurrentSpacePos ) const;
static OUString DelLeadingBlanks(const OUString& rStr);
static OUString DelTrailingBlanks( const OUString& rStr );
static sal_Int32 GetLeadingBlanks( const OUString& rStr );
static sal_Int32 GetTrailingBlanks( const OUString& rStr );
bool IsFirstCharCapital( const SwTextNode& rNd ) const;
sal_uInt16 GetDigitLevel( const SwTextNode& rTextNd, sal_Int32& rPos,
OUString* pPrefix = nullptr, OUString* pPostfix = nullptr,
OUString* pNumTypes = nullptr ) const;
/// get the FORMATTED TextFrame
SwTextFrame* GetFrame( const SwTextNode& rTextNd ) 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 HasSelBlanks( SwPaM& rPam );
static bool HasBreakAttr( const SwTextNode& );
void DeleteSel( SwPaM& rPam );
bool DeleteCurNxtPara( const OUString& rNxtPara );
/// delete in the node start and/or end
void DeleteCurrentParagraph( 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 );
/// delete the previous paragraph
void DelPrevPara();
/// execute AutoCorrect on current TextNode
void AutoCorrect( sal_Int32 nSttPos = 0 );
bool CanJoin( const SwTextNode* pTextNd ) const
{
return !m_bEnd && pTextNd &&
!IsEmptyLine( *pTextNd ) &&
!IsNoAlphaLine( *pTextNd) &&
!IsEnumericChar( *pTextNd ) &&
((COMPLETE_STRING - 50 - pTextNd->GetText().getLength()) >
m_pCurTextNd->GetText().getLength()) &&
!HasBreakAttr( *pTextNd );
}
/// is a dot at the end ??
static bool IsSentenceAtEnd( const SwTextNode& rTextNd );
bool DoUnderline();
bool DoTable();
void SetRedlineText_( sal_uInt16 nId );
bool SetRedlineText( sal_uInt16 nId ) {
if( m_aFlags.bWithRedlining )
SetRedlineText_( nId );
return true;
}
void ClearRedlineText() {
if( m_aFlags.bWithRedlining )
m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment(nullptr);
}
public:
SwAutoFormat( SwEditShell* pEdShell, SvxSwAutoFormatFlags const & rFlags,
SwNodeIndex const * pSttNd = nullptr, SwNodeIndex const * pEndNd = nullptr );
};
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() );
OSL_ENSURE( pFrame, "For Autoformat a Layout is needed" );
if( m_aFlags.bAFormatByInput && !pFrame->isFrameAreaDefinitionValid() )
{
DisableCallbackAction a(const_cast<SwRootFrame&>(*pFrame->getRootFrame()));
SwRect aTmpFrame( pFrame->getFrameArea() );
SwRect aTmpPrt( pFrame->getFramePrintArea() );
pFrame->Calc(pFrame->getRootFrame()->GetCurrShell()->GetOut());
if( pFrame->getFrameArea() != aTmpFrame || pFrame->getFramePrintArea() != aTmpPrt ||
( pFrame->IsTextFrame() && !const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(pFrame))->GetPaintSwRect().IsEmpty() ) )
{
pFrame->SetCompletePaint();
}
}
return const_cast<SwTextFrame*>(static_cast<const SwTextFrame*>(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_NUMBULLET:
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:
nSeqNo = ++m_nRedlAutoFormatSeqId;
break;
}
}
#if OSL_DEBUG_LEVEL > 0
else
sText = "Action text is missing";
#endif
m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment( &sText, nSeqNo );
}
OUString 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 OUString();
}
++m_aNdIdx;
if( m_aNdIdx.GetIndex() >= m_aEndNdIdx.GetIndex() )
{
m_bEnd = true;
return OUString();
}
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 OUString();
}
else if( pNewNd->IsTableNode() )
m_aNdIdx = *pNewNd->EndOfSectionNode();
else if( pNewNd->IsSectionNode() )
{
const SwSection& rSect = pNewNd->GetSectionNode()->GetSection();
if( rSect.IsHiddenFlag() || rSect.IsProtectFlag() )
m_aNdIdx = *pNewNd->EndOfSectionNode();
}
} while( !pNewNd->IsTextNode() );
if( !m_aFlags.bAFormatByInput )
::SetProgressState( m_aNdIdx.GetIndex() + m_nEndNdIdx - m_aEndNdIdx.GetIndex(),
m_pDoc->GetDocShell() );
m_pCurTextNd = static_cast<SwTextNode*>(pNewNd);
m_pCurTextFrame = GetFrame( *m_pCurTextNd );
return m_pCurTextNd->GetText();
}
bool SwAutoFormat::HasObjects( const SwNode& rNd )
{
// Is there something bound to the paragraph in the paragraph
// like borders, DrawObjects, ...
bool bRet = false;
const SwFrameFormats& rFormats = *m_pDoc->GetSpzFrameFormats();
for( auto pFrameFormat : rFormats )
{
const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor();
if ((RndStdIds::FLY_AT_PAGE != rAnchor.GetAnchorId()) &&
rAnchor.GetContentAnchor() &&
&rAnchor.GetContentAnchor()->nNode.GetNode() == &rNd )
{
bRet = true;
break;
}
}
return bRet;
}
const SwTextNode* SwAutoFormat::GetNextNode() const
{
if( m_aNdIdx.GetIndex()+1 >= m_aEndNdIdx.GetIndex() )
return nullptr;
return m_pDoc->GetNodes()[ m_aNdIdx.GetIndex() + 1 ]->GetTextNode();
}
bool SwAutoFormat::IsOneLine( const SwTextNode& rNd ) const
{
SwTextFrameInfo aFInfo( GetFrame( rNd ) );
return aFInfo.IsOneLine();
}
bool SwAutoFormat::IsFastFullLine( const SwTextNode& rNd ) const
{
bool bRet = m_aFlags.bRightMargin;
if( bRet )
{
SwTextFrameInfo aFInfo( GetFrame( rNd ) );
bRet = aFInfo.IsFilled( m_aFlags.nRightMargin );
}
return bRet;
}
bool SwAutoFormat::IsEnumericChar( const SwTextNode& rNd ) const
{
const OUString& rText = rNd.GetText();
sal_Int32 nBlnks = GetLeadingBlanks( rText );
const sal_Int32 nLen = rText.getLength() - nBlnks;
if( !nLen )
return false;
// -, +, * separated by blank ??
if (2 < nLen && IsSpace(rText[nBlnks + 1]))
{
if (StrChr(pBulletChar, rText[nBlnks]))
return true;
// Should there be a symbol font at the position?
SwTextFrameInfo aFInfo( GetFrame( rNd ) );
if( aFInfo.IsBullet( nBlnks ))
return true;
}
// 1.) / 1. / 1.1.1 / (1). / (1) / ....
return USHRT_MAX != GetDigitLevel( rNd, nBlnks );
}
bool SwAutoFormat::IsBlanksInString( const SwTextNode& rNd )
{
// Search more than 5 consecutive blanks/tabs in the string.
OUString sTmp( DelLeadingBlanks(rNd.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 SwTextNode& rNd, sal_uInt16 *pDigitLvl ) const
{
sal_uInt16 nLvl = 0, nBlnk = 0;
const OUString& rText = rNd.GetText();
if( pDigitLvl )
*pDigitLvl = USHRT_MAX;
if( RES_POOLCOLL_TEXT_MOVE == rNd.GetTextColl()->GetPoolFormatId() )
{
if( m_aFlags.bAFormatByInput )
{
nLvl = rNd.GetAutoFormatLvl();
const_cast<SwTextNode&>(rNd).SetAutoFormatLvl( 0 );
if( nLvl )
return nLvl;
}
++nLvl;
}
for (sal_Int32 n = 0, nEnd = rText.getLength(); n < nEnd; ++n)
{
switch (rText[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( rNd, n );
return nLvl;
}
}
return nLvl;
}
sal_Int32 SwAutoFormat::GetBigIndent( sal_Int32& rCurrentSpacePos ) const
{
SwTextFrameInfo aFInfo( GetFrame( *m_pCurTextNd ) );
const SwTextFrame* pNxtFrame = nullptr;
if( !m_bMoreLines )
{
const SwTextNode* pNxtNd = GetNextNode();
if( !CanJoin( pNxtNd ) || !IsOneLine( *pNxtNd ) )
return 0;
pNxtFrame = GetFrame( *pNxtNd );
}
return aFInfo.GetBigIndent( rCurrentSpacePos, pNxtFrame );
}
bool SwAutoFormat::IsNoAlphaLine( const SwTextNode& rNd ) const
{
const OUString& rStr = rNd.GetText();
if( rStr.isEmpty() )
return false;
// or better: determine via number of AlphaNum and !AlphaNum characters
sal_Int32 nANChar = 0, nBlnk = 0;
CharClass& rCC = GetCharClass( rNd.GetSwAttrSet().GetLanguage().GetLanguage() );
for( sal_Int32 n = 0, nEnd = rStr.getLength(); n < nEnd; ++n )
if( IsSpace( rStr[ n ] ) )
++nBlnk;
else if( rCC.isLetterNumeric( rStr, 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_pCurTextNd->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 );
m_aDelPam.SetMark();
m_aDelPam.GetMark()->nContent = 0;
editeng::SvxBorderLine aLine;
switch( eState )
{
case 1: // single, 0.05 pt
aLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID);
aLine.SetWidth( DEF_LINE_WIDTH_0 );
break;
case 2: // single, 1.0 pt
aLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID);
aLine.SetWidth( DEF_LINE_WIDTH_1 );
break;
case 3: // double, 1.0 pt
aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
aLine.SetWidth( DEF_LINE_WIDTH_1 );
break;
case 4: // double (thick/thin), 4.0 pt
aLine.SetBorderLineStyle(SvxBorderLineStyle::THICKTHIN_SMALLGAP);
aLine.SetWidth( DEF_LINE_WIDTH_3 );
break;
case 5: // double (thin/thick), 4.0 pt
aLine.SetBorderLineStyle(SvxBorderLineStyle::THINTHICK_SMALLGAP);
aLine.SetWidth( DEF_LINE_WIDTH_3 );
break;
case 6: // double, 2.5 pt
aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
aLine.SetWidth( DEF_LINE_WIDTH_2 );
break;
}
SfxItemSet aSet(m_pDoc->GetAttrPool(),
svl::Items<RES_PARATR_CONNECT_BORDER, RES_PARATR_CONNECT_BORDER,
RES_BOX, RES_BOX>{});
aSet.Put( SwParaConnectBorderItem( false ) );
SvxBoxItem aBox( RES_BOX );
aBox.SetLine( &aLine, SvxBoxItemLine::BOTTOM );
aBox.SetDistance(42, SvxBoxItemLine::BOTTOM ); // ~0,75 mm
aSet.Put(aBox);
m_pDoc->getIDocumentContentOperations().InsertItemSet( m_aDelPam, aSet );
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_pCurTextNd->GetText();
sal_Int32 nSttPlus = GetLeadingBlanks( rTmp );
sal_Int32 nEndPlus = GetTrailingBlanks( rTmp );
sal_Unicode cChar;
if( 2 > nEndPlus - nSttPlus ||
( '+' != ( cChar = rTmp[nSttPlus]) && '|' != cChar ) ||
( '+' != ( cChar = rTmp[nEndPlus - 1]) && '|' != cChar ))
return false;
SwTextFrameInfo aInfo( m_pCurTextFrame );
sal_Int32 n = nSttPlus;
std::vector<sal_uInt16> aPosArr;
while (n < rTmp.getLength())
{
switch (rTmp[n])
{
case '-':
case '_':
case '=':
case ' ':
case '\t':
break;
case '+':
case '|':
aPosArr.push_back( static_cast<sal_uInt16>(aInfo.GetCharPos(n)) );
break;
default:
return false;
}
if( ++n == nEndPlus )
break;
}
if( 1 < aPosArr.size() )
{
// get the text node's alignment
sal_uInt16 nColCnt = aPosArr.size() - 1;
SwTwips nSttPos = aPosArr[ 0 ];
sal_Int16 eHori;
switch( m_pCurTextNd->GetSwAttrSet().GetAdjust().GetAdjust() )
{
case SvxAdjust::Center: eHori = text::HoriOrientation::CENTER; break;
case SvxAdjust::Right: eHori = text::HoriOrientation::RIGHT; break;
default:
if( nSttPos )
{
eHori = text::HoriOrientation::NONE;
// then - as last - we need to add the current frame width into the array
aPosArr.push_back( static_cast<sal_uInt16>(m_pCurTextFrame->getFrameArea().Width()) );
}
else
eHori = text::HoriOrientation::LEFT;
break;
}
// then create a table that matches the character
DelEmptyLine();
SwNodeIndex aIdx( m_aDelPam.GetPoint()->nNode );
m_aDelPam.Move( fnMoveForward );
m_pDoc->InsertTable( SwInsertTableOptions( SwInsertTableFlags::All , 1 ),
*m_aDelPam.GetPoint(), 1, nColCnt, eHori,
nullptr, &aPosArr );
m_aDelPam.GetPoint()->nNode = aIdx;
}
return 1 < aPosArr.size();
}
OUString SwAutoFormat::DelLeadingBlanks( const OUString& rStr )
{
sal_Int32 nL, n;
for( nL = rStr.getLength(), n = 0; n < nL && IsSpace( rStr[n] ); ++n )
;
if( n ) // no Spaces
return rStr.copy(n);
return rStr;
}
OUString SwAutoFormat::DelTrailingBlanks( const OUString& rStr )
{
sal_Int32 nL = rStr.getLength(), n = nL;
if( !nL )
return rStr;
while( --n && IsSpace( rStr[ n ] ) )
;
if( n+1 != nL ) // no Spaces
return rStr.copy( 0, n+1 );
return rStr;
}
sal_Int32 SwAutoFormat::GetLeadingBlanks( const OUString& rStr )
{
sal_Int32 nL;
sal_Int32 n;
for( nL = rStr.getLength(), n = 0; n < nL && IsSpace( rStr[ n ] ); ++n )
;
return n;
}
sal_Int32 SwAutoFormat::GetTrailingBlanks( const OUString& rStr )
{
sal_Int32 nL = rStr.getLength(), n = nL;
if( !nL )
return 0;
while( --n && IsSpace( rStr[ n ] ) )
;
return ++n;
}
bool SwAutoFormat::IsFirstCharCapital( const SwTextNode& rNd ) const
{
const OUString& rText = rNd.GetText();
for( sal_Int32 n = 0, nEnd = rText.getLength(); n < nEnd; ++n )
if (!IsSpace(rText[n]))
{
CharClass& rCC = GetCharClass( rNd.GetSwAttrSet().
GetLanguage().GetLanguage() );
sal_Int32 nCharType = rCC.getCharacterType( rText, n );
return CharClass::isLetterType( nCharType ) &&
0 != ( i18n::KCharacterType::UPPER &
nCharType );
}
return false;
}
sal_uInt16 SwAutoFormat::GetDigitLevel( const SwTextNode& rNd, sal_Int32& rPos,
OUString* pPrefix, OUString* pPostfix, OUString* pNumTypes ) const
{
// check for 1.) / 1. / 1.1.1 / (1). / (1) / ....
const OUString& rText = rNd.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;
CharClass& rCC = GetCharClass( rNd.GetSwAttrSet().GetLanguage().GetLanguage() );
while (nPos < rText.getLength() && nDigitLvl < MAXLEVEL - 1)
{
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 += OUStringLiteral1('0' + SVX_NUM_ARABIC);
eScan = eScan | CHG;
}
else if( pNumTypes && !(eScan & DIGIT) )
*pNumTypes += OUStringLiteral1('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 &= ~(UPPER_ALPHA|LOWER_ALPHA)) |= eTmpScan;
if( pNumTypes )
(*pNumTypes) = pNumTypes->replaceAt( pNumTypes->getLength() - 1, 1, OUString(c) );
}
if( eScan & DELIM )
{
if( eScan & CHG ) // not if it starts with a number
{
++nDigitLvl;
if( pPostfix )
*pPostfix += "\x01";
}
if( pNumTypes )
*pNumTypes += OUStringLiteral1(cNumTyp);
eScan = eScan | CHG;
}
else if( pNumTypes && !(eScan & eTmpScan) )
*pNumTypes += OUStringLiteral1(cNumTyp);
eScan &= ~DELIM; // remove Delim
// if another type is set, stop here
if( 0 != ( eScan & ~CHG ) && eTmpScan != ( eScan & ~CHG ))
return USHRT_MAX;
if( eTmpScan & (UPPER_ALPHA | LOWER_ALPHA) )
{
// allow characters only if they appear once
return USHRT_MAX;
}
else
{
// roman numbers, check if valid characters
sal_uInt16 nVal;
bool bError = false;
switch( cLow )
{
case 'm': nVal = 1000; goto CHECK_ROMAN_1;
case 'd': nVal = 500; goto CHECK_ROMAN_5;
case 'c': nVal = 100; goto CHECK_ROMAN_1;
case 'l': nVal = 50; goto CHECK_ROMAN_5;
case 'x': nVal = 10; goto CHECK_ROMAN_1;
case 'v': nVal = 5; goto CHECK_ROMAN_5;
CHECK_ROMAN_1:
{
int nMod5 = nStart % (nVal * 5);
int nLast = nStart % nVal;
int n10 = nVal / 10;
if( nMod5 == ((3 * nVal) + n10 ) ||
nMod5 == ((4 * nVal) + n10 ) ||
nLast == n10 )
nStart = static_cast<sal_uInt16>(nStart + (n10 * 8));
else if( nMod5 == 0 ||
nMod5 == (1 * nVal) ||
nMod5 == (2 * nVal) )
nStart = nStart + nVal;
else
bError = true;
}
break;
CHECK_ROMAN_5:
{
if( ( nStart / nVal ) & 1 )
bError = true;
else
{
int nMod = nStart % nVal;
int n10 = nVal / 5;
if( n10 == nMod )
nStart = static_cast<sal_uInt16>(nStart + (3 * n10));
else if( 0 == nMod )
nStart = nStart + nVal;
else
bError = true;
}
}
break;
case 'i':
if( nStart % 5 >= 3 )
bError = true;
else
nStart += 1;
break;
default:
bError = true;
}
if( bError )
return USHRT_MAX;
}
eScan |= eTmpScan; // add Digit
++nDigitCnt;
}
else if( (256 > cCurrentChar &&
strchr( ".)(", cCurrentChar )) ||
0x3002 == cCurrentChar /* Chinese trad. dot */||
0xff0e == cCurrentChar /* Japanese dot */||
0xFF08 == cCurrentChar /* opening bracket Chin./Jap.*/||
0xFF09 == cCurrentChar )/* closing bracket Chin./Jap. */
{
if(cCurrentChar == '(' || cCurrentChar == 0xFF09)
nOpeningParentheses++;
else if(cCurrentChar == ')'|| cCurrentChar == 0xFF08)
nClosingParentheses++;
// only if no numbers were read until here
if( pPrefix && !( eScan & ( NO_DELIM | CHG )) )
*pPrefix += OUStringLiteral1(rText[nPos]);
else if( pPostfix )
*pPostfix += OUStringLiteral1(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 == 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 = nPos;
return nDigitLvl; // 0 .. 9 (MAXLEVEL - 1)
}
void SwAutoFormat::SetColl( sal_uInt16 nId, bool bHdLineOrText )
{
m_aDelPam.DeleteMark();
m_aDelPam.GetPoint()->nNode = m_aNdIdx;
m_aDelPam.GetPoint()->nContent.Assign( m_pCurTextNd, 0 );
// keep hard tabs, alignment, language, hyphenation, DropCaps and nearly all frame attributes
SfxItemSet aSet(
m_pDoc->GetAttrPool(),
svl::Items<
RES_CHRATR_LANGUAGE, RES_CHRATR_LANGUAGE,
RES_PARATR_ADJUST, RES_PARATR_ADJUST,
RES_PARATR_TABSTOP, RES_PARATR_DROP,
RES_BACKGROUND, RES_SHADOW>{});
if( m_pCurTextNd->HasSwAttrSet() )
{
aSet.Put( *m_pCurTextNd->GetpSwAttrSet() );
// take HeaderLine/TextBody only if centered or right aligned, otherwise only justification
SvxAdjustItem const * pAdj;
if( SfxItemState::SET == aSet.GetItemState( RES_PARATR_ADJUST,
false, reinterpret_cast<const SfxPoolItem**>(&pAdj) ))
{
SvxAdjust eAdj = pAdj->GetAdjust();
if( bHdLineOrText ? (SvxAdjust::Right != eAdj &&
SvxAdjust::Center != eAdj)
: SvxAdjust::Block != eAdj )
aSet.ClearItem( RES_PARATR_ADJUST );
}
}
m_pDoc->SetTextFormatCollByAutoFormat( *m_aDelPam.GetPoint(), nId, &aSet );
}
bool SwAutoFormat::HasSelBlanks( SwPaM& rPam )
{
// Is there a Blank at the beginning or end?
// Do not delete it, it will be inserted again.
SwPosition * pPos = rPam.End();
sal_Int32 nBlnkPos = pPos->nContent.GetIndex();
SwTextNode* pTextNd = pPos->nNode.GetNode().GetTextNode();
if (nBlnkPos && nBlnkPos-- < pTextNd->GetText().getLength() &&
(' ' == pTextNd->GetText()[nBlnkPos]))
--pPos->nContent;
else
{
pPos = rPam.GetPoint() == pPos ? rPam.GetMark() : rPam.GetPoint();
nBlnkPos = pPos->nContent.GetIndex();
pTextNd = pPos->nNode.GetNode().GetTextNode();
if (nBlnkPos < pTextNd->GetText().getLength() &&
(' ' == pTextNd->GetText()[nBlnkPos]))
++pPos->nContent;
else
return false;
}
return true;
}
bool SwAutoFormat::HasBreakAttr( const SwTextNode& rTextNd )
{
const SfxItemSet* pSet = rTextNd.GetpSwAttrSet();
if( !pSet )
return false;
const SfxPoolItem* pItem;
if( SfxItemState::SET == pSet->GetItemState( RES_BREAK, false, &pItem )
&& SvxBreak::NONE != static_cast<const SvxFormatBreakItem*>(pItem)->GetBreak() )
return true;
if( SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC, false, &pItem )
&& static_cast<const SwFormatPageDesc*>(pItem)->GetPageDesc()
&& UseOnPage::NONE != static_cast<const SwFormatPageDesc*>(pItem)->GetPageDesc()->GetUseOn() )
return true;
return false;
}
/// Is there a dot at the end?
bool SwAutoFormat::IsSentenceAtEnd( const SwTextNode& rTextNd )
{
const OUString& rStr = rTextNd.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::DeleteCurrentParagraph( bool bStart, bool bEnd )
{
if( m_aFlags.bAFormatByInput
? m_aFlags.bAFormatByInpDelSpacesAtSttEnd
: m_aFlags.bAFormatDelSpacesAtSttEnd )
{
// delete blanks at the end of the current and at the beginning of the next one
m_aDelPam.DeleteMark();
m_aDelPam.GetPoint()->nNode = m_aNdIdx;
sal_Int32 nPos(0);
if( bStart && 0 != ( nPos = GetLeadingBlanks( m_pCurTextNd->GetText() )))
{
m_aDelPam.GetPoint()->nContent.Assign( m_pCurTextNd, 0 );
m_aDelPam.SetMark();
m_aDelPam.GetPoint()->nContent = nPos;
DeleteSel( m_aDelPam );
m_aDelPam.DeleteMark();
}
if (bEnd && m_pCurTextNd->GetText().getLength() !=
( nPos = GetTrailingBlanks( m_pCurTextNd->GetText() )) )
{
m_aDelPam.GetPoint()->nContent.Assign(
m_pCurTextNd, m_pCurTextNd->GetText().getLength());
m_aDelPam.SetMark();
m_aDelPam.GetPoint()->nContent = nPos;
DeleteSel( m_aDelPam );
m_aDelPam.DeleteMark();
}
}
}
void SwAutoFormat::DeleteSel( SwPaM& rDelPam )
{
if( m_aFlags.bWithRedlining )
{
// 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 = rDelPam.GetPrev();
rDelPam.GetRingContainer().merge( pShCursor->GetRingContainer() );
m_pEditShell->DeleteSel( rDelPam );
// and remove Pam again:
SwPaM* p;
SwPaM* pNext = &rDelPam;
do {
p = pNext;
pNext = p->GetNext();
p->MoveTo( &rDelPam );
} while( p != pPrev );
m_aNdIdx = aTmp.GetPoint()->nNode;
m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
}
else
m_pEditShell->DeleteSel( rDelPam );
}
bool SwAutoFormat::DeleteCurNxtPara( const OUString& rNxtPara )
{
// delete blanks at the end of the current and at the beginning of the next one
m_aDelPam.DeleteMark();
m_aDelPam.GetPoint()->nNode = m_aNdIdx;
m_aDelPam.GetPoint()->nContent.Assign( m_pCurTextNd,
GetTrailingBlanks( m_pCurTextNd->GetText() ) );
m_aDelPam.SetMark();
++m_aDelPam.GetPoint()->nNode;
SwTextNode* pTNd = m_aDelPam.GetNode().GetTextNode();
if( !pTNd )
{
// then delete only up to end of the paragraph
--m_aDelPam.GetPoint()->nNode;
m_aDelPam.GetPoint()->nContent = m_pCurTextNd->GetText().getLength();
}
else
m_aDelPam.GetPoint()->nContent.Assign( pTNd,
GetLeadingBlanks( rNxtPara ));
// Is there a Blank at the beginning or end?
// Do not delete it, it will be inserted again.
bool bHasBlnks = HasSelBlanks( m_aDelPam );
if( *m_aDelPam.GetPoint() != *m_aDelPam.GetMark() )
DeleteSel( m_aDelPam );
m_aDelPam.DeleteMark();
return !bHasBlnks;
}
void SwAutoFormat::DelEmptyLine( bool bTstNextPara )
{
SetRedlineText( STR_AUTOFMTREDL_DEL_EMPTY_PARA );
// delete blanks in empty paragraph
m_aDelPam.DeleteMark();
m_aDelPam.GetPoint()->nNode = m_aNdIdx;
m_aDelPam.GetPoint()->nContent.Assign(
m_pCurTextNd, m_pCurTextNd->GetText().getLength() );
m_aDelPam.SetMark();
--m_aDelPam.GetMark()->nNode;
SwTextNode* pTNd = m_aDelPam.GetNode( false ).GetTextNode();
if( pTNd )
// first use the previous text node
m_aDelPam.GetMark()->nContent.Assign(pTNd, pTNd->GetText().getLength());
else if( bTstNextPara )
{
// then try the next (at the beginning of a Doc, table cells, borders, ...)
m_aDelPam.GetMark()->nNode += 2;
pTNd = m_aDelPam.GetNode( false ).GetTextNode();
if( pTNd )
{
m_aDelPam.GetMark()->nContent.Assign( pTNd, 0 );
m_aDelPam.GetPoint()->nContent = 0;
}
}
else
{
m_aDelPam.GetMark()->nNode = m_aNdIdx;
m_aDelPam.GetMark()->nContent = 0;
pTNd = m_pCurTextNd;
}
if( pTNd )
DeleteSel( m_aDelPam );
m_aDelPam.DeleteMark();
ClearRedlineText();
}
void SwAutoFormat::DelMoreLinesBlanks( bool bWithLineBreaks )
{
if( m_aFlags.bAFormatByInput
? m_aFlags.bAFormatByInpDelSpacesBetweenLines
: m_aFlags.bAFormatDelSpacesBetweenLines )
{
// delete all blanks on the left and right of the indentation
m_aDelPam.DeleteMark();
m_aDelPam.GetPoint()->nNode = m_aNdIdx;
m_aDelPam.GetPoint()->nContent.Assign( m_pCurTextNd, 0 );
SwTextFrameInfo aFInfo( m_pCurTextFrame );
aFInfo.GetSpaces( m_aDelPam, !m_aFlags.bAFormatByInput || bWithLineBreaks );
do {
SwPaM* pNxt = m_aDelPam.GetNext();
if( pNxt->HasMark() && *pNxt->GetPoint() != *pNxt->GetMark() )
{
bool bHasBlnks = HasSelBlanks( *pNxt );
DeleteSel( *pNxt );
if( !bHasBlnks )
{
m_pDoc->getIDocumentContentOperations().InsertString( *pNxt, OUString(' ') );
}
}
if( pNxt == &m_aDelPam )
break;
delete pNxt;
} while( true );
m_aDelPam.DeleteMark();
}
}
// delete the previous paragraph
void SwAutoFormat::DelPrevPara()
{
m_aDelPam.DeleteMark();
m_aDelPam.GetPoint()->nNode = m_aNdIdx;
m_aDelPam.GetPoint()->nContent.Assign( m_pCurTextNd, 0 );
m_aDelPam.SetMark();
--m_aDelPam.GetPoint()->nNode;
SwTextNode* pTNd = m_aDelPam.GetNode().GetTextNode();
if( pTNd )
{
// use the previous text node first
m_aDelPam.GetPoint()->nContent.Assign(pTNd, pTNd->GetText().getLength());
DeleteSel( m_aDelPam );
}
m_aDelPam.DeleteMark();
}
void SwAutoFormat::BuildIndent()
{
SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_INDENT );
// read all succeeding paragraphs that belong to this indentation
bool bBreak = true;
if( m_bMoreLines )
DelMoreLinesBlanks( true );
else
bBreak = !IsFastFullLine( *m_pCurTextNd ) ||
IsBlanksInString( *m_pCurTextNd ) ||
IsSentenceAtEnd( *m_pCurTextNd );
SetColl( RES_POOLCOLL_TEXT_IDENT );
if( !bBreak )
{
SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
const SwTextNode* pNxtNd = GetNextNode();
if( pNxtNd && !m_bEnd )
{
do {
bBreak = !IsFastFullLine( *pNxtNd ) ||
IsBlanksInString( *pNxtNd ) ||
IsSentenceAtEnd( *pNxtNd );
if( DeleteCurNxtPara( pNxtNd->GetText() ))
{
m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
}
if( bBreak )
break;
pNxtNd = GetNextNode();
} while( CanJoin( pNxtNd ) &&
!CalcLevel( *pNxtNd ) );
}
}
DeleteCurrentParagraph();
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_pCurTextNd ) ||
IsBlanksInString( *m_pCurTextNd ) ||
IsSentenceAtEnd( *m_pCurTextNd );
if( m_aFlags.bAFormatByInput )
m_pCurTextNd->SetAutoFormatLvl( static_cast<sal_uInt8>(CalcLevel( *m_pCurTextNd )) );
SetColl( RES_POOLCOLL_TEXT_MOVE );
if( !bBreak )
{
SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
const SwTextNode* pNxtNd = GetNextNode();
while( CanJoin( pNxtNd ) &&
CalcLevel( *pNxtNd ) )
{
bBreak = !IsFastFullLine( *pNxtNd ) || IsBlanksInString( *pNxtNd ) ||
IsSentenceAtEnd( *pNxtNd );
if( DeleteCurNxtPara( pNxtNd->GetText() ) )
{
m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
}
if( bBreak )
break;
pNxtNd = GetNextNode();
}
}
DeleteCurrentParagraph();
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_pCurTextNd ) ||
IsBlanksInString( *m_pCurTextNd ) ||
IsSentenceAtEnd( *m_pCurTextNd );
SetColl( RES_POOLCOLL_TEXT, true );
if( !bBreak )
{
SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
const SwTextNode* pNxtNd = GetNextNode();
while( CanJoin( pNxtNd ) &&
!CalcLevel( *pNxtNd ) )
{
bBreak = !IsFastFullLine( *pNxtNd ) || IsBlanksInString( *pNxtNd ) ||
IsSentenceAtEnd( *pNxtNd );
if( DeleteCurNxtPara( pNxtNd->GetText() ) )
{
m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
}
if( bBreak )
break;
const SwTextNode* pCurrNode = pNxtNd;
pNxtNd = GetNextNode();
if(!pNxtNd || pCurrNode == pNxtNd)
break;
}
}
DeleteCurrentParagraph();
AutoCorrect();
}
void SwAutoFormat::BuildEnum( sal_uInt16 nLvl, sal_uInt16 nDigitLevel )
{
SetRedlineText( STR_AUTOFMTREDL_SET_NUMBULLET );
bool bBreak = true;
// first, determine current indentation and frame width
SwTwips nFrameWidth = m_pCurTextFrame->getFramePrintArea().Width();
SwTwips nLeftTextPos;
{
sal_Int32 nPos(0);
while (nPos < m_pCurTextNd->GetText().getLength() &&
IsSpace(m_pCurTextNd->GetText()[nPos]))
{
++nPos;
}
SwTextFrameInfo aInfo( m_pCurTextFrame );
nLeftTextPos = aInfo.GetCharPos(nPos);
nLeftTextPos -= m_pCurTextNd->GetSwAttrSet().GetLRSpace().GetLeft();
}
if( m_bMoreLines )
DelMoreLinesBlanks();
else
bBreak = !IsFastFullLine( *m_pCurTextNd ) ||
IsBlanksInString( *m_pCurTextNd ) ||
IsSentenceAtEnd( *m_pCurTextNd );
bool bRTL = m_pEditShell->IsInRightToLeftText();
DeleteCurrentParagraph();
bool bChgBullet = false, bChgEnum = false;
sal_Int32 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 && nullptr != (pCur = m_pCurTextNd->GetNumRule()) )
aRule = *pCur;
// replace bullet character with defined one
const OUString& rStr = m_pCurTextNd->GetText();
sal_Int32 nTextStt = 0;
const sal_Unicode* pFndBulletChr = nullptr;
if (m_aFlags.bChgEnumNum && 2 < rStr.getLength())
pFndBulletChr = StrChr(pBulletChar, rStr[nTextStt]);
if (nullptr != pFndBulletChr && IsSpace(rStr[nTextStt + 1]))
{
if( m_aFlags.bAFormatByInput )
{
if( m_aFlags.bSetNumRule )
{
SwCharFormat* pCFormat = m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool(
RES_POOLCHR_BUL_LEVEL );
bChgBullet = true;
// Was the format already somewhere adjusted?
if( !aRule.GetNumFormat( nLvl ) )
{
int nBulletPos = pFndBulletChr - pBulletChar;
sal_Unicode cBullChar;
const vcl::Font* pBullFnt( nullptr );
if( nBulletPos < cnPosEnDash )
{
cBullChar = m_aFlags.cBullet;
pBullFnt = &m_aFlags.aBulletFont;
}
else
{
cBullChar = nBulletPos < cnPosEmDash
? cStarSymbolEnDash
: cStarSymbolEmDash;
// #i63395#
// Only apply user defined default bullet font
if ( numfunc::IsDefBulletFontUserDefined() )
{
pBullFnt = &numfunc::GetDefBulletFont();
}
}
sal_Int32 nAbsPos = lBullIndent;
SwTwips nSpaceSteps = nLvl
? nLeftTextPos / nLvl
: lBullIndent;
for( sal_uInt8 n = 0; n < MAXLEVEL; ++n, nAbsPos = nAbsPos + nSpaceSteps )
{
SwNumFormat aFormat( aRule.Get( n ) );
aFormat.SetBulletFont( pBullFnt );
aFormat.SetBulletChar( cBullChar );
aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL);
// #i93908# clear suffix for bullet lists
aFormat.SetPrefix(OUString());
aFormat.SetSuffix(OUString());
aFormat.SetFirstLineOffset( lBullFirstLineOffset );
aFormat.SetAbsLSpace( nAbsPos );
if( !aFormat.GetCharFormat() )
aFormat.SetCharFormat( pCFormat );
if( bRTL )
aFormat.SetNumAdjust( SvxAdjust::Right );
aRule.Set( n, aFormat );
if( n == nLvl &&
nFrameWidth < ( nSpaceSteps * MAXLEVEL ) )
nSpaceSteps = ( nFrameWidth - nLeftTextPos ) /
( MAXLEVEL - nLvl );
}
}
}
}
else
{
bChgBullet = true;
SetColl( static_cast<sal_uInt16>(RES_POOLCOLL_BUL_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;
if( USHRT_MAX != ( nDigitLevel = GetDigitLevel( *m_pCurTextNd, nTextStt,
&aPrefix, &aPostfix, &aNumTypes )) )
{
bChgEnum = true;
// Level 0 and Indentation, determine level by left indentation and default NumIndent
if( !nDigitLevel && nLeftTextPos )
nLvl = std::min( sal_uInt16( nLeftTextPos / lNumIndent ),
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 );
if( !nDigitLevel )
{
SwNumFormat aFormat( aRule.Get( nLvl ) );
aFormat.SetStart( static_cast<sal_uInt16>(aPrefix.getToken( 1,
u'\x0001' ).toInt32()));
aFormat.SetPrefix( aPrefix.getToken( 0, u'\x0001' ));
aFormat.SetSuffix( aPostfix.getToken( 0, u'\x0001' ));
aFormat.SetIncludeUpperLevels( 0 );
if( !aFormat.GetCharFormat() )
aFormat.SetCharFormat( pCFormat );
if( !aNumTypes.isEmpty() )
aFormat.SetNumberingType(static_cast<SvxNumType>(aNumTypes[ 0 ] - '0'));
if( bRTL )
aFormat.SetNumAdjust( SvxAdjust::Right );
aRule.Set( nLvl, aFormat );
}
else
{
auto const nSpaceSteps = nLvl ? nLeftTextPos / nLvl : 0;
sal_uInt16 n;
for( n = 0; n <= nLvl; ++n )
{
SwNumFormat aFormat( aRule.Get( n ) );
aFormat.SetStart( static_cast<sal_uInt16>(aPrefix.getToken( n+1,
u'\x0001' ).toInt32() ));
if( !n )
aFormat.SetPrefix( aPrefix.getToken( n, u'\x0001' ));
aFormat.SetSuffix( aPostfix.getToken( n, u'\x0001' ));
aFormat.SetIncludeUpperLevels( MAXLEVEL );
if( n < aNumTypes.getLength() )
aFormat.SetNumberingType(static_cast<SvxNumType>(aNumTypes[ n ] - '0'));
aFormat.SetAbsLSpace( nSpaceSteps * n
+ lNumIndent );
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
+ lNumIndent );
aRule.Set( n, aFormat );
}
}
}
}
else if( !m_aFlags.bAFormatByInput )
SetColl( static_cast<sal_uInt16>(RES_POOLCOLL_NUM_LEVEL1 + ( std::min( nLvl, cnNumBullColls ) * 4 ) ));
else
bChgEnum = false;
}
if ( bChgEnum || bChgBullet )
{
m_aDelPam.DeleteMark();
m_aDelPam.GetPoint()->nNode = m_aNdIdx;
if( m_aFlags.bSetNumRule )
{
if( m_aFlags.bAFormatByInput )
{
m_aDelPam.SetMark();
++m_aDelPam.GetMark()->nNode;
m_aDelPam.GetNode(false).GetTextNode()->SetAttrListLevel( nLvl );
}
m_pCurTextNd->SetAttrListLevel(nLvl);
// start new list
m_pDoc->SetNumRule( m_aDelPam, aRule, true );
m_aDelPam.DeleteMark();
m_aDelPam.GetPoint()->nContent.Assign( m_pCurTextNd, 0 );
}
else
m_aDelPam.GetPoint()->nContent.Assign( m_pCurTextNd,
bChgEnum ? nTextStt : 0 );
m_aDelPam.SetMark();
if ( bChgBullet )
nTextStt += 2;
while( nTextStt < rStr.getLength() && IsSpace( rStr[ nTextStt ] ))
nTextStt++;
m_aDelPam.GetPoint()->nContent = nTextStt;
DeleteSel( m_aDelPam );
if( !m_aFlags.bSetNumRule )
{
OUString sChgStr('\t');
if( bChgBullet )
sChgStr = OUStringLiteral1( m_aFlags.cBullet ) + sChgStr;
m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, sChgStr );
SfxItemSet aSet( m_pDoc->GetAttrPool(), aTextNodeSetRange );
if( bChgBullet )
{
m_aDelPam.GetPoint()->nContent = 0;
m_aDelPam.SetMark();
m_aDelPam.GetMark()->nContent = 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 = 2;
aSet.ClearItem();
}
SvxTabStopItem aTStops( RES_PARATR_TABSTOP );
aTStops.Insert( SvxTabStop( 0 ) );
aSet.Put( aTStops );
m_pDoc->SetFormatItemByAutoFormat( m_aDelPam, aSet );
}
}
if( bBreak )
{
AutoCorrect( nAutoCorrPos ); /* Offset due to Bullet + Tab */
return;
}
const SwTextNode* pNxtNd = GetNextNode();
while( CanJoin( pNxtNd ) &&
nLvl == CalcLevel( *pNxtNd ) )
{
SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
bBreak = !IsFastFullLine( *pNxtNd ) || IsBlanksInString( *pNxtNd ) ||
IsSentenceAtEnd( *pNxtNd );
if( DeleteCurNxtPara( pNxtNd->GetText() ) )
{
m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
}
if( bBreak )
break;
const SwTextNode* pCurrNode = pNxtNd;
pNxtNd = GetNextNode();
if(!pNxtNd || pCurrNode == pNxtNd)
break;
}
DeleteCurrentParagraph( 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;
sal_Int32 nSpacePos = 0;
const sal_Int32 nTextPos = GetBigIndent( nSpacePos );
if( m_bMoreLines )
DelMoreLinesBlanks( true );
else
bBreak = !IsFastFullLine( *m_pCurTextNd ) ||
( !nTextPos && IsBlanksInString( *m_pCurTextNd )) ||
IsSentenceAtEnd( *m_pCurTextNd );
SetColl( static_cast<sal_uInt16>( nTextPos
? RES_POOLCOLL_CONFRONTATION
: RES_POOLCOLL_TEXT_NEGIDENT ) );
if( nTextPos )
{
const OUString& rStr = m_pCurTextNd->GetText();
bool bInsTab = true;
if ('\t' == rStr[nSpacePos+1]) // leave tab alone
{
--nSpacePos;
bInsTab = false;
}
sal_Int32 nSpaceStt = nSpacePos;
while (nSpaceStt && IsSpace(rStr[--nSpaceStt]))
;
++nSpaceStt;
if (bInsTab && '\t' == rStr[nSpaceStt]) // leave tab alone
{
++nSpaceStt;
bInsTab = false;
}
m_aDelPam.DeleteMark();
m_aDelPam.GetPoint()->nNode = m_aNdIdx;
m_aDelPam.GetPoint()->nContent.Assign( m_pCurTextNd, nSpacePos );
// delete old Spaces, etc.
if( nSpaceStt < nSpacePos )
{
m_aDelPam.SetMark();
m_aDelPam.GetMark()->nContent = 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 SwTextNode* pNxtNd = GetNextNode();
while( CanJoin( pNxtNd ) &&
20 < std::abs( static_cast<long>(nSpaces - aFInfo.SetFrame(
GetFrame( *pNxtNd ) ).GetLineStart() ))
)
{
bBreak = !IsFastFullLine( *pNxtNd ) ||
IsBlanksInString( *pNxtNd ) ||
IsSentenceAtEnd( *pNxtNd );
if( DeleteCurNxtPara( pNxtNd->GetText() ) )
{
m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
}
if( bBreak )
break;
pNxtNd = GetNextNode();
}
}
DeleteCurrentParagraph();
AutoCorrect();
}
void SwAutoFormat::BuildHeadLine( sal_uInt16 nLvl )
{
if( m_aFlags.bWithRedlining )
{
OUString sText(SwViewShell::GetShellRes()->GetAutoFormatNameLst()[
STR_AUTOFMTREDL_SET_TMPL_HEADLINE ] );
sText = sText.replaceAll( "$(ARG1)", OUString::number( nLvl + 1 ) );
m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment( &sText );
}
SetColl( static_cast<sal_uInt16>(RES_POOLCOLL_HEADLINE1 + nLvl ), true );
if( m_aFlags.bAFormatByInput )
{
SwTextFormatColl& rNxtColl = m_pCurTextNd->GetTextColl()->GetNextTextFormatColl();
DelPrevPara();
DeleteCurrentParagraph( true, false );
(void)DeleteCurNxtPara( OUString() );
m_aDelPam.DeleteMark();
m_aDelPam.GetPoint()->nNode = m_aNdIdx.GetIndex() + 1;
m_aDelPam.GetPoint()->nContent.Assign( m_aDelPam.GetContentNode(), 0 );
m_pDoc->SetTextFormatColl( m_aDelPam, &rNxtColl );
}
else
{
DeleteCurrentParagraph();
AutoCorrect();
}
}
/// Start autocorrection for the current TextNode
void SwAutoFormat::AutoCorrect( sal_Int32 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.bChgToEnEmDash && !m_aFlags.bSetINetAttr &&
!m_aFlags.bChgWeightUnderl && !m_aFlags.bAddNonBrkSpace) )
return;
const OUString* pText = &m_pCurTextNd->GetText();
if (nPos >= pText->getLength())
return;
bool bGetLanguage = m_aFlags.bChgOrdinalNumber ||
m_aFlags.bChgToEnEmDash || m_aFlags.bSetINetAttr ||
m_aFlags.bCapitalStartWord || m_aFlags.bCapitalStartSentence ||
m_aFlags.bAddNonBrkSpace;
m_aDelPam.DeleteMark();
m_aDelPam.GetPoint()->nNode = m_aNdIdx;
m_aDelPam.GetPoint()->nContent.Assign( m_pCurTextNd, 0 );
SwAutoCorrDoc aACorrDoc( *m_pEditShell, m_aDelPam );
SwTextFrameInfo aFInfo( nullptr );
sal_Int32 nSttPos, nLastBlank = nPos;
bool bFirst = m_aFlags.bCapitalStartSentence, bFirstSent = bFirst;
sal_Unicode cChar = 0;
bool bNbspRunNext = false;
CharClass& rAppCC = GetAppCharClass();
do {
while (nPos < pText->getLength() && IsSpace(cChar = (*pText)[nPos]))
++nPos;
if (nPos == pText->getLength())
break; // that's it
if( ( ( bReplaceQuote && '\"' == cChar ) ||
( bReplaceSglQuote && '\'' == cChar ) ) &&
(!nPos || ' ' == (*pText)[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()->nContent = nPos;
bool bSetHardBlank = false;
OUString sReplace( pATst->GetQuote( aACorrDoc,
nPos, cChar, true ));
m_aDelPam.SetMark();
m_aDelPam.GetPoint()->nContent = nPos+1;
if( 2 == sReplace.getLength() && ' ' == sReplace[ 1 ])
{
sReplace = sReplace.copy( 0, 1 );
bSetHardBlank = true;
}
m_pDoc->getIDocumentContentOperations().ReplaceRange( m_aDelPam, sReplace, false );
if( m_aFlags.bWithRedlining )
{
m_aNdIdx = m_aDelPam.GetPoint()->nNode;
m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
pText = &m_pCurTextNd->GetText();
m_aDelPam.SetMark();
aFInfo.SetFrame( nullptr );
}
nPos += 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)[nPos-1]))
nLastBlank = nPos;
for (nSttPos = nPos; !bBreak && nPos < pText->getLength(); ++nPos)
switch (cChar = (*pText)[nPos])
{
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()->nContent = nPos;
OUString sReplace( pATst->GetQuote( aACorrDoc,
nPos, cChar, false ));
if( 2 == sReplace.getLength() && ' ' == sReplace[ 0 ])
{
sReplace = sReplace.copy( 1 );
bSetHardBlank = true;
}
m_aDelPam.SetMark();
m_aDelPam.GetPoint()->nContent = nPos+1;
m_pDoc->getIDocumentContentOperations().ReplaceRange( m_aDelPam, sReplace, false );
if( m_aFlags.bWithRedlining )
{
m_aNdIdx = m_aDelPam.GetPoint()->nNode;
m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
pText = &m_pCurTextNd->GetText();
m_aDelPam.SetMark();
m_aDelPam.DeleteMark();
aFInfo.SetFrame( nullptr );
}
nPos += sReplace.getLength() - 1;
m_aDelPam.DeleteMark();
if( bSetHardBlank )
{
m_aDelPam.GetPoint()->nContent = nPos;
m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(CHAR_HARDBLANK) );
m_aDelPam.GetPoint()->nContent = ++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)[nSttPos - 1] : 0;
m_aDelPam.GetPoint()->nContent = nPos;
if( pATst->FnChgWeightUnderl( aACorrDoc, *pText, nPos ))
{
if( m_aFlags.bWithRedlining )
{
m_aNdIdx = m_aDelPam.GetPoint()->nNode;
m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
pText = &m_pCurTextNd->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_aDelPam.GetPoint()->nContent.GetIndex() - 1;
// Was a character deleted before starting?
if (cBlank && cBlank != (*pText)[nSttPos - 1])
--nSttPos;
}
}
}
break;
case '/':
if ( m_aFlags.bAddNonBrkSpace )
{
LanguageType eLang = bGetLanguage
? m_pCurTextNd->GetLang( nSttPos )
: LANGUAGE_SYSTEM;
SetRedlineText( STR_AUTOFMTREDL_NON_BREAK_SPACE );
if ( pATst->FnAddNonBrkSpace( aACorrDoc, *pText, nPos, eLang, bNbspRunNext ) )
--nPos;
}
break;
case '.':
case '!':
case '?':
if( m_aFlags.bCapitalStartSentence )
bFirstSent = true;
SAL_FALLTHROUGH;
default:
if( !( rAppCC.isLetterNumeric( *pText, 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 == pText->getLength())
bCallACorr = true;
}
else
bCallACorr = true;
if( bCallACorr )
{
m_aDelPam.GetPoint()->nContent = nPos;
SetRedlineText( STR_AUTOFMTREDL_USE_REPLACE );
if( m_aFlags.bAutoCorrect &&
aACorrDoc.ChgAutoCorrWord( nSttPos, nPos, *pATst, nullptr ) )
{
nPos = m_aDelPam.GetPoint()->nContent.GetIndex();
if( m_aFlags.bWithRedlining )
{
m_aNdIdx = m_aDelPam.GetPoint()->nNode;
m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
pText = &m_pCurTextNd->GetText();
m_aDelPam.SetMark();
m_aDelPam.DeleteMark();
}
continue; // do not check further
}
LanguageType eLang = bGetLanguage
? m_pCurTextNd->GetLang( nSttPos )
: LANGUAGE_SYSTEM;
if ( m_aFlags.bAddNonBrkSpace )
{
SetRedlineText( STR_AUTOFMTREDL_NON_BREAK_SPACE );
pATst->FnAddNonBrkSpace( aACorrDoc, *pText, nPos, eLang, bNbspRunNext );
}
if( ( m_aFlags.bChgOrdinalNumber &&
SetRedlineText( STR_AUTOFMTREDL_ORDINAL ) &&
pATst->FnChgOrdinalNumber( aACorrDoc, *pText, nSttPos, nPos, eLang ) ) ||
( m_aFlags.bChgToEnEmDash &&
SetRedlineText( STR_AUTOFMTREDL_DASH ) &&
pATst->FnChgToEnEmDash( aACorrDoc, *pText, nSttPos, nPos, eLang ) ) ||
( m_aFlags.bSetINetAttr &&
(nPos == pText->getLength() || IsSpace((*pText)[nPos])) &&
SetRedlineText( STR_AUTOFMTREDL_DETECT_URL ) &&
pATst->FnSetINetAttr( aACorrDoc, *pText, nLastBlank, nPos, eLang ) ) )
nPos = m_aDelPam.GetPoint()->nContent.GetIndex();
else
{
// two capital letters at the beginning of a word?
if( m_aFlags.bCapitalStartWord )
{
SetRedlineText( STR_AUTOFMTREDL_CPTL_STT_WORD );
pATst->FnCapitalStartWord( aACorrDoc, *pText, nSttPos, 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, nSttPos, nPos, eLang);
}
bFirst = bFirstSent;
bFirstSent = false;
if( m_aFlags.bWithRedlining )
{
m_aNdIdx = m_aDelPam.GetPoint()->nNode;
m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
pText = &m_pCurTextNd->GetText();
m_aDelPam.SetMark();
m_aDelPam.DeleteMark();
}
}
}
} while (nPos < pText->getLength());
ClearRedlineText();
}
SwAutoFormat::SwAutoFormat( SwEditShell* pEdShell, SvxSwAutoFormatFlags const & rFlags,
SwNodeIndex const * pSttNd, SwNodeIndex const * pEndNd )
: m_aFlags( rFlags ),
m_aDelPam( pEdShell->GetDoc()->GetNodes().GetEndOfExtras() ),
m_aNdIdx( pEdShell->GetDoc()->GetNodes().GetEndOfExtras(), +1 ),
m_aEndNdIdx( pEdShell->GetDoc()->GetNodes().GetEndOfContent() ),
m_pEditShell( pEdShell ),
m_pDoc( pEdShell->GetDoc() ),
m_pCurTextNd( nullptr ), m_pCurTextFrame( nullptr ),
m_nRedlAutoFormatSeqId( 0 )
{
OSL_ENSURE( (pSttNd && pEndNd) || (!pSttNd && !pEndNd),
"Got no area" );
if( m_aFlags.bSetNumRule && !m_aFlags.bAFormatByInput )
m_aFlags.bSetNumRule = false;
bool bReplaceStyles = !m_aFlags.bAFormatByInput || m_aFlags.bReplaceStyles;
const SwTextNode* pNxtNd = nullptr;
bool bNxtEmpty = false;
bool bNxtAlpha = false;
sal_uInt16 nNxtLevel = 0;
bool bEmptyLine;
// set area for autoformatting
if( pSttNd )
{
m_aNdIdx = *pSttNd;
--m_aNdIdx; // for GoNextPara, one paragraph prior to that
m_aEndNdIdx = *pEndNd;
++m_aEndNdIdx;
// check the previous TextNode
pNxtNd = m_aNdIdx.GetNode().GetTextNode();
bEmptyLine = !pNxtNd ||
IsEmptyLine( *pNxtNd ) ||
IsNoAlphaLine( *pNxtNd );
}
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, m_aNdIdx.GetIndex(),
m_nEndNdIdx = m_aEndNdIdx.GetIndex(),
m_pDoc->GetDocShell() );
RedlineFlags eRedlMode = m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags(), eOldMode = eRedlMode;
if( m_aFlags.bWithRedlining )
{
m_pDoc->SetAutoFormatRedline( true );
eRedlMode = RedlineFlags::On | RedlineFlags::ShowInsert;
}
else
eRedlMode = RedlineFlags::ShowInsert | RedlineFlags::Ignore;
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;
m_nLastCalcHeadLvl = 0;
m_nLastHeadLvl = USHRT_MAX;
sal_uInt16 nLevel = 0;
sal_uInt16 nDigitLvl = 0;
// set defaults
SwTextFrameInfo aFInfo( nullptr );
enum Format_Status
{
READ_NEXT_PARA,
TST_EMPTY_LINE,
TST_ALPHA_LINE,
GET_ALL_INFO,
IS_ONE_LINE,
TST_ENUMERIC,
TST_IDENT,
TST_NEG_IDENT,
TST_TXT_BODY,
HAS_FMTCOLL,
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_pCurTextNd ) )
{
if( m_aFlags.bDelEmptyNode && !HasObjects( *m_pCurTextNd ) )
{
bEmptyLine = true;
sal_uLong nOldCnt = m_pDoc->GetNodes().Count();
DelEmptyLine();
// Was there really a deletion of a node?
if( nOldCnt != m_pDoc->GetNodes().Count() )
--m_aNdIdx; // do not skip the next paragraph
}
eStat = READ_NEXT_PARA;
}
else
eStat = TST_ALPHA_LINE;
break;
case TST_ALPHA_LINE:
if( IsNoAlphaLine( *m_pCurTextNd ))
{
// recognize a table definition +---+---+
if( m_aFlags.bAFormatByInput && m_aFlags.bCreateTable && DoTable() )
{
//JP 30.09.96: DoTable() builds on PopCursor and MoveCursor after AutoFormat!
pEdShell->Pop(SwCursorShell::PopMode::DeleteCurrent);
*pEdShell->GetCursor() = m_aDelPam;
pEdShell->Push();
eStat = IS_END;
break;
}
// Check for 3 "---" or "===". In this case, the previous paragraph should be
// underlined and the current be deleted!
if( !DoUnderline() && bReplaceStyles )
{
SetColl( RES_POOLCOLL_STANDARD, true );
bEmptyLine = true;
}
eStat = READ_NEXT_PARA;
}
else
eStat = GET_ALL_INFO;
break;
case GET_ALL_INFO:
{
if( m_pCurTextNd->GetNumRule() )
{
// do nothing in numbering, go to next
bEmptyLine = false;
eStat = READ_NEXT_PARA;
// delete all blanks at beginning/end and in between
//JP 29.04.98: first only "all in between"
DelMoreLinesBlanks();
break;
}
aFInfo.SetFrame( m_pCurTextFrame );
// so far: if there were templates assigned, keep these and go to next node
sal_uInt16 nPoolId = m_pCurTextNd->GetTextColl()->GetPoolFormatId();
if( IsPoolUserFormat( nPoolId )
? !m_aFlags.bChgUserColl
: ( RES_POOLCOLL_STANDARD != nPoolId &&
( !m_aFlags.bAFormatByInput ||
(RES_POOLCOLL_TEXT_MOVE != nPoolId &&
RES_POOLCOLL_TEXT != nPoolId )) ))
{
eStat = HAS_FMTCOLL;
break;
}
// check for hard spaces or LRSpaces set by the template
if( IsPoolUserFormat( nPoolId ) ||
RES_POOLCOLL_STANDARD == nPoolId )
{
short nSz;
SvxLRSpaceItem const * pLRSpace;
if( SfxItemState::SET == m_pCurTextNd->GetSwAttrSet().
GetItemState( RES_LR_SPACE, true,
reinterpret_cast<const SfxPoolItem**>(&pLRSpace) ) &&
( 0 != (nSz = pLRSpace->GetTextFirstLineOfst()) ||
0 != pLRSpace->GetTextLeft() ) )
{
// exception: numbering/enumeration can have an indentation
if( IsEnumericChar( *m_pCurTextNd ))
{
nLevel = CalcLevel( *m_pCurTextNd, &nDigitLvl );
if( nLevel >= MAXLEVEL )
nLevel = MAXLEVEL-1;
BuildEnum( nLevel, nDigitLvl );
eStat = READ_NEXT_PARA;
break;
}
// never merge (maybe only indent as exception)
m_bMoreLines = true;
if( bReplaceStyles )
{
// then use one of our templates
if( 0 < nSz ) // positive 1st line indentation
BuildIndent();
else if( 0 > nSz ) // negative 1st line indentation
BuildNegIndent( aFInfo.GetLineStart() );
else if( pLRSpace->GetTextLeft() ) // is indentation
BuildTextIndent();
}
eStat = READ_NEXT_PARA;
break;
}
}
nLevel = CalcLevel( *m_pCurTextNd, &nDigitLvl );
m_bMoreLines = !IsOneLine( *m_pCurTextNd );
pNxtNd = GetNextNode();
if( pNxtNd )
{
bNxtEmpty = IsEmptyLine( *pNxtNd );
bNxtAlpha = IsNoAlphaLine( *pNxtNd );
nNxtLevel = CalcLevel( *pNxtNd );
if( !bEmptyLine && HasBreakAttr( *m_pCurTextNd ) )
bEmptyLine = true;
if( !bNxtEmpty && HasBreakAttr( *pNxtNd ) )
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_pCurTextNd->GetText()) );
if( sClrStr.isEmpty() )
{
bEmptyLine = true;
eStat = READ_NEXT_PARA;
break; // read next paragraph
}
// check if headline
if( !bEmptyLine || !IsFirstCharCapital( *m_pCurTextNd ) ||
IsBlanksInString( *m_pCurTextNd ) )
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
|| ( pNxtNd && IsEnumericChar( *pNxtNd ))
)
{
// one level below?
if( nLevel >= MAXLEVEL )
nLevel = MAXLEVEL-1;
if( USHRT_MAX == m_nLastHeadLvl )
m_nLastHeadLvl = 0;
else if( m_nLastCalcHeadLvl < nLevel )
{
if( m_nLastHeadLvl+1 < MAXLEVEL )
++m_nLastHeadLvl;
}
// one level above?
else if( m_nLastCalcHeadLvl > nLevel )
{
if( m_nLastHeadLvl )
--m_nLastHeadLvl;
}
m_nLastCalcHeadLvl = nLevel;
if( m_aFlags.bAFormatByInput )
BuildHeadLine( nLevel );
else
BuildHeadLine( m_nLastHeadLvl );
eStat = READ_NEXT_PARA;
break;
}
}
}
break;
case TST_ENUMERIC:
{
bEmptyLine = false;
if( IsEnumericChar( *m_pCurTextNd ))
{
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 && pNxtNd && !m_bEnd &&
!bNxtEmpty && !bNxtAlpha && !nNxtLevel &&
!IsEnumericChar( *pNxtNd ) )
{
// 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 && pNxtNd && !m_bEnd &&
!bNxtEmpty && !bNxtAlpha && nNxtLevel &&
!IsEnumericChar( *pNxtNd ) )
{
// 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_pCurTextNd->HasSwAttrSet() )
{
short nSz;
SvxLRSpaceItem const * pLRSpace;
if( bReplaceStyles &&
SfxItemState::SET == m_pCurTextNd->GetSwAttrSet().
GetItemState( RES_LR_SPACE, false,
reinterpret_cast<const SfxPoolItem**>(&pLRSpace) ) &&
( 0 != (nSz = pLRSpace->GetTextFirstLineOfst()) ||
0 != pLRSpace->GetTextLeft() ) )
{
// then use one of our templates
if( 0 < nSz ) // positive 1st line indentation
BuildIndent();
else if( 0 > nSz ) // negative 1st line indentation
{
BuildNegIndent( aFInfo.GetLineStart() );
}
else if( pLRSpace->GetTextLeft() ) // is indentation
BuildTextIndent();
else
BuildText();
}
}
}
break;
case IS_END:
m_bEnd = true;
break;
}
}
if( m_aFlags.bWithRedlining )
m_pDoc->SetAutoFormatRedline( false );
m_pDoc->getIDocumentRedlineAccess().SetRedlineFlags( eOldMode );
// restore undo (in case it has been changed)
m_pDoc->GetIDocumentUndoRedo().DoUndo(bUndoState);
// disable display of percentage again
if( !m_aFlags.bAFormatByInput )
::EndProgress( m_pDoc->GetDocShell() );
}
void SwEditShell::AutoFormat( const SvxSwAutoFormatFlags* pAFlags )
{
std::unique_ptr<SwWait> pWait;
SET_CURR_SHELL( this );
StartAllAction();
StartUndo( SwUndoId::AUTOFORMAT );
SvxSwAutoFormatFlags aAFFlags; // use default values or add params?
if( pAFlags )
{
aAFFlags = *pAFlags;
if( !aAFFlags.bAFormatByInput )
pWait.reset(new SwWait( *GetDoc()->GetDocShell(), true ));
}
SwPaM* pCursor = GetCursor();
// There are more than one or a selection is open
if( pCursor->GetNext() != pCursor || pCursor->HasMark() )
{
for(SwPaM& rPaM : GetCursor()->GetRingContainer())
{
if( rPaM.HasMark() )
{
SwAutoFormat aFormat( this, aAFFlags, &(rPaM.Start()->nNode),
&(rPaM.End()->nNode) );
}
}
}
else
{
SwAutoFormat aFormat( this, aAFFlags );
}
EndUndo( SwUndoId::AUTOFORMAT );
EndAllAction();
}
void SwEditShell::AutoFormatBySplitNode()
{
SET_CURR_SHELL( this );
SwPaM* pCursor = GetCursor();
if( pCursor->IsMultiSelection() || !pCursor->Move( fnMoveBackward, GoInNode ) )
return;
StartAllAction();
StartUndo( SwUndoId::AUTOFORMAT );
bool bRange = false;
pCursor->SetMark();
SwIndex* pContent = &pCursor->GetMark()->nContent;
if( pContent->GetIndex() )
{
*pContent = 0;
bRange = true;
}
else
{
// then go one node backwards
SwNodeIndex aNdIdx( pCursor->GetMark()->nNode, -1 );
SwTextNode* pTextNd = aNdIdx.GetNode().GetTextNode();
if (pTextNd && !pTextNd->GetText().isEmpty())
{
pContent->Assign( pTextNd, 0 );
pCursor->GetMark()->nNode = aNdIdx;
bRange = true;
}
}
if( bRange )
{
Push(); // save cursor
SvxSwAutoFormatFlags aAFFlags = *GetAutoFormatFlags(); // use default values so far
SwAutoFormat aFormat( this, aAFFlags, &pCursor->GetMark()->nNode,
&pCursor->GetPoint()->nNode );
SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect();
if( pACorr && !pACorr->IsAutoCorrFlag( ACFlags::CapitalStartSentence | ACFlags::CapitalStartWord |
ACFlags::AddNonBrkSpace | ACFlags::ChgOrdinalNumber |
ACFlags::ChgToEnEmDash | ACFlags::SetINetAttr | ACFlags::Autocorrect ))
pACorr = nullptr;
if( pACorr )
AutoCorrect( *pACorr,false, u'\0' );
//JP 30.09.96: DoTable() builds on PopCursor and MoveCursor!
Pop(PopMode::DeleteCurrent);
pCursor = GetCursor();
}
pCursor->DeleteMark();
pCursor->Move( fnMoveForward, GoInNode );
EndUndo( SwUndoId::AUTOFORMAT );
EndAllAction();
}
SvxSwAutoFormatFlags* SwEditShell::GetAutoFormatFlags()
{
if (!s_pAutoFormatFlags)
s_pAutoFormatFlags = new SvxSwAutoFormatFlags;
return s_pAutoFormatFlags;
}
void SwEditShell::SetAutoFormatFlags(SvxSwAutoFormatFlags const * pFlags)
{
SvxSwAutoFormatFlags* pEditFlags = GetAutoFormatFlags();
pEditFlags->bSetNumRule = pFlags->bSetNumRule;
pEditFlags->bChgEnumNum = pFlags->bChgEnumNum;
pEditFlags->bSetBorder = pFlags->bSetBorder;
pEditFlags->bCreateTable = pFlags->bCreateTable;
pEditFlags->bReplaceStyles = pFlags->bReplaceStyles;
pEditFlags->bAFormatByInpDelSpacesAtSttEnd =
pFlags->bAFormatByInpDelSpacesAtSttEnd;
pEditFlags->bAFormatByInpDelSpacesBetweenLines =
pFlags->bAFormatByInpDelSpacesBetweenLines;
//JP 15.12.98: copy BulletChar and Font into "normal" ones
// because AutoFormat can only work with the latter!
pEditFlags->cBullet = pFlags->cByInputBullet;
pEditFlags->aBulletFont = pFlags->aByInputBulletFont;
pEditFlags->cByInputBullet = pFlags->cByInputBullet;
pEditFlags->aByInputBulletFont = pFlags->aByInputBulletFont;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V728 An excessive check can be simplified. The '(A && B) || (!A && !B)' expression is equivalent to the 'bool(A) == bool(B)' expression.
↑ V560 A part of conditional expression is always true: !m_bEnd.
↑ V560 A part of conditional expression is always true: !m_bEnd.