Files
libreoffice/vcl/source/edit/textdoc.cxx
Michaël Lefèvre 63d8977f9f fdo#75757 remove inheritance from std::vector
For TextDoc, also removing ToolsList

Change-Id: Id818f61f562317ce106414937253f1748a33315a
Reviewed-on: https://gerrit.libreoffice.org/12454
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
Tested-by: Caolán McNamara <caolanm@redhat.com>
2014-11-18 11:32:19 +00:00

563 lines
17 KiB
C++

/* -*- 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 <textdoc.hxx>
#include <stdlib.h>
#include <boost/mem_fn.hpp>
#include <osl/diagnose.h>
// compare function called by QuickSort
static bool CompareStart( const TextCharAttrib& pFirst, const TextCharAttrib& pSecond )
{
return pFirst.GetStart() < pSecond.GetStart();
}
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;
}
TextCharAttribList::TextCharAttribList()
{
mbHasEmptyAttribs = false;
}
TextCharAttribList::~TextCharAttribList()
{
// PTRARR_DEL
}
void TextCharAttribList::Clear()
{
maAttribs.clear();
}
void TextCharAttribList::InsertAttrib( TextCharAttrib* pAttrib )
{
if ( pAttrib->IsEmpty() )
mbHasEmptyAttribs = true;
const sal_uInt16 nStart = pAttrib->GetStart(); // maybe better for Comp.Opt.
bool bInserted = false;
for (TextCharAttribs::iterator it = maAttribs.begin(); it != maAttribs.end(); ++it)
{
if ( it->GetStart() > nStart )
{
maAttribs.insert( it, pAttrib );
bInserted = true;
break;
}
}
if ( !bInserted )
maAttribs.push_back( pAttrib );
}
void TextCharAttribList::ResortAttribs()
{
maAttribs.sort(CompareStart);
}
TextCharAttrib* TextCharAttribList::FindAttrib( sal_uInt16 nWhich, sal_uInt16 nPos )
{
for (TextCharAttribs::reverse_iterator it = maAttribs.rbegin(); it != maAttribs.rend(); ++it)
{
if ( it->GetEnd() < nPos )
return 0;
if ( ( it->Which() == nWhich ) && it->IsIn(nPos) )
return &*it;
}
return NULL;
}
const TextCharAttrib* TextCharAttribList::FindNextAttrib( sal_uInt16 nWhich, sal_uInt16 nFromPos, sal_uInt16 nMaxPos ) const
{
DBG_ASSERT( nWhich, "FindNextAttrib: Which?" );
for (TextCharAttribs::const_iterator it = maAttribs.begin(); it != maAttribs.end(); ++it)
{
if ( ( it->GetStart() >= nFromPos ) &&
( it->GetEnd() <= nMaxPos ) &&
( it->Which() == nWhich ) )
return &*it;
}
return NULL;
}
bool TextCharAttribList::HasAttrib( sal_uInt16 nWhich ) const
{
for (TextCharAttribs::const_reverse_iterator it = maAttribs.rbegin(); it != maAttribs.rend(); ++it)
{
if ( it->Which() == nWhich )
return true;
}
return false;
}
bool TextCharAttribList::HasBoundingAttrib( sal_uInt16 nBound )
{
for (TextCharAttribs::reverse_iterator it = maAttribs.rbegin(); it != maAttribs.rend(); ++it)
{
if ( it->GetEnd() < nBound )
return false;
if ( ( it->GetStart() == nBound ) || ( it->GetEnd() == nBound ) )
return true;
}
return false;
}
TextCharAttrib* TextCharAttribList::FindEmptyAttrib( sal_uInt16 nWhich, sal_uInt16 nPos )
{
if ( !mbHasEmptyAttribs )
return 0;
for (TextCharAttribs::iterator it = maAttribs.begin(); it != maAttribs.end(); ++it)
{
if ( it->GetStart() > nPos )
return 0;
if ( ( it->GetStart() == nPos ) && ( it->GetEnd() == nPos ) && ( it->Which() == nWhich ) )
return &*it;
}
return 0;
}
void TextCharAttribList::DeleteEmptyAttribs()
{
maAttribs.erase_if(boost::mem_fn(&TextCharAttrib::IsEmpty));
mbHasEmptyAttribs = false;
}
TextNode::TextNode( const OUString& rText ) :
maText( rText )
{
}
void TextNode::ExpandAttribs( sal_uInt16 nIndex, sal_uInt16 nNew )
{
if ( !nNew )
return;
bool bResort = false;
sal_uInt16 nAttribs = maCharAttribs.Count();
for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
{
TextCharAttrib* pAttrib = maCharAttribs.GetAttrib( nAttr );
if ( pAttrib->GetEnd() >= nIndex )
{
// move all attributes that are behind the cursor
if ( pAttrib->GetStart() > nIndex )
{
pAttrib->MoveForward( nNew );
}
// 0: expand empty attribute, if at cursor
else if ( pAttrib->IsEmpty() )
{
// Do not check the index; empty one may only be here.
// If checking later anyway, special case:
// Start == 0; AbsLen == 1, nNew = 1 => Expand due to new paragraph!
// Start <= nIndex, End >= nIndex => Start=End=nIndex!
pAttrib->Expand( nNew );
}
// 1: attribute starts before and reaches up to index
else if ( pAttrib->GetEnd() == nIndex ) // start must be before
{
// Only expand if no feature and not in Exclude list!
// Otherwise e.g. an UL would go until the new ULDB, thus expand both.
if ( !maCharAttribs.FindEmptyAttrib( pAttrib->Which(), nIndex ) )
{
pAttrib->Expand( nNew );
}
else
bResort = true;
}
// 2: attribute starts before and reaches past the index
else if ( ( pAttrib->GetStart() < nIndex ) && ( pAttrib->GetEnd() > nIndex ) )
{
pAttrib->Expand( nNew );
}
// 3: attribute starts at Index
else if ( pAttrib->GetStart() == nIndex )
{
if ( nIndex == 0 )
{
pAttrib->Expand( nNew );
}
else
pAttrib->MoveForward( nNew );
}
}
DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Expand: Attribut verdreht!" );
DBG_ASSERT( ( pAttrib->GetEnd() <= maText.getLength() ), "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;
bool bResort = false;
sal_uInt16 nEndChanges = nIndex+nDeleted;
for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
{
TextCharAttrib* pAttrib = maCharAttribs.GetAttrib( nAttr );
bool bDelAttr = false;
if ( pAttrib->GetEnd() >= nIndex )
{
// move all attributes that are behind the cursor
if ( pAttrib->GetStart() >= nEndChanges )
{
pAttrib->MoveBackward( nDeleted );
}
// 1. delete inner attributes
else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() <= nEndChanges ) )
{
// special case: attribute covers the region exactly
// => keep as an empty attribute
if ( ( pAttrib->GetStart() == nIndex ) && ( pAttrib->GetEnd() == nEndChanges ) )
pAttrib->GetEnd() = nIndex; // empty
else
bDelAttr = true;
}
// 2. attribute starts before, ends inside or after
else if ( ( pAttrib->GetStart() <= nIndex ) && ( pAttrib->GetEnd() > nIndex ) )
{
if ( pAttrib->GetEnd() <= nEndChanges ) // ends inside
pAttrib->GetEnd() = nIndex;
else
pAttrib->Collaps( nDeleted ); // ends after
}
// 3. attribute starts inside, ends after
else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() > nEndChanges ) )
{
// features are not allowed to expand!
pAttrib->GetStart() = nEndChanges;
pAttrib->MoveBackward( nDeleted );
}
}
DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Collaps: Attribut verdreht!" );
DBG_ASSERT( ( pAttrib->GetEnd() <= maText.getLength()) || bDelAttr, "Collaps: Attrib groesser als Absatz!" );
if ( bDelAttr /* || pAttrib->IsEmpty() */ )
{
bResort = true;
maCharAttribs.RemoveAttrib( nAttr );
delete pAttrib;
nAttr--;
}
else if ( pAttrib->IsEmpty() )
maCharAttribs.HasEmptyAttribs() = true;
}
if ( bResort )
maCharAttribs.ResortAttribs();
}
void TextNode::InsertText( sal_uInt16 nPos, const OUString& rText )
{
maText = maText.replaceAt( nPos, 0, rText );
ExpandAttribs( nPos, rText.getLength() );
}
void TextNode::InsertText( sal_uInt16 nPos, sal_Unicode c )
{
maText = maText.replaceAt( nPos, 0, OUString(c) );
ExpandAttribs( nPos, 1 );
}
void TextNode::RemoveText( sal_uInt16 nPos, sal_uInt16 nChars )
{
maText = maText.replaceAt( nPos, nChars, "" );
CollapsAttribs( nPos, nChars );
}
TextNode* TextNode::Split( sal_uInt16 nPos, bool bKeepEndingAttribs )
{
OUString aNewText;
if ( nPos < maText.getLength() )
{
aNewText = maText.copy( nPos );
maText = maText.copy(0, nPos);
}
TextNode* pNew = new TextNode( aNewText );
for ( sal_uInt16 nAttr = 0; nAttr < maCharAttribs.Count(); nAttr++ )
{
TextCharAttrib* pAttrib = maCharAttribs.GetAttrib( nAttr );
if ( pAttrib->GetEnd() < nPos )
{
// no change
;
}
else if ( pAttrib->GetEnd() == nPos )
{
// must be copied as an empty attribute
// !FindAttrib only sensible if traversing backwards through the list!
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() ) )
{
// If cutting at the very beginning, the attribute has to be
// copied and changed
TextCharAttrib* pNewAttrib = new TextCharAttrib( *pAttrib );
pNewAttrib->GetStart() = 0;
pNewAttrib->GetEnd() = pAttrib->GetEnd()-nPos;
pNew->maCharAttribs.InsertAttrib( pNewAttrib );
// trim
pAttrib->GetEnd() = nPos;
}
else
{
DBG_ASSERT( pAttrib->GetStart() >= nPos, "Start < nPos!" );
DBG_ASSERT( pAttrib->GetEnd() >= nPos, "End < nPos!" );
// move all into the new 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_Int32 nOldLen = maText.getLength();
maText += rNode.GetText();
const sal_uInt16 nAttribs = rNode.GetCharAttribs().Count();
for ( sal_uInt16 nAttr = 0; nAttr < nAttribs; nAttr++ )
{
const TextCharAttrib& rAttrib = rNode.GetCharAttrib( nAttr );
bool bMelted = false;
if ( rAttrib.GetStart() == 0 )
{
// potentially merge attributes
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() == rAttrib.Which() ) &&
( pTmpAttrib->GetAttr() == rAttrib.GetAttr() ) )
{
pTmpAttrib->GetEnd() =
pTmpAttrib->GetEnd() + rAttrib.GetLen();
bMelted = true;
break; // there can be only one of this type at this position
}
}
}
}
if ( !bMelted )
{
TextCharAttrib* pNewAttrib = new TextCharAttrib( rAttrib );
pNewAttrib->GetStart() = pNewAttrib->GetStart() + nOldLen;
pNewAttrib->GetEnd() = pNewAttrib->GetEnd() + nOldLen;
maCharAttribs.InsertAttrib( pNewAttrib );
}
}
}
bool TextNode::operator==(TextNode const& other) const
{
return maText == other.maText && &maCharAttribs == &other.maCharAttribs;
}
TextDoc::TextDoc()
{
mnLeftMargin = 0;
};
void TextDoc::Clear()
{
maTextNodes.clear();
}
OUString TextDoc::GetText( const sal_Unicode* pSep ) const
{
sal_uLong nNodes = maTextNodes.size();
OUString aASCIIText;
sal_uLong nLastNode = nNodes-1;
for ( sal_uLong nNode = 0; nNode < nNodes; nNode++ )
{
const TextNode& pNode = maTextNodes[ nNode ];
OUString aTmp( pNode.GetText() );
aASCIIText += aTmp;
if ( pSep && ( nNode != nLastNode ) )
aASCIIText += pSep;
}
return aASCIIText;
}
OUString TextDoc::GetText( sal_uLong nPara ) const
{
OUString aText;
if ( nPara < maTextNodes.size() )
aText = maTextNodes[ nPara ].GetText();
return aText;
}
sal_uLong TextDoc::GetTextLen( const sal_Unicode* pSep, const TextSelection* pSel ) const
{
sal_uLong nLen = 0;
sal_uLong nNodes = maTextNodes.size();
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++ )
{
const TextNode& pNode = maTextNodes[ nNode ];
sal_uInt16 nS = 0;
sal_Int32 nE = pNode.GetText().getLength();
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: Newline not allowed in paragraph!" );
DBG_ASSERT( c != 0x0D, "TextDoc::InsertText: Newline not allowed in paragraph!" );
TextNode& pNode = maTextNodes[ rPaM.GetPara() ];
pNode.InsertText( rPaM.GetIndex(), c );
TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+1 );
return aPaM;
}
TextPaM TextDoc::InsertText( const TextPaM& rPaM, const OUString& rStr )
{
DBG_ASSERT( rStr.indexOf( 0x0A ) == -1, "TextDoc::InsertText: Newline not allowed in paragraph!" );
DBG_ASSERT( rStr.indexOf( 0x0D ) == -1, "TextDoc::InsertText: Newline not allowed in paragraph!" );
TextNode& pNode = maTextNodes[ rPaM.GetPara() ];
pNode.InsertText( rPaM.GetIndex(), rStr );
TextPaM aPaM( rPaM.GetPara(), rPaM.GetIndex()+rStr.getLength() );
return aPaM;
}
TextPaM TextDoc::InsertParaBreak( const TextPaM& rPaM, bool bKeepEndingAttribs )
{
TextNode& pNode = maTextNodes[ rPaM.GetPara() ];
TextNode* pNew = pNode.Split( rPaM.GetIndex(), bKeepEndingAttribs );
maTextNodes.insert( maTextNodes.begin()+rPaM.GetPara()+1, pNew );
TextPaM aPaM( rPaM.GetPara()+1, 0 );
return aPaM;
}
TextPaM TextDoc::ConnectParagraphs( TextNode& pLeft, const TextNode& pRight )
{
sal_Int32 nPrevLen = pLeft.GetText().getLength();
pLeft.Append( pRight );
// the paragraph on the right vanishes
RemoveNode( ::std::find( maTextNodes.cbegin(), maTextNodes.cend(), pRight ) - maTextNodes.cbegin() );
sal_uLong nLeft = std::find( maTextNodes.begin(), maTextNodes.end(), pLeft ) - maTextNodes.begin();
TextPaM aPaM( nLeft, nPrevLen );
return aPaM;
}
TextPaM TextDoc::RemoveChars( const TextPaM& rPaM, sal_uInt16 nChars )
{
TextNode& pNode = maTextNodes[ rPaM.GetPara() ];
pNode.RemoveText( rPaM.GetIndex(), nChars );
return rPaM;
}
bool TextDoc::IsValidPaM( const TextPaM& rPaM )
{
if ( rPaM.GetPara() >= maTextNodes.size() )
{
OSL_FAIL( "PaM: Para out of range" );
return false;
}
TextNode& pNode = maTextNodes[ rPaM.GetPara() ];
if ( rPaM.GetIndex() > pNode.GetText().getLength() )
{
OSL_FAIL( "PaM: Index out of range" );
return false;
}
return true;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */