/**************************************************************
 * 
 * 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
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_svtools.hxx"
#include <textdoc.hxx>

#include <stdlib.h>

SV_IMPL_PTRARR( TextCharAttribs, TextCharAttribPtr );



// Vergleichmethode wird von QuickSort gerufen...

EXTERN_C
#if defined( PM2 ) && (!defined( CSET ) && !defined ( MTW ) && !defined( WTC ))
int _stdcall
#else
#ifdef WNT
#if _MSC_VER >= 1200
int __cdecl
#else
int _cdecl
#endif
#else
int
#endif
#endif

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;
}

#ifdef  DBG_UTIL
sal_Bool TextCharAttribList::DbgCheckAttribs()
{
	sal_Bool bOK = sal_True;
	for ( sal_uInt16 nAttr = 0; nAttr < Count(); nAttr++ )
	{
		TextCharAttrib* pAttr = GetObject( nAttr );
		if ( pAttr->GetStart() > pAttr->GetEnd() )
		{
			bOK = sal_False;
			DBG_ERROR( "Attr verdreht" );
		}
	}
	return bOK;
}
#endif

// -------------------------------------------------------------------------
// (+) 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();

#ifdef EDITDEBUG
	DBG_ASSERT( CheckOrderedList( (TextCharAttribs*)&maCharAttribs ), "Expand: Start-Liste verdreht" );
#endif
}

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();

#ifdef EDITDEBUG
	DBG_ASSERT( CheckOrderedList( (TextCharAttribs)&maCharAttribs ), "Collaps: Start-Liste verdreht" );
#endif
}

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();

#ifdef EDITDEBUG
	DBG_ASSERT( maCharAttribs.DbgCheckAttribs(), "Attribute VOR AppendAttribs kaputt" );
#endif

	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 );
		}
	}

#ifdef EDITDEBUG
	DBG_ASSERT( maCharAttribs.DbgCheckAttribs(), "Attribute NACH AppendAttribs kaputt" );
#endif
}

// -------------------------------------------------------------------------
// (+) 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 )
	{
		DBG_ERROR( "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() )
	{
		DBG_ERROR( "PaM: Para out of range" );
		return sal_False;
	}
	TextNode * pNode = maTextNodes.GetObject( rPaM.GetPara() );
	if ( rPaM.GetIndex() > pNode->GetText().Len() )
	{
		DBG_ERROR( "PaM: Index out of range" );
		return sal_False;
	}
	return sal_True;
}

/*

void TextDoc::InsertAttribInSelection( TextNode* pNode, sal_uInt16 nStart, sal_uInt16 nEnd, const SfxPoolItem& rPoolItem )
{
	DBG_ASSERT( pNode, "Wohin mit dem Attribut?" );
	DBG_ASSERT( nEnd <= pNode->Len(), "InsertAttrib: Attribut zu gross!" );

	// fuer Optimierung:
	// dieses endet am Anfang der Selektion => kann erweitert werden
	TextCharAttrib* pEndingAttrib = 0;
	// dieses startet am Ende der Selektion => kann erweitert werden
	TextCharAttrib* pStartingAttrib = 0;

	DBG_ASSERT( nStart <= nEnd, "Kleiner Rechenfehler in InsertAttribInSelection" );

	RemoveAttribs( pNode, nStart, nEnd, pStartingAttrib, pEndingAttrib, rPoolItem.Which() );

	if ( pStartingAttrib && pEndingAttrib &&
		 ( *(pStartingAttrib->GetItem()) == rPoolItem ) &&
		 ( *(pEndingAttrib->GetItem()) == rPoolItem ) )
	{
		// wird ein groesses Attribut.
		pEndingAttrib->GetEnd() = pStartingAttrib->GetEnd();
		pCurPool->Remove( *(pStartingAttrib->GetItem()) );
		pNode->GetCharAttribs().GetAttribs().Remove( pNode->GetCharAttribs().GetAttribs().GetPos( pStartingAttrib ) );
		delete pStartingAttrib;
	}
	else if ( pStartingAttrib && ( *(pStartingAttrib->GetItem()) == rPoolItem ) )
		pStartingAttrib->GetStart() = nStart;
	else if ( pEndingAttrib && ( *(pEndingAttrib->GetItem()) == rPoolItem ) )
		pEndingAttrib->GetEnd() = nEnd;
	else
		InsertAttrib( rPoolItem, pNode, nStart, nEnd );

	if ( pStartingAttrib )
		pNode->GetCharAttribs().ResortAttribs();
}

sal_Bool TextDoc::RemoveAttribs( TextNode* pNode, sal_uInt16 nStart, sal_uInt16 nEnd, sal_uInt16 nWhich )
{
	TextCharAttrib* pStarting;
	TextCharAttrib* pEnding;
	return RemoveAttribs( pNode, nStart, nEnd, pStarting, pEnding, nWhich );
}

sal_Bool TextDoc::RemoveAttribs( TextNode* pNode, sal_uInt16 nStart, sal_uInt16 nEnd, TextCharAttrib*& rpStarting, TextCharAttrib*& rpEnding, sal_uInt16 nWhich )
{
	DBG_ASSERT( pNode, "Wohin mit dem Attribut?" );
	DBG_ASSERT( nEnd <= pNode->Len(), "InsertAttrib: Attribut zu gross!" );

	// dieses endet am Anfang der Selektion => kann erweitert werden
	rpEnding = 0;
	// dieses startet am Ende der Selektion => kann erweitert werden
	rpStarting = 0;

	sal_Bool bChanged = sal_False;

	DBG_ASSERT( nStart <= nEnd, "Kleiner Rechenfehler in InsertAttribInSelection" );

	// ueber die Attribute iterieren...
	sal_uInt16 nAttr = 0;
	TextCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
	while ( pAttr )
	{
		sal_Bool bRemoveAttrib = sal_False;
		if ( !nWhich || ( pAttr->Which() == nWhich ) )
		{
			// Attribut beginnt in Selection
			if ( ( pAttr->GetStart() >= nStart ) && ( pAttr->GetStart() <= nEnd ) )
			{
				bChanged = sal_True;
				if ( pAttr->GetEnd() > nEnd )
				{
					pAttr->GetStart() = nEnd;	// dann faengt es dahinter an
					rpStarting = pAttr;
					break;	// es kann kein weiteres Attrib hier liegen
				}
				else if ( !pAttr->IsFeature() || ( pAttr->GetStart() == nStart ) )
				{
					// Feature nur loeschen, wenn genau an der Stelle
					bRemoveAttrib = sal_True;
				}
			}

			// Attribut endet in Selection
			else if ( ( pAttr->GetEnd() >= nStart ) && ( pAttr->GetEnd() <= nEnd ) )
			{
				bChanged = sal_True;
				if ( ( pAttr->GetStart() < nStart ) && !pAttr->IsFeature() )
				{
					pAttr->GetEnd() = nStart;	// dann hoert es hier auf
					rpEnding = pAttr;
				}
				else if ( !pAttr->IsFeature() || ( pAttr->GetStart() == nStart ) )
				{
					// Feature nur loeschen, wenn genau an der Stelle
					bRemoveAttrib = sal_True;
				}
			}
			// Attribut ueberlappt die Selektion
			else if ( ( pAttr->GetStart() <= nStart ) && ( pAttr->GetEnd() >= nEnd ) )
			{
				bChanged = sal_True;
				if ( pAttr->GetStart() == nStart )
				{
					pAttr->GetStart() = nEnd;
					rpStarting = pAttr;
					break;	// es kann weitere Attribute geben!
				}
				else if ( pAttr->GetEnd() == nEnd )
				{
					pAttr->GetEnd() = nStart;
					rpEnding = pAttr;
					break;	// es kann weitere Attribute geben!
				}
				else // Attribut muss gesplittet werden...
				{
					sal_uInt16 nOldEnd = pAttr->GetEnd();
					pAttr->GetEnd() = nStart;
					rpEnding = pAttr;
//					sal_uLong nSavePos = pNode->GetCharAttribs().GetStartList().GetCurPos();
					InsertAttrib( *pAttr->GetItem(), pNode, nEnd, nOldEnd );
//					pNode->GetCharAttribs().GetStartList().Seek( nSavePos );
					break;	// es kann weitere Attribute geben!
				}
			}
		}
		if ( bRemoveAttrib )
		{
			DBG_ASSERT( ( pAttr != rpStarting ) && ( pAttr != rpEnding ), "Loeschen und behalten des gleichen Attributs ?" );
			pNode->GetCharAttribs().GetAttribs().Remove(nAttr);
			pCurPool->Remove( *pAttr->GetItem() );
			delete pAttr;
			nAttr--;
		}
		nAttr++;
		pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
	}
	return bChanged;
}

#pragma SEG_FUNCDEF(editdoc_3f)

void TextDoc::InsertAttrib( const SfxPoolItem& rPoolItem, TextNode* pNode, sal_uInt16 nStart, sal_uInt16 nEnd )
{
	// Diese Methode prueft nicht mehr, ob ein entspr. Attribut
	// schon an der Stelle existiert!

	// pruefen, ob neues Attrib oder einfach nur Ende eines Attribs...
//	const SfxPoolItem& rDefItem = pNode->GetContentAttribs().GetItem( rPoolItem.Which() );
//	sal_Bool bCreateAttrib = ( rDefItem != rPoolItem );

	// Durch den Verlust der Exclude-Liste geht es nicht mehr, dass ich
	// kein neues Attribut benoetige und nur das alte nicht expandiere...
//	if ( !bCreateAttrib )
	{
		// => Wenn schon Default-Item, dann wenigstens nur dann einstellen,
		// wenn davor wirklich ein entsprechendes Attribut.
//		if ( pNode->GetCharAttribs().FindAttrib( rPoolItem.Which(), nStart ) )
//			bCreateAttrib = sal_True;
		// Aber kleiner Trost:
		// Die wenigsten schreiben, aendern das Attr, schreiben, und
		// stellen dann wieder das Default-Attr ein.
	}

	// 22.9.95:
	// Die Uberlegung, einfach das andere Attribut nicht zu expandieren, war
	// sowieso falsch, da das DefAttr aus einer Vorlage kommen kann,
	// die irgendwann verschwindet!
//	if ( bCreateAttrib )
//	{
		TextCharAttrib* pAttrib = MakeCharAttrib( *pCurPool, rPoolItem, nStart, nEnd );
		DBG_ASSERT( pAttrib, "MakeCharAttrib fehlgeschlagen!" );
		pNode->GetCharAttribs().InsertAttrib( pAttrib );
//	}
//	else
//	{
//		TextCharAttrib* pTmpAttrib =
//			pNode->GetCharAttribs().FindAnyAttrib( rPoolItem.Which() );
//		if ( pTmpAttrib ) 	// sonst benoetige ich es sowieso nicht....
//		{
//			aExcludeList.Insert( pTmpAttrib->GetItem() );
//		}
//	}
}

#pragma SEG_FUNCDEF(editdoc_40)

void TextDoc::InsertAttrib( TextNode* pNode, sal_uInt16 nStart, sal_uInt16 nEnd, const SfxPoolItem& rPoolItem )
{
	if ( nStart != nEnd )
	{
		InsertAttribInSelection( pNode, nStart, nEnd, rPoolItem );
	}
	else
	{
		// Pruefen, ob schon ein neues Attribut mit der WhichId an der Stelle:
		TextCharAttrib* pAttr = pNode->GetCharAttribs().FindEmptyAttrib( rPoolItem.Which(), nStart );
		if ( pAttr )
		{
			// Attribut entfernen....
			pNode->GetCharAttribs().GetAttribs().Remove(
				pNode->GetCharAttribs().GetAttribs().GetPos( pAttr ) );
		}

		// pruefen, ob ein 'gleiches' Attribut an der Stelle liegt.
		pAttr = pNode->GetCharAttribs().FindAttrib( rPoolItem.Which(), nStart );
		if ( pAttr )
		{
			if ( pAttr->IsInside( nStart ) )	// splitten
			{
				// ???????????????????????????????
				// eigentlich noch pruefen, ob wirklich splittet, oder return !
				// ???????????????????????????????
				sal_uInt16 nOldEnd = pAttr->GetEnd();
				pAttr->GetEnd() = nStart;
				pAttr = MakeCharAttrib( *pCurPool, *(pAttr->GetItem()), nStart, nOldEnd );
				pNode->GetCharAttribs().InsertAttrib( pAttr );
			}
			else if ( pAttr->GetEnd() == nStart )
			{
				DBG_ASSERT( !pAttr->IsEmpty(), "Doch noch ein leeres Attribut?" );
				// pruefen, ob genau das gleiche Attribut
				if ( *(pAttr->GetItem()) == rPoolItem )
					return;
			}
		}
		InsertAttrib( rPoolItem, pNode, nStart, nStart );
	}
}

#pragma SEG_FUNCDEF(editdoc_41)

void TextDoc::FindAttribs( TextNode* pNode, sal_uInt16 nStartPos, sal_uInt16 nEndPos, SfxItemSet& rCurSet )
{
	DBG_ASSERT( pNode, "Wo soll ich suchen ?" );
	DBG_ASSERT( nStartPos <= nEndPos, "Ungueltiger Bereich!" );

	sal_uInt16 nAttr = 0;
	TextCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
	// keine Selection...
	if ( nStartPos == nEndPos )
	{
		while ( pAttr && ( pAttr->GetStart() <= nEndPos) )
		{
			const SfxPoolItem* pItem = 0;
			// Attribut liegt dadrueber...
			if ( ( pAttr->GetStart() < nStartPos ) && ( pAttr->GetEnd() > nStartPos ) )
				pItem = pAttr->GetItem();
			// Attribut endet hier, ist nicht leer
			else if ( ( pAttr->GetStart() < nStartPos ) && ( pAttr->GetEnd() == nStartPos ) )
			{
				if ( !pNode->GetCharAttribs().FindEmptyAttrib( pAttr->GetItem()->Which(), nStartPos ) )
					pItem = pAttr->GetItem();
			}
			// Attribut endet hier, ist leer
			else if ( ( pAttr->GetStart() == nStartPos ) && ( pAttr->GetEnd() == nStartPos ) )
			{
//				if ( aExcludeList.FindAttrib( pAttr->GetItem()->Which() ) )
					pItem = pAttr->GetItem();
//				else if ( pNode->Len() == 0 )	// Sonderfall
//					pItem = pAttr->GetItem();
			}
			// Attribut beginnt hier
			else if ( ( pAttr->GetStart() == nStartPos ) && ( pAttr->GetEnd() > nStartPos ) )
			{
				if ( nStartPos == 0 ) 	// Sonderfall
					pItem = pAttr->GetItem();
			}

			if ( pItem )
			{
				sal_uInt16 nWhich = pItem->Which();
				if ( rCurSet.GetItemState( nWhich ) == SFX_ITEM_OFF )
				{
					rCurSet.Put( *pItem );
				}
				else if ( rCurSet.GetItemState( nWhich ) == SFX_ITEM_ON )
				{
					const SfxPoolItem& rItem = rCurSet.Get( nWhich );
					if ( rItem != *pItem )
					{
						rCurSet.InvalidateItem( nWhich );
					}
				}
			}
			nAttr++;
			pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
		}
	}
	else	// Selektion
	{
		while ( pAttr && ( pAttr->GetStart() < nEndPos) )
		{
			const SfxPoolItem* pItem = 0;
			// Attribut liegt dadrueber...
			if ( ( pAttr->GetStart() <= nStartPos ) && ( pAttr->GetEnd() >= nEndPos ) )
				pItem = pAttr->GetItem();
			// Attribut startet mitten drin...
			else if ( pAttr->GetStart() >= nStartPos )
			{
				// !!! pItem = pAttr->GetItem();
				// einfach nur pItem reicht nicht, da ich z.B. bei Shadow
				// niemals ein ungleiches Item finden wuerde, da ein solche
				// seine Anwesenheit durch Abwesenheit repraesentiert!
				// if ( ... )
				// Es muesste geprueft werden, on genau das gleiche Attribut
				// an der Bruchstelle aufsetzt, was recht aufwendig ist.
				// Da ich beim Einfuegen von Attributen aber etwas optimiere
				// tritt der Fall nicht so schnell auf...
				// Also aus Geschwindigkeitsgruenden:
				rCurSet.InvalidateItem( pAttr->GetItem()->Which() );

			}
			// Attribut endet mitten drin...
			else if ( pAttr->GetEnd() > nStartPos )
			{
				// pItem = pAttr->GetItem();
				// s.o.

				// -----------------31.05.95 16:01-------------------
				//  Ist falsch, wenn das gleiche Attribut sofort wieder
				//  eingestellt wird!
				//  => Sollte am besten nicht vorkommen, also gleich beim
				//  	Setzen von Attributen richtig machen!
				// --------------------------------------------------
				rCurSet.InvalidateItem( pAttr->GetItem()->Which() );
			}

			if ( pItem )
			{
				sal_uInt16 nWhich = pItem->Which();
				if ( rCurSet.GetItemState( nWhich ) == SFX_ITEM_OFF )
				{
					rCurSet.Put( *pItem );
				}
				else if ( rCurSet.GetItemState( nWhich ) == SFX_ITEM_ON )
				{
					const SfxPoolItem& rItem = rCurSet.Get( nWhich );
					if ( rItem != *pItem )
					{
						rCurSet.InvalidateItem( nWhich );
					}
				}
			}
			nAttr++;
			pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
		}
	}
}


*/


