/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include // Vergleichmethode wird von QuickSort gerufen... static bool CompareStart( const TextCharAttrib* pFirst, const TextCharAttrib* pSecond ) { return pFirst->GetStart() < pSecond->GetStart(); } // ------------------------------------------------------------------------- // (+) class TextCharAttrib // ------------------------------------------------------------------------- TextCharAttrib::TextCharAttrib( const TextAttrib& rAttr, sal_uInt16 nStart, sal_uInt16 nEnd ) { mpAttr = rAttr.Clone(); mnStart = nStart, mnEnd = nEnd; } TextCharAttrib::TextCharAttrib( const TextCharAttrib& rTextCharAttrib ) { mpAttr = rTextCharAttrib.GetAttr().Clone(); mnStart = rTextCharAttrib.mnStart; mnEnd = rTextCharAttrib.mnEnd; } TextCharAttrib::~TextCharAttrib() { delete mpAttr; } // ------------------------------------------------------------------------- // (+) class TextCharAttribList // ------------------------------------------------------------------------- TextCharAttribList::TextCharAttribList() { mbHasEmptyAttribs = sal_False; } TextCharAttribList::~TextCharAttribList() { // PTRARR_DEL } void TextCharAttribList::Clear( sal_Bool bDestroyAttribs ) { if ( bDestroyAttribs ) for(iterator it = begin(); it != end(); ++it) delete *it; TextCharAttribs::clear(); } void TextCharAttribList::InsertAttrib( TextCharAttrib* pAttrib ) { if ( pAttrib->IsEmpty() ) mbHasEmptyAttribs = sal_True; const sal_uInt16 nCount = size(); const sal_uInt16 nStart = pAttrib->GetStart(); // vielleicht besser fuer Comp.Opt. sal_Bool bInserted = sal_False; for ( sal_uInt16 x = 0; x < nCount; x++ ) { TextCharAttrib* pCurAttrib = GetAttrib( x ); if ( pCurAttrib->GetStart() > nStart ) { insert( begin() + x, pAttrib ); bInserted = sal_True; break; } } if ( !bInserted ) push_back( pAttrib ); } void TextCharAttribList::ResortAttribs() { if ( !empty() ) std::sort( begin(), end(), CompareStart ); } TextCharAttrib* TextCharAttribList::FindAttrib( sal_uInt16 nWhich, sal_uInt16 nPos ) { // Rueckwaerts, falls eins dort endet, das naechste startet. // => Das startende gilt... for ( sal_uInt16 nAttr = size(); nAttr; ) { TextCharAttrib* pAttr = GetAttrib( --nAttr ); if ( pAttr->GetEnd() < nPos ) return 0; if ( ( pAttr->Which() == nWhich ) && pAttr->IsIn(nPos) ) return pAttr; } return NULL; } TextCharAttrib* TextCharAttribList::FindNextAttrib( sal_uInt16 nWhich, sal_uInt16 nFromPos, sal_uInt16 nMaxPos ) const { DBG_ASSERT( nWhich, "FindNextAttrib: Which?" ); const sal_uInt16 nAttribs = size(); for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ ) { TextCharAttrib* pAttr = GetAttrib( nAttr ); if ( ( pAttr->GetStart() >= nFromPos ) && ( pAttr->GetEnd() <= nMaxPos ) && ( pAttr->Which() == nWhich ) ) return pAttr; } return NULL; } sal_Bool TextCharAttribList::HasAttrib( sal_uInt16 nWhich ) const { for ( sal_uInt16 nAttr = size(); nAttr; ) { const TextCharAttrib* pAttr = GetAttrib( --nAttr ); if ( pAttr->Which() == nWhich ) return sal_True; } return sal_False; } sal_Bool TextCharAttribList::HasBoundingAttrib( sal_uInt16 nBound ) { // Rueckwaerts, falls eins dort endet, das naechste startet. // => Das startende gilt... for ( sal_uInt16 nAttr = size(); nAttr; ) { TextCharAttrib* pAttr = GetAttrib( --nAttr ); if ( pAttr->GetEnd() < nBound ) return sal_False; if ( ( pAttr->GetStart() == nBound ) || ( pAttr->GetEnd() == nBound ) ) return sal_True; } return sal_False; } TextCharAttrib* TextCharAttribList::FindEmptyAttrib( sal_uInt16 nWhich, sal_uInt16 nPos ) { if ( !mbHasEmptyAttribs ) return 0; const sal_uInt16 nAttribs = size(); for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ ) { TextCharAttrib* pAttr = GetAttrib( nAttr ); if ( pAttr->GetStart() > nPos ) return 0; if ( ( pAttr->GetStart() == nPos ) && ( pAttr->GetEnd() == nPos ) && ( pAttr->Which() == nWhich ) ) return pAttr; } return 0; } void TextCharAttribList::DeleteEmptyAttribs() { for ( sal_uInt16 nAttr = 0; nAttr < size(); nAttr++ ) { TextCharAttrib* pAttr = GetAttrib( nAttr ); if ( pAttr->IsEmpty() ) { erase( begin() + nAttr ); delete pAttr; nAttr--; } } mbHasEmptyAttribs = sal_False; } // ------------------------------------------------------------------------- // (+) class TextNode // ------------------------------------------------------------------------- TextNode::TextNode( const String& rText ) : maText( rText ) { } void TextNode::ExpandAttribs( sal_uInt16 nIndex, sal_uInt16 nNew ) { if ( !nNew ) return; sal_Bool bResort = sal_False; sal_uInt16 nAttribs = maCharAttribs.Count(); for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ ) { TextCharAttrib* pAttrib = maCharAttribs.GetAttrib( nAttr ); if ( pAttrib->GetEnd() >= nIndex ) { // Alle Attribute hinter der Einfuegeposition verschieben... if ( pAttrib->GetStart() > nIndex ) { pAttrib->MoveForward( nNew ); } // 0: Leeres Attribut expandieren, wenn an Einfuegestelle else if ( pAttrib->IsEmpty() ) { // Index nicht pruefen, leeres durfte nur dort liegen. // Wenn spaeter doch Ueberpruefung: // Spezialfall: Start == 0; AbsLen == 1, nNew = 1 => Expand, weil durch Absatzumbruch! // Start <= nIndex, End >= nIndex => Start=End=nIndex! // if ( pAttrib->GetStart() == nIndex ) pAttrib->Expand( nNew ); } // 1: Attribut startet davor, geht bis Index... else if ( pAttrib->GetEnd() == nIndex ) // Start muss davor liegen { // Nur expandieren, wenn kein Feature, // und wenn nicht in ExcludeListe! // Sonst geht z.B. ein UL bis zum neuen ULDB, beide expandieren if ( !maCharAttribs.FindEmptyAttrib( pAttrib->Which(), nIndex ) ) { pAttrib->Expand( nNew ); } else bResort = sal_True; } // 2: Attribut startet davor, geht hinter Index... else if ( ( pAttrib->GetStart() < nIndex ) && ( pAttrib->GetEnd() > nIndex ) ) { pAttrib->Expand( nNew ); } // 3: Attribut startet auf Index... else if ( pAttrib->GetStart() == nIndex ) { if ( nIndex == 0 ) { pAttrib->Expand( nNew ); // bResort = sal_True; // es gibt ja keine Features mehr... } else pAttrib->MoveForward( nNew ); } } DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Expand: Attribut verdreht!" ); DBG_ASSERT( ( pAttrib->GetEnd() <= maText.Len() ), "Expand: Attrib groesser als Absatz!" ); DBG_ASSERT( !pAttrib->IsEmpty(), "Leeres Attribut nach ExpandAttribs?" ); } if ( bResort ) maCharAttribs.ResortAttribs(); } void TextNode::CollapsAttribs( sal_uInt16 nIndex, sal_uInt16 nDeleted ) { if ( !nDeleted ) return; sal_Bool bResort = sal_False; sal_uInt16 nEndChanges = nIndex+nDeleted; for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ ) { TextCharAttrib* pAttrib = maCharAttribs.GetAttrib( nAttr ); sal_Bool bDelAttr = sal_False; if ( pAttrib->GetEnd() >= nIndex ) { // Alles Attribute hinter der Einfuegeposition verschieben... if ( pAttrib->GetStart() >= nEndChanges ) { pAttrib->MoveBackward( nDeleted ); } // 1. Innenliegende Attribute loeschen... else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() <= nEndChanges ) ) { // Spezialfall: Attrubt deckt genau den Bereich ab // => als leeres Attribut behalten. if ( ( pAttrib->GetStart() == nIndex ) && ( pAttrib->GetEnd() == nEndChanges ) ) pAttrib->GetEnd() = nIndex; // leer else bDelAttr = sal_True; } // 2. Attribut beginnt davor, endet drinnen oder dahinter... else if ( ( pAttrib->GetStart() <= nIndex ) && ( pAttrib->GetEnd() > nIndex ) ) { if ( pAttrib->GetEnd() <= nEndChanges ) // endet drinnen pAttrib->GetEnd() = nIndex; else pAttrib->Collaps( nDeleted ); // endet dahinter } // 3. Attribut beginnt drinnen, endet dahinter... else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() > nEndChanges ) ) { // Features duerfen nicht expandieren! pAttrib->GetStart() = nEndChanges; pAttrib->MoveBackward( nDeleted ); } } DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Collaps: Attribut verdreht!" ); DBG_ASSERT( ( pAttrib->GetEnd() <= maText.Len()) || bDelAttr, "Collaps: Attrib groesser als Absatz!" ); if ( bDelAttr /* || pAttrib->IsEmpty() */ ) { bResort = sal_True; maCharAttribs.RemoveAttrib( nAttr ); delete pAttrib; nAttr--; } else if ( pAttrib->IsEmpty() ) maCharAttribs.HasEmptyAttribs() = sal_True; } if ( bResort ) maCharAttribs.ResortAttribs(); } void TextNode::InsertText( sal_uInt16 nPos, const String& rText ) { maText.Insert( rText, nPos ); ExpandAttribs( nPos, rText.Len() ); } void TextNode::InsertText( sal_uInt16 nPos, sal_Unicode c ) { maText.Insert( c, nPos ); ExpandAttribs( nPos, 1 ); } void TextNode::RemoveText( sal_uInt16 nPos, sal_uInt16 nChars ) { maText.Erase( nPos, nChars ); CollapsAttribs( nPos, nChars ); } TextNode* TextNode::Split( sal_uInt16 nPos, sal_Bool bKeepEndingAttribs ) { String aNewText; if ( nPos < maText.Len() ) { aNewText = maText.Copy( nPos ); maText.Erase( nPos ); } TextNode* pNew = new TextNode( aNewText ); for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ ) { TextCharAttrib* pAttrib = maCharAttribs.GetAttrib( nAttr ); if ( pAttrib->GetEnd() < nPos ) { // bleiben unveraendert.... ; } else if ( pAttrib->GetEnd() == nPos ) { // muessen als leeres Attribut kopiert werden. // !FindAttrib nur sinnvoll, wenn Rueckwaerts durch Liste! if ( bKeepEndingAttribs && !pNew->maCharAttribs.FindAttrib( pAttrib->Which(), 0 ) ) { TextCharAttrib* pNewAttrib = new TextCharAttrib( *pAttrib ); pNewAttrib->GetStart() = 0; pNewAttrib->GetEnd() = 0; pNew->maCharAttribs.InsertAttrib( pNewAttrib ); } } else if ( pAttrib->IsInside( nPos ) || ( !nPos && !pAttrib->GetStart() ) ) { // Wenn ganz vorne gecuttet wird, muss das Attribut erhalten bleiben! // muessen kopiert und geaendert werden TextCharAttrib* pNewAttrib = new TextCharAttrib( *pAttrib ); pNewAttrib->GetStart() = 0; pNewAttrib->GetEnd() = pAttrib->GetEnd()-nPos; pNew->maCharAttribs.InsertAttrib( pNewAttrib ); // stutzen: pAttrib->GetEnd() = nPos; } else { DBG_ASSERT( pAttrib->GetStart() >= nPos, "Start < nPos!" ); DBG_ASSERT( pAttrib->GetEnd() >= nPos, "End < nPos!" ); // alle dahinter verschieben in den neuen Node (this) maCharAttribs.RemoveAttrib( nAttr ); pNew->maCharAttribs.InsertAttrib( pAttrib ); pAttrib->GetStart() = pAttrib->GetStart() - nPos; pAttrib->GetEnd() = pAttrib->GetEnd() - nPos; nAttr--; } } return pNew; } void TextNode::Append( const TextNode& rNode ) { sal_uInt16 nOldLen = maText.Len(); maText += rNode.GetText(); const sal_uInt16 nAttribs = rNode.GetCharAttribs().Count(); for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ ) { TextCharAttrib* pAttrib = rNode.GetCharAttribs().GetAttrib( nAttr ); sal_Bool bMelted = sal_False; if ( pAttrib->GetStart() == 0 ) { // Evtl koennen Attribute zusammengefasst werden: sal_uInt16 nTmpAttribs = maCharAttribs.Count(); for ( sal_uInt16 nTmpAttr = 0; nTmpAttr < nTmpAttribs; nTmpAttr++ ) { TextCharAttrib* pTmpAttrib = maCharAttribs.GetAttrib( nTmpAttr ); if ( pTmpAttrib->GetEnd() == nOldLen ) { if ( ( pTmpAttrib->Which() == pAttrib->Which() ) && ( pTmpAttrib->GetAttr() == pAttrib->GetAttr() ) ) { pTmpAttrib->GetEnd() = pTmpAttrib->GetEnd() + pAttrib->GetLen(); bMelted = sal_True; break; // es kann nur eins von der Sorte an der Stelle geben } } } } if ( !bMelted ) { TextCharAttrib* pNewAttrib = new TextCharAttrib( *pAttrib ); pNewAttrib->GetStart() = pNewAttrib->GetStart() + nOldLen; pNewAttrib->GetEnd() = pNewAttrib->GetEnd() + nOldLen; maCharAttribs.InsertAttrib( pNewAttrib ); } } } // ------------------------------------------------------------------------- // (+) class TextDoc // ------------------------------------------------------------------------- TextDoc::TextDoc() { mnLeftMargin = 0; }; TextDoc::~TextDoc() { DestroyTextNodes(); } void TextDoc::Clear() { DestroyTextNodes(); } void TextDoc::DestroyTextNodes() { for ( sal_uLong nNode = 0; nNode < maTextNodes.Count(); nNode++ ) delete maTextNodes.GetObject( nNode ); maTextNodes.clear(); } String TextDoc::GetText( const sal_Unicode* pSep ) const { sal_uLong nLen = GetTextLen( pSep ); sal_uLong nNodes = maTextNodes.Count(); if ( nLen > STRING_MAXLEN ) { OSL_FAIL( "Text zu gross fuer String" ); return String(); } String aASCIIText; sal_uLong nLastNode = nNodes-1; for ( sal_uLong nNode = 0; nNode < nNodes; nNode++ ) { TextNode* pNode = maTextNodes.GetObject( nNode ); String aTmp( pNode->GetText() ); aASCIIText += aTmp; if ( pSep && ( nNode != nLastNode ) ) aASCIIText += pSep; } return aASCIIText; } XubString TextDoc::GetText( sal_uLong nPara ) const { XubString aText; TextNode* pNode = ( nPara < maTextNodes.Count() ) ? maTextNodes.GetObject( nPara ) : 0; if ( pNode ) aText = pNode->GetText(); return aText; } sal_uLong TextDoc::GetTextLen( const sal_Unicode* pSep, const TextSelection* pSel ) const { sal_uLong nLen = 0; sal_uLong nNodes = maTextNodes.Count(); if ( nNodes ) { sal_uLong nStartNode = 0; sal_uLong nEndNode = nNodes-1; if ( pSel ) { nStartNode = pSel->GetStart().GetPara(); nEndNode = pSel->GetEnd().GetPara(); } for ( sal_uLong nNode = nStartNode; nNode <= nEndNode; nNode++ ) { TextNode* pNode = maTextNodes.GetObject( nNode ); sal_uInt16 nS = 0; sal_uLong nE = pNode->GetText().Len(); if ( pSel && ( nNode == pSel->GetStart().GetPara() ) ) nS = pSel->GetStart().GetIndex(); if ( pSel && ( nNode == pSel->GetEnd().GetPara() ) ) nE = pSel->GetEnd().GetIndex(); nLen += ( nE - nS ); } if ( pSep ) nLen += (nEndNode-nStartNode) * rtl_ustr_getLength(pSep); } return nLen; } TextPaM TextDoc::InsertText( const TextPaM& rPaM, sal_Unicode c ) { DBG_ASSERT( c != 0x0A, "TextDoc::InsertText: Zeilentrenner in Absatz nicht erlaubt!" ); DBG_ASSERT( c != 0x0D, "TextDoc::InsertText: Zeilentrenner in Absatz nicht erlaubt!" ); TextNode* pNode = maTextNodes.GetObject( rPaM.GetPara() ); pNode->InsertText( rPaM.GetIndex(), c ); TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+1 ); return aPaM; } TextPaM TextDoc::InsertText( const TextPaM& rPaM, const XubString& rStr ) { DBG_ASSERT( rStr.Search( 0x0A ) == STRING_NOTFOUND, "TextDoc::InsertText: Zeilentrenner in Absatz nicht erlaubt!" ); DBG_ASSERT( rStr.Search( 0x0D ) == STRING_NOTFOUND, "TextDoc::InsertText: Zeilentrenner in Absatz nicht erlaubt!" ); TextNode* pNode = maTextNodes.GetObject( rPaM.GetPara() ); pNode->InsertText( rPaM.GetIndex(), rStr ); TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+rStr.Len() ); return aPaM; } TextPaM TextDoc::InsertParaBreak( const TextPaM& rPaM, sal_Bool bKeepEndingAttribs ) { TextNode* pNode = maTextNodes.GetObject( rPaM.GetPara() ); TextNode* pNew = pNode->Split( rPaM.GetIndex(), bKeepEndingAttribs ); maTextNodes.Insert( pNew, rPaM.GetPara()+1 ); TextPaM aPaM( rPaM.GetPara()+1, 0 ); return aPaM; } TextPaM TextDoc::ConnectParagraphs( TextNode* pLeft, TextNode* pRight ) { sal_uInt16 nPrevLen = pLeft->GetText().Len(); pLeft->Append( *pRight ); // der rechte verschwindet. sal_uLong nRight = maTextNodes.GetPos( pRight ); maTextNodes.Remove( nRight ); delete pRight; sal_uLong nLeft = maTextNodes.GetPos( pLeft ); TextPaM aPaM( nLeft, nPrevLen ); return aPaM; } TextPaM TextDoc::RemoveChars( const TextPaM& rPaM, sal_uInt16 nChars ) { TextNode* pNode = maTextNodes.GetObject( rPaM.GetPara() ); pNode->RemoveText( rPaM.GetIndex(), nChars ); return rPaM; } sal_Bool TextDoc::IsValidPaM( const TextPaM& rPaM ) { if ( rPaM.GetPara() >= maTextNodes.Count() ) { OSL_FAIL( "PaM: Para out of range" ); return sal_False; } TextNode * pNode = maTextNodes.GetObject( rPaM.GetPara() ); if ( rPaM.GetIndex() > pNode->GetText().Len() ) { OSL_FAIL( "PaM: Index out of range" ); return sal_False; } return sal_True; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */