sw a11y: clang-format SidebarWinAccessible code
[LibreOffice.git] / vcl / source / edit / textdoc.cxx
bloba6d7bd8c240e9528d4fb1e5c6078e8159e2f762a
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 <memory>
21 #include "textdoc.hxx"
22 #include <osl/diagnose.h>
23 #include <sal/log.hxx>
24 #include <rtl/ustrbuf.hxx>
25 #include <utility>
27 // compare function called by QuickSort
28 static bool CompareStart( const std::unique_ptr<TextCharAttrib>& pFirst, const std::unique_ptr<TextCharAttrib>& pSecond )
30 return pFirst->GetStart() < pSecond->GetStart();
33 TextCharAttrib::TextCharAttrib( const TextAttrib& rAttr, sal_Int32 nStart, sal_Int32 nEnd )
34 : mpAttr(rAttr.Clone())
35 , mnStart(nStart)
36 , mnEnd(nEnd)
40 TextCharAttrib::TextCharAttrib( const TextCharAttrib& rTextCharAttrib )
41 : mpAttr(rTextCharAttrib.mpAttr->Clone())
42 , mnStart(rTextCharAttrib.mnStart)
43 , mnEnd(rTextCharAttrib.mnEnd)
47 TextCharAttribList::TextCharAttribList()
48 : mbHasEmptyAttribs(false)
52 TextCharAttribList::~TextCharAttribList()
54 // PTRARR_DEL
57 void TextCharAttribList::Clear()
59 maAttribs.clear();
62 void TextCharAttribList::InsertAttrib( std::unique_ptr<TextCharAttrib> pAttrib )
64 if ( pAttrib->IsEmpty() )
65 mbHasEmptyAttribs = true;
67 const sal_Int32 nStart = pAttrib->GetStart(); // maybe better for Comp.Opt.
68 bool bInserted = false;
69 auto it = std::find_if(maAttribs.begin(), maAttribs.end(),
70 [nStart](std::unique_ptr<TextCharAttrib>& rAttrib) { return rAttrib->GetStart() > nStart; });
71 if (it != maAttribs.end())
73 maAttribs.insert( it, std::move(pAttrib) );
74 bInserted = true;
76 if ( !bInserted )
77 maAttribs.push_back( std::move(pAttrib) );
80 void TextCharAttribList::ResortAttribs()
82 std::sort( maAttribs.begin(), maAttribs.end(), CompareStart );
85 TextCharAttrib* TextCharAttribList::FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos )
87 for (std::vector<std::unique_ptr<TextCharAttrib> >::reverse_iterator it = maAttribs.rbegin(); it != maAttribs.rend(); ++it)
89 if ( (*it)->GetEnd() < nPos )
90 return nullptr;
92 if ( ( (*it)->Which() == nWhich ) && (*it)->IsIn(nPos) )
93 return it->get();
95 return nullptr;
98 bool TextCharAttribList::HasBoundingAttrib( sal_Int32 nBound )
100 for (std::vector<std::unique_ptr<TextCharAttrib> >::reverse_iterator it = maAttribs.rbegin(); it != maAttribs.rend(); ++it)
102 if ( (*it)->GetEnd() < nBound )
103 return false;
105 if ( ( (*it)->GetStart() == nBound ) || ( (*it)->GetEnd() == nBound ) )
106 return true;
108 return false;
111 TextCharAttrib* TextCharAttribList::FindEmptyAttrib( sal_uInt16 nWhich, sal_Int32 nPos )
113 if ( !mbHasEmptyAttribs )
114 return nullptr;
116 for (auto const& attrib : maAttribs)
118 if ( attrib->GetStart() > nPos )
119 return nullptr;
121 if ( ( attrib->GetStart() == nPos ) && ( attrib->GetEnd() == nPos ) && ( attrib->Which() == nWhich ) )
122 return attrib.get();
124 return nullptr;
127 void TextCharAttribList::DeleteEmptyAttribs()
129 std::erase_if(
130 maAttribs,
131 [] (const std::unique_ptr<TextCharAttrib>& rAttrib) { return rAttrib->IsEmpty(); } );
132 mbHasEmptyAttribs = false;
135 TextNode::TextNode( OUString aText ) :
136 maText(std::move( aText ))
140 void TextNode::ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNew )
142 if ( !nNew )
143 return;
145 bool bResort = false;
146 sal_uInt16 nAttribs = maCharAttribs.Count();
147 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
149 TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
150 if ( rAttrib.GetEnd() >= nIndex )
152 // move all attributes that are behind the cursor
153 if ( rAttrib.GetStart() > nIndex )
155 rAttrib.MoveForward( nNew );
157 // 0: expand empty attribute, if at cursor
158 else if ( rAttrib.IsEmpty() )
160 // Do not check the index; empty one may only be here.
161 // If checking later anyway, special case:
162 // Start == 0; AbsLen == 1, nNew = 1 => Expand due to new paragraph!
163 // Start <= nIndex, End >= nIndex => Start=End=nIndex!
164 rAttrib.Expand( nNew );
166 // 1: attribute starts before and reaches up to index
167 else if ( rAttrib.GetEnd() == nIndex ) // start must be before
169 // Only expand if no feature and not in Exclude list!
170 // Otherwise e.g. an UL would go until the new ULDB, thus expand both.
171 if ( !maCharAttribs.FindEmptyAttrib( rAttrib.Which(), nIndex ) )
173 rAttrib.Expand( nNew );
175 else
176 bResort = true;
178 // 2: attribute starts before and reaches past the index
179 else if ( ( rAttrib.GetStart() < nIndex ) && ( rAttrib.GetEnd() > nIndex ) )
181 rAttrib.Expand( nNew );
183 // 3: attribute starts at Index
184 else if ( rAttrib.GetStart() == nIndex )
186 if ( nIndex == 0 )
188 rAttrib.Expand( nNew );
190 else
191 rAttrib.MoveForward( nNew );
195 SAL_WARN_IF( rAttrib.GetStart() > rAttrib.GetEnd(), "vcl", "Expand: attribute twisted!" );
196 SAL_WARN_IF( ( rAttrib.GetEnd() > maText.getLength() ), "vcl", "Expand: attribute greater than paragraph!" );
197 SAL_WARN_IF( rAttrib.IsEmpty(), "vcl", "Empty attribute after ExpandAttribs?" );
200 if ( bResort )
201 maCharAttribs.ResortAttribs();
204 void TextNode::CollapseAttribs( sal_Int32 nIndex, sal_Int32 nDeleted )
206 if ( !nDeleted )
207 return;
209 bool bResort = false;
210 const sal_Int32 nEndChanges = nIndex+nDeleted;
212 for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
214 TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
215 bool bDelAttr = false;
216 if ( rAttrib.GetEnd() >= nIndex )
218 // move all attributes that are behind the cursor
219 if ( rAttrib.GetStart() >= nEndChanges )
221 rAttrib.MoveBackward( nDeleted );
223 // 1. delete inner attributes
224 else if ( ( rAttrib.GetStart() >= nIndex ) && ( rAttrib.GetEnd() <= nEndChanges ) )
226 // special case: attribute covers the region exactly
227 // => keep as an empty attribute
228 if ( ( rAttrib.GetStart() == nIndex ) && ( rAttrib.GetEnd() == nEndChanges ) )
229 rAttrib.SetEnd(nIndex); // empty
230 else
231 bDelAttr = true;
233 // 2. attribute starts before, ends inside or after
234 else if ( ( rAttrib.GetStart() <= nIndex ) && ( rAttrib.GetEnd() > nIndex ) )
236 if ( rAttrib.GetEnd() <= nEndChanges ) // ends inside
237 rAttrib.SetEnd(nIndex);
238 else
239 rAttrib.Collaps( nDeleted ); // ends after
241 // 3. attribute starts inside, ends after
242 else if ( ( rAttrib.GetStart() >= nIndex ) && ( rAttrib.GetEnd() > nEndChanges ) )
244 // features are not allowed to expand!
245 rAttrib.SetStart(nEndChanges);
246 rAttrib.MoveBackward( nDeleted );
250 SAL_WARN_IF( rAttrib.GetStart() > rAttrib.GetEnd(), "vcl", "Collaps: attribute twisted!" );
251 SAL_WARN_IF( ( rAttrib.GetEnd() > maText.getLength()) && !bDelAttr, "vcl", "Collaps: attribute greater than paragraph!" );
252 if ( bDelAttr /* || rAttrib.IsEmpty() */ )
254 bResort = true;
255 maCharAttribs.RemoveAttrib( nAttr );
256 nAttr--;
258 else if ( rAttrib.IsEmpty() )
259 maCharAttribs.HasEmptyAttribs() = true;
262 if ( bResort )
263 maCharAttribs.ResortAttribs();
266 void TextNode::InsertText( sal_Int32 nPos, std::u16string_view rText )
268 maText = maText.replaceAt( nPos, 0, rText );
269 ExpandAttribs( nPos, rText.size() );
272 void TextNode::InsertText( sal_Int32 nPos, sal_Unicode c )
274 maText = maText.replaceAt( nPos, 0, rtl::OUStringChar(c) );
275 ExpandAttribs( nPos, 1 );
278 void TextNode::RemoveText( sal_Int32 nPos, sal_Int32 nChars )
280 maText = maText.replaceAt( nPos, nChars, u"" );
281 CollapseAttribs( nPos, nChars );
284 std::unique_ptr<TextNode> TextNode::Split( sal_Int32 nPos )
286 OUString aNewText;
287 if ( nPos < maText.getLength() )
289 aNewText = maText.copy( nPos );
290 maText = maText.copy(0, nPos);
292 std::unique_ptr<TextNode> pNew(new TextNode( aNewText ));
294 for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
296 TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
297 if ( rAttrib.GetEnd() < nPos )
299 // no change
302 else if ( rAttrib.GetEnd() == nPos )
304 // must be copied as an empty attribute
305 // !FindAttrib only sensible if traversing backwards through the list!
306 if ( !pNew->maCharAttribs.FindAttrib( rAttrib.Which(), 0 ) )
308 std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
309 pNewAttrib->SetStart(0);
310 pNewAttrib->SetEnd(0);
311 pNew->maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
314 else if ( rAttrib.IsInside( nPos ) || ( !nPos && !rAttrib.GetStart() ) )
316 // If cutting at the very beginning, the attribute has to be
317 // copied and changed
318 std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
319 pNewAttrib->SetStart(0);
320 pNewAttrib->SetEnd(rAttrib.GetEnd()-nPos);
321 pNew->maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
322 // trim
323 rAttrib.SetEnd(nPos);
325 else
327 SAL_WARN_IF( rAttrib.GetStart() < nPos, "vcl", "Start < nPos!" );
328 SAL_WARN_IF( rAttrib.GetEnd() < nPos, "vcl", "End < nPos!" );
329 // move all into the new node (this)
330 pNew->maCharAttribs.InsertAttrib(maCharAttribs.RemoveAttrib(nAttr));
331 rAttrib.SetStart( rAttrib.GetStart() - nPos );
332 rAttrib.SetEnd( rAttrib.GetEnd() - nPos );
333 nAttr--;
336 return pNew;
339 void TextNode::Append( const TextNode& rNode )
341 sal_Int32 nOldLen = maText.getLength();
343 maText += rNode.GetText();
345 const sal_uInt16 nAttribs = rNode.GetCharAttribs().Count();
346 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
348 const TextCharAttrib& rAttrib = rNode.GetCharAttrib( nAttr );
349 bool bMelted = false;
350 if ( rAttrib.GetStart() == 0 )
352 // potentially merge attributes
353 sal_uInt16 nTmpAttribs = maCharAttribs.Count();
354 for ( sal_uInt16 nTmpAttr = 0; nTmpAttr < nTmpAttribs; nTmpAttr++ )
356 TextCharAttrib& rTmpAttrib = maCharAttribs.GetAttrib( nTmpAttr );
358 if ( rTmpAttrib.GetEnd() == nOldLen )
360 if ( ( rTmpAttrib.Which() == rAttrib.Which() ) &&
361 ( rTmpAttrib.GetAttr() == rAttrib.GetAttr() ) )
363 rTmpAttrib.SetEnd( rTmpAttrib.GetEnd() + rAttrib.GetLen() );
364 bMelted = true;
365 break; // there can be only one of this type at this position
371 if ( !bMelted )
373 std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
374 pNewAttrib->SetStart( pNewAttrib->GetStart() + nOldLen );
375 pNewAttrib->SetEnd( pNewAttrib->GetEnd() + nOldLen );
376 maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
381 TextDoc::TextDoc()
382 : mnLeftMargin(0)
386 TextDoc::~TextDoc()
388 DestroyTextNodes();
391 void TextDoc::Clear()
393 DestroyTextNodes();
396 void TextDoc::DestroyTextNodes()
398 maTextNodes.clear();
401 OUString TextDoc::GetText( const sal_Unicode* pSep ) const
403 sal_uInt32 nNodes = static_cast<sal_uInt32>(maTextNodes.size());
405 OUStringBuffer aASCIIText;
406 const sal_uInt32 nLastNode = nNodes-1;
407 for ( sal_uInt32 nNode = 0; nNode < nNodes; ++nNode )
409 TextNode* pNode = maTextNodes[ nNode ].get();
410 aASCIIText.append(pNode->GetText());
411 if ( pSep && ( nNode != nLastNode ) )
412 aASCIIText.append(pSep);
415 return aASCIIText.makeStringAndClear();
418 OUString TextDoc::GetText( sal_uInt32 nPara ) const
420 TextNode* pNode = ( nPara < maTextNodes.size() ) ? maTextNodes[ nPara ].get() : nullptr;
421 if ( pNode )
422 return pNode->GetText();
424 return OUString();
427 sal_Int32 TextDoc::GetTextLen( const sal_Unicode* pSep, const TextSelection* pSel ) const
429 sal_Int32 nLen = 0;
430 sal_uInt32 nNodes = static_cast<sal_uInt32>(maTextNodes.size());
431 if ( nNodes )
433 sal_uInt32 nStartNode = 0;
434 sal_uInt32 nEndNode = nNodes-1;
435 if ( pSel )
437 nStartNode = pSel->GetStart().GetPara();
438 nEndNode = pSel->GetEnd().GetPara();
441 for ( sal_uInt32 nNode = nStartNode; nNode <= nEndNode; ++nNode )
443 TextNode* pNode = maTextNodes[ nNode ].get();
445 sal_Int32 nS = 0;
446 sal_Int32 nE = pNode->GetText().getLength();
447 if ( pSel && ( nNode == pSel->GetStart().GetPara() ) )
448 nS = pSel->GetStart().GetIndex();
449 if ( pSel && ( nNode == pSel->GetEnd().GetPara() ) )
450 nE = pSel->GetEnd().GetIndex();
452 nLen += ( nE - nS );
455 if ( pSep )
456 nLen += (nEndNode-nStartNode) * rtl_ustr_getLength(pSep);
459 return nLen;
462 TextPaM TextDoc::InsertText( const TextPaM& rPaM, sal_Unicode c )
464 SAL_WARN_IF( c == 0x0A, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
465 SAL_WARN_IF( c == 0x0D, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
467 TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
468 pNode->InsertText( rPaM.GetIndex(), c );
470 TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+1 );
471 return aPaM;
474 TextPaM TextDoc::InsertText( const TextPaM& rPaM, std::u16string_view rStr )
476 SAL_WARN_IF( rStr.find( 0x0A ) != std::u16string_view::npos, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
477 SAL_WARN_IF( rStr.find( 0x0D ) != std::u16string_view::npos, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
479 TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
480 pNode->InsertText( rPaM.GetIndex(), rStr );
482 TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+rStr.size() );
483 return aPaM;
486 TextPaM TextDoc::InsertParaBreak( const TextPaM& rPaM )
488 TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
489 std::unique_ptr<TextNode> pNew = pNode->Split( rPaM.GetIndex() );
491 SAL_WARN_IF( maTextNodes.size()>=SAL_MAX_UINT32, "vcl", "InsertParaBreak: more than 4Gi paragraphs!" );
492 maTextNodes.insert( maTextNodes.begin() + rPaM.GetPara() + 1, std::move(pNew) );
494 TextPaM aPaM( rPaM.GetPara()+1, 0 );
495 return aPaM;
498 TextPaM TextDoc::ConnectParagraphs( TextNode* pLeft, const TextNode* pRight )
500 sal_Int32 nPrevLen = pLeft->GetText().getLength();
501 pLeft->Append( *pRight );
503 // the paragraph on the right vanishes
504 maTextNodes.erase( std::find_if( maTextNodes.begin(), maTextNodes.end(),
505 [&] (std::unique_ptr<TextNode> const & p) { return p.get() == pRight; } ) );
507 sal_Int32 nLeft = ::std::find_if( maTextNodes.begin(), maTextNodes.end(),
508 [&] (std::unique_ptr<TextNode> const & p) { return p.get() == pLeft; } )
509 - maTextNodes.begin();
510 TextPaM aPaM( nLeft, nPrevLen );
511 return aPaM;
514 void TextDoc::RemoveChars( const TextPaM& rPaM, sal_Int32 nChars )
516 TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
517 pNode->RemoveText( rPaM.GetIndex(), nChars );
520 bool TextDoc::IsValidPaM( const TextPaM& rPaM )
522 if ( rPaM.GetPara() >= maTextNodes.size() )
524 OSL_FAIL( "PaM: Para out of range" );
525 return false;
527 TextNode * pNode = maTextNodes[ rPaM.GetPara() ].get();
528 if ( rPaM.GetIndex() > pNode->GetText().getLength() )
530 OSL_FAIL( "PaM: Index out of range" );
531 return false;
533 return true;
536 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */