Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sw / source / core / edit / autofmt.cxx
blob010e0b20b80e63e82ab661fd6aa42b3ffb2526d2
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <hintids.hxx>
22 #include <osl/diagnose.h>
23 #include <unotools/charclass.hxx>
25 #include <editeng/boxitem.hxx>
26 #include <editeng/lrspitem.hxx>
27 #include <editeng/formatbreakitem.hxx>
28 #include <editeng/adjustitem.hxx>
29 #include <editeng/tstpitem.hxx>
30 #include <editeng/fontitem.hxx>
31 #include <editeng/langitem.hxx>
32 #include <editeng/acorrcfg.hxx>
33 #include <o3tl/string_view.hxx>
35 #include <swwait.hxx>
36 #include <fmtpdsc.hxx>
37 #include <doc.hxx>
38 #include <IDocumentUndoRedo.hxx>
39 #include <DocumentRedlineManager.hxx>
40 #include <IDocumentStylePoolAccess.hxx>
41 #include <redline.hxx>
42 #include <unocrsr.hxx>
43 #include <docary.hxx>
44 #include <editsh.hxx>
45 #include <contentindex.hxx>
46 #include <pam.hxx>
47 #include <swundo.hxx>
48 #include <poolfmt.hxx>
49 #include <ndtxt.hxx>
50 #include <rootfrm.hxx>
51 #include <txtfrm.hxx>
52 #include <frminf.hxx>
53 #include <pagedesc.hxx>
54 #include <paratr.hxx>
55 #include <acorrect.hxx>
56 #include <shellres.hxx>
57 #include <section.hxx>
58 #include <frmatr.hxx>
59 #include <charatr.hxx>
60 #include <mdiexp.hxx>
61 #include <strings.hrc>
62 #include <comcore.hxx>
63 #include <numrule.hxx>
64 #include <itabenum.hxx>
66 #include <memory>
67 #include <utility>
69 using namespace ::com::sun::star;
71 //JP 16.12.99: definition:
72 // from pos cPosEnDash to cPosEmDash all chars changed to em dashes,
73 // from pos cPosEmDash to cPosEnd all chars changed to em dashes
74 // all other chars are changed to the user configuration
76 const sal_Unicode pBulletChar[6] = { '+', '*', '-', 0x2013, 0x2014, 0 };
77 const int cnPosEnDash = 2, cnPosEmDash = 4;
79 const sal_Unicode cStarSymbolEnDash = 0x2013;
80 const sal_Unicode cStarSymbolEmDash = 0x2014;
82 SvxSwAutoFormatFlags* SwEditShell::s_pAutoFormatFlags = nullptr;
84 // Number of num-/bullet-paragraph templates. MAXLEVEL will soon be raised
85 // to x, but not the number of templates. (Artifact from <= 4.0)
86 const sal_uInt16 cnNumBullColls = 4;
88 class SwAutoFormat
90 SvxSwAutoFormatFlags m_aFlags;
91 SwPaM m_aDelPam; // a Pam that can be used
92 SwNodeIndex m_aNdIdx; // the index on the current TextNode
93 SwNodeIndex m_aEndNdIdx; // index on the end of the area
95 SwEditShell* m_pEditShell;
96 SwDoc* m_pDoc;
97 SwTextNode* m_pCurTextNd; // the current TextNode
98 SwTextFrame* m_pCurTextFrame; // frame of the current TextNode
99 bool m_bIsRightToLeft; // text direction of the current frame
100 SwNodeOffset m_nEndNdIdx; // for the percentage-display
101 mutable std::optional<CharClass> m_oCharClass; // Character classification
102 mutable LanguageType m_eCharClassLang;
104 sal_uInt16 m_nRedlAutoFormatSeqId;
106 enum
108 NONE = 0,
109 DELIM = 1,
110 DIGIT = 2,
111 CHG = 4,
112 LOWER_ALPHA = 8,
113 UPPER_ALPHA = 16,
114 LOWER_ROMAN = 32,
115 UPPER_ROMAN = 64,
116 NO_DELIM = (DIGIT|LOWER_ALPHA|UPPER_ALPHA|LOWER_ROMAN|UPPER_ROMAN)
119 bool m_bEnd : 1;
120 bool m_bMoreLines : 1;
122 CharClass& GetCharClass( LanguageType eLang ) const
124 if( !m_oCharClass || eLang != m_eCharClassLang )
126 m_oCharClass.emplace( LanguageTag( eLang ) );
127 m_eCharClassLang = eLang;
129 return *m_oCharClass;
132 static bool IsSpace( const sal_Unicode c )
133 { return (' ' == c || '\t' == c || 0x0a == c|| 0x3000 == c /* Jap. space */); }
135 void SetColl( sal_uInt16 nId, bool bHdLineOrText = false );
136 void GoNextPara();
137 static bool HasObjects(const SwTextFrame &);
139 // TextNode methods
140 const SwTextFrame * GetNextNode(bool isCheckEnd = true) const;
141 static bool IsEmptyLine(const SwTextFrame & rFrame)
143 return rFrame.GetText().isEmpty()
144 || rFrame.GetText().getLength() == GetLeadingBlanks(rFrame.GetText());
147 bool IsOneLine(const SwTextFrame &) const;
148 bool IsFastFullLine(const SwTextFrame &) const;
149 bool IsNoAlphaLine(const SwTextFrame &) const;
150 bool IsEnumericChar(const SwTextFrame &) const;
151 static bool IsBlanksInString(const SwTextFrame&);
152 sal_uInt16 CalcLevel(const SwTextFrame&, sal_uInt16 *pDigitLvl = nullptr) const;
153 sal_Int32 GetBigIndent(TextFrameIndex & rCurrentSpacePos) const;
155 static OUString DelLeadingBlanks(const OUString& rStr);
156 static OUString DelTrailingBlanks( const OUString& rStr );
157 static sal_Int32 GetLeadingBlanks( std::u16string_view aStr );
158 static sal_Int32 GetTrailingBlanks( std::u16string_view aStr );
160 bool IsFirstCharCapital(const SwTextFrame & rNd) const;
161 sal_uInt16 GetDigitLevel(const SwTextFrame& rFrame, TextFrameIndex& rPos,
162 OUString* pPrefix = nullptr, OUString* pPostfix = nullptr,
163 OUString* pNumTypes = nullptr ) const;
164 /// get the FORMATTED TextFrame
165 SwTextFrame* GetFrame( const SwTextNode& rTextNd ) const;
166 SwTextFrame * EnsureFormatted(SwTextFrame const&) const;
168 void BuildIndent();
169 void BuildText();
170 void BuildTextIndent();
171 void BuildEnum( sal_uInt16 nLvl, sal_uInt16 nDigitLevel );
172 void BuildNegIndent( SwTwips nSpaces );
173 void BuildHeadLine( sal_uInt16 nLvl );
175 static bool HasBreakAttr(const SwTextFrame &);
176 void DeleteSel( SwPaM& rPam );
177 void DeleteSelImpl(SwPaM & rDelPam, SwPaM & rPamToCorrect);
178 bool DeleteJoinCurNextPara(SwTextFrame const* pNextFrame, bool bIgnoreLeadingBlanks = false);
179 /// delete in the node start and/or end
180 void DeleteLeadingTrailingBlanks( bool bStart = true, bool bEnd = true );
181 void DelEmptyLine( bool bTstNextPara = true );
182 /// when using multiline paragraphs delete the "left" and/or
183 /// "right" margins
184 void DelMoreLinesBlanks( bool bWithLineBreaks = false );
185 /// join with the previous paragraph
186 void JoinPrevPara();
187 /// execute AutoCorrect on current TextNode
188 void AutoCorrect(TextFrameIndex nSttPos = TextFrameIndex(0));
190 bool CanJoin(const SwTextFrame * pNextFrame) const
192 return !m_bEnd && pNextFrame
193 && !IsEmptyLine(*pNextFrame)
194 && !IsNoAlphaLine(*pNextFrame)
195 && !IsEnumericChar(*pNextFrame)
196 // check the last / first nodes here...
197 && ((COMPLETE_STRING - 50 - pNextFrame->GetTextNodeFirst()->GetText().getLength())
198 > (m_pCurTextFrame->GetMergedPara()
199 ? m_pCurTextFrame->GetMergedPara()->pLastNode
200 : m_pCurTextNd)->GetText().getLength())
201 && !HasBreakAttr(*pNextFrame);
204 /// is a dot at the end ??
205 static bool IsSentenceAtEnd(const SwTextFrame & rTextFrame);
207 bool DoUnderline();
208 bool DoTable();
210 void SetRedlineText_( sal_uInt16 nId );
211 bool SetRedlineText( sal_uInt16 nId ) {
212 if( m_aFlags.bWithRedlining )
213 SetRedlineText_( nId );
214 return true;
216 void ClearRedlineText() {
217 if( m_aFlags.bWithRedlining )
218 m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment(nullptr);
221 public:
222 SwAutoFormat( SwEditShell* pEdShell, SvxSwAutoFormatFlags aFlags,
223 SwNode const * pSttNd = nullptr, SwNode const * pEndNd = nullptr );
226 static const sal_Unicode* StrChr( const sal_Unicode* pSrc, sal_Unicode c )
228 while( *pSrc && *pSrc != c )
229 ++pSrc;
230 return *pSrc ? pSrc : nullptr;
233 SwTextFrame* SwAutoFormat::GetFrame( const SwTextNode& rTextNd ) const
235 // get the Frame
236 const SwContentFrame *pFrame = rTextNd.getLayoutFrame( m_pEditShell->GetLayout() );
237 assert(pFrame && "For Autoformat a Layout is needed");
238 return EnsureFormatted(*static_cast<SwTextFrame const*>(pFrame));
241 SwTextFrame * SwAutoFormat::EnsureFormatted(SwTextFrame const& rFrame) const
243 SwTextFrame *const pFrame(const_cast<SwTextFrame*>(&rFrame));
244 if( m_aFlags.bAFormatByInput && !pFrame->isFrameAreaDefinitionValid() )
246 DisableCallbackAction a(*pFrame->getRootFrame());
247 SwRect aTmpFrame( pFrame->getFrameArea() );
248 SwRect aTmpPrt( pFrame->getFramePrintArea() );
249 pFrame->Calc(pFrame->getRootFrame()->GetCurrShell()->GetOut());
251 if( pFrame->getFrameArea() != aTmpFrame || pFrame->getFramePrintArea() != aTmpPrt ||
252 !pFrame->GetPaintSwRect().IsEmpty())
254 pFrame->SetCompletePaint();
258 return pFrame->GetFormatted();
261 void SwAutoFormat::SetRedlineText_( sal_uInt16 nActionId )
263 OUString sText;
264 sal_uInt16 nSeqNo = 0;
265 if( STR_AUTOFMTREDL_END > nActionId )
267 sText = SwViewShell::GetShellRes()->GetAutoFormatNameLst()[ nActionId ];
268 switch( nActionId )
270 case STR_AUTOFMTREDL_SET_NUMBER_BULLET:
271 case STR_AUTOFMTREDL_DEL_MORELINES:
273 // AutoCorrect actions
274 case STR_AUTOFMTREDL_USE_REPLACE:
275 case STR_AUTOFMTREDL_CPTL_STT_WORD:
276 case STR_AUTOFMTREDL_CPTL_STT_SENT:
277 case STR_AUTOFMTREDL_TYPO:
278 case STR_AUTOFMTREDL_UNDER:
279 case STR_AUTOFMTREDL_BOLD:
280 case STR_AUTOFMTREDL_FRACTION:
281 case STR_AUTOFMTREDL_DASH:
282 case STR_AUTOFMTREDL_ORDINAL:
283 case STR_AUTOFMTREDL_NON_BREAK_SPACE:
284 case STR_AUTOFMTREDL_TRANSLITERATE_RTL:
285 nSeqNo = ++m_nRedlAutoFormatSeqId;
286 break;
289 #if OSL_DEBUG_LEVEL > 0
290 else
291 sText = "Action text is missing";
292 #endif
294 m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment( &sText, nSeqNo );
297 void SwAutoFormat::GoNextPara()
299 SwNode* pNewNd = nullptr;
300 do {
301 // has to be checked twice before and after incrementation
302 if( m_aNdIdx.GetIndex() >= m_aEndNdIdx.GetIndex() )
304 m_bEnd = true;
305 return;
308 sw::GotoNextLayoutTextFrame(m_aNdIdx, m_pEditShell->GetLayout());
309 if( m_aNdIdx.GetIndex() >= m_aEndNdIdx.GetIndex() )
311 m_bEnd = true;
312 return;
314 else
315 pNewNd = &m_aNdIdx.GetNode();
317 // not a TextNode ->
318 // TableNode : skip table
319 // NoTextNode : skip nodes
320 // EndNode : at the end, terminate
321 if( pNewNd->IsEndNode() )
323 m_bEnd = true;
324 return;
326 else if( pNewNd->IsTableNode() )
327 m_aNdIdx = *pNewNd->EndOfSectionNode();
328 else if( pNewNd->IsSectionNode() )
330 const SwSection& rSect = pNewNd->GetSectionNode()->GetSection();
331 if( rSect.IsHiddenFlag() || rSect.IsProtectFlag() )
332 m_aNdIdx = *pNewNd->EndOfSectionNode();
334 } while( !pNewNd->IsTextNode() );
336 if( !m_aFlags.bAFormatByInput )
337 ::SetProgressState( sal_Int32(m_aNdIdx.GetIndex() + m_nEndNdIdx - m_aEndNdIdx.GetIndex()),
338 m_pDoc->GetDocShell() );
340 m_pCurTextNd = static_cast<SwTextNode*>(pNewNd);
341 m_pCurTextFrame = GetFrame( *m_pCurTextNd );
342 m_bIsRightToLeft = m_pCurTextFrame->IsRightToLeft();
345 bool SwAutoFormat::HasObjects(const SwTextFrame & rFrame)
347 // Is there something bound to the paragraph in the paragraph
348 // like Frames, DrawObjects, ...
349 SwNodeIndex node(*rFrame.GetTextNodeFirst());
352 if (!node.GetNode().GetAnchoredFlys().empty())
353 return true;
354 ++node;
356 while (sw::FrameContainsNode(rFrame, node.GetIndex()));
357 return false;
360 const SwTextFrame* SwAutoFormat::GetNextNode(bool const isCheckEnd) const
362 SwNodeIndex tmp(m_aNdIdx);
363 sw::GotoNextLayoutTextFrame(tmp, m_pEditShell->GetLayout());
364 if ((isCheckEnd && m_aEndNdIdx <= tmp) || !tmp.GetNode().IsTextNode())
365 return nullptr;
366 // note: the returned frame is not necessarily formatted, have to call
367 // EnsureFormatted for that
368 return static_cast<SwTextFrame*>(tmp.GetNode().GetTextNode()->getLayoutFrame(m_pEditShell->GetLayout()));
371 bool SwAutoFormat::IsOneLine(const SwTextFrame & rFrame) const
373 SwTextFrameInfo aFInfo( EnsureFormatted(rFrame) );
374 return aFInfo.IsOneLine();
377 bool SwAutoFormat::IsFastFullLine(const SwTextFrame & rFrame) const
379 bool bRet = m_aFlags.bRightMargin;
380 if( bRet )
382 SwTextFrameInfo aFInfo( EnsureFormatted(rFrame) );
383 bRet = aFInfo.IsFilled( m_aFlags.nRightMargin );
385 return bRet;
388 bool SwAutoFormat::IsEnumericChar(const SwTextFrame& rFrame) const
390 const OUString& rText = rFrame.GetText();
391 TextFrameIndex nBlanks(GetLeadingBlanks(rText));
392 const TextFrameIndex nLen = TextFrameIndex(rText.getLength()) - nBlanks;
393 if( !nLen )
394 return false;
396 // -, +, * separated by blank ??
397 if (TextFrameIndex(2) < nLen && IsSpace(rText[sal_Int32(nBlanks) + 1]))
399 if (StrChr(pBulletChar, rText[sal_Int32(nBlanks)]))
400 return true;
401 // Should there be a symbol font at the position?
402 SwTextFrameInfo aFInfo( EnsureFormatted(rFrame) );
403 if (aFInfo.IsBullet(nBlanks))
404 return true;
407 // 1.) / 1. / 1.1.1 / (1). / (1) / ...
408 return USHRT_MAX != GetDigitLevel(rFrame, nBlanks);
411 bool SwAutoFormat::IsBlanksInString(const SwTextFrame& rFrame)
413 // Search more than 5 consecutive blanks/tabs in the string.
414 OUString sTmp( DelLeadingBlanks(rFrame.GetText()) );
415 const sal_Int32 nLen = sTmp.getLength();
416 sal_Int32 nIdx = 0;
417 while (nIdx < nLen)
419 // Skip non-blanks
420 while (nIdx < nLen && !IsSpace(sTmp[nIdx])) ++nIdx;
421 if (nIdx == nLen)
422 return false;
423 // Then count consecutive blanks
424 const sal_Int32 nFirst = nIdx;
425 while (nIdx < nLen && IsSpace(sTmp[nIdx])) ++nIdx;
426 // And exit if enough consecutive blanks were found
427 if (nIdx-nFirst > 5)
428 return true;
430 return false;
433 sal_uInt16 SwAutoFormat::CalcLevel(const SwTextFrame & rFrame,
434 sal_uInt16 *const pDigitLvl) const
436 sal_uInt16 nLvl = 0, nBlnk = 0;
437 const OUString& rText = rFrame.GetText();
438 if( pDigitLvl )
439 *pDigitLvl = USHRT_MAX;
441 if (RES_POOLCOLL_TEXT_MOVE == rFrame.GetTextNodeForParaProps()->GetTextColl()->GetPoolFormatId())
443 if( m_aFlags.bAFormatByInput )
445 // this is very non-obvious: on the *first* invocation of
446 // AutoFormat, the node will have the tabs (any number) converted
447 // to a fixed indent in BuildTextIndent(), and the number of tabs
448 // is stored in the node;
449 // on the *second* invocation of AutoFormat, CalcLevel() will
450 // retrieve the stored number, and it will be used by
451 // BuildHeadLine() to select the corresponding heading style.
452 nLvl = rFrame.GetTextNodeForParaProps()->GetAutoFormatLvl();
453 const_cast<SwTextNode *>(rFrame.GetTextNodeForParaProps())->SetAutoFormatLvl(0);
454 if( nLvl )
455 return nLvl;
457 ++nLvl;
460 for (TextFrameIndex n(0),
461 nEnd(rText.getLength()); n < nEnd; ++n)
463 switch (rText[sal_Int32(n)])
465 case ' ': if( 3 == ++nBlnk )
467 ++nLvl;
468 nBlnk = 0;
470 break;
471 case '\t': ++nLvl;
472 nBlnk = 0;
473 break;
474 default:
475 if( pDigitLvl )
476 // test 1.) / 1. / 1.1.1 / (1). / (1) / ...
477 *pDigitLvl = GetDigitLevel(rFrame, n);
478 return nLvl;
481 return nLvl;
484 sal_Int32 SwAutoFormat::GetBigIndent(TextFrameIndex & rCurrentSpacePos) const
486 SwTextFrameInfo aFInfo( m_pCurTextFrame );
487 const SwTextFrame* pNextFrame = nullptr;
489 if( !m_bMoreLines )
491 pNextFrame = GetNextNode();
492 if (!CanJoin(pNextFrame) || !IsOneLine(*pNextFrame))
493 return 0;
495 pNextFrame = EnsureFormatted(*pNextFrame);
498 return aFInfo.GetBigIndent( rCurrentSpacePos, pNextFrame );
501 bool SwAutoFormat::IsNoAlphaLine(const SwTextFrame & rFrame) const
503 const OUString& rStr = rFrame.GetText();
504 if( rStr.isEmpty() )
505 return false;
506 // or better: determine via number of AlphaNum and !AlphaNum characters
507 sal_Int32 nANChar = 0, nBlnk = 0;
509 for (TextFrameIndex n(0),
510 nEnd(rStr.getLength()); n < nEnd; ++n)
511 if (IsSpace(rStr[sal_Int32(n)]))
512 ++nBlnk;
513 else
515 auto const pair = rFrame.MapViewToModel(n);
516 CharClass& rCC = GetCharClass(pair.first->GetSwAttrSet().GetLanguage().GetLanguage());
517 if (rCC.isLetterNumeric(rStr, sal_Int32(n)))
518 ++nANChar;
521 // If there are 75% of non-alphanumeric characters, then true
522 sal_uLong nLen = rStr.getLength() - nBlnk;
523 nLen = ( nLen * 3 ) / 4; // long overflow, if the strlen > sal_uInt16
524 return sal_Int32(nLen) < (rStr.getLength() - nANChar - nBlnk);
527 bool SwAutoFormat::DoUnderline()
529 if( !m_aFlags.bSetBorder )
530 return false;
532 OUString const& rText(m_pCurTextFrame->GetText());
533 int eState = 0;
534 sal_Int32 nCnt = 0;
535 while (nCnt < rText.getLength())
537 int eTmp = 0;
538 switch (rText[nCnt])
540 case '-': eTmp = 1; break;
541 case '_': eTmp = 2; break;
542 case '=': eTmp = 3; break;
543 case '*': eTmp = 4; break;
544 case '~': eTmp = 5; break;
545 case '#': eTmp = 6; break;
546 default:
547 return false;
549 if( 0 == eState )
550 eState = eTmp;
551 else if( eState != eTmp )
552 return false;
553 ++nCnt;
556 if( 2 < nCnt )
558 // then underline the previous paragraph if one exists
559 DelEmptyLine( false ); // -> point will be on end of current paragraph
560 // WARNING: rText may be deleted now, m_pCurTextFrame may be nullptr
561 m_aDelPam.SetMark();
562 // apply to last node & rely on InsertItemSet to apply it to props-node
564 editeng::SvxBorderLine aLine;
565 switch( eState )
567 case 1: // single, hairline
568 aLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID);
569 aLine.SetWidth( SvxBorderLineWidth::Hairline );
570 break;
571 case 2: // single, thin
572 aLine.SetBorderLineStyle(SvxBorderLineStyle::SOLID);
573 aLine.SetWidth( SvxBorderLineWidth::Thin );
574 break;
575 case 3: // double, thin
576 aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
577 aLine.SetWidth( SvxBorderLineWidth::Thin );
578 break;
579 case 4: // double, thick/thin
580 aLine.SetBorderLineStyle(SvxBorderLineStyle::THICKTHIN_SMALLGAP);
581 aLine.SetWidth( SvxBorderLineWidth::Thick );
582 break;
583 case 5: // double, thin/thick
584 aLine.SetBorderLineStyle(SvxBorderLineStyle::THINTHICK_SMALLGAP);
585 aLine.SetWidth( SvxBorderLineWidth::Thick );
586 break;
587 case 6: // double, medium
588 aLine.SetBorderLineStyle(SvxBorderLineStyle::DOUBLE);
589 aLine.SetWidth( SvxBorderLineWidth::Medium );
590 break;
592 SfxItemSetFixed<RES_PARATR_CONNECT_BORDER, RES_PARATR_CONNECT_BORDER,
593 RES_BOX, RES_BOX> aSet(m_pDoc->GetAttrPool());
594 aSet.Put( SwParaConnectBorderItem( false ) );
595 SvxBoxItem aBox( RES_BOX );
596 aBox.SetLine( &aLine, SvxBoxItemLine::BOTTOM );
597 aBox.SetDistance(42, SvxBoxItemLine::BOTTOM ); // ~0,75 mm
598 aSet.Put(aBox);
599 m_pDoc->getIDocumentContentOperations().InsertItemSet(m_aDelPam, aSet,
600 SetAttrMode::DEFAULT, m_pEditShell->GetLayout());
602 m_aDelPam.DeleteMark();
604 return 2 < nCnt;
607 bool SwAutoFormat::DoTable()
609 if( !m_aFlags.bCreateTable || !m_aFlags.bAFormatByInput ||
610 m_pCurTextNd->FindTableNode() )
611 return false;
613 const OUString& rTmp = m_pCurTextFrame->GetText();
614 TextFrameIndex nSttPlus(GetLeadingBlanks(rTmp));
615 TextFrameIndex nEndPlus(GetTrailingBlanks(rTmp));
616 sal_Unicode cChar;
618 if (TextFrameIndex(2) > nEndPlus - nSttPlus
619 || ('+' != (cChar = rTmp[sal_Int32(nSttPlus)]) && '|' != cChar)
620 || ('+' != (cChar = rTmp[sal_Int32(nEndPlus) - 1]) && '|' != cChar))
621 return false;
623 SwTextFrameInfo aInfo( m_pCurTextFrame );
625 TextFrameIndex n = nSttPlus;
626 std::vector<sal_uInt16> aPosArr;
628 while (n < TextFrameIndex(rTmp.getLength()))
630 switch (rTmp[sal_Int32(n)])
632 case '-':
633 case '_':
634 case '=':
635 case ' ':
636 case '\t':
637 break;
639 case '+':
640 case '|':
641 aPosArr.push_back( o3tl::narrowing<sal_uInt16>(aInfo.GetCharPos(n)) );
642 break;
644 default:
645 return false;
647 if( ++n == nEndPlus )
648 break;
651 if( 1 < aPosArr.size() )
653 // get the text node's alignment
654 sal_uInt16 nColCnt = aPosArr.size() - 1;
655 SwTwips nSttPos = aPosArr[ 0 ];
656 sal_Int16 eHori;
657 switch (m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust().GetAdjust())
659 case SvxAdjust::Center: eHori = text::HoriOrientation::CENTER; break;
660 case SvxAdjust::Right: eHori = text::HoriOrientation::RIGHT; break;
662 default:
663 if( nSttPos )
665 eHori = text::HoriOrientation::NONE;
666 // then - as last - we need to add the current frame width into the array
667 aPosArr.push_back( o3tl::narrowing<sal_uInt16>(m_pCurTextFrame->getFrameArea().Width()) );
669 else
670 eHori = text::HoriOrientation::LEFT;
671 break;
674 // then create a table that matches the character
675 DelEmptyLine();
676 // WARNING: rTmp may be deleted now, m_pCurTextFrame may be nullptr
677 SwNodeIndex aIdx( m_aDelPam.GetPoint()->GetNode() );
678 m_aDelPam.Move( fnMoveForward );
679 m_pDoc->InsertTable( SwInsertTableOptions( SwInsertTableFlags::All , 1 ),
680 *m_aDelPam.GetPoint(), 1, nColCnt, eHori,
681 nullptr, &aPosArr );
682 m_aDelPam.GetPoint()->Assign(aIdx);
684 return 1 < aPosArr.size();
687 OUString SwAutoFormat::DelLeadingBlanks( const OUString& rStr )
689 sal_Int32 nL, n;
690 for( nL = rStr.getLength(), n = 0; n < nL && IsSpace( rStr[n] ); ++n )
692 if( n ) // no Spaces
693 return rStr.copy(n);
694 return rStr;
697 OUString SwAutoFormat::DelTrailingBlanks( const OUString& rStr )
699 sal_Int32 nL = rStr.getLength(), n = nL;
700 if( !nL )
701 return rStr;
703 while( --n && IsSpace( rStr[ n ] ) )
705 if( n+1 != nL ) // no Spaces
706 return rStr.copy( 0, n+1 );
707 return rStr;
710 sal_Int32 SwAutoFormat::GetLeadingBlanks( std::u16string_view aStr )
712 size_t nL;
713 size_t n;
715 for( nL = aStr.size(), n = 0; n < nL && IsSpace( aStr[ n ] ); ++n )
717 return n;
720 sal_Int32 SwAutoFormat::GetTrailingBlanks( std::u16string_view aStr )
722 size_t nL = aStr.size(), n = nL;
723 if( !nL )
724 return 0;
726 while( --n && IsSpace( aStr[ n ] ) )
728 return ++n;
731 bool SwAutoFormat::IsFirstCharCapital(const SwTextFrame& rFrame) const
733 const OUString& rText = rFrame.GetText();
734 for (TextFrameIndex n(0),
735 nEnd(rText.getLength()); n < nEnd; ++n)
736 if (!IsSpace(rText[sal_Int32(n)]))
738 auto const pair = rFrame.MapViewToModel(n);
739 CharClass& rCC = GetCharClass( pair.first->GetSwAttrSet().
740 GetLanguage().GetLanguage() );
741 sal_Int32 nCharType = rCC.getCharacterType(rText, sal_Int32(n));
742 return CharClass::isLetterType( nCharType ) &&
743 0 != ( i18n::KCharacterType::UPPER &
744 nCharType );
746 return false;
749 sal_uInt16
750 SwAutoFormat::GetDigitLevel(const SwTextFrame& rFrame, TextFrameIndex& rPos,
751 OUString* pPrefix, OUString* pPostfix, OUString* pNumTypes ) const
754 // check for 1.) / 1. / 1.1.1 / (1). / (1) / ...
755 const OUString& rText = rFrame.GetText();
756 sal_Int32 nPos(rPos);
757 int eScan = NONE;
759 sal_uInt16 nStart = 0;
760 sal_uInt8 nDigitLvl = 0, nDigitCnt = 0;
761 // count number of parenthesis to assure a sensible order is found
762 sal_uInt16 nOpeningParentheses = 0;
763 sal_uInt16 nClosingParentheses = 0;
765 while (nPos < rText.getLength() && nDigitLvl < MAXLEVEL - 1)
767 auto const pair = rFrame.MapViewToModel(TextFrameIndex(nPos));
768 CharClass& rCC = GetCharClass(pair.first->GetSwAttrSet().GetLanguage().GetLanguage());
769 const sal_Unicode cCurrentChar = rText[nPos];
770 if( ('0' <= cCurrentChar && '9' >= cCurrentChar) ||
771 (0xff10 <= cCurrentChar && 0xff19 >= cCurrentChar) )
773 if( eScan & DELIM )
775 if( eScan & CHG ) // not if it starts with a number
777 ++nDigitLvl;
778 if( pPostfix )
779 *pPostfix += "\x01";
782 if( pNumTypes )
783 *pNumTypes += OUStringChar(sal_Unicode('0' + SVX_NUM_ARABIC));
785 eScan = eScan | CHG;
787 else if( pNumTypes && !(eScan & DIGIT) )
788 *pNumTypes += OUStringChar(sal_Unicode('0' + SVX_NUM_ARABIC));
790 eScan &= ~DELIM; // remove Delim
791 if( 0 != (eScan & ~CHG) && DIGIT != (eScan & ~CHG))
792 return USHRT_MAX;
794 eScan |= DIGIT; // add Digit
795 if( 3 == ++nDigitCnt ) // more than 2 numbers are not an enum anymore
796 return USHRT_MAX;
798 nStart *= 10;
799 nStart += cCurrentChar <= '9' ? cCurrentChar - '0' : cCurrentChar - 0xff10;
801 else if( rCC.isAlpha( rText, nPos ) )
803 bool bIsUpper =
804 0 != ( i18n::KCharacterType::UPPER &
805 rCC.getCharacterType( rText, nPos ));
806 sal_Unicode cLow = rCC.lowercase(rText, nPos, 1)[0], cNumTyp;
807 int eTmpScan;
809 // Roman numbers are "mdclxvi". Since we want to start numbering with c or d more often,
810 // convert first to characters and later to roman numbers if needed.
811 if( 256 > cLow && strchr( "mdclxvi", cLow ) )
813 if( bIsUpper )
815 cNumTyp = '0' + SVX_NUM_ROMAN_UPPER;
816 eTmpScan = UPPER_ROMAN;
818 else
820 cNumTyp = '0' + SVX_NUM_ROMAN_LOWER;
821 eTmpScan = LOWER_ROMAN;
824 else if( bIsUpper )
826 cNumTyp = '0' + SVX_NUM_CHARS_UPPER_LETTER;
827 eTmpScan = UPPER_ALPHA;
829 else
831 cNumTyp = '0' + SVX_NUM_CHARS_LOWER_LETTER;
832 eTmpScan = LOWER_ALPHA;
835 // Switch to roman numbers (only for c/d!)
836 if( 1 == nDigitCnt && ( eScan & (UPPER_ALPHA|LOWER_ALPHA) ) &&
837 ( 3 == nStart || 4 == nStart) && 256 > cLow &&
838 strchr( "mdclxvi", cLow ) &&
839 (( eScan & UPPER_ALPHA ) ? (eTmpScan & (UPPER_ALPHA|UPPER_ROMAN))
840 : (eTmpScan & (LOWER_ALPHA|LOWER_ROMAN))) )
842 sal_Unicode c = '0';
843 nStart = 3 == nStart ? 100 : 500;
844 if( UPPER_ALPHA == eTmpScan )
846 eTmpScan = UPPER_ROMAN;
847 c += SVX_NUM_ROMAN_UPPER;
849 else
851 eTmpScan = LOWER_ROMAN;
852 c += SVX_NUM_ROMAN_LOWER;
855 eScan = (eScan & ~(UPPER_ALPHA|LOWER_ALPHA)) | eTmpScan;
856 if( pNumTypes )
857 (*pNumTypes) = pNumTypes->replaceAt( pNumTypes->getLength() - 1, 1, rtl::OUStringChar(c) );
860 if( eScan & DELIM )
862 if( eScan & CHG ) // not if it starts with a number
864 ++nDigitLvl;
865 if( pPostfix )
866 *pPostfix += "\x01";
869 if( pNumTypes )
870 *pNumTypes += OUStringChar(cNumTyp);
871 eScan = eScan | CHG;
873 else if( pNumTypes && !(eScan & eTmpScan) )
874 *pNumTypes += OUStringChar(cNumTyp);
876 eScan &= ~DELIM; // remove Delim
878 // if another type is set, stop here
879 if( 0 != ( eScan & ~CHG ) && eTmpScan != ( eScan & ~CHG ))
880 return USHRT_MAX;
882 if( eTmpScan & (UPPER_ALPHA | LOWER_ALPHA) )
884 // allow characters only if they appear once
885 return USHRT_MAX;
887 else
889 // roman numbers, check if valid characters
890 sal_uInt16 nVal;
891 bool bError = false;
892 switch( cLow )
894 case 'm': nVal = 1000; goto CHECK_ROMAN_1;
895 case 'd': nVal = 500; goto CHECK_ROMAN_5;
896 case 'c': nVal = 100; goto CHECK_ROMAN_1;
897 case 'l': nVal = 50; goto CHECK_ROMAN_5;
898 case 'x': nVal = 10; goto CHECK_ROMAN_1;
899 case 'v': nVal = 5; goto CHECK_ROMAN_5;
901 CHECK_ROMAN_1:
903 int nMod5 = nStart % (nVal * 5);
904 int nLast = nStart % nVal;
905 int n10 = nVal / 10;
907 if( nMod5 == ((3 * nVal) + n10 ) ||
908 nMod5 == ((4 * nVal) + n10 ) ||
909 nLast == n10 )
910 nStart = o3tl::narrowing<sal_uInt16>(nStart + (n10 * 8));
911 else if( nMod5 == 0 ||
912 nMod5 == (1 * nVal) ||
913 nMod5 == (2 * nVal) )
914 nStart = nStart + nVal;
915 else
916 bError = true;
918 break;
920 CHECK_ROMAN_5:
922 if( ( nStart / nVal ) & 1 )
923 bError = true;
924 else
926 int nMod = nStart % nVal;
927 int n10 = nVal / 5;
928 if( n10 == nMod )
929 nStart = o3tl::narrowing<sal_uInt16>(nStart + (3 * n10));
930 else if( 0 == nMod )
931 nStart = nStart + nVal;
932 else
933 bError = true;
936 break;
938 case 'i':
939 if( nStart % 5 >= 3 )
940 bError = true;
941 else
942 nStart += 1;
943 break;
945 default:
946 bError = true;
949 if( bError )
950 return USHRT_MAX;
952 eScan |= eTmpScan; // add Digit
953 ++nDigitCnt;
955 else if( (256 > cCurrentChar &&
956 strchr( ".)(", cCurrentChar )) ||
957 0x3002 == cCurrentChar /* Chinese trad. dot */||
958 0xff0e == cCurrentChar /* Japanese dot */||
959 0xFF08 == cCurrentChar /* opening bracket Chin./Jap.*/||
960 0xFF09 == cCurrentChar )/* closing bracket Chin./Jap. */
962 if(cCurrentChar == '(' || cCurrentChar == 0xFF09)
963 nOpeningParentheses++;
964 else if(cCurrentChar == ')'|| cCurrentChar == 0xFF08)
965 nClosingParentheses++;
966 // only if no numbers were read until here
967 if( pPrefix && !( eScan & ( NO_DELIM | CHG )) )
968 *pPrefix += OUStringChar(rText[nPos]);
969 else if( pPostfix )
970 *pPostfix += OUStringChar(rText[nPos]);
972 if( NO_DELIM & eScan )
974 eScan |= CHG;
975 if( pPrefix )
976 *pPrefix += "\x01" + OUString::number( nStart );
978 eScan &= ~NO_DELIM; // remove Delim
979 eScan |= DELIM; // add Digit
980 nDigitCnt = 0;
981 nStart = 0;
983 else
984 break;
985 ++nPos;
987 if (!( CHG & eScan ) || rPos == TextFrameIndex(nPos) ||
988 nPos == rText.getLength() || !IsSpace(rText[nPos]) ||
989 (nOpeningParentheses > nClosingParentheses))
990 return USHRT_MAX;
992 if( (NO_DELIM & eScan) && pPrefix ) // do not forget the last one
993 *pPrefix += "\x01" + OUString::number( nStart );
995 rPos = TextFrameIndex(nPos);
996 return nDigitLvl; // 0 .. 9 (MAXLEVEL - 1)
999 void SwAutoFormat::SetColl( sal_uInt16 nId, bool bHdLineOrText )
1001 m_aDelPam.DeleteMark();
1002 m_aDelPam.GetPoint()->Assign( *m_pCurTextFrame->GetTextNodeForParaProps() );
1004 // keep hard tabs, alignment, language, hyphenation, DropCaps and nearly all frame attributes
1005 SfxItemSetFixed<
1006 RES_CHRATR_LANGUAGE, RES_CHRATR_LANGUAGE,
1007 RES_PARATR_ADJUST, RES_PARATR_ADJUST,
1008 RES_PARATR_TABSTOP, RES_PARATR_DROP,
1009 RES_BACKGROUND, RES_SHADOW> aSet(m_pDoc->GetAttrPool());
1011 if (m_aDelPam.GetPoint()->GetNode().GetTextNode()->HasSwAttrSet())
1013 aSet.Put(*m_aDelPam.GetPoint()->GetNode().GetTextNode()->GetpSwAttrSet());
1014 // take HeaderLine/TextBody only if centered or right aligned, otherwise only justification
1015 if( SvxAdjustItem const * pAdj = aSet.GetItemIfSet( RES_PARATR_ADJUST, false) )
1017 SvxAdjust eAdj = pAdj->GetAdjust();
1018 if( bHdLineOrText ? (SvxAdjust::Right != eAdj &&
1019 SvxAdjust::Center != eAdj)
1020 : SvxAdjust::Block != eAdj )
1021 aSet.ClearItem( RES_PARATR_ADJUST );
1025 m_pDoc->SetTextFormatCollByAutoFormat( *m_aDelPam.GetPoint(), nId, &aSet );
1028 static bool HasSelBlanks(
1029 SwTextFrame const*const pStartFrame, TextFrameIndex & rStartIndex,
1030 SwTextFrame const*const pEndFrame, TextFrameIndex & rEndIndex)
1032 if (TextFrameIndex(0) < rEndIndex
1033 && rEndIndex < TextFrameIndex(pEndFrame->GetText().getLength())
1034 && ' ' == pEndFrame->GetText()[sal_Int32(rEndIndex) - 1])
1036 --rEndIndex;
1037 return true;
1039 if (rStartIndex < TextFrameIndex(pStartFrame->GetText().getLength())
1040 && ' ' == pStartFrame->GetText()[sal_Int32(rStartIndex)])
1042 ++rStartIndex;
1043 return true;
1045 return false;
1048 bool SwAutoFormat::HasBreakAttr(const SwTextFrame& rTextFrame)
1050 const SfxItemSet *const pSet = rTextFrame.GetTextNodeFirst()->GetpSwAttrSet();
1051 if( !pSet )
1052 return false;
1054 const SvxFormatBreakItem* pBreakItem = pSet->GetItemIfSet( RES_BREAK, false );
1055 if( pBreakItem && SvxBreak::NONE != pBreakItem->GetBreak() )
1056 return true;
1058 const SwFormatPageDesc* pItem = pSet->GetItemIfSet( RES_PAGEDESC, false );
1059 if( pItem && pItem->GetPageDesc()
1060 && UseOnPage::NONE != pItem->GetPageDesc()->GetUseOn() )
1061 return true;
1062 return false;
1065 /// Is there a dot at the end?
1066 bool SwAutoFormat::IsSentenceAtEnd(const SwTextFrame & rTextFrame)
1068 const OUString& rStr = rTextFrame.GetText();
1069 sal_Int32 n = rStr.getLength();
1070 if( !n )
1071 return true;
1073 while( --n && IsSpace( rStr[ n ] ) )
1075 return '.' == rStr[ n ];
1078 /// Delete beginning and/or end in a node
1079 void SwAutoFormat::DeleteLeadingTrailingBlanks(bool bStart, bool bEnd)
1081 if( !(m_aFlags.bAFormatByInput
1082 ? m_aFlags.bAFormatByInpDelSpacesAtSttEnd
1083 : m_aFlags.bAFormatDelSpacesAtSttEnd) )
1084 return;
1086 // delete blanks at the end of the current and at the beginning of the next one
1087 m_aDelPam.DeleteMark();
1088 TextFrameIndex nPos(GetLeadingBlanks(m_pCurTextFrame->GetText()));
1089 if (bStart && TextFrameIndex(0) != nPos)
1091 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0));
1092 m_aDelPam.SetMark();
1093 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
1094 DeleteSel( m_aDelPam );
1095 m_aDelPam.DeleteMark();
1097 nPos = TextFrameIndex(GetTrailingBlanks(m_pCurTextFrame->GetText()));
1098 if (bEnd && TextFrameIndex(m_pCurTextFrame->GetText().getLength()) != nPos)
1100 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(
1101 TextFrameIndex(m_pCurTextFrame->GetText().getLength()));
1102 m_aDelPam.SetMark();
1103 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
1104 DeleteSel( m_aDelPam );
1105 m_aDelPam.DeleteMark();
1109 namespace sw {
1111 bool GetRanges(std::vector<std::shared_ptr<SwUnoCursor>> & rRanges,
1112 SwDoc & rDoc, SwPaM const& rDelPam)
1114 bool isNoRedline(true);
1115 SwRedlineTable::size_type tmp;
1116 IDocumentRedlineAccess const& rIDRA(rDoc.getIDocumentRedlineAccess());
1117 if (!(rIDRA.GetRedlineFlags() & RedlineFlags::ShowDelete))
1119 return isNoRedline;
1121 rIDRA.GetRedline(*rDelPam.Start(), &tmp);
1122 SwPosition const* pCurrent(rDelPam.Start());
1123 for ( ; tmp < rIDRA.GetRedlineTable().size(); ++tmp)
1125 SwRangeRedline const*const pRedline(rIDRA.GetRedlineTable()[tmp]);
1126 if (*rDelPam.End() <= *pRedline->Start())
1128 break;
1130 if (*pRedline->End() <= *rDelPam.Start())
1132 continue;
1134 if (pRedline->GetType() == RedlineType::Delete)
1136 assert(*pRedline->Start() != *pRedline->End());
1137 isNoRedline = false;
1138 if (*pCurrent < *pRedline->Start())
1140 rRanges.push_back(rDoc.CreateUnoCursor(*pCurrent));
1141 rRanges.back()->SetMark();
1142 *rRanges.back()->GetPoint() = *pRedline->Start();
1144 pCurrent = pRedline->End();
1147 if (!isNoRedline && *pCurrent < *rDelPam.End())
1149 rRanges.push_back(rDoc.CreateUnoCursor(*pCurrent));
1150 rRanges.back()->SetMark();
1151 *rRanges.back()->GetPoint() = *rDelPam.End();
1153 return isNoRedline;
1156 } // namespace sw
1158 void SwAutoFormat::DeleteSel(SwPaM & rDelPam)
1160 std::vector<std::shared_ptr<SwUnoCursor>> ranges; // need correcting cursor
1161 if (GetRanges(ranges, *m_pDoc, rDelPam))
1163 DeleteSelImpl(rDelPam, rDelPam);
1165 else
1167 for (auto const& pCursor : ranges)
1169 DeleteSelImpl(*pCursor, rDelPam);
1174 void SwAutoFormat::DeleteSelImpl(SwPaM & rDelPam, SwPaM & rPamToCorrect)
1176 if (m_aFlags.bWithRedlining || &rDelPam != &rPamToCorrect)
1178 // Add to Shell-Cursor-Ring so that DelPam will be moved as well!
1179 SwPaM* pShCursor = m_pEditShell->GetCursor_();
1180 SwPaM aTmp( *m_pCurTextNd, 0, pShCursor );
1182 SwPaM* pPrev = rPamToCorrect.GetPrev();
1183 rPamToCorrect.GetRingContainer().merge( pShCursor->GetRingContainer() );
1185 m_pEditShell->DeleteSel(rDelPam, true);
1187 // and remove Pam again:
1188 SwPaM* p;
1189 SwPaM* pNext = &rPamToCorrect;
1190 do {
1191 p = pNext;
1192 pNext = p->GetNext();
1193 p->MoveTo( &rPamToCorrect );
1194 } while( p != pPrev );
1196 m_aNdIdx = aTmp.GetPoint()->GetNode();
1197 m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
1198 m_pCurTextFrame = GetFrame(*m_pCurTextNd); // keep it up to date
1200 else
1201 m_pEditShell->DeleteSel(rDelPam, true);
1204 bool SwAutoFormat::DeleteJoinCurNextPara(SwTextFrame const*const pNextFrame,
1205 bool const bIgnoreLeadingBlanks)
1207 // delete blanks at the end of the current and at the beginning of the next one
1208 m_aDelPam.DeleteMark();
1209 TextFrameIndex nTrailingPos(GetTrailingBlanks(m_pCurTextFrame->GetText()));
1211 SwTextFrame const*const pEndFrame(pNextFrame ? pNextFrame : m_pCurTextFrame);
1212 TextFrameIndex nLeadingPos(0);
1213 if (pNextFrame)
1215 nLeadingPos = TextFrameIndex(
1216 bIgnoreLeadingBlanks ? 0 : GetLeadingBlanks(pNextFrame->GetText()));
1218 else
1220 nLeadingPos = TextFrameIndex(m_pCurTextFrame->GetText().getLength());
1223 // Is there a Blank at the beginning or end?
1224 // Do not delete it, it will be inserted again.
1225 bool bHasBlnks = HasSelBlanks(m_pCurTextFrame, nTrailingPos, pEndFrame, nLeadingPos);
1227 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nTrailingPos);
1228 m_aDelPam.SetMark();
1229 *m_aDelPam.GetPoint() = pEndFrame->MapViewToModelPos(nLeadingPos);
1231 if( *m_aDelPam.GetPoint() != *m_aDelPam.GetMark() )
1232 DeleteSel( m_aDelPam );
1233 m_aDelPam.DeleteMark();
1234 // note: keep m_aDelPam point at insert pos. for clients
1236 return !bHasBlnks;
1239 void SwAutoFormat::DelEmptyLine( bool bTstNextPara )
1241 SetRedlineText( STR_AUTOFMTREDL_DEL_EMPTY_PARA );
1242 // delete blanks in empty paragraph
1243 m_aDelPam.DeleteMark();
1244 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(
1245 TextFrameIndex(0));
1246 m_aDelPam.SetMark();
1248 m_aDelPam.GetMark()->Assign( m_pCurTextFrame->GetTextNodeFirst()->GetIndex() - 1 );
1249 SwTextNode* pTNd = m_aDelPam.GetMarkNode().GetTextNode();
1250 if( pTNd )
1251 // first use the previous text node
1252 m_aDelPam.GetMark()->SetContent(pTNd->GetText().getLength());
1253 else if( bTstNextPara )
1255 // then try the next (at the beginning of a Doc, table cells, frames, ...)
1256 const SwTextNode* pNext = m_pCurTextFrame->GetMergedPara()
1257 ? m_pCurTextFrame->GetMergedPara()->pLastNode
1258 : m_pCurTextNd;
1259 m_aDelPam.GetMark()->Assign(pNext->GetIndex() + 1);
1260 pTNd = m_aDelPam.GetMarkNode().GetTextNode();
1261 if( pTNd )
1263 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(
1264 TextFrameIndex(m_pCurTextFrame->GetText().getLength()));
1267 if( pTNd )
1268 { // join with previous or next paragraph
1269 DeleteSel(m_aDelPam);
1271 assert(m_aDelPam.GetPointNode().IsTextNode());
1272 assert(!m_aDelPam.HasMark());
1273 m_aDelPam.SetMark(); // mark remains at join position
1274 m_pCurTextFrame = GetFrame(*m_aDelPam.GetPointNode().GetTextNode());
1275 // replace until the end of the merged paragraph
1276 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(
1277 TextFrameIndex(m_pCurTextFrame->GetText().getLength()));
1278 if (*m_aDelPam.GetPoint() != *m_aDelPam.GetMark())
1279 { // tdf#137245 replace (not delete) to preserve any flys
1280 m_pDoc->getIDocumentContentOperations().ReplaceRange(m_aDelPam, "", false);
1283 m_aDelPam.DeleteMark();
1284 ClearRedlineText();
1285 // note: this likely has deleted m_pCurTextFrame - update it...
1286 m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
1287 m_pCurTextFrame = m_pCurTextNd ? GetFrame( *m_pCurTextNd ) : nullptr;
1290 void SwAutoFormat::DelMoreLinesBlanks( bool bWithLineBreaks )
1292 if( !(m_aFlags.bAFormatByInput
1293 ? m_aFlags.bAFormatByInpDelSpacesBetweenLines
1294 : m_aFlags.bAFormatDelSpacesBetweenLines) )
1295 return;
1297 // delete all blanks on the left and right of the indentation
1298 m_aDelPam.DeleteMark();
1300 SwTextFrameInfo aFInfo( m_pCurTextFrame );
1301 std::vector<std::pair<TextFrameIndex, TextFrameIndex>> spaces;
1302 aFInfo.GetSpaces(spaces, !m_aFlags.bAFormatByInput || bWithLineBreaks);
1304 // tdf#123285 iterate backwards - delete invalidates following indexes
1305 for (auto iter = spaces.rbegin(); iter != spaces.rend(); ++iter)
1307 auto & rSpaceRange(*iter);
1308 assert(rSpaceRange.first != rSpaceRange.second);
1309 bool const bHasBlanks = HasSelBlanks(
1310 m_pCurTextFrame, rSpaceRange.first,
1311 m_pCurTextFrame, rSpaceRange.second);
1312 if (rSpaceRange.first != rSpaceRange.second)
1314 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(rSpaceRange.first);
1315 m_aDelPam.SetMark();
1316 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(rSpaceRange.second);
1317 DeleteSel(m_aDelPam);
1318 if (!bHasBlanks)
1320 m_pDoc->getIDocumentContentOperations().InsertString(m_aDelPam, OUString(' '));
1322 m_aDelPam.DeleteMark();
1327 void SwAutoFormat::JoinPrevPara()
1329 m_aDelPam.DeleteMark();
1330 m_aDelPam.GetPoint()->Assign( *m_pCurTextFrame->GetTextNodeFirst() );
1331 m_aDelPam.SetMark();
1333 m_aDelPam.GetPoint()->Adjust(SwNodeOffset(-1));
1334 SwTextNode* pTNd = m_aDelPam.GetPointNode().GetTextNode();
1335 if( pTNd )
1337 // use the previous text node first
1338 m_aDelPam.GetPoint()->SetContent(pTNd->GetText().getLength());
1339 DeleteSel( m_aDelPam );
1341 m_aDelPam.DeleteMark();
1344 void SwAutoFormat::BuildIndent()
1346 SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_INDENT );
1348 // read all succeeding paragraphs that belong to this indentation
1349 bool bBreak = true;
1350 if( m_bMoreLines )
1351 DelMoreLinesBlanks( true );
1352 else
1353 bBreak = !IsFastFullLine(*m_pCurTextFrame)
1354 || IsBlanksInString(*m_pCurTextFrame)
1355 || IsSentenceAtEnd(*m_pCurTextFrame);
1356 SetColl( RES_POOLCOLL_TEXT_IDENT );
1357 if( !bBreak )
1359 SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
1360 const SwTextFrame * pNextFrame = GetNextNode();
1361 if (pNextFrame && !m_bEnd)
1363 do {
1364 bBreak = !IsFastFullLine(*pNextFrame)
1365 || IsBlanksInString(*pNextFrame)
1366 || IsSentenceAtEnd(*pNextFrame);
1367 if (DeleteJoinCurNextPara(pNextFrame))
1369 m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
1371 if( bBreak )
1372 break;
1373 pNextFrame = GetNextNode();
1375 while (CanJoin(pNextFrame)
1376 && !CalcLevel(*pNextFrame));
1379 DeleteLeadingTrailingBlanks();
1380 AutoCorrect();
1383 void SwAutoFormat::BuildTextIndent()
1385 SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_TEXT_INDENT);
1386 // read all succeeding paragraphs that belong to this indentation
1387 bool bBreak = true;
1388 if( m_bMoreLines )
1389 DelMoreLinesBlanks( true );
1390 else
1391 bBreak = !IsFastFullLine(*m_pCurTextFrame)
1392 || IsBlanksInString(*m_pCurTextFrame)
1393 || IsSentenceAtEnd(*m_pCurTextFrame);
1395 if( m_aFlags.bAFormatByInput )
1397 const_cast<SwTextNode*>(m_pCurTextFrame->GetTextNodeForParaProps())->SetAutoFormatLvl(
1398 static_cast<sal_uInt8>(CalcLevel(*m_pCurTextFrame)));
1401 SetColl( RES_POOLCOLL_TEXT_MOVE );
1402 if( !bBreak )
1404 SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
1405 const SwTextFrame * pNextFrame = GetNextNode();
1406 while (CanJoin(pNextFrame) &&
1407 CalcLevel(*pNextFrame))
1409 bBreak = !IsFastFullLine(*pNextFrame)
1410 || IsBlanksInString(*pNextFrame)
1411 || IsSentenceAtEnd(*pNextFrame);
1412 if (DeleteJoinCurNextPara(pNextFrame))
1414 m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
1416 if( bBreak )
1417 break;
1418 pNextFrame = GetNextNode();
1421 DeleteLeadingTrailingBlanks();
1422 AutoCorrect();
1425 void SwAutoFormat::BuildText()
1427 SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_TEXT );
1428 // read all succeeding paragraphs that belong to this text without indentation
1429 bool bBreak = true;
1430 if( m_bMoreLines )
1431 DelMoreLinesBlanks();
1432 else
1433 bBreak = !IsFastFullLine(*m_pCurTextFrame)
1434 || IsBlanksInString(*m_pCurTextFrame)
1435 || IsSentenceAtEnd(*m_pCurTextFrame);
1436 if( !bBreak )
1438 SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
1439 const SwTextFrame * pNextFrame = GetNextNode();
1440 while (CanJoin(pNextFrame) &&
1441 !CalcLevel(*pNextFrame))
1443 bBreak = !IsFastFullLine(*pNextFrame)
1444 || IsBlanksInString(*pNextFrame)
1445 || IsSentenceAtEnd(*pNextFrame);
1446 if (DeleteJoinCurNextPara(pNextFrame))
1448 m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
1450 if( bBreak )
1451 break;
1452 const SwTextFrame *const pCurrNode = pNextFrame;
1453 pNextFrame = GetNextNode();
1454 if (!pNextFrame || pCurrNode == pNextFrame)
1455 break;
1458 DeleteLeadingTrailingBlanks();
1459 AutoCorrect();
1462 void SwAutoFormat::BuildEnum( sal_uInt16 nLvl, sal_uInt16 nDigitLevel )
1464 SetRedlineText( STR_AUTOFMTREDL_SET_NUMBER_BULLET );
1466 bool bBreak = true;
1468 // first, determine current indentation and frame width
1469 SwTwips nFrameWidth = m_pCurTextFrame->getFramePrintArea().Width();
1470 SwTwips nLeftTextPos;
1472 TextFrameIndex nPos(0);
1473 while (nPos < TextFrameIndex(m_pCurTextFrame->GetText().getLength())
1474 && IsSpace(m_pCurTextFrame->GetText()[sal_Int32(nPos)]))
1476 ++nPos;
1479 SwTextFrameInfo aInfo( m_pCurTextFrame );
1480 nLeftTextPos = aInfo.GetCharPos(nPos);
1481 nLeftTextPos -= m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetTextLeftMargin().GetLeft(m_pCurTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent());
1484 if( m_bMoreLines )
1485 DelMoreLinesBlanks();
1486 else
1487 bBreak = !IsFastFullLine(*m_pCurTextFrame)
1488 || IsBlanksInString(*m_pCurTextFrame)
1489 || IsSentenceAtEnd(*m_pCurTextFrame);
1490 bool bRTL = m_pEditShell->IsInRightToLeftText();
1491 DeleteLeadingTrailingBlanks();
1493 bool bChgBullet = false, bChgEnum = false;
1494 TextFrameIndex nAutoCorrPos(0);
1496 // if numbering is set, get the current one
1497 SwNumRule aRule( m_pDoc->GetUniqueNumRuleName(),
1498 // #i89178#
1499 numfunc::GetDefaultPositionAndSpaceMode() );
1501 const SwNumRule* pCur = nullptr;
1502 if (m_aFlags.bSetNumRule)
1504 pCur = m_pCurTextFrame->GetTextNodeForParaProps()->GetNumRule();
1505 if (pCur)
1507 aRule = *pCur;
1511 // replace bullet character with defined one
1512 const OUString& rStr = m_pCurTextFrame->GetText();
1513 TextFrameIndex nTextStt(0);
1514 const sal_Unicode* pFndBulletChr = nullptr;
1515 if (m_aFlags.bChgEnumNum && 2 < rStr.getLength())
1516 pFndBulletChr = StrChr(pBulletChar, rStr[sal_Int32(nTextStt)]);
1517 if (nullptr != pFndBulletChr && IsSpace(rStr[sal_Int32(nTextStt) + 1]))
1519 if( m_aFlags.bAFormatByInput )
1521 if( m_aFlags.bSetNumRule )
1523 SwCharFormat* pCFormat = m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool(
1524 RES_POOLCHR_BULLET_LEVEL );
1525 bChgBullet = true;
1526 // Was the format already somewhere adjusted?
1527 if( !aRule.GetNumFormat( nLvl ) )
1529 int nBulletPos = pFndBulletChr - pBulletChar;
1530 sal_UCS4 cBullChar;
1531 const vcl::Font* pBullFnt( nullptr );
1532 if( nBulletPos < cnPosEnDash )
1534 cBullChar = m_aFlags.cBullet;
1535 pBullFnt = &m_aFlags.aBulletFont;
1537 else
1539 cBullChar = nBulletPos < cnPosEmDash
1540 ? cStarSymbolEnDash
1541 : cStarSymbolEmDash;
1542 // #i63395#
1543 // Only apply user defined default bullet font
1544 if ( numfunc::IsDefBulletFontUserDefined() )
1546 pBullFnt = &numfunc::GetDefBulletFont();
1550 sal_Int32 nAbsPos = lBulletIndent;
1551 SwTwips nSpaceSteps = nLvl
1552 ? nLeftTextPos / nLvl
1553 : lBulletIndent;
1554 for( sal_uInt8 n = 0; n < MAXLEVEL; ++n, nAbsPos = nAbsPos + nSpaceSteps )
1556 SwNumFormat aFormat( aRule.Get( n ) );
1557 aFormat.SetBulletFont( pBullFnt );
1558 aFormat.SetBulletChar( cBullChar );
1559 aFormat.SetNumberingType(SVX_NUM_CHAR_SPECIAL);
1560 // #i93908# clear suffix for bullet lists
1561 aFormat.SetListFormat("", "", n);
1562 aFormat.SetFirstLineOffset( lBulletFirstLineOffset );
1563 aFormat.SetAbsLSpace( nAbsPos );
1564 if( !aFormat.GetCharFormat() )
1565 aFormat.SetCharFormat( pCFormat );
1566 if( bRTL )
1567 aFormat.SetNumAdjust( SvxAdjust::Right );
1569 aRule.Set( n, aFormat );
1571 if( n == nLvl &&
1572 nFrameWidth < ( nSpaceSteps * MAXLEVEL ) )
1573 nSpaceSteps = ( nFrameWidth - nLeftTextPos ) /
1574 ( MAXLEVEL - nLvl );
1579 else
1581 bChgBullet = true;
1582 SetColl( o3tl::narrowing<sal_uInt16>(RES_POOLCOLL_BULLET_LEVEL1 + ( std::min( nLvl, cnNumBullColls ) * 4 )) );
1585 else
1587 // Then it is a numbering
1589 //JP 21.11.97: The NumLevel is either the DigitLevel or, if the latter is not existent or 0,
1590 // it is determined by the indentation level.
1592 OUString aPostfix, aPrefix, aNumTypes;
1593 nDigitLevel = GetDigitLevel(*m_pCurTextFrame, nTextStt,
1594 &aPrefix, &aPostfix, &aNumTypes);
1595 if (USHRT_MAX != nDigitLevel)
1597 bChgEnum = true;
1599 // Level 0 and Indentation, determine level by left indentation and default NumIndent
1600 if( !nDigitLevel && nLeftTextPos )
1601 nLvl = std::min( sal_uInt16( nLeftTextPos / lNumberIndent ),
1602 sal_uInt16( MAXLEVEL - 1 ) );
1603 else
1604 nLvl = nDigitLevel;
1607 if( bChgEnum && m_aFlags.bSetNumRule )
1609 if( !pCur ) // adjust NumRule if it is new
1611 SwCharFormat* pCFormat = m_pDoc->getIDocumentStylePoolAccess().GetCharFormatFromPool(
1612 RES_POOLCHR_NUM_LEVEL );
1614 sal_Int32 nPrefixIdx{ 0 };
1615 if( !nDigitLevel )
1617 SwNumFormat aFormat( aRule.Get( nLvl ) );
1618 const OUString sPrefix = aPrefix.getToken(0, u'\x0001', nPrefixIdx);
1619 aFormat.SetStart( o3tl::narrowing<sal_uInt16>(o3tl::toInt32(o3tl::getToken(aPrefix, 0, u'\x0001', nPrefixIdx ))));
1620 aFormat.SetListFormat(sPrefix, aPostfix.getToken(0, u'\x0001'), nLvl);
1621 aFormat.SetIncludeUpperLevels( 0 );
1623 if( !aFormat.GetCharFormat() )
1624 aFormat.SetCharFormat( pCFormat );
1626 if( !aNumTypes.isEmpty() )
1627 aFormat.SetNumberingType(static_cast<SvxNumType>(aNumTypes[ 0 ] - '0'));
1629 if( bRTL )
1630 aFormat.SetNumAdjust( SvxAdjust::Right );
1631 aRule.Set( nLvl, aFormat );
1633 else
1635 auto const nSpaceSteps = nLvl ? nLeftTextPos / nLvl : 0;
1636 sal_uInt16 n;
1637 sal_Int32 nPostfixIdx{ 0 };
1638 for( n = 0; n <= nLvl; ++n )
1640 SwNumFormat aFormat( aRule.Get( n ) );
1642 const OUString sPrefix = n ? "" : aPrefix.getToken(0, u'\x0001', nPrefixIdx);
1643 aFormat.SetStart( o3tl::narrowing<sal_uInt16>(o3tl::toInt32(o3tl::getToken(aPrefix, 0, u'\x0001', nPrefixIdx )) ));
1644 aFormat.SetListFormat(sPrefix, aPostfix.getToken(0, u'\x0001', nPostfixIdx), n);
1645 aFormat.SetIncludeUpperLevels( MAXLEVEL );
1646 if( n < aNumTypes.getLength() )
1647 aFormat.SetNumberingType(static_cast<SvxNumType>(aNumTypes[ n ] - '0'));
1649 aFormat.SetAbsLSpace( nSpaceSteps * n
1650 + lNumberIndent );
1652 if( !aFormat.GetCharFormat() )
1653 aFormat.SetCharFormat( pCFormat );
1654 if( bRTL )
1655 aFormat.SetNumAdjust( SvxAdjust::Right );
1657 aRule.Set( n, aFormat );
1660 // Does it fit completely into the frame?
1661 bool bDefStep = nFrameWidth < (nSpaceSteps * MAXLEVEL);
1662 for( ; n < MAXLEVEL; ++n )
1664 SwNumFormat aFormat( aRule.Get( n ) );
1665 aFormat.SetIncludeUpperLevels( MAXLEVEL );
1666 if( bDefStep )
1667 aFormat.SetAbsLSpace( nLeftTextPos +
1668 SwNumRule::GetNumIndent(static_cast<sal_uInt8>(n-nLvl)));
1669 else
1670 aFormat.SetAbsLSpace( nSpaceSteps * n
1671 + lNumberIndent );
1672 aRule.Set( n, aFormat );
1677 else if( !m_aFlags.bAFormatByInput )
1678 SetColl( o3tl::narrowing<sal_uInt16>(RES_POOLCOLL_NUM_LEVEL1 + ( std::min( nLvl, cnNumBullColls ) * 4 ) ));
1679 else
1680 bChgEnum = false;
1683 if ( bChgEnum || bChgBullet )
1685 m_aDelPam.DeleteMark();
1686 m_aDelPam.GetPoint()->Assign( *m_pCurTextFrame->GetTextNodeForParaProps() );
1688 if( m_aFlags.bSetNumRule )
1690 if( m_aFlags.bAFormatByInput )
1692 m_aDelPam.SetMark();
1693 SwTextFrame const*const pNextFrame = GetNextNode(false);
1694 assert(pNextFrame);
1695 m_aDelPam.GetMark()->Assign( *pNextFrame->GetTextNodeForParaProps() );
1696 m_aDelPam.GetMarkNode().GetTextNode()->SetAttrListLevel( nLvl );
1699 const_cast<SwTextNode*>(m_pCurTextFrame->GetTextNodeForParaProps())->SetAttrListLevel(nLvl);
1701 // start new list
1702 m_pDoc->SetNumRule(m_aDelPam, aRule, true, m_pEditShell->GetLayout());
1703 m_aDelPam.DeleteMark();
1705 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0));
1707 else
1709 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(
1710 bChgEnum ? nTextStt : TextFrameIndex(0));
1712 m_aDelPam.SetMark();
1714 if ( bChgBullet )
1715 nTextStt += TextFrameIndex(2);
1717 while (nTextStt < TextFrameIndex(rStr.getLength()) && IsSpace(rStr[sal_Int32(nTextStt)]))
1718 nTextStt++;
1720 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nTextStt);
1721 DeleteSel( m_aDelPam );
1723 if( !m_aFlags.bSetNumRule )
1725 OUString sChgStr('\t');
1726 if( bChgBullet )
1727 sChgStr = OUString(&m_aFlags.cBullet, 1) + sChgStr;
1728 m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, sChgStr );
1730 SfxItemSet aSet( m_pDoc->GetAttrPool(), aTextNodeSetRange );
1731 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0));
1732 assert(&m_aDelPam.GetPoint()->GetNode() == m_pCurTextFrame->GetTextNodeForParaProps());
1733 if( bChgBullet )
1735 m_aDelPam.SetMark();
1736 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(1));
1737 SetAllScriptItem( aSet,
1738 SvxFontItem( m_aFlags.aBulletFont.GetFamilyType(),
1739 m_aFlags.aBulletFont.GetFamilyName(),
1740 m_aFlags.aBulletFont.GetStyleName(),
1741 m_aFlags.aBulletFont.GetPitch(),
1742 m_aFlags.aBulletFont.GetCharSet(),
1743 RES_CHRATR_FONT ) );
1744 m_pDoc->SetFormatItemByAutoFormat( m_aDelPam, aSet );
1745 m_aDelPam.DeleteMark();
1746 nAutoCorrPos = TextFrameIndex(2);
1747 aSet.ClearItem();
1749 SvxTabStopItem aTStops( RES_PARATR_TABSTOP );
1750 aTStops.Insert( SvxTabStop( 0 ) );
1751 aSet.Put( aTStops );
1752 assert(&m_aDelPam.GetPoint()->GetNode() == m_pCurTextFrame->GetTextNodeForParaProps());
1753 m_pDoc->SetFormatItemByAutoFormat( m_aDelPam, aSet );
1757 if( bBreak )
1759 AutoCorrect( nAutoCorrPos ); /* Offset due to Bullet + Tab */
1760 return;
1763 const SwTextFrame * pNextFrame = GetNextNode();
1764 while (CanJoin(pNextFrame)
1765 && nLvl == CalcLevel(*pNextFrame))
1767 SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
1768 bBreak = !IsFastFullLine(*pNextFrame)
1769 || IsBlanksInString(*pNextFrame)
1770 || IsSentenceAtEnd(*pNextFrame);
1771 if (DeleteJoinCurNextPara(pNextFrame))
1773 m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
1775 if( bBreak )
1776 break;
1777 const SwTextFrame *const pCurrNode = pNextFrame;
1778 pNextFrame = GetNextNode();
1779 if (!pNextFrame || pCurrNode == pNextFrame)
1780 break;
1782 DeleteLeadingTrailingBlanks( false );
1783 AutoCorrect( nAutoCorrPos );
1786 void SwAutoFormat::BuildNegIndent( SwTwips nSpaces )
1788 SetRedlineText( STR_AUTOFMTREDL_SET_TMPL_NEG_INDENT );
1789 // Test of contraposition (n words, divided by spaces/tabs, with same indentation in 2nd line)
1791 // read all succeeding paragraphs that belong to this enumeration
1792 bool bBreak = true;
1793 TextFrameIndex nSpacePos(0);
1794 const sal_Int32 nTextPos = GetBigIndent( nSpacePos );
1795 if( m_bMoreLines )
1796 DelMoreLinesBlanks( true );
1797 else
1798 bBreak = !IsFastFullLine(*m_pCurTextFrame)
1799 || (!nTextPos && IsBlanksInString(*m_pCurTextFrame))
1800 || IsSentenceAtEnd(*m_pCurTextFrame);
1802 SetColl( o3tl::narrowing<sal_uInt16>( nTextPos
1803 ? RES_POOLCOLL_CONFRONTATION
1804 : RES_POOLCOLL_TEXT_NEGIDENT ) );
1806 if( nTextPos )
1808 const OUString& rStr = m_pCurTextFrame->GetText();
1809 bool bInsTab = true;
1811 if ('\t' == rStr[sal_Int32(nSpacePos) + 1]) // leave tab alone
1813 --nSpacePos;
1814 bInsTab = false;
1817 TextFrameIndex nSpaceStt = nSpacePos;
1818 while (nSpaceStt && IsSpace(rStr[sal_Int32(--nSpaceStt)]))
1820 ++nSpaceStt;
1822 if (bInsTab && '\t' == rStr[sal_Int32(nSpaceStt)]) // leave tab alone
1824 ++nSpaceStt;
1825 bInsTab = false;
1828 m_aDelPam.DeleteMark();
1829 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nSpacePos);
1831 // delete old Spaces, etc.
1832 if( nSpaceStt < nSpacePos )
1834 m_aDelPam.SetMark();
1835 *m_aDelPam.GetMark() = m_pCurTextFrame->MapViewToModelPos(nSpaceStt);
1836 DeleteSel( m_aDelPam );
1837 if( bInsTab )
1839 m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString('\t') );
1844 if( !bBreak )
1846 SetRedlineText( STR_AUTOFMTREDL_DEL_MORELINES );
1847 SwTextFrameInfo aFInfo( m_pCurTextFrame );
1848 const SwTextFrame * pNextFrame = GetNextNode();
1849 while (CanJoin(pNextFrame) &&
1850 20 < std::abs( static_cast<tools::Long>(nSpaces - aFInfo.SetFrame(
1851 EnsureFormatted(*pNextFrame)).GetLineStart()) )
1854 bBreak = !IsFastFullLine(*pNextFrame)
1855 || IsBlanksInString(*pNextFrame)
1856 || IsSentenceAtEnd(*pNextFrame);
1857 if (DeleteJoinCurNextPara(pNextFrame))
1859 m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(' ') );
1861 if( bBreak )
1862 break;
1863 pNextFrame = GetNextNode();
1866 DeleteLeadingTrailingBlanks();
1867 AutoCorrect();
1870 void SwAutoFormat::BuildHeadLine( sal_uInt16 nLvl )
1872 if( m_aFlags.bWithRedlining )
1874 OUString sText(SwViewShell::GetShellRes()->GetAutoFormatNameLst()[
1875 STR_AUTOFMTREDL_SET_TMPL_HEADLINE ] );
1876 sText = sText.replaceAll( "$(ARG1)", OUString::number( nLvl + 1 ) );
1877 m_pDoc->GetDocumentRedlineManager().SetAutoFormatRedlineComment( &sText );
1880 SetColl( o3tl::narrowing<sal_uInt16>(RES_POOLCOLL_HEADLINE1 + nLvl ), true );
1881 if( m_aFlags.bAFormatByInput )
1883 SwTextFormatColl& rNxtColl = m_pCurTextFrame->GetTextNodeForParaProps()->GetTextColl()->GetNextTextFormatColl();
1885 JoinPrevPara();
1887 DeleteLeadingTrailingBlanks( true, false );
1888 const SwTextFrame* pNextFrame = GetNextNode(false);
1889 if (pNextFrame->GetNext())
1891 (void)DeleteJoinCurNextPara(pNextFrame, true);
1892 pNextFrame = GetNextNode(false);
1894 m_aDelPam.DeleteMark();
1895 m_aDelPam.GetPoint()->Assign( *pNextFrame->GetTextNodeForParaProps() );
1896 m_pDoc->SetTextFormatColl( m_aDelPam, &rNxtColl );
1898 else
1900 DeleteLeadingTrailingBlanks();
1901 AutoCorrect();
1905 /// Start autocorrection for the current TextNode
1906 void SwAutoFormat::AutoCorrect(TextFrameIndex nPos)
1908 SvxAutoCorrect* pATst = SvxAutoCorrCfg::Get().GetAutoCorrect();
1909 ACFlags aSvxFlags = pATst->GetFlags( );
1910 bool bReplaceQuote( aSvxFlags & ACFlags::ChgQuotes );
1911 bool bReplaceSglQuote( aSvxFlags & ACFlags::ChgSglQuotes );
1913 if( m_aFlags.bAFormatByInput ||
1914 (!m_aFlags.bAutoCorrect && !bReplaceQuote && !bReplaceSglQuote &&
1915 !m_aFlags.bCapitalStartSentence && !m_aFlags.bCapitalStartWord &&
1916 !m_aFlags.bChgOrdinalNumber && !m_aFlags.bTransliterateRTL &&
1917 !m_aFlags.bChgToEnEmDash && !m_aFlags.bSetINetAttr &&
1918 !m_aFlags.bChgWeightUnderl && !m_aFlags.bAddNonBrkSpace) )
1919 return;
1921 const OUString* pText = &m_pCurTextFrame->GetText();
1922 if (TextFrameIndex(pText->getLength()) <= nPos)
1923 return;
1925 bool bGetLanguage = m_aFlags.bChgOrdinalNumber || m_aFlags.bTransliterateRTL ||
1926 m_aFlags.bChgToEnEmDash || m_aFlags.bSetINetAttr ||
1927 m_aFlags.bCapitalStartWord || m_aFlags.bCapitalStartSentence ||
1928 m_aFlags.bAddNonBrkSpace;
1930 m_aDelPam.DeleteMark();
1931 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(TextFrameIndex(0));
1933 SwAutoCorrDoc aACorrDoc( *m_pEditShell, m_aDelPam );
1935 SwTextFrameInfo aFInfo( nullptr );
1937 TextFrameIndex nSttPos, nLastBlank = nPos;
1938 bool bFirst = m_aFlags.bCapitalStartSentence, bFirstSent = bFirst;
1939 sal_Unicode cChar = 0;
1940 bool bNbspRunNext = false;
1942 CharClass& rAppCC = GetAppCharClass();
1944 do {
1945 while (nPos < TextFrameIndex(pText->getLength())
1946 && IsSpace(cChar = (*pText)[sal_Int32(nPos)]))
1947 ++nPos;
1948 if (nPos == TextFrameIndex(pText->getLength()))
1949 break; // that's it
1951 if( ( ( bReplaceQuote && '\"' == cChar ) ||
1952 ( bReplaceSglQuote && '\'' == cChar ) ) &&
1953 (!nPos || ' ' == (*pText)[sal_Int32(nPos)-1]))
1956 // note: special case symbol fonts !!!
1957 if( !aFInfo.GetFrame() )
1958 aFInfo.SetFrame( GetFrame( *m_pCurTextNd ) );
1959 if( !aFInfo.IsBullet( nPos ))
1961 SetRedlineText( STR_AUTOFMTREDL_TYPO );
1962 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
1963 bool bSetHardBlank = false;
1965 OUString sReplace( pATst->GetQuote( aACorrDoc,
1966 sal_Int32(nPos), cChar, true ));
1968 m_aDelPam.SetMark();
1969 m_aDelPam.GetPoint()->SetContent( m_aDelPam.GetMark()->GetContentIndex() + 1 );
1970 if( 2 == sReplace.getLength() && ' ' == sReplace[ 1 ])
1972 sReplace = sReplace.copy( 0, 1 );
1973 bSetHardBlank = true;
1975 m_pDoc->getIDocumentContentOperations().ReplaceRange( m_aDelPam, sReplace, false );
1977 if( m_aFlags.bWithRedlining )
1979 m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
1980 m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
1981 m_pCurTextFrame = GetFrame( *m_pCurTextNd );
1982 pText = &m_pCurTextFrame->GetText();
1983 m_aDelPam.SetMark();
1984 aFInfo.SetFrame( nullptr );
1987 nPos += TextFrameIndex(sReplace.getLength() - 1);
1988 m_aDelPam.DeleteMark();
1989 if( bSetHardBlank )
1991 m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(CHAR_HARDBLANK) );
1992 ++nPos;
1997 bool bCallACorr = false;
1998 int bBreak = 0;
1999 if (nPos && IsSpace((*pText)[sal_Int32(nPos) - 1]))
2000 nLastBlank = nPos;
2001 for (nSttPos = nPos; !bBreak && nPos < TextFrameIndex(pText->getLength()); ++nPos)
2003 cChar = (*pText)[sal_Int32(nPos)];
2004 switch (cChar)
2006 case '\"':
2007 case '\'':
2008 if( ( cChar == '\"' && bReplaceQuote ) || ( cChar == '\'' && bReplaceSglQuote ) )
2010 // consider Symbolfonts!
2011 if( !aFInfo.GetFrame() )
2012 aFInfo.SetFrame( GetFrame( *m_pCurTextNd ) );
2013 if( !aFInfo.IsBullet( nPos ))
2015 SetRedlineText( STR_AUTOFMTREDL_TYPO );
2016 bool bSetHardBlank = false;
2017 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
2018 OUString sReplace( pATst->GetQuote( aACorrDoc,
2019 sal_Int32(nPos), cChar, false) );
2021 if( 2 == sReplace.getLength() && ' ' == sReplace[ 0 ])
2023 sReplace = sReplace.copy( 1 );
2024 bSetHardBlank = true;
2027 m_aDelPam.SetMark();
2028 m_aDelPam.GetPoint()->SetContent( m_aDelPam.GetMark()->GetContentIndex() + 1 );
2029 m_pDoc->getIDocumentContentOperations().ReplaceRange( m_aDelPam, sReplace, false );
2031 if( m_aFlags.bWithRedlining )
2033 m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
2034 m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
2035 m_pCurTextFrame = GetFrame( *m_pCurTextNd );
2036 pText = &m_pCurTextFrame->GetText();
2037 m_aDelPam.SetMark();
2038 m_aDelPam.DeleteMark();
2039 aFInfo.SetFrame( nullptr );
2042 nPos += TextFrameIndex(sReplace.getLength() - 1);
2043 m_aDelPam.DeleteMark();
2045 if( bSetHardBlank )
2047 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
2048 m_pDoc->getIDocumentContentOperations().InsertString( m_aDelPam, OUString(CHAR_HARDBLANK) );
2049 ++nPos;
2050 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
2054 break;
2055 case '*':
2056 case '_':
2057 if( m_aFlags.bChgWeightUnderl )
2059 // consider Symbolfonts!
2060 if( !aFInfo.GetFrame() )
2061 aFInfo.SetFrame( GetFrame( *m_pCurTextNd ) );
2062 if( !aFInfo.IsBullet( nPos ))
2064 SetRedlineText( '*' == cChar
2065 ? STR_AUTOFMTREDL_BOLD
2066 : STR_AUTOFMTREDL_UNDER );
2068 sal_Unicode cBlank = nSttPos ? (*pText)[sal_Int32(nSttPos) - 1] : 0;
2069 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
2071 if (pATst->FnChgWeightUnderl(aACorrDoc, *pText, sal_Int32(nPos)))
2073 if( m_aFlags.bWithRedlining )
2075 m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
2076 m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
2077 m_pCurTextFrame = GetFrame( *m_pCurTextNd );
2078 pText = &m_pCurTextFrame->GetText();
2079 m_aDelPam.SetMark();
2080 m_aDelPam.DeleteMark();
2081 aFInfo.SetFrame( nullptr );
2083 //#125102# in case of the mode RedlineFlags::ShowDelete the ** are still contained in pText
2084 if(!(m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags() & RedlineFlags::ShowDelete))
2085 nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint()) - TextFrameIndex(1);
2086 // Was a character deleted before starting?
2087 if (cBlank && cBlank != (*pText)[sal_Int32(nSttPos) - 1])
2088 --nSttPos;
2092 break;
2093 case '/':
2094 if ( m_aFlags.bAddNonBrkSpace )
2096 LanguageType eLang = bGetLanguage
2097 ? m_pCurTextFrame->GetLangOfChar(nSttPos, 0, true)
2098 : LANGUAGE_SYSTEM;
2100 SetRedlineText( STR_AUTOFMTREDL_NON_BREAK_SPACE );
2101 if (pATst->FnAddNonBrkSpace(aACorrDoc, *pText, sal_Int32(nPos), eLang, bNbspRunNext))
2102 --nPos;
2104 break;
2106 case '.':
2107 case '!':
2108 case '?':
2109 if( m_aFlags.bCapitalStartSentence )
2110 bFirstSent = true;
2111 [[fallthrough]];
2112 default:
2113 if (!(rAppCC.isBase(*pText, sal_Int32(nPos))
2114 || '/' == cChar )) // '/' should not be a word separator (e.g. '1/2' needs to be handled as one word for replacement)
2116 --nPos; // revert ++nPos which was decremented in for loop
2117 ++bBreak;
2119 break;
2123 if( nPos == nSttPos )
2125 if (++nPos == TextFrameIndex(pText->getLength()))
2126 bCallACorr = true;
2128 else
2129 bCallACorr = true;
2131 if( bCallACorr )
2133 *m_aDelPam.GetPoint() = m_pCurTextFrame->MapViewToModelPos(nPos);
2134 SetRedlineText( STR_AUTOFMTREDL_USE_REPLACE );
2136 LanguageType eLang = bGetLanguage
2137 ? m_pCurTextFrame->GetLangOfChar(nSttPos, 0, true)
2138 : LANGUAGE_SYSTEM;
2140 if( m_bIsRightToLeft && m_aFlags.bTransliterateRTL && eLang == LANGUAGE_HUNGARIAN &&
2141 SetRedlineText( STR_AUTOFMTREDL_TRANSLITERATE_RTL ) &&
2142 aACorrDoc.TransliterateRTLWord(reinterpret_cast<sal_Int32&>(nSttPos), sal_Int32(nPos), /*bApply=*/true))
2144 nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint());
2146 if( m_aFlags.bWithRedlining )
2148 m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
2149 m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
2150 m_pCurTextFrame = GetFrame( *m_pCurTextNd );
2151 pText = &m_pCurTextFrame->GetText();
2152 m_aDelPam.SetMark();
2153 m_aDelPam.DeleteMark();
2156 continue; // do not check further
2159 if( m_aFlags.bAutoCorrect &&
2160 aACorrDoc.ChgAutoCorrWord(reinterpret_cast<sal_Int32&>(nSttPos), sal_Int32(nPos), *pATst, nullptr) )
2162 nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint());
2163 if( m_aFlags.bWithRedlining )
2165 m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
2166 m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
2167 m_pCurTextFrame = GetFrame( *m_pCurTextNd );
2168 pText = &m_pCurTextFrame->GetText();
2169 m_aDelPam.SetMark();
2170 m_aDelPam.DeleteMark();
2173 continue; // do not check further
2176 if ( m_aFlags.bAddNonBrkSpace && nPos < TextFrameIndex(pText->getLength()) )
2178 SetRedlineText( STR_AUTOFMTREDL_NON_BREAK_SPACE );
2179 pATst->FnAddNonBrkSpace(aACorrDoc, *pText, sal_Int32(nPos), eLang, bNbspRunNext);
2182 if( ( m_aFlags.bChgOrdinalNumber &&
2183 SetRedlineText( STR_AUTOFMTREDL_ORDINAL ) &&
2184 pATst->FnChgOrdinalNumber(aACorrDoc, *pText, sal_Int32(nSttPos), sal_Int32(nPos), eLang)) ||
2185 ( m_aFlags.bChgToEnEmDash &&
2186 SetRedlineText( STR_AUTOFMTREDL_DASH ) &&
2187 pATst->FnChgToEnEmDash(aACorrDoc, *pText, sal_Int32(nSttPos), sal_Int32(nPos), eLang)) ||
2188 ( m_aFlags.bSetINetAttr &&
2189 (nPos == TextFrameIndex(pText->getLength()) || IsSpace((*pText)[sal_Int32(nPos)])) &&
2190 SetRedlineText( STR_AUTOFMTREDL_DETECT_URL ) &&
2191 pATst->FnSetINetAttr(aACorrDoc, *pText, sal_Int32(nLastBlank), sal_Int32(nPos), eLang)) ||
2192 ( m_aFlags.bSetDOIAttr &&
2193 (nPos == TextFrameIndex(pText->getLength()) || IsSpace((*pText)[sal_Int32(nPos)])) &&
2194 SetRedlineText( STR_AUTOFMTREDL_DETECT_DOI ) &&
2195 pATst->FnSetDOIAttr(aACorrDoc, *pText, sal_Int32(nLastBlank), sal_Int32(nPos), eLang)))
2197 nPos = m_pCurTextFrame->MapModelToViewPos(*m_aDelPam.GetPoint());
2199 else
2201 // two capital letters at the beginning of a word?
2202 if( m_aFlags.bCapitalStartWord )
2204 SetRedlineText( STR_AUTOFMTREDL_CPTL_STT_WORD );
2205 pATst->FnCapitalStartWord(aACorrDoc, *pText, sal_Int32(nSttPos), sal_Int32(nPos), eLang);
2207 // capital letter at the beginning of a sentence?
2208 if( m_aFlags.bCapitalStartSentence && bFirst )
2210 SetRedlineText( STR_AUTOFMTREDL_CPTL_STT_SENT );
2211 pATst->FnCapitalStartSentence(aACorrDoc, *pText, true, sal_Int32(nSttPos), sal_Int32(nPos), eLang);
2214 bFirst = bFirstSent;
2215 bFirstSent = false;
2217 if( m_aFlags.bWithRedlining )
2219 m_aNdIdx = m_aDelPam.GetPoint()->GetNode();
2220 m_pCurTextNd = m_aNdIdx.GetNode().GetTextNode();
2221 m_pCurTextFrame = GetFrame( *m_pCurTextNd );
2222 pText = &m_pCurTextFrame->GetText();
2223 m_aDelPam.SetMark();
2224 m_aDelPam.DeleteMark();
2229 while (nPos < TextFrameIndex(pText->getLength()));
2230 ClearRedlineText();
2233 SwAutoFormat::SwAutoFormat( SwEditShell* pEdShell, SvxSwAutoFormatFlags aFlags,
2234 SwNode const * pSttNd, SwNode const * pEndNd )
2235 : m_aFlags(std::move( aFlags )),
2236 m_aDelPam( pEdShell->GetDoc()->GetNodes().GetEndOfExtras() ),
2237 m_aNdIdx( pEdShell->GetDoc()->GetNodes().GetEndOfExtras(), SwNodeOffset(+1) ),
2238 m_aEndNdIdx( pEdShell->GetDoc()->GetNodes().GetEndOfContent() ),
2239 m_pEditShell( pEdShell ),
2240 m_pDoc( pEdShell->GetDoc() ),
2241 m_pCurTextNd( nullptr ), m_pCurTextFrame( nullptr ),
2242 m_nRedlAutoFormatSeqId( 0 )
2244 OSL_ENSURE( (pSttNd && pEndNd) || (!pSttNd && !pEndNd),
2245 "Got no area" );
2247 if( m_aFlags.bSetNumRule && !m_aFlags.bAFormatByInput )
2248 m_aFlags.bSetNumRule = false;
2250 bool bReplaceStyles = !m_aFlags.bAFormatByInput || m_aFlags.bReplaceStyles;
2252 const SwTextFrame * pNextFrame = nullptr;
2253 bool bNxtEmpty = false;
2254 bool bNxtAlpha = false;
2255 sal_uInt16 nNxtLevel = 0;
2256 bool bEmptyLine;
2258 // set area for autoformatting
2259 if( pSttNd )
2261 m_aNdIdx = *pSttNd;
2262 // for GoNextPara, one paragraph prior to that
2263 sw::GotoPrevLayoutTextFrame(m_aNdIdx, m_pEditShell->GetLayout());
2264 m_aEndNdIdx = *pEndNd;
2265 sw::GotoNextLayoutTextFrame(m_aEndNdIdx, m_pEditShell->GetLayout());
2267 // check the previous TextNode
2268 SwTextFrame const*const pPrevFrame = m_aNdIdx.GetNode().GetTextNode()
2269 ? static_cast<SwTextFrame const*>(m_aNdIdx.GetNode().GetTextNode()->getLayoutFrame(m_pEditShell->GetLayout()))
2270 : nullptr;
2271 bEmptyLine = !pPrevFrame
2272 || IsEmptyLine(*pPrevFrame)
2273 || IsNoAlphaLine(*pPrevFrame);
2275 else
2276 bEmptyLine = true; // at document beginning
2278 m_bEnd = false;
2280 // set value for percentage display
2281 m_nEndNdIdx = m_aEndNdIdx.GetIndex();
2283 if( !m_aFlags.bAFormatByInput )
2285 m_nEndNdIdx = m_aEndNdIdx.GetIndex();
2286 ::StartProgress( STR_STATSTR_AUTOFORMAT, sal_Int32(m_aNdIdx.GetIndex()),
2287 sal_Int32(m_nEndNdIdx),
2288 m_pDoc->GetDocShell() );
2291 RedlineFlags eRedlMode = m_pDoc->getIDocumentRedlineAccess().GetRedlineFlags(), eOldMode = eRedlMode;
2292 if( m_aFlags.bWithRedlining )
2294 m_pDoc->SetAutoFormatRedline( true );
2295 eRedlMode = RedlineFlags::On | (eOldMode & RedlineFlags::ShowMask);
2297 else
2298 eRedlMode = RedlineFlags::Ignore | (eOldMode & RedlineFlags::ShowMask);
2299 m_pDoc->getIDocumentRedlineAccess().SetRedlineFlags( eRedlMode );
2301 // save undo state (might be turned off)
2302 bool const bUndoState = m_pDoc->GetIDocumentUndoRedo().DoesUndo();
2304 // If multiple lines, then do not merge with next paragraph
2305 m_bMoreLines = false;
2307 sal_uInt16 nLastCalcHeadLvl = 0;
2308 sal_uInt16 nLastHeadLvl = USHRT_MAX;
2309 sal_uInt16 nLevel = 0;
2310 sal_uInt16 nDigitLvl = 0;
2312 // set defaults
2313 SwTextFrameInfo aFInfo( nullptr );
2315 enum Format_Status
2317 READ_NEXT_PARA, // -> ISEND, TST_EMPTY_LINE
2318 TST_EMPTY_LINE, // -> READ_NEXT_PARA, TST_ALPHA_LINE
2319 TST_ALPHA_LINE, // -> READ_NEXT_PARA, GET_ALL_INFO, IS_END
2320 GET_ALL_INFO, // -> READ_NEXT_PARA, IS_ONE_LINE, TST_ENUMERIC, HAS_FMTCOLL
2321 IS_ONE_LINE, // -> READ_NEXT_PARA, TST_ENUMERIC
2322 TST_ENUMERIC, // -> READ_NEXT_PARA, TST_IDENT, TST_NEG_IDENT
2323 TST_IDENT, // -> READ_NEXT_PARA, TST_TXT_BODY
2324 TST_NEG_IDENT, // -> READ_NEXT_PARA, TST_TXT_BODY
2325 TST_TXT_BODY, // -> READ_NEXT_PARA
2326 HAS_FMTCOLL, // -> READ_NEXT_PARA
2327 IS_END
2328 } eStat;
2330 // This is the automat for autoformatting
2331 eStat = READ_NEXT_PARA;
2332 while( !m_bEnd )
2334 switch( eStat )
2336 case READ_NEXT_PARA:
2338 GoNextPara();
2339 eStat = m_bEnd ? IS_END : TST_EMPTY_LINE;
2341 break;
2343 case TST_EMPTY_LINE:
2344 if (IsEmptyLine(*m_pCurTextFrame))
2346 if (m_aFlags.bDelEmptyNode && !HasObjects(*m_pCurTextFrame))
2348 bEmptyLine = true;
2349 SwNodeOffset nOldCnt = m_pDoc->GetNodes().Count();
2350 DelEmptyLine();
2351 // Was there really a deletion of a node?
2352 if( nOldCnt != m_pDoc->GetNodes().Count() )
2354 // do not skip the next paragraph
2355 sw::GotoPrevLayoutTextFrame(m_aNdIdx, m_pEditShell->GetLayout());
2358 eStat = READ_NEXT_PARA;
2360 else
2361 eStat = TST_ALPHA_LINE;
2362 break;
2364 case TST_ALPHA_LINE:
2365 if (IsNoAlphaLine(*m_pCurTextFrame))
2367 // recognize a table definition +---+---+
2368 if( m_aFlags.bAFormatByInput && m_aFlags.bCreateTable && DoTable() )
2370 //JP 30.09.96: DoTable() builds on PopCursor and MoveCursor after AutoFormat!
2371 pEdShell->Pop(SwCursorShell::PopMode::DeleteCurrent);
2372 static_cast<SwPaM&>(*pEdShell->GetCursor()) = m_aDelPam;
2373 pEdShell->Push();
2375 eStat = IS_END;
2376 break;
2379 // Check for 3 "---" or "===". In this case, the previous paragraph should be
2380 // underlined and the current be deleted!
2381 if( !DoUnderline() && bReplaceStyles )
2383 SetColl( RES_POOLCOLL_STANDARD, true );
2384 bEmptyLine = true;
2386 eStat = READ_NEXT_PARA;
2388 else
2389 eStat = GET_ALL_INFO;
2390 break;
2392 case GET_ALL_INFO:
2394 if (m_pCurTextFrame->GetTextNodeForParaProps()->GetNumRule())
2396 // do nothing in numbering, go to next
2397 bEmptyLine = false;
2398 eStat = READ_NEXT_PARA;
2399 // delete all blanks at beginning/end and in between
2400 //JP 29.04.98: first only "all in between"
2401 DelMoreLinesBlanks();
2402 // auto correct paragraphs that fail to enter state HAS_FMTCOLL
2403 AutoCorrect();
2404 break;
2407 aFInfo.SetFrame( m_pCurTextFrame );
2409 // so far: if there were templates assigned, keep these and go to next node
2410 sal_uInt16 nPoolId = m_pCurTextFrame->GetTextNodeForParaProps()->GetTextColl()->GetPoolFormatId();
2411 if( IsPoolUserFormat( nPoolId )
2412 ? !m_aFlags.bChgUserColl
2413 : ( RES_POOLCOLL_STANDARD != nPoolId &&
2414 ( !m_aFlags.bAFormatByInput ||
2415 (RES_POOLCOLL_TEXT_MOVE != nPoolId &&
2416 RES_POOLCOLL_TEXT != nPoolId )) ))
2418 eStat = HAS_FMTCOLL;
2419 break;
2422 // replace custom styles with text body
2423 if ( IsPoolUserFormat( nPoolId ) && m_aFlags.bChgUserColl )
2425 SetColl( RES_POOLCOLL_TEXT, true );
2428 // check for left margin set by the style
2429 if( IsPoolUserFormat( nPoolId ) ||
2430 RES_POOLCOLL_STANDARD == nPoolId )
2432 SvxFirstLineIndentItem const*const pFirstLineIndent(
2433 m_pCurTextFrame->GetTextNodeForParaProps()
2434 ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_FIRSTLINE));
2435 SvxTextLeftMarginItem const*const pTextLeftMargin(
2436 m_pCurTextFrame->GetTextNodeForParaProps()
2437 ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_TEXTLEFT));
2438 short nSz(pFirstLineIndent ? pFirstLineIndent->GetTextFirstLineOffset() : 0);
2439 if (0 != nSz ||
2440 (pTextLeftMargin && 0 != pTextLeftMargin->GetTextLeft()))
2442 // exception: numbering/enumeration can have an indentation
2443 if (IsEnumericChar(*m_pCurTextFrame))
2445 nLevel = CalcLevel(*m_pCurTextFrame, &nDigitLvl);
2446 if( nLevel >= MAXLEVEL )
2447 nLevel = MAXLEVEL-1;
2448 BuildEnum( nLevel, nDigitLvl );
2449 eStat = READ_NEXT_PARA;
2450 break;
2453 // never merge (maybe only indent as exception)
2454 m_bMoreLines = true;
2456 if( bReplaceStyles )
2458 // then use one of our templates
2459 if( 0 < nSz ) // positive 1st line indentation
2460 BuildIndent();
2461 else if( 0 > nSz ) // negative 1st line indentation
2462 BuildNegIndent( aFInfo.GetLineStart() );
2463 else if (pTextLeftMargin && pTextLeftMargin->GetTextLeft() != 0) // is indentation
2464 BuildTextIndent();
2466 eStat = READ_NEXT_PARA;
2467 break;
2471 nLevel = CalcLevel( *m_pCurTextFrame, &nDigitLvl );
2472 m_bMoreLines = !IsOneLine(*m_pCurTextFrame);
2473 // note: every use of pNextFrame in following states, until the
2474 // next READ_NEXT_PARA, relies on this update
2475 pNextFrame = GetNextNode();
2476 if (pNextFrame)
2478 bNxtEmpty = IsEmptyLine(*pNextFrame);
2479 bNxtAlpha = IsNoAlphaLine(*pNextFrame);
2480 nNxtLevel = CalcLevel(*pNextFrame);
2482 if (!bEmptyLine && HasBreakAttr(*m_pCurTextFrame))
2483 bEmptyLine = true;
2484 if (!bNxtEmpty && HasBreakAttr(*pNextFrame))
2485 bNxtEmpty = true;
2488 else
2490 bNxtEmpty = false;
2491 bNxtAlpha = false;
2492 nNxtLevel = 0;
2494 eStat = !m_bMoreLines ? IS_ONE_LINE : TST_ENUMERIC;
2496 break;
2498 case IS_ONE_LINE:
2500 eStat = TST_ENUMERIC;
2501 if( !bReplaceStyles )
2502 break;
2504 const OUString sClrStr( DelLeadingBlanks(m_pCurTextFrame->GetText()) );
2506 if( sClrStr.isEmpty() )
2508 bEmptyLine = true;
2509 eStat = READ_NEXT_PARA;
2510 break; // read next paragraph
2513 // check if headline
2514 if (!bEmptyLine || !IsFirstCharCapital(*m_pCurTextFrame)
2515 || IsBlanksInString(*m_pCurTextFrame))
2516 break;
2518 bEmptyLine = false;
2519 const OUString sEndClrStr( DelTrailingBlanks(sClrStr) );
2520 const sal_Unicode cLast = sEndClrStr[sEndClrStr.getLength() - 1];
2522 // not, then check if headline
2523 if( ':' == cLast )
2525 BuildHeadLine( 2 );
2526 eStat = READ_NEXT_PARA;
2527 break;
2529 else if( 256 <= cLast || !strchr( ",.;", cLast ) )
2531 if( bNxtEmpty || bNxtAlpha
2532 || (pNextFrame && IsEnumericChar(*pNextFrame)))
2535 // one level below?
2536 if( nLevel >= MAXLEVEL )
2537 nLevel = MAXLEVEL-1;
2539 if( USHRT_MAX == nLastHeadLvl )
2540 nLastHeadLvl = 0;
2541 else if( nLastCalcHeadLvl < nLevel )
2543 if( nLastHeadLvl+1 < MAXLEVEL )
2544 ++nLastHeadLvl;
2546 // one level above?
2547 else if( nLastCalcHeadLvl > nLevel )
2549 if( nLastHeadLvl )
2550 --nLastHeadLvl;
2552 nLastCalcHeadLvl = nLevel;
2554 if( m_aFlags.bAFormatByInput )
2555 BuildHeadLine( nLevel );
2556 else
2557 BuildHeadLine( nLastHeadLvl );
2558 eStat = READ_NEXT_PARA;
2559 break;
2563 break;
2565 case TST_ENUMERIC:
2567 bEmptyLine = false;
2568 if (IsEnumericChar(*m_pCurTextFrame))
2570 if( nLevel >= MAXLEVEL )
2571 nLevel = MAXLEVEL-1;
2572 BuildEnum( nLevel, nDigitLvl );
2573 eStat = READ_NEXT_PARA;
2575 else if( bReplaceStyles )
2576 eStat = nLevel ? TST_IDENT : TST_NEG_IDENT;
2577 else
2578 eStat = READ_NEXT_PARA;
2580 break;
2582 case TST_IDENT:
2583 // Spaces at the beginning, check again for indentation
2584 if( m_bMoreLines && nLevel )
2586 SwTwips nSz = aFInfo.GetFirstIndent();
2587 if( 0 < nSz ) // positive 1st line indentation
2588 BuildIndent();
2589 else if( 0 > nSz ) // negative 1st line indentation
2590 BuildNegIndent( aFInfo.GetLineStart() );
2591 else // is indentation
2592 BuildTextIndent();
2593 eStat = READ_NEXT_PARA;
2595 else if (nLevel && pNextFrame &&
2596 !bNxtEmpty && !bNxtAlpha && !nNxtLevel &&
2597 !IsEnumericChar(*pNextFrame))
2599 // is an indentation
2600 BuildIndent();
2601 eStat = READ_NEXT_PARA;
2603 else
2604 eStat = TST_TXT_BODY;
2605 break;
2607 case TST_NEG_IDENT:
2608 // no spaces at the beginning, check again for negative indentation
2610 if( m_bMoreLines && !nLevel )
2612 SwTwips nSz = aFInfo.GetFirstIndent();
2613 if( 0 < nSz ) // positive 1st line indentation
2614 BuildIndent();
2615 else if( 0 > nSz ) // negative 1st line indentation
2616 BuildNegIndent( aFInfo.GetLineStart() );
2617 else // is _no_ indentation
2618 BuildText();
2619 eStat = READ_NEXT_PARA;
2621 else if (!nLevel && pNextFrame &&
2622 !bNxtEmpty && !bNxtAlpha && nNxtLevel &&
2623 !IsEnumericChar(*pNextFrame))
2625 // is a negative indentation
2626 BuildNegIndent( aFInfo.GetLineStart() );
2627 eStat = READ_NEXT_PARA;
2629 else
2630 eStat = TST_TXT_BODY;
2632 break;
2634 case TST_TXT_BODY:
2636 if( m_bMoreLines )
2638 SwTwips nSz = aFInfo.GetFirstIndent();
2639 if( 0 < nSz ) // positive 1st line indentation
2640 BuildIndent();
2641 else if( 0 > nSz ) // negative 1st line indentation
2642 BuildNegIndent( aFInfo.GetLineStart() );
2643 else if( nLevel ) // is indentation
2644 BuildTextIndent();
2645 else
2646 BuildText();
2648 else if( nLevel )
2649 BuildTextIndent();
2650 else
2651 BuildText();
2652 eStat = READ_NEXT_PARA;
2654 break;
2656 case HAS_FMTCOLL:
2658 // so far: if there were templates assigned, keep these and go to next node
2659 bEmptyLine = false;
2660 eStat = READ_NEXT_PARA;
2661 // delete all blanks at beginning/end and in between
2662 //JP 29.04.98: first only "all in between"
2663 DelMoreLinesBlanks();
2665 // handle hard attributes
2666 if (m_pCurTextFrame->GetTextNodeForParaProps()->HasSwAttrSet())
2668 SvxFirstLineIndentItem const*const pFirstLineIndent(
2669 m_pCurTextFrame->GetTextNodeForParaProps()
2670 ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_FIRSTLINE, false));
2671 SvxTextLeftMarginItem const*const pTextLeftMargin(
2672 m_pCurTextFrame->GetTextNodeForParaProps()
2673 ->GetSwAttrSet().GetItemIfSet(RES_MARGIN_TEXTLEFT, false));
2674 short nSz(pFirstLineIndent ? pFirstLineIndent->GetTextFirstLineOffset() : 0);
2675 if( bReplaceStyles &&
2676 (0 != nSz ||
2677 (pTextLeftMargin && 0 != pTextLeftMargin->GetTextLeft())))
2679 // then use one of our templates
2680 if( 0 < nSz ) // positive 1st line indentation
2681 BuildIndent();
2682 else if( 0 > nSz ) // negative 1st line indentation
2684 BuildNegIndent( aFInfo.GetLineStart() );
2686 else if (pTextLeftMargin && pTextLeftMargin->GetTextLeft()) // is indentation
2687 BuildTextIndent();
2688 else
2689 BuildText();
2692 // force auto correct
2693 AutoCorrect();
2695 break;
2697 case IS_END:
2698 m_bEnd = true;
2699 break;
2703 if( m_aFlags.bWithRedlining )
2704 m_pDoc->SetAutoFormatRedline( false );
2705 m_pDoc->getIDocumentRedlineAccess().SetRedlineFlags( eOldMode );
2707 // restore undo (in case it has been changed)
2708 m_pDoc->GetIDocumentUndoRedo().DoUndo(bUndoState);
2710 // disable display of percentage again
2711 if( !m_aFlags.bAFormatByInput )
2712 ::EndProgress( m_pDoc->GetDocShell() );
2715 void SwEditShell::AutoFormat( const SvxSwAutoFormatFlags* pAFlags )
2717 std::optional<SwWait> oWait;
2719 CurrShell aCurr( this );
2720 StartAllAction();
2721 StartUndo( SwUndoId::AUTOFORMAT );
2723 SvxSwAutoFormatFlags aAFFlags; // use default values or add params?
2724 if( pAFlags )
2726 aAFFlags = *pAFlags;
2727 if( !aAFFlags.bAFormatByInput )
2728 oWait.emplace( *GetDoc()->GetDocShell(), true );
2731 SwPaM* pCursor = GetCursor();
2732 // There are more than one or a selection is open
2733 if( pCursor->GetNext() != pCursor || pCursor->HasMark() )
2735 for(SwPaM& rPaM : GetCursor()->GetRingContainer())
2737 if( rPaM.HasMark() )
2739 SwAutoFormat aFormat( this, aAFFlags, &rPaM.Start()->GetNode(),
2740 &rPaM.End()->GetNode() );
2744 else
2746 SwAutoFormat aFormat( this, std::move(aAFFlags) );
2749 EndUndo( SwUndoId::AUTOFORMAT );
2750 EndAllAction();
2753 void SwEditShell::AutoFormatBySplitNode()
2755 CurrShell aCurr( this );
2756 SwPaM* pCursor = GetCursor();
2757 if( pCursor->IsMultiSelection() || !pCursor->Move( fnMoveBackward, GoInNode ) )
2758 return;
2760 StartAllAction();
2761 StartUndo( SwUndoId::AUTOFORMAT );
2763 bool bRange = false;
2764 pCursor->SetMark();
2765 SwPosition* pMarkPos = pCursor->GetMark();
2766 if( pMarkPos->GetContentIndex() )
2768 pMarkPos->SetContent(0);
2769 bRange = true;
2771 else
2773 // then go one node backwards
2774 SwNodeIndex aNdIdx(pCursor->GetMark()->GetNode());
2775 sw::GotoPrevLayoutTextFrame(aNdIdx, GetLayout());
2776 SwTextNode* pTextNd = aNdIdx.GetNode().GetTextNode();
2777 if (pTextNd && !pTextNd->GetText().isEmpty())
2779 pCursor->GetMark()->Assign( aNdIdx );
2780 bRange = true;
2784 if( bRange )
2786 Push(); // save cursor
2788 SvxSwAutoFormatFlags aAFFlags = *GetAutoFormatFlags(); // use default values so far
2790 SwAutoFormat aFormat( this, std::move(aAFFlags), &pCursor->GetMark()->GetNode(),
2791 &pCursor->GetPoint()->GetNode() );
2792 SvxAutoCorrect* pACorr = SvxAutoCorrCfg::Get().GetAutoCorrect();
2793 if( pACorr && !pACorr->IsAutoCorrFlag( ACFlags::CapitalStartSentence | ACFlags::CapitalStartWord |
2794 ACFlags::AddNonBrkSpace | ACFlags::ChgOrdinalNumber | ACFlags::TransliterateRTL |
2795 ACFlags::ChgToEnEmDash | ACFlags::SetINetAttr | ACFlags::Autocorrect |
2796 ACFlags::SetDOIAttr ))
2797 pACorr = nullptr;
2799 if( pACorr )
2800 AutoCorrect( *pACorr,false, u'\0' );
2802 //JP 30.09.96: DoTable() builds on PopCursor and MoveCursor!
2803 Pop(PopMode::DeleteCurrent);
2804 pCursor = GetCursor();
2806 pCursor->DeleteMark();
2807 pCursor->Move( fnMoveForward, GoInNode );
2809 EndUndo( SwUndoId::AUTOFORMAT );
2810 EndAllAction();
2814 SvxSwAutoFormatFlags* SwEditShell::GetAutoFormatFlags()
2816 if (!s_pAutoFormatFlags)
2817 s_pAutoFormatFlags = new SvxSwAutoFormatFlags;
2819 return s_pAutoFormatFlags;
2822 void SwEditShell::SetAutoFormatFlags(SvxSwAutoFormatFlags const * pFlags)
2824 SvxSwAutoFormatFlags* pEditFlags = GetAutoFormatFlags();
2826 pEditFlags->bSetNumRule = pFlags->bSetNumRule;
2827 pEditFlags->bChgEnumNum = pFlags->bChgEnumNum;
2828 pEditFlags->bSetBorder = pFlags->bSetBorder;
2829 pEditFlags->bCreateTable = pFlags->bCreateTable;
2830 pEditFlags->bReplaceStyles = pFlags->bReplaceStyles;
2831 pEditFlags->bAFormatByInpDelSpacesAtSttEnd =
2832 pFlags->bAFormatByInpDelSpacesAtSttEnd;
2833 pEditFlags->bAFormatByInpDelSpacesBetweenLines =
2834 pFlags->bAFormatByInpDelSpacesBetweenLines;
2836 //JP 15.12.98: copy BulletChar and Font into "normal" ones
2837 // because AutoFormat can only work with the latter!
2838 pEditFlags->cBullet = pFlags->cByInputBullet;
2839 pEditFlags->aBulletFont = pFlags->aByInputBulletFont;
2840 pEditFlags->cByInputBullet = pFlags->cByInputBullet;
2841 pEditFlags->aByInputBulletFont = pFlags->aByInputBulletFont;
2844 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */