Files
libreoffice/svtools/source/edit/textdoc.cxx
Thomas Arnhold 30fb54aca3 remove formfeeds
2012-04-12 12:35:48 +02:00

645 lines
20 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright 2000, 2010 Oracle and/or its affiliates.
*
* OpenOffice.org - a multi-platform office productivity suite
*
* This file is part of OpenOffice.org.
*
* OpenOffice.org is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3
* only, as published by the Free Software Foundation.
*
* OpenOffice.org is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License version 3 for more details
* (a copy is included in the LICENSE file that accompanied this code).
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with OpenOffice.org. If not, see
* <http://www.openoffice.org/license.html>
* for a copy of the LGPLv3 License.
*
************************************************************************/
#include <textdoc.hxx>
#include <stdlib.h>
SV_IMPL_PTRARR( TextCharAttribs, TextCharAttribPtr );
// Vergleichmethode wird von QuickSort gerufen...
extern "C" {
int SAL_CALL CompareStart( const void* pFirst, const void* pSecond )
{
if ( (*((TextCharAttrib**)pFirst))->GetStart() < (*((TextCharAttrib**)pSecond))->GetStart() )
return (-1);
else if ( (*((TextCharAttrib**)pFirst))->GetStart() > (*((TextCharAttrib**)pSecond))->GetStart() )
return (1);
return 0;
}
}
// -------------------------------------------------------------------------
// (+) 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 )
TextCharAttribs::DeleteAndDestroy( 0, Count() );
else
TextCharAttribs::Remove( 0, Count() );
}
void TextCharAttribList::InsertAttrib( TextCharAttrib* pAttrib )
{
if ( pAttrib->IsEmpty() )
mbHasEmptyAttribs = sal_True;
const sal_uInt16 nCount = Count();
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 = GetObject( x );
if ( pCurAttrib->GetStart() > nStart )
{
Insert( pAttrib, x );
bInserted = sal_True;
break;
}
}
if ( !bInserted )
Insert( pAttrib, nCount );
}
void TextCharAttribList::ResortAttribs()
{
if ( Count() )
qsort( (void*)GetData(), Count(), sizeof( TextCharAttrib* ), 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 = Count(); nAttr; )
{
TextCharAttrib* pAttr = GetObject( --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 = Count();
for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
{
TextCharAttrib* pAttr = GetObject( 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 = Count(); nAttr; )
{
const TextCharAttrib* pAttr = GetObject( --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 = Count(); nAttr; )
{
TextCharAttrib* pAttr = GetObject( --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 = Count();
for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
{
TextCharAttrib* pAttr = GetObject( 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 < Count(); nAttr++ )
{
TextCharAttrib* pAttr = GetObject( nAttr );
if ( pAttr->IsEmpty() )
{
Remove( 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 xub_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) * String( pSep ).Len();
}
return nLen;
}
TextPaM TextDoc::InsertText( const TextPaM& rPaM, xub_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: */