bump product version to 4.2.0.1
[LibreOffice.git] / vcl / source / edit / textdoc.cxx
blobbf22b77bdc3b0512e72f1135684f2b78f9f87dc7
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 <tools/string.hxx>
21 #include <textdoc.hxx>
22 #include <stdlib.h>
24 // compare function called by QuickSort
25 static bool CompareStart( const TextCharAttrib* pFirst, const TextCharAttrib* pSecond )
27 return pFirst->GetStart() < pSecond->GetStart();
30 TextCharAttrib::TextCharAttrib( const TextAttrib& rAttr, sal_uInt16 nStart, sal_uInt16 nEnd )
32 mpAttr = rAttr.Clone();
33 mnStart = nStart,
34 mnEnd = nEnd;
37 TextCharAttrib::TextCharAttrib( const TextCharAttrib& rTextCharAttrib )
39 mpAttr = rTextCharAttrib.GetAttr().Clone();
40 mnStart = rTextCharAttrib.mnStart;
41 mnEnd = rTextCharAttrib.mnEnd;
44 TextCharAttrib::~TextCharAttrib()
46 delete mpAttr;
49 TextCharAttribList::TextCharAttribList()
51 mbHasEmptyAttribs = sal_False;
54 TextCharAttribList::~TextCharAttribList()
56 // PTRARR_DEL
59 void TextCharAttribList::Clear( sal_Bool bDestroyAttribs )
61 if ( bDestroyAttribs )
62 for(iterator it = begin(); it != end(); ++it)
63 delete *it;
64 TextCharAttribs::clear();
68 void TextCharAttribList::InsertAttrib( TextCharAttrib* pAttrib )
70 if ( pAttrib->IsEmpty() )
71 mbHasEmptyAttribs = sal_True;
73 const sal_uInt16 nCount = size();
74 const sal_uInt16 nStart = pAttrib->GetStart(); // maybe better for Comp.Opt.
75 bool bInserted = false;
76 for ( sal_uInt16 x = 0; x < nCount; x++ )
78 TextCharAttrib* pCurAttrib = GetAttrib( x );
79 if ( pCurAttrib->GetStart() > nStart )
81 insert( begin() + x, pAttrib );
82 bInserted = true;
83 break;
86 if ( !bInserted )
87 push_back( pAttrib );
90 void TextCharAttribList::ResortAttribs()
92 if ( !empty() )
93 std::sort( begin(), end(), CompareStart );
96 TextCharAttrib* TextCharAttribList::FindAttrib( sal_uInt16 nWhich, sal_uInt16 nPos )
98 // backwards; if one ends there and the next starts there
99 // ==> the starting one counts
100 for ( sal_uInt16 nAttr = size(); nAttr; )
102 TextCharAttrib* pAttr = GetAttrib( --nAttr );
104 if ( pAttr->GetEnd() < nPos )
105 return 0;
107 if ( ( pAttr->Which() == nWhich ) && pAttr->IsIn(nPos) )
108 return pAttr;
110 return NULL;
113 TextCharAttrib* TextCharAttribList::FindNextAttrib( sal_uInt16 nWhich, sal_uInt16 nFromPos, sal_uInt16 nMaxPos ) const
115 DBG_ASSERT( nWhich, "FindNextAttrib: Which?" );
116 const sal_uInt16 nAttribs = size();
117 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
119 TextCharAttrib* pAttr = GetAttrib( nAttr );
120 if ( ( pAttr->GetStart() >= nFromPos ) &&
121 ( pAttr->GetEnd() <= nMaxPos ) &&
122 ( pAttr->Which() == nWhich ) )
123 return pAttr;
125 return NULL;
128 sal_Bool TextCharAttribList::HasAttrib( sal_uInt16 nWhich ) const
130 for ( sal_uInt16 nAttr = size(); nAttr; )
132 const TextCharAttrib* pAttr = GetAttrib( --nAttr );
133 if ( pAttr->Which() == nWhich )
134 return sal_True;
136 return sal_False;
139 sal_Bool TextCharAttribList::HasBoundingAttrib( sal_uInt16 nBound )
141 // backwards; if one ends there and the next starts there
142 // ==> the starting one counts
143 for ( sal_uInt16 nAttr = size(); nAttr; )
145 TextCharAttrib* pAttr = GetAttrib( --nAttr );
147 if ( pAttr->GetEnd() < nBound )
148 return sal_False;
150 if ( ( pAttr->GetStart() == nBound ) || ( pAttr->GetEnd() == nBound ) )
151 return sal_True;
153 return sal_False;
156 TextCharAttrib* TextCharAttribList::FindEmptyAttrib( sal_uInt16 nWhich, sal_uInt16 nPos )
158 if ( !mbHasEmptyAttribs )
159 return 0;
161 const sal_uInt16 nAttribs = size();
162 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
164 TextCharAttrib* pAttr = GetAttrib( nAttr );
165 if ( pAttr->GetStart() > nPos )
166 return 0;
168 if ( ( pAttr->GetStart() == nPos ) && ( pAttr->GetEnd() == nPos ) && ( pAttr->Which() == nWhich ) )
169 return pAttr;
171 return 0;
174 void TextCharAttribList::DeleteEmptyAttribs()
176 for ( sal_uInt16 nAttr = 0; nAttr < size(); nAttr++ )
178 TextCharAttrib* pAttr = GetAttrib( nAttr );
179 if ( pAttr->IsEmpty() )
181 erase( begin() + nAttr );
182 delete pAttr;
183 nAttr--;
186 mbHasEmptyAttribs = sal_False;
189 TextNode::TextNode( const OUString& rText ) :
190 maText( rText )
194 void TextNode::ExpandAttribs( sal_uInt16 nIndex, sal_uInt16 nNew )
196 if ( !nNew )
197 return;
199 bool bResort = false;
200 sal_uInt16 nAttribs = maCharAttribs.Count();
201 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
203 TextCharAttrib* pAttrib = maCharAttribs.GetAttrib( nAttr );
204 if ( pAttrib->GetEnd() >= nIndex )
206 // move all attributes that are behind the cursor
207 if ( pAttrib->GetStart() > nIndex )
209 pAttrib->MoveForward( nNew );
211 // 0: expand empty attribute, if at cursor
212 else if ( pAttrib->IsEmpty() )
214 // Do not check the index; empty one may only be here.
215 // If checking later anyway, special case:
216 // Start == 0; AbsLen == 1, nNew = 1 => Expand due to new paragraph!
217 // Start <= nIndex, End >= nIndex => Start=End=nIndex!
218 pAttrib->Expand( nNew );
220 // 1: attribute starts before and reaches up to index
221 else if ( pAttrib->GetEnd() == nIndex ) // start must be before
223 // Only expand if no feature and not in Exclude list!
224 // Otherwise e.g. an UL would go until the new ULDB, thus expand both.
225 if ( !maCharAttribs.FindEmptyAttrib( pAttrib->Which(), nIndex ) )
227 pAttrib->Expand( nNew );
229 else
230 bResort = true;
232 // 2: attribute starts before and reaches past the index
233 else if ( ( pAttrib->GetStart() < nIndex ) && ( pAttrib->GetEnd() > nIndex ) )
235 pAttrib->Expand( nNew );
237 // 3: attribute starts at Index
238 else if ( pAttrib->GetStart() == nIndex )
240 if ( nIndex == 0 )
242 pAttrib->Expand( nNew );
244 else
245 pAttrib->MoveForward( nNew );
249 DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Expand: Attribut verdreht!" );
250 DBG_ASSERT( ( pAttrib->GetEnd() <= maText.getLength() ), "Expand: Attrib groesser als Absatz!" );
251 DBG_ASSERT( !pAttrib->IsEmpty(), "Leeres Attribut nach ExpandAttribs?" );
254 if ( bResort )
255 maCharAttribs.ResortAttribs();
258 void TextNode::CollapsAttribs( sal_uInt16 nIndex, sal_uInt16 nDeleted )
260 if ( !nDeleted )
261 return;
263 bool bResort = false;
264 sal_uInt16 nEndChanges = nIndex+nDeleted;
266 for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
268 TextCharAttrib* pAttrib = maCharAttribs.GetAttrib( nAttr );
269 bool bDelAttr = false;
270 if ( pAttrib->GetEnd() >= nIndex )
272 // move all attributes that are behind the cursor
273 if ( pAttrib->GetStart() >= nEndChanges )
275 pAttrib->MoveBackward( nDeleted );
277 // 1. delete inner attributes
278 else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() <= nEndChanges ) )
280 // special case: attribute covers the region exactly
281 // => keep as an empty attribute
282 if ( ( pAttrib->GetStart() == nIndex ) && ( pAttrib->GetEnd() == nEndChanges ) )
283 pAttrib->GetEnd() = nIndex; // empty
284 else
285 bDelAttr = true;
287 // 2. attribute starts before, ends inside or after
288 else if ( ( pAttrib->GetStart() <= nIndex ) && ( pAttrib->GetEnd() > nIndex ) )
290 if ( pAttrib->GetEnd() <= nEndChanges ) // ends inside
291 pAttrib->GetEnd() = nIndex;
292 else
293 pAttrib->Collaps( nDeleted ); // ends after
295 // 3. attribute starts inside, ends after
296 else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() > nEndChanges ) )
298 // features are not allowed to expand!
299 pAttrib->GetStart() = nEndChanges;
300 pAttrib->MoveBackward( nDeleted );
304 DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Collaps: Attribut verdreht!" );
305 DBG_ASSERT( ( pAttrib->GetEnd() <= maText.getLength()) || bDelAttr, "Collaps: Attrib groesser als Absatz!" );
306 if ( bDelAttr /* || pAttrib->IsEmpty() */ )
308 bResort = true;
309 maCharAttribs.RemoveAttrib( nAttr );
310 delete pAttrib;
311 nAttr--;
313 else if ( pAttrib->IsEmpty() )
314 maCharAttribs.HasEmptyAttribs() = sal_True;
317 if ( bResort )
318 maCharAttribs.ResortAttribs();
321 void TextNode::InsertText( sal_uInt16 nPos, const OUString& rText )
323 maText = maText.replaceAt( nPos, 0, rText );
324 ExpandAttribs( nPos, rText.getLength() );
327 void TextNode::InsertText( sal_uInt16 nPos, sal_Unicode c )
329 maText = maText.replaceAt( nPos, 0, OUString(c) );
330 ExpandAttribs( nPos, 1 );
333 void TextNode::RemoveText( sal_uInt16 nPos, sal_uInt16 nChars )
335 maText = maText.replaceAt( nPos, nChars, "" );
336 CollapsAttribs( nPos, nChars );
339 TextNode* TextNode::Split( sal_uInt16 nPos, sal_Bool bKeepEndingAttribs )
341 OUString aNewText;
342 if ( nPos < maText.getLength() )
344 aNewText = maText.copy( nPos );
345 maText = maText.copy(0, nPos);
347 TextNode* pNew = new TextNode( aNewText );
349 for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
351 TextCharAttrib* pAttrib = maCharAttribs.GetAttrib( nAttr );
352 if ( pAttrib->GetEnd() < nPos )
354 // no change
357 else if ( pAttrib->GetEnd() == nPos )
359 // must be copied as an empty attribute
360 // !FindAttrib only sensible if traversing backwards through the list!
361 if ( bKeepEndingAttribs && !pNew->maCharAttribs.FindAttrib( pAttrib->Which(), 0 ) )
363 TextCharAttrib* pNewAttrib = new TextCharAttrib( *pAttrib );
364 pNewAttrib->GetStart() = 0;
365 pNewAttrib->GetEnd() = 0;
366 pNew->maCharAttribs.InsertAttrib( pNewAttrib );
369 else if ( pAttrib->IsInside( nPos ) || ( !nPos && !pAttrib->GetStart() ) )
371 // If cutting at the very beginning, the attribute has to be
372 // copied and changed
373 TextCharAttrib* pNewAttrib = new TextCharAttrib( *pAttrib );
374 pNewAttrib->GetStart() = 0;
375 pNewAttrib->GetEnd() = pAttrib->GetEnd()-nPos;
376 pNew->maCharAttribs.InsertAttrib( pNewAttrib );
377 // trim
378 pAttrib->GetEnd() = nPos;
380 else
382 DBG_ASSERT( pAttrib->GetStart() >= nPos, "Start < nPos!" );
383 DBG_ASSERT( pAttrib->GetEnd() >= nPos, "End < nPos!" );
384 // move all into the new node (this)
385 maCharAttribs.RemoveAttrib( nAttr );
386 pNew->maCharAttribs.InsertAttrib( pAttrib );
387 pAttrib->GetStart() = pAttrib->GetStart() - nPos;
388 pAttrib->GetEnd() = pAttrib->GetEnd() - nPos;
389 nAttr--;
392 return pNew;
395 void TextNode::Append( const TextNode& rNode )
397 sal_Int32 nOldLen = maText.getLength();
399 maText += rNode.GetText();
401 const sal_uInt16 nAttribs = rNode.GetCharAttribs().Count();
402 for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
404 TextCharAttrib* pAttrib = rNode.GetCharAttribs().GetAttrib( nAttr );
405 bool bMelted = false;
406 if ( pAttrib->GetStart() == 0 )
408 // potentially merge attributes
409 sal_uInt16 nTmpAttribs = maCharAttribs.Count();
410 for ( sal_uInt16 nTmpAttr = 0; nTmpAttr < nTmpAttribs; nTmpAttr++ )
412 TextCharAttrib* pTmpAttrib = maCharAttribs.GetAttrib( nTmpAttr );
414 if ( pTmpAttrib->GetEnd() == nOldLen )
416 if ( ( pTmpAttrib->Which() == pAttrib->Which() ) &&
417 ( pTmpAttrib->GetAttr() == pAttrib->GetAttr() ) )
419 pTmpAttrib->GetEnd() =
420 pTmpAttrib->GetEnd() + pAttrib->GetLen();
421 bMelted = true;
422 break; // there can be only one of this type at this position
428 if ( !bMelted )
430 TextCharAttrib* pNewAttrib = new TextCharAttrib( *pAttrib );
431 pNewAttrib->GetStart() = pNewAttrib->GetStart() + nOldLen;
432 pNewAttrib->GetEnd() = pNewAttrib->GetEnd() + nOldLen;
433 maCharAttribs.InsertAttrib( pNewAttrib );
438 TextDoc::TextDoc()
440 mnLeftMargin = 0;
443 TextDoc::~TextDoc()
445 DestroyTextNodes();
448 void TextDoc::Clear()
450 DestroyTextNodes();
453 void TextDoc::DestroyTextNodes()
455 for ( sal_uLong nNode = 0; nNode < maTextNodes.Count(); nNode++ )
456 delete maTextNodes.GetObject( nNode );
457 maTextNodes.clear();
460 OUString TextDoc::GetText( const sal_Unicode* pSep ) const
462 sal_uLong nLen = GetTextLen( pSep );
463 sal_uLong nNodes = maTextNodes.Count();
465 if ( nLen > STRING_MAXLEN )
467 OSL_FAIL( "Text zu gross fuer String" );
468 return OUString();
471 OUString aASCIIText;
472 sal_uLong nLastNode = nNodes-1;
473 for ( sal_uLong nNode = 0; nNode < nNodes; nNode++ )
475 TextNode* pNode = maTextNodes.GetObject( nNode );
476 OUString aTmp( pNode->GetText() );
477 aASCIIText += aTmp;
478 if ( pSep && ( nNode != nLastNode ) )
479 aASCIIText += pSep;
482 return aASCIIText;
485 OUString TextDoc::GetText( sal_uLong nPara ) const
487 OUString 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_Int32 nE = pNode->GetText().getLength();
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 OUString& rStr )
546 DBG_ASSERT( rStr.indexOf( 0x0A ) == -1, "TextDoc::InsertText: Zeilentrenner in Absatz nicht erlaubt!" );
547 DBG_ASSERT( rStr.indexOf( 0x0D ) == -1, "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.getLength() );
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_Int32 nPrevLen = pLeft->GetText().getLength();
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().getLength() )
600 OSL_FAIL( "PaM: Index out of range" );
601 return sal_False;
603 return sal_True;
606 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */