Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / vcl / source / edit / textdoc.cxx
blob1c54fbbfbf813a43115c868ae273474b58f504fd
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>
25 // compare function called by QuickSort
26 static bool CompareStart( const std::unique_ptr<TextCharAttrib>& pFirst, const std::unique_ptr<TextCharAttrib>& pSecond )
28 return pFirst->GetStart() < pSecond->GetStart();
31 TextCharAttrib::TextCharAttrib( const TextAttrib& rAttr, sal_Int32 nStart, sal_Int32 nEnd )
32 : mpAttr(rAttr.Clone())
33 , mnStart(nStart)
34 , mnEnd(nEnd)
38 TextCharAttrib::TextCharAttrib( const TextCharAttrib& rTextCharAttrib )
39 : mpAttr(rTextCharAttrib.mpAttr->Clone())
40 , mnStart(rTextCharAttrib.mnStart)
41 , mnEnd(rTextCharAttrib.mnEnd)
45 TextCharAttribList::TextCharAttribList()
46 : mbHasEmptyAttribs(false)
50 TextCharAttribList::~TextCharAttribList()
52 // PTRARR_DEL
55 void TextCharAttribList::Clear()
57 maAttribs.clear();
60 void TextCharAttribList::InsertAttrib( std::unique_ptr<TextCharAttrib> pAttrib )
62 if ( pAttrib->IsEmpty() )
63 mbHasEmptyAttribs = true;
65 const sal_Int32 nStart = pAttrib->GetStart(); // maybe better for Comp.Opt.
66 bool bInserted = false;
67 auto it = std::find_if(maAttribs.begin(), maAttribs.end(),
68 [nStart](std::unique_ptr<TextCharAttrib>& rAttrib) { return rAttrib->GetStart() > nStart; });
69 if (it != maAttribs.end())
71 maAttribs.insert( it, std::move(pAttrib) );
72 bInserted = true;
74 if ( !bInserted )
75 maAttribs.push_back( std::move(pAttrib) );
78 void TextCharAttribList::ResortAttribs()
80 std::sort( maAttribs.begin(), maAttribs.end(), CompareStart );
83 TextCharAttrib* TextCharAttribList::FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos )
85 for (std::vector<std::unique_ptr<TextCharAttrib> >::reverse_iterator it = maAttribs.rbegin(); it != maAttribs.rend(); ++it)
87 if ( (*it)->GetEnd() < nPos )
88 return nullptr;
90 if ( ( (*it)->Which() == nWhich ) && (*it)->IsIn(nPos) )
91 return it->get();
93 return nullptr;
96 bool TextCharAttribList::HasBoundingAttrib( sal_Int32 nBound )
98 for (std::vector<std::unique_ptr<TextCharAttrib> >::reverse_iterator it = maAttribs.rbegin(); it != maAttribs.rend(); ++it)
100 if ( (*it)->GetEnd() < nBound )
101 return false;
103 if ( ( (*it)->GetStart() == nBound ) || ( (*it)->GetEnd() == nBound ) )
104 return true;
106 return false;
109 TextCharAttrib* TextCharAttribList::FindEmptyAttrib( sal_uInt16 nWhich, sal_Int32 nPos )
111 if ( !mbHasEmptyAttribs )
112 return nullptr;
114 for (auto const& attrib : maAttribs)
116 if ( attrib->GetStart() > nPos )
117 return nullptr;
119 if ( ( attrib->GetStart() == nPos ) && ( attrib->GetEnd() == nPos ) && ( attrib->Which() == nWhich ) )
120 return attrib.get();
122 return nullptr;
125 void TextCharAttribList::DeleteEmptyAttribs()
127 maAttribs.erase(
128 std::remove_if( maAttribs.begin(), maAttribs.end(),
129 [] (const std::unique_ptr<TextCharAttrib>& rAttrib) { return rAttrib->IsEmpty(); } ),
130 maAttribs.end() );
131 mbHasEmptyAttribs = false;
134 TextNode::TextNode( const OUString& rText ) :
135 maText( rText )
139 void TextNode::ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNew )
141 if ( !nNew )
142 return;
144 bool bResort = false;
145 sal_uInt16 nAttribs = maCharAttribs.Count();
146 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
148 TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
149 if ( rAttrib.GetEnd() >= nIndex )
151 // move all attributes that are behind the cursor
152 if ( rAttrib.GetStart() > nIndex )
154 rAttrib.MoveForward( nNew );
156 // 0: expand empty attribute, if at cursor
157 else if ( rAttrib.IsEmpty() )
159 // Do not check the index; empty one may only be here.
160 // If checking later anyway, special case:
161 // Start == 0; AbsLen == 1, nNew = 1 => Expand due to new paragraph!
162 // Start <= nIndex, End >= nIndex => Start=End=nIndex!
163 rAttrib.Expand( nNew );
165 // 1: attribute starts before and reaches up to index
166 else if ( rAttrib.GetEnd() == nIndex ) // start must be before
168 // Only expand if no feature and not in Exclude list!
169 // Otherwise e.g. an UL would go until the new ULDB, thus expand both.
170 if ( !maCharAttribs.FindEmptyAttrib( rAttrib.Which(), nIndex ) )
172 rAttrib.Expand( nNew );
174 else
175 bResort = true;
177 // 2: attribute starts before and reaches past the index
178 else if ( ( rAttrib.GetStart() < nIndex ) && ( rAttrib.GetEnd() > nIndex ) )
180 rAttrib.Expand( nNew );
182 // 3: attribute starts at Index
183 else if ( rAttrib.GetStart() == nIndex )
185 if ( nIndex == 0 )
187 rAttrib.Expand( nNew );
189 else
190 rAttrib.MoveForward( nNew );
194 SAL_WARN_IF( rAttrib.GetStart() > rAttrib.GetEnd(), "vcl", "Expand: attribute twisted!" );
195 SAL_WARN_IF( ( rAttrib.GetEnd() > maText.getLength() ), "vcl", "Expand: attribute greater than paragraph!" );
196 SAL_WARN_IF( rAttrib.IsEmpty(), "vcl", "Empty attribute after ExpandAttribs?" );
199 if ( bResort )
200 maCharAttribs.ResortAttribs();
203 void TextNode::CollapseAttribs( sal_Int32 nIndex, sal_Int32 nDeleted )
205 if ( !nDeleted )
206 return;
208 bool bResort = false;
209 const sal_Int32 nEndChanges = nIndex+nDeleted;
211 for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
213 TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
214 bool bDelAttr = false;
215 if ( rAttrib.GetEnd() >= nIndex )
217 // move all attributes that are behind the cursor
218 if ( rAttrib.GetStart() >= nEndChanges )
220 rAttrib.MoveBackward( nDeleted );
222 // 1. delete inner attributes
223 else if ( ( rAttrib.GetStart() >= nIndex ) && ( rAttrib.GetEnd() <= nEndChanges ) )
225 // special case: attribute covers the region exactly
226 // => keep as an empty attribute
227 if ( ( rAttrib.GetStart() == nIndex ) && ( rAttrib.GetEnd() == nEndChanges ) )
228 rAttrib.SetEnd(nIndex); // empty
229 else
230 bDelAttr = true;
232 // 2. attribute starts before, ends inside or after
233 else if ( ( rAttrib.GetStart() <= nIndex ) && ( rAttrib.GetEnd() > nIndex ) )
235 if ( rAttrib.GetEnd() <= nEndChanges ) // ends inside
236 rAttrib.SetEnd(nIndex);
237 else
238 rAttrib.Collaps( nDeleted ); // ends after
240 // 3. attribute starts inside, ends after
241 else if ( ( rAttrib.GetStart() >= nIndex ) && ( rAttrib.GetEnd() > nEndChanges ) )
243 // features are not allowed to expand!
244 rAttrib.SetStart(nEndChanges);
245 rAttrib.MoveBackward( nDeleted );
249 SAL_WARN_IF( rAttrib.GetStart() > rAttrib.GetEnd(), "vcl", "Collaps: attribute twisted!" );
250 SAL_WARN_IF( ( rAttrib.GetEnd() > maText.getLength()) && !bDelAttr, "vcl", "Collaps: attribute greater than paragraph!" );
251 if ( bDelAttr /* || rAttrib.IsEmpty() */ )
253 bResort = true;
254 maCharAttribs.RemoveAttrib( nAttr );
255 nAttr--;
257 else if ( rAttrib.IsEmpty() )
258 maCharAttribs.HasEmptyAttribs() = true;
261 if ( bResort )
262 maCharAttribs.ResortAttribs();
265 void TextNode::InsertText( sal_Int32 nPos, const OUString& rText )
267 maText = maText.replaceAt( nPos, 0, rText );
268 ExpandAttribs( nPos, rText.getLength() );
271 void TextNode::InsertText( sal_Int32 nPos, sal_Unicode c )
273 maText = maText.replaceAt( nPos, 0, OUString(c) );
274 ExpandAttribs( nPos, 1 );
277 void TextNode::RemoveText( sal_Int32 nPos, sal_Int32 nChars )
279 maText = maText.replaceAt( nPos, nChars, "" );
280 CollapseAttribs( nPos, nChars );
283 std::unique_ptr<TextNode> TextNode::Split( sal_Int32 nPos )
285 OUString aNewText;
286 if ( nPos < maText.getLength() )
288 aNewText = maText.copy( nPos );
289 maText = maText.copy(0, nPos);
291 std::unique_ptr<TextNode> pNew(new TextNode( aNewText ));
293 for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
295 TextCharAttrib& rAttrib = maCharAttribs.GetAttrib( nAttr );
296 if ( rAttrib.GetEnd() < nPos )
298 // no change
301 else if ( rAttrib.GetEnd() == nPos )
303 // must be copied as an empty attribute
304 // !FindAttrib only sensible if traversing backwards through the list!
305 if ( !pNew->maCharAttribs.FindAttrib( rAttrib.Which(), 0 ) )
307 std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
308 pNewAttrib->SetStart(0);
309 pNewAttrib->SetEnd(0);
310 pNew->maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
313 else if ( rAttrib.IsInside( nPos ) || ( !nPos && !rAttrib.GetStart() ) )
315 // If cutting at the very beginning, the attribute has to be
316 // copied and changed
317 std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
318 pNewAttrib->SetStart(0);
319 pNewAttrib->SetEnd(rAttrib.GetEnd()-nPos);
320 pNew->maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
321 // trim
322 rAttrib.SetEnd(nPos);
324 else
326 SAL_WARN_IF( rAttrib.GetStart() < nPos, "vcl", "Start < nPos!" );
327 SAL_WARN_IF( rAttrib.GetEnd() < nPos, "vcl", "End < nPos!" );
328 // move all into the new node (this)
329 pNew->maCharAttribs.InsertAttrib(maCharAttribs.RemoveAttrib(nAttr));
330 rAttrib.SetStart( rAttrib.GetStart() - nPos );
331 rAttrib.SetEnd( rAttrib.GetEnd() - nPos );
332 nAttr--;
335 return pNew;
338 void TextNode::Append( const TextNode& rNode )
340 sal_Int32 nOldLen = maText.getLength();
342 maText += rNode.GetText();
344 const sal_uInt16 nAttribs = rNode.GetCharAttribs().Count();
345 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
347 const TextCharAttrib& rAttrib = rNode.GetCharAttrib( nAttr );
348 bool bMelted = false;
349 if ( rAttrib.GetStart() == 0 )
351 // potentially merge attributes
352 sal_uInt16 nTmpAttribs = maCharAttribs.Count();
353 for ( sal_uInt16 nTmpAttr = 0; nTmpAttr < nTmpAttribs; nTmpAttr++ )
355 TextCharAttrib& rTmpAttrib = maCharAttribs.GetAttrib( nTmpAttr );
357 if ( rTmpAttrib.GetEnd() == nOldLen )
359 if ( ( rTmpAttrib.Which() == rAttrib.Which() ) &&
360 ( rTmpAttrib.GetAttr() == rAttrib.GetAttr() ) )
362 rTmpAttrib.SetEnd( rTmpAttrib.GetEnd() + rAttrib.GetLen() );
363 bMelted = true;
364 break; // there can be only one of this type at this position
370 if ( !bMelted )
372 std::unique_ptr<TextCharAttrib> pNewAttrib(new TextCharAttrib( rAttrib ));
373 pNewAttrib->SetStart( pNewAttrib->GetStart() + nOldLen );
374 pNewAttrib->SetEnd( pNewAttrib->GetEnd() + nOldLen );
375 maCharAttribs.InsertAttrib( std::move(pNewAttrib) );
380 TextDoc::TextDoc()
381 : mnLeftMargin(0)
385 TextDoc::~TextDoc()
387 DestroyTextNodes();
390 void TextDoc::Clear()
392 DestroyTextNodes();
395 void TextDoc::DestroyTextNodes()
397 maTextNodes.clear();
400 OUString TextDoc::GetText( const sal_Unicode* pSep ) const
402 sal_uInt32 nNodes = static_cast<sal_uInt32>(maTextNodes.size());
404 OUStringBuffer aASCIIText;
405 const sal_uInt32 nLastNode = nNodes-1;
406 for ( sal_uInt32 nNode = 0; nNode < nNodes; ++nNode )
408 TextNode* pNode = maTextNodes[ nNode ].get();
409 aASCIIText.append(pNode->GetText());
410 if ( pSep && ( nNode != nLastNode ) )
411 aASCIIText.append(pSep);
414 return aASCIIText.makeStringAndClear();
417 OUString TextDoc::GetText( sal_uInt32 nPara ) const
419 TextNode* pNode = ( nPara < maTextNodes.size() ) ? maTextNodes[ nPara ].get() : nullptr;
420 if ( pNode )
421 return pNode->GetText();
423 return OUString();
426 sal_Int32 TextDoc::GetTextLen( const sal_Unicode* pSep, const TextSelection* pSel ) const
428 sal_Int32 nLen = 0;
429 sal_uInt32 nNodes = static_cast<sal_uInt32>(maTextNodes.size());
430 if ( nNodes )
432 sal_uInt32 nStartNode = 0;
433 sal_uInt32 nEndNode = nNodes-1;
434 if ( pSel )
436 nStartNode = pSel->GetStart().GetPara();
437 nEndNode = pSel->GetEnd().GetPara();
440 for ( sal_uInt32 nNode = nStartNode; nNode <= nEndNode; ++nNode )
442 TextNode* pNode = maTextNodes[ nNode ].get();
444 sal_Int32 nS = 0;
445 sal_Int32 nE = pNode->GetText().getLength();
446 if ( pSel && ( nNode == pSel->GetStart().GetPara() ) )
447 nS = pSel->GetStart().GetIndex();
448 if ( pSel && ( nNode == pSel->GetEnd().GetPara() ) )
449 nE = pSel->GetEnd().GetIndex();
451 nLen += ( nE - nS );
454 if ( pSep )
455 nLen += (nEndNode-nStartNode) * rtl_ustr_getLength(pSep);
458 return nLen;
461 TextPaM TextDoc::InsertText( const TextPaM& rPaM, sal_Unicode c )
463 SAL_WARN_IF( c == 0x0A, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
464 SAL_WARN_IF( c == 0x0D, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
466 TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
467 pNode->InsertText( rPaM.GetIndex(), c );
469 TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+1 );
470 return aPaM;
473 TextPaM TextDoc::InsertText( const TextPaM& rPaM, const OUString& rStr )
475 SAL_WARN_IF( rStr.indexOf( 0x0A ) != -1, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
476 SAL_WARN_IF( rStr.indexOf( 0x0D ) != -1, "vcl", "TextDoc::InsertText: Line separator in paragraph not allowed!" );
478 TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
479 pNode->InsertText( rPaM.GetIndex(), rStr );
481 TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+rStr.getLength() );
482 return aPaM;
485 TextPaM TextDoc::InsertParaBreak( const TextPaM& rPaM )
487 TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
488 std::unique_ptr<TextNode> pNew = pNode->Split( rPaM.GetIndex() );
490 SAL_WARN_IF( maTextNodes.size()>=SAL_MAX_UINT32, "vcl", "InsertParaBreak: more than 4Gi paragraphs!" );
491 maTextNodes.insert( maTextNodes.begin() + rPaM.GetPara() + 1, std::move(pNew) );
493 TextPaM aPaM( rPaM.GetPara()+1, 0 );
494 return aPaM;
497 TextPaM TextDoc::ConnectParagraphs( TextNode* pLeft, const TextNode* pRight )
499 sal_Int32 nPrevLen = pLeft->GetText().getLength();
500 pLeft->Append( *pRight );
502 // the paragraph on the right vanishes
503 maTextNodes.erase( std::find_if( maTextNodes.begin(), maTextNodes.end(),
504 [&] (std::unique_ptr<TextNode> const & p) { return p.get() == pRight; } ) );
506 sal_uLong nLeft = ::std::find_if( maTextNodes.begin(), maTextNodes.end(),
507 [&] (std::unique_ptr<TextNode> const & p) { return p.get() == pLeft; } )
508 - maTextNodes.begin();
509 TextPaM aPaM( nLeft, nPrevLen );
510 return aPaM;
513 void TextDoc::RemoveChars( const TextPaM& rPaM, sal_Int32 nChars )
515 TextNode* pNode = maTextNodes[ rPaM.GetPara() ].get();
516 pNode->RemoveText( rPaM.GetIndex(), nChars );
519 bool TextDoc::IsValidPaM( const TextPaM& rPaM )
521 if ( rPaM.GetPara() >= maTextNodes.size() )
523 OSL_FAIL( "PaM: Para out of range" );
524 return false;
526 TextNode * pNode = maTextNodes[ rPaM.GetPara() ].get();
527 if ( rPaM.GetIndex() > pNode->GetText().getLength() )
529 OSL_FAIL( "PaM: Index out of range" );
530 return false;
532 return true;
535 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */