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 <textdoc.hxx>
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();
38 TextCharAttrib::TextCharAttrib( const TextCharAttrib
& rTextCharAttrib
)
40 mpAttr
= rTextCharAttrib
.GetAttr().Clone();
41 mnStart
= rTextCharAttrib
.mnStart
;
42 mnEnd
= rTextCharAttrib
.mnEnd
;
45 TextCharAttrib::~TextCharAttrib()
50 TextCharAttribList::TextCharAttribList()
52 mbHasEmptyAttribs
= sal_False
;
55 TextCharAttribList::~TextCharAttribList()
60 void TextCharAttribList::Clear( sal_Bool bDestroyAttribs
)
62 if ( bDestroyAttribs
)
63 for(iterator it
= begin(); it
!= end(); ++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
);
91 void TextCharAttribList::ResortAttribs()
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
)
108 if ( ( pAttr
->Which() == nWhich
) && pAttr
->IsIn(nPos
) )
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
) )
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
)
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
)
151 if ( ( pAttr
->GetStart() == nBound
) || ( pAttr
->GetEnd() == nBound
) )
157 TextCharAttrib
* TextCharAttribList::FindEmptyAttrib( sal_uInt16 nWhich
, sal_uInt16 nPos
)
159 if ( !mbHasEmptyAttribs
)
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
)
169 if ( ( pAttr
->GetStart() == nPos
) && ( pAttr
->GetEnd() == nPos
) && ( pAttr
->Which() == nWhich
) )
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
);
187 mbHasEmptyAttribs
= sal_False
;
190 TextNode::TextNode( const String
& rText
) :
195 void TextNode::ExpandAttribs( sal_uInt16 nIndex
, sal_uInt16 nNew
)
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
);
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
)
243 pAttrib
->Expand( nNew
);
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?" );
256 maCharAttribs
.ResortAttribs();
259 void TextNode::CollapsAttribs( sal_uInt16 nIndex
, sal_uInt16 nDeleted
)
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
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
;
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() */ )
310 maCharAttribs
.RemoveAttrib( nAttr
);
314 else if ( pAttrib
->IsEmpty() )
315 maCharAttribs
.HasEmptyAttribs() = sal_True
;
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
)
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
)
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
);
379 pAttrib
->GetEnd() = nPos
;
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
;
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();
423 break; // there can be only one of this type at this position
431 TextCharAttrib
* pNewAttrib
= new TextCharAttrib( *pAttrib
);
432 pNewAttrib
->GetStart() = pNewAttrib
->GetStart() + nOldLen
;
433 pNewAttrib
->GetEnd() = pNewAttrib
->GetEnd() + nOldLen
;
434 maCharAttribs
.InsertAttrib( pNewAttrib
);
449 void TextDoc::Clear()
454 void TextDoc::DestroyTextNodes()
456 for ( sal_uLong nNode
= 0; nNode
< maTextNodes
.Count(); nNode
++ )
457 delete maTextNodes
.GetObject( nNode
);
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" );
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() );
479 if ( pSep
&& ( nNode
!= nLastNode
) )
486 XubString
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_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();
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 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() );
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_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
);
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().Len() )
600 OSL_FAIL( "PaM: Index out of range" );
606 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */