update credits
[LibreOffice.git] / vcl / source / edit / textdoc.cxx
blob5ef6cf13dec53d0a4799f8ad63ef8f6b39849157
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 <textdoc.hxx>
22 #include <stdlib.h>
25 // compare function called by QuickSort
26 static bool CompareStart( const TextCharAttrib* pFirst, const TextCharAttrib* pSecond )
28 return pFirst->GetStart() < pSecond->GetStart();
31 TextCharAttrib::TextCharAttrib( const TextAttrib& rAttr, sal_uInt16 nStart, sal_uInt16 nEnd )
33 mpAttr = rAttr.Clone();
34 mnStart = nStart,
35 mnEnd = nEnd;
38 TextCharAttrib::TextCharAttrib( const TextCharAttrib& rTextCharAttrib )
40 mpAttr = rTextCharAttrib.GetAttr().Clone();
41 mnStart = rTextCharAttrib.mnStart;
42 mnEnd = rTextCharAttrib.mnEnd;
45 TextCharAttrib::~TextCharAttrib()
47 delete mpAttr;
50 TextCharAttribList::TextCharAttribList()
52 mbHasEmptyAttribs = sal_False;
55 TextCharAttribList::~TextCharAttribList()
57 // PTRARR_DEL
60 void TextCharAttribList::Clear( sal_Bool bDestroyAttribs )
62 if ( bDestroyAttribs )
63 for(iterator it = begin(); it != end(); ++it)
64 delete *it;
65 TextCharAttribs::clear();
69 void TextCharAttribList::InsertAttrib( TextCharAttrib* pAttrib )
71 if ( pAttrib->IsEmpty() )
72 mbHasEmptyAttribs = sal_True;
74 const sal_uInt16 nCount = size();
75 const sal_uInt16 nStart = pAttrib->GetStart(); // maybe better for Comp.Opt.
76 bool bInserted = false;
77 for ( sal_uInt16 x = 0; x < nCount; x++ )
79 TextCharAttrib* pCurAttrib = GetAttrib( x );
80 if ( pCurAttrib->GetStart() > nStart )
82 insert( begin() + x, pAttrib );
83 bInserted = true;
84 break;
87 if ( !bInserted )
88 push_back( pAttrib );
91 void TextCharAttribList::ResortAttribs()
93 if ( !empty() )
94 std::sort( begin(), end(), CompareStart );
97 TextCharAttrib* TextCharAttribList::FindAttrib( sal_uInt16 nWhich, sal_uInt16 nPos )
99 // backwards; if one ends there and the next starts there
100 // ==> the starting one counts
101 for ( sal_uInt16 nAttr = size(); nAttr; )
103 TextCharAttrib* pAttr = GetAttrib( --nAttr );
105 if ( pAttr->GetEnd() < nPos )
106 return 0;
108 if ( ( pAttr->Which() == nWhich ) && pAttr->IsIn(nPos) )
109 return pAttr;
111 return NULL;
114 TextCharAttrib* TextCharAttribList::FindNextAttrib( sal_uInt16 nWhich, sal_uInt16 nFromPos, sal_uInt16 nMaxPos ) const
116 DBG_ASSERT( nWhich, "FindNextAttrib: Which?" );
117 const sal_uInt16 nAttribs = size();
118 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
120 TextCharAttrib* pAttr = GetAttrib( nAttr );
121 if ( ( pAttr->GetStart() >= nFromPos ) &&
122 ( pAttr->GetEnd() <= nMaxPos ) &&
123 ( pAttr->Which() == nWhich ) )
124 return pAttr;
126 return NULL;
129 sal_Bool TextCharAttribList::HasAttrib( sal_uInt16 nWhich ) const
131 for ( sal_uInt16 nAttr = size(); nAttr; )
133 const TextCharAttrib* pAttr = GetAttrib( --nAttr );
134 if ( pAttr->Which() == nWhich )
135 return sal_True;
137 return sal_False;
140 sal_Bool TextCharAttribList::HasBoundingAttrib( sal_uInt16 nBound )
142 // backwards; if one ends there and the next starts there
143 // ==> the starting one counts
144 for ( sal_uInt16 nAttr = size(); nAttr; )
146 TextCharAttrib* pAttr = GetAttrib( --nAttr );
148 if ( pAttr->GetEnd() < nBound )
149 return sal_False;
151 if ( ( pAttr->GetStart() == nBound ) || ( pAttr->GetEnd() == nBound ) )
152 return sal_True;
154 return sal_False;
157 TextCharAttrib* TextCharAttribList::FindEmptyAttrib( sal_uInt16 nWhich, sal_uInt16 nPos )
159 if ( !mbHasEmptyAttribs )
160 return 0;
162 const sal_uInt16 nAttribs = size();
163 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
165 TextCharAttrib* pAttr = GetAttrib( nAttr );
166 if ( pAttr->GetStart() > nPos )
167 return 0;
169 if ( ( pAttr->GetStart() == nPos ) && ( pAttr->GetEnd() == nPos ) && ( pAttr->Which() == nWhich ) )
170 return pAttr;
172 return 0;
175 void TextCharAttribList::DeleteEmptyAttribs()
177 for ( sal_uInt16 nAttr = 0; nAttr < size(); nAttr++ )
179 TextCharAttrib* pAttr = GetAttrib( nAttr );
180 if ( pAttr->IsEmpty() )
182 erase( begin() + nAttr );
183 delete pAttr;
184 nAttr--;
187 mbHasEmptyAttribs = sal_False;
190 TextNode::TextNode( const String& rText ) :
191 maText( rText )
195 void TextNode::ExpandAttribs( sal_uInt16 nIndex, sal_uInt16 nNew )
197 if ( !nNew )
198 return;
200 bool bResort = false;
201 sal_uInt16 nAttribs = maCharAttribs.Count();
202 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
204 TextCharAttrib* pAttrib = maCharAttribs.GetAttrib( nAttr );
205 if ( pAttrib->GetEnd() >= nIndex )
207 // move all attributes that are behind the cursor
208 if ( pAttrib->GetStart() > nIndex )
210 pAttrib->MoveForward( nNew );
212 // 0: expand empty attribute, if at cursor
213 else if ( pAttrib->IsEmpty() )
215 // Do not check the index; empty one may only be here.
216 // If checking later anyway, special case:
217 // Start == 0; AbsLen == 1, nNew = 1 => Expand due to new paragraph!
218 // Start <= nIndex, End >= nIndex => Start=End=nIndex!
219 pAttrib->Expand( nNew );
221 // 1: attribute starts before and reaches up to index
222 else if ( pAttrib->GetEnd() == nIndex ) // start must be before
224 // Only expand if no feature and not in Exclude list!
225 // Otherwise e.g. an UL would go until the new ULDB, thus expand both.
226 if ( !maCharAttribs.FindEmptyAttrib( pAttrib->Which(), nIndex ) )
228 pAttrib->Expand( nNew );
230 else
231 bResort = true;
233 // 2: attribute starts before and reaches past the index
234 else if ( ( pAttrib->GetStart() < nIndex ) && ( pAttrib->GetEnd() > nIndex ) )
236 pAttrib->Expand( nNew );
238 // 3: attribute starts at Index
239 else if ( pAttrib->GetStart() == nIndex )
241 if ( nIndex == 0 )
243 pAttrib->Expand( nNew );
245 else
246 pAttrib->MoveForward( nNew );
250 DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Expand: Attribut verdreht!" );
251 DBG_ASSERT( ( pAttrib->GetEnd() <= maText.Len() ), "Expand: Attrib groesser als Absatz!" );
252 DBG_ASSERT( !pAttrib->IsEmpty(), "Leeres Attribut nach ExpandAttribs?" );
255 if ( bResort )
256 maCharAttribs.ResortAttribs();
259 void TextNode::CollapsAttribs( sal_uInt16 nIndex, sal_uInt16 nDeleted )
261 if ( !nDeleted )
262 return;
264 bool bResort = false;
265 sal_uInt16 nEndChanges = nIndex+nDeleted;
267 for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
269 TextCharAttrib* pAttrib = maCharAttribs.GetAttrib( nAttr );
270 bool bDelAttr = false;
271 if ( pAttrib->GetEnd() >= nIndex )
273 // move all attributes that are behind the cursor
274 if ( pAttrib->GetStart() >= nEndChanges )
276 pAttrib->MoveBackward( nDeleted );
278 // 1. delete inner attributes
279 else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() <= nEndChanges ) )
281 // special case: attribute covers the region exactly
282 // => keep as an empty attribute
283 if ( ( pAttrib->GetStart() == nIndex ) && ( pAttrib->GetEnd() == nEndChanges ) )
284 pAttrib->GetEnd() = nIndex; // empty
285 else
286 bDelAttr = true;
288 // 2. attribute starts before, ends inside or after
289 else if ( ( pAttrib->GetStart() <= nIndex ) && ( pAttrib->GetEnd() > nIndex ) )
291 if ( pAttrib->GetEnd() <= nEndChanges ) // ends inside
292 pAttrib->GetEnd() = nIndex;
293 else
294 pAttrib->Collaps( nDeleted ); // ends after
296 // 3. attribute starts inside, ends after
297 else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() > nEndChanges ) )
299 // features are not allowed to expand!
300 pAttrib->GetStart() = nEndChanges;
301 pAttrib->MoveBackward( nDeleted );
305 DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Collaps: Attribut verdreht!" );
306 DBG_ASSERT( ( pAttrib->GetEnd() <= maText.Len()) || bDelAttr, "Collaps: Attrib groesser als Absatz!" );
307 if ( bDelAttr /* || pAttrib->IsEmpty() */ )
309 bResort = true;
310 maCharAttribs.RemoveAttrib( nAttr );
311 delete pAttrib;
312 nAttr--;
314 else if ( pAttrib->IsEmpty() )
315 maCharAttribs.HasEmptyAttribs() = sal_True;
318 if ( bResort )
319 maCharAttribs.ResortAttribs();
322 void TextNode::InsertText( sal_uInt16 nPos, const String& rText )
324 maText.Insert( rText, nPos );
325 ExpandAttribs( nPos, rText.Len() );
328 void TextNode::InsertText( sal_uInt16 nPos, sal_Unicode c )
330 maText.Insert( c, nPos );
331 ExpandAttribs( nPos, 1 );
334 void TextNode::RemoveText( sal_uInt16 nPos, sal_uInt16 nChars )
336 maText.Erase( nPos, nChars );
337 CollapsAttribs( nPos, nChars );
340 TextNode* TextNode::Split( sal_uInt16 nPos, sal_Bool bKeepEndingAttribs )
342 String aNewText;
343 if ( nPos < maText.Len() )
345 aNewText = maText.Copy( nPos );
346 maText.Erase( nPos );
348 TextNode* pNew = new TextNode( aNewText );
350 for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
352 TextCharAttrib* pAttrib = maCharAttribs.GetAttrib( nAttr );
353 if ( pAttrib->GetEnd() < nPos )
355 // no change
358 else if ( pAttrib->GetEnd() == nPos )
360 // must be copied as an empty attribute
361 // !FindAttrib only sensible if traversing backwards through the list!
362 if ( bKeepEndingAttribs && !pNew->maCharAttribs.FindAttrib( pAttrib->Which(), 0 ) )
364 TextCharAttrib* pNewAttrib = new TextCharAttrib( *pAttrib );
365 pNewAttrib->GetStart() = 0;
366 pNewAttrib->GetEnd() = 0;
367 pNew->maCharAttribs.InsertAttrib( pNewAttrib );
370 else if ( pAttrib->IsInside( nPos ) || ( !nPos && !pAttrib->GetStart() ) )
372 // If cutting at the very beginning, the attribute has to be
373 // copied and changed
374 TextCharAttrib* pNewAttrib = new TextCharAttrib( *pAttrib );
375 pNewAttrib->GetStart() = 0;
376 pNewAttrib->GetEnd() = pAttrib->GetEnd()-nPos;
377 pNew->maCharAttribs.InsertAttrib( pNewAttrib );
378 // trim
379 pAttrib->GetEnd() = nPos;
381 else
383 DBG_ASSERT( pAttrib->GetStart() >= nPos, "Start < nPos!" );
384 DBG_ASSERT( pAttrib->GetEnd() >= nPos, "End < nPos!" );
385 // move all into the new node (this)
386 maCharAttribs.RemoveAttrib( nAttr );
387 pNew->maCharAttribs.InsertAttrib( pAttrib );
388 pAttrib->GetStart() = pAttrib->GetStart() - nPos;
389 pAttrib->GetEnd() = pAttrib->GetEnd() - nPos;
390 nAttr--;
393 return pNew;
396 void TextNode::Append( const TextNode& rNode )
398 sal_uInt16 nOldLen = maText.Len();
400 maText += rNode.GetText();
402 const sal_uInt16 nAttribs = rNode.GetCharAttribs().Count();
403 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
405 TextCharAttrib* pAttrib = rNode.GetCharAttribs().GetAttrib( nAttr );
406 bool bMelted = false;
407 if ( pAttrib->GetStart() == 0 )
409 // potentially merge attributes
410 sal_uInt16 nTmpAttribs = maCharAttribs.Count();
411 for ( sal_uInt16 nTmpAttr = 0; nTmpAttr < nTmpAttribs; nTmpAttr++ )
413 TextCharAttrib* pTmpAttrib = maCharAttribs.GetAttrib( nTmpAttr );
415 if ( pTmpAttrib->GetEnd() == nOldLen )
417 if ( ( pTmpAttrib->Which() == pAttrib->Which() ) &&
418 ( pTmpAttrib->GetAttr() == pAttrib->GetAttr() ) )
420 pTmpAttrib->GetEnd() =
421 pTmpAttrib->GetEnd() + pAttrib->GetLen();
422 bMelted = true;
423 break; // there can be only one of this type at this position
429 if ( !bMelted )
431 TextCharAttrib* pNewAttrib = new TextCharAttrib( *pAttrib );
432 pNewAttrib->GetStart() = pNewAttrib->GetStart() + nOldLen;
433 pNewAttrib->GetEnd() = pNewAttrib->GetEnd() + nOldLen;
434 maCharAttribs.InsertAttrib( pNewAttrib );
439 TextDoc::TextDoc()
441 mnLeftMargin = 0;
444 TextDoc::~TextDoc()
446 DestroyTextNodes();
449 void TextDoc::Clear()
451 DestroyTextNodes();
454 void TextDoc::DestroyTextNodes()
456 for ( sal_uLong nNode = 0; nNode < maTextNodes.Count(); nNode++ )
457 delete maTextNodes.GetObject( nNode );
458 maTextNodes.clear();
461 String TextDoc::GetText( const sal_Unicode* pSep ) const
463 sal_uLong nLen = GetTextLen( pSep );
464 sal_uLong nNodes = maTextNodes.Count();
466 if ( nLen > STRING_MAXLEN )
468 OSL_FAIL( "Text zu gross fuer String" );
469 return String();
472 String aASCIIText;
473 sal_uLong nLastNode = nNodes-1;
474 for ( sal_uLong nNode = 0; nNode < nNodes; nNode++ )
476 TextNode* pNode = maTextNodes.GetObject( nNode );
477 String aTmp( pNode->GetText() );
478 aASCIIText += aTmp;
479 if ( pSep && ( nNode != nLastNode ) )
480 aASCIIText += pSep;
483 return aASCIIText;
486 XubString TextDoc::GetText( sal_uLong nPara ) const
488 XubString aText;
489 TextNode* pNode = ( nPara < maTextNodes.Count() ) ? maTextNodes.GetObject( nPara ) : 0;
490 if ( pNode )
491 aText = pNode->GetText();
493 return aText;
497 sal_uLong TextDoc::GetTextLen( const sal_Unicode* pSep, const TextSelection* pSel ) const
499 sal_uLong nLen = 0;
500 sal_uLong nNodes = maTextNodes.Count();
501 if ( nNodes )
503 sal_uLong nStartNode = 0;
504 sal_uLong nEndNode = nNodes-1;
505 if ( pSel )
507 nStartNode = pSel->GetStart().GetPara();
508 nEndNode = pSel->GetEnd().GetPara();
511 for ( sal_uLong nNode = nStartNode; nNode <= nEndNode; nNode++ )
513 TextNode* pNode = maTextNodes.GetObject( nNode );
515 sal_uInt16 nS = 0;
516 sal_uLong nE = pNode->GetText().Len();
517 if ( pSel && ( nNode == pSel->GetStart().GetPara() ) )
518 nS = pSel->GetStart().GetIndex();
519 if ( pSel && ( nNode == pSel->GetEnd().GetPara() ) )
520 nE = pSel->GetEnd().GetIndex();
522 nLen += ( nE - nS );
525 if ( pSep )
526 nLen += (nEndNode-nStartNode) * rtl_ustr_getLength(pSep);
529 return nLen;
532 TextPaM TextDoc::InsertText( const TextPaM& rPaM, sal_Unicode c )
534 DBG_ASSERT( c != 0x0A, "TextDoc::InsertText: Zeilentrenner in Absatz nicht erlaubt!" );
535 DBG_ASSERT( c != 0x0D, "TextDoc::InsertText: Zeilentrenner in Absatz nicht erlaubt!" );
537 TextNode* pNode = maTextNodes.GetObject( rPaM.GetPara() );
538 pNode->InsertText( rPaM.GetIndex(), c );
540 TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+1 );
541 return aPaM;
544 TextPaM TextDoc::InsertText( const TextPaM& rPaM, const XubString& rStr )
546 DBG_ASSERT( rStr.Search( 0x0A ) == STRING_NOTFOUND, "TextDoc::InsertText: Zeilentrenner in Absatz nicht erlaubt!" );
547 DBG_ASSERT( rStr.Search( 0x0D ) == STRING_NOTFOUND, "TextDoc::InsertText: Zeilentrenner in Absatz nicht erlaubt!" );
549 TextNode* pNode = maTextNodes.GetObject( rPaM.GetPara() );
550 pNode->InsertText( rPaM.GetIndex(), rStr );
552 TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+rStr.Len() );
553 return aPaM;
556 TextPaM TextDoc::InsertParaBreak( const TextPaM& rPaM, sal_Bool bKeepEndingAttribs )
558 TextNode* pNode = maTextNodes.GetObject( rPaM.GetPara() );
559 TextNode* pNew = pNode->Split( rPaM.GetIndex(), bKeepEndingAttribs );
561 maTextNodes.Insert( pNew, rPaM.GetPara()+1 );
563 TextPaM aPaM( rPaM.GetPara()+1, 0 );
564 return aPaM;
567 TextPaM TextDoc::ConnectParagraphs( TextNode* pLeft, TextNode* pRight )
569 sal_uInt16 nPrevLen = pLeft->GetText().Len();
570 pLeft->Append( *pRight );
572 // the paragraph on the right vanishes
573 sal_uLong nRight = maTextNodes.GetPos( pRight );
574 maTextNodes.Remove( nRight );
575 delete pRight;
577 sal_uLong nLeft = maTextNodes.GetPos( pLeft );
578 TextPaM aPaM( nLeft, nPrevLen );
579 return aPaM;
582 TextPaM TextDoc::RemoveChars( const TextPaM& rPaM, sal_uInt16 nChars )
584 TextNode* pNode = maTextNodes.GetObject( rPaM.GetPara() );
585 pNode->RemoveText( rPaM.GetIndex(), nChars );
587 return rPaM;
590 sal_Bool TextDoc::IsValidPaM( const TextPaM& rPaM )
592 if ( rPaM.GetPara() >= maTextNodes.Count() )
594 OSL_FAIL( "PaM: Para out of range" );
595 return sal_False;
597 TextNode * pNode = maTextNodes.GetObject( rPaM.GetPara() );
598 if ( rPaM.GetIndex() > pNode->GetText().Len() )
600 OSL_FAIL( "PaM: Index out of range" );
601 return sal_False;
603 return sal_True;
606 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */