1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
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();
37 TextCharAttrib::TextCharAttrib( const TextCharAttrib
& rTextCharAttrib
)
39 mpAttr
= rTextCharAttrib
.GetAttr().Clone();
40 mnStart
= rTextCharAttrib
.mnStart
;
41 mnEnd
= rTextCharAttrib
.mnEnd
;
44 TextCharAttrib::~TextCharAttrib()
49 TextCharAttribList::TextCharAttribList()
51 mbHasEmptyAttribs
= sal_False
;
54 TextCharAttribList::~TextCharAttribList()
59 void TextCharAttribList::Clear( sal_Bool bDestroyAttribs
)
61 if ( bDestroyAttribs
)
62 for(iterator it
= begin(); it
!= end(); ++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
);
90 void TextCharAttribList::ResortAttribs()
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
)
107 if ( ( pAttr
->Which() == nWhich
) && pAttr
->IsIn(nPos
) )
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
) )
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
)
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
)
150 if ( ( pAttr
->GetStart() == nBound
) || ( pAttr
->GetEnd() == nBound
) )
156 TextCharAttrib
* TextCharAttribList::FindEmptyAttrib( sal_uInt16 nWhich
, sal_uInt16 nPos
)
158 if ( !mbHasEmptyAttribs
)
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
)
168 if ( ( pAttr
->GetStart() == nPos
) && ( pAttr
->GetEnd() == nPos
) && ( pAttr
->Which() == nWhich
) )
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
);
186 mbHasEmptyAttribs
= sal_False
;
189 TextNode::TextNode( const OUString
& rText
) :
194 void TextNode::ExpandAttribs( sal_uInt16 nIndex
, sal_uInt16 nNew
)
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
);
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
)
242 pAttrib
->Expand( nNew
);
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?" );
255 maCharAttribs
.ResortAttribs();
258 void TextNode::CollapsAttribs( sal_uInt16 nIndex
, sal_uInt16 nDeleted
)
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
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
;
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() */ )
309 maCharAttribs
.RemoveAttrib( nAttr
);
313 else if ( pAttrib
->IsEmpty() )
314 maCharAttribs
.HasEmptyAttribs() = sal_True
;
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
)
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
)
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
);
378 pAttrib
->GetEnd() = nPos
;
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
;
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();
422 break; // there can be only one of this type at this position
430 TextCharAttrib
* pNewAttrib
= new TextCharAttrib( *pAttrib
);
431 pNewAttrib
->GetStart() = pNewAttrib
->GetStart() + nOldLen
;
432 pNewAttrib
->GetEnd() = pNewAttrib
->GetEnd() + nOldLen
;
433 maCharAttribs
.InsertAttrib( pNewAttrib
);
448 void TextDoc::Clear()
453 void TextDoc::DestroyTextNodes()
455 for ( sal_uLong nNode
= 0; nNode
< maTextNodes
.Count(); nNode
++ )
456 delete maTextNodes
.GetObject( nNode
);
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" );
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() );
478 if ( pSep
&& ( nNode
!= nLastNode
) )
485 OUString
TextDoc::GetText( sal_uLong nPara
) const
489 TextNode
* pNode
= ( nPara
< maTextNodes
.Count() ) ? maTextNodes
.GetObject( nPara
) : 0;
491 aText
= pNode
->GetText();
497 sal_uLong
TextDoc::GetTextLen( const sal_Unicode
* pSep
, const TextSelection
* pSel
) const
500 sal_uLong nNodes
= maTextNodes
.Count();
503 sal_uLong nStartNode
= 0;
504 sal_uLong nEndNode
= nNodes
-1;
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
);
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();
526 nLen
+= (nEndNode
-nStartNode
) * rtl_ustr_getLength(pSep
);
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 );
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() );
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 );
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
);
577 sal_uLong nLeft
= maTextNodes
.GetPos( pLeft
);
578 TextPaM
aPaM( nLeft
, nPrevLen
);
582 TextPaM
TextDoc::RemoveChars( const TextPaM
& rPaM
, sal_uInt16 nChars
)
584 TextNode
* pNode
= maTextNodes
.GetObject( rPaM
.GetPara() );
585 pNode
->RemoveText( rPaM
.GetIndex(), nChars
);
590 sal_Bool
TextDoc::IsValidPaM( const TextPaM
& rPaM
)
592 if ( rPaM
.GetPara() >= maTextNodes
.Count() )
594 OSL_FAIL( "PaM: Para out of range" );
597 TextNode
* pNode
= maTextNodes
.GetObject( rPaM
.GetPara() );
598 if ( rPaM
.GetIndex() > pNode
->GetText().getLength() )
600 OSL_FAIL( "PaM: Index out of range" );
606 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */