You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3306 lines
122 KiB
3306 lines
122 KiB
/* This file is part of the KDE project
|
|
Copyright (C) 2001-2006 David Faure <faure@kde.org>
|
|
Copyright (C) 2005-2006 Martin Ellis <martin.ellis@kdemail.net>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library 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
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "KoTextParag.h"
|
|
#include "KoTextDocument.h"
|
|
#include "KoParagCounter.h"
|
|
#include "KoTextZoomHandler.h"
|
|
#include "KoStyleCollection.h"
|
|
#include "KoVariable.h"
|
|
#include <KoOasisContext.h>
|
|
#include <KoXmlWriter.h>
|
|
#include <KoGenStyles.h>
|
|
#include <KoDom.h>
|
|
#include <KoXmlNS.h>
|
|
#include <kglobal.h>
|
|
#include <klocale.h>
|
|
#include <kdebug.h>
|
|
#include <kglobalsettings.h>
|
|
#include <assert.h>
|
|
|
|
//#define DEBUG_PAINT
|
|
|
|
KoTextParag::KoTextParag( KoTextDocument *d, KoTextParag *pr, KoTextParag *nx, bool updateIds )
|
|
: p( pr ), n( nx ), doc( d ),
|
|
m_invalid( true ),
|
|
changed( FALSE ),
|
|
fullWidth( TRUE ),
|
|
newLinesAllowed( TRUE ), // default in kotext
|
|
visible( TRUE ), //breakable( TRUE ),
|
|
movedDown( FALSE ),
|
|
m_toc( false ),
|
|
align( 0 ),
|
|
m_lineChanged( -1 ),
|
|
m_wused( 0 ),
|
|
mSelections( 0 ),
|
|
mFloatingItems( 0 ),
|
|
tArray( 0 )
|
|
{
|
|
defFormat = formatCollection()->defaultFormat();
|
|
/*if ( !doc ) {
|
|
tabStopWidth = defFormat->width( 'x' ) * 8;
|
|
commandHistory = new KoTextDocCommandHistory( 100 );
|
|
}*/
|
|
|
|
if ( p ) {
|
|
p->n = this;
|
|
}
|
|
if ( n ) {
|
|
n->p = this;
|
|
}
|
|
|
|
if ( !p && doc )
|
|
doc->setFirstParag( this );
|
|
if ( !n && doc )
|
|
doc->setLastParag( this );
|
|
|
|
//firstFormat = TRUE; //// unused
|
|
//firstPProcess = TRUE;
|
|
//state = -1;
|
|
//needPreProcess = FALSE;
|
|
|
|
if ( p )
|
|
id = p->id + 1;
|
|
else
|
|
id = 0;
|
|
if ( n && updateIds ) {
|
|
KoTextParag *s = n;
|
|
while ( s ) {
|
|
s->id = s->p->id + 1;
|
|
//s->lm = s->rm = s->tm = s->bm = -1, s->flm = -1;
|
|
s = s->n;
|
|
}
|
|
}
|
|
|
|
str = new KoTextString();
|
|
str->insert( 0, " ", formatCollection()->defaultFormat() );
|
|
setJoinBorder( true );
|
|
}
|
|
|
|
KoTextParag::~KoTextParag()
|
|
{
|
|
//kdDebug(32500) << "KoTextParag::~KoTextParag " << this << " id=" << paragId() << endl;
|
|
|
|
// #107961: unregister custom items; KoTextString::clear() will delete them
|
|
const int len = str->length();
|
|
for ( int i = 0; i < len; ++i ) {
|
|
KoTextStringChar *c = at( i );
|
|
if ( doc && c->isCustom() ) {
|
|
doc->unregisterCustomItem( c->customItem(), this );
|
|
//removeCustomItem();
|
|
}
|
|
}
|
|
|
|
delete str;
|
|
str = 0;
|
|
// if ( doc && p == doc->minwParag ) {
|
|
// doc->minwParag = 0;
|
|
// doc->minw = 0;
|
|
// }
|
|
if ( !doc ) {
|
|
//delete pFormatter;
|
|
//delete commandHistory;
|
|
}
|
|
delete [] tArray;
|
|
//delete eData;
|
|
TQMap<int, KoTextParagLineStart*>::Iterator it = lineStarts.begin();
|
|
for ( ; it != lineStarts.end(); ++it )
|
|
delete *it;
|
|
if ( mSelections ) delete mSelections;
|
|
if ( mFloatingItems ) delete mFloatingItems;
|
|
|
|
if (p)
|
|
p->setNext(n);
|
|
if (n)
|
|
n->setPrev(p);
|
|
|
|
//// kotext
|
|
if ( doc && !doc->isDestroying() )
|
|
{
|
|
doc->informParagraphDeleted( this );
|
|
}
|
|
//kdDebug(32500) << "KoTextParag::~KoTextParag " << this << " done" << endl;
|
|
////
|
|
}
|
|
|
|
void KoTextParag::setNext( KoTextParag *s )
|
|
{
|
|
n = s;
|
|
if ( !n && doc )
|
|
doc->setLastParag( this );
|
|
}
|
|
|
|
void KoTextParag::setPrev( KoTextParag *s )
|
|
{
|
|
p = s;
|
|
if ( !p && doc )
|
|
doc->setFirstParag( this );
|
|
}
|
|
|
|
void KoTextParag::tqinvalidate( int /*chr, ignored*/ )
|
|
{
|
|
m_invalid = true;
|
|
#if 0
|
|
if ( invalid < 0 )
|
|
invalid = chr;
|
|
else
|
|
invalid = TQMIN( invalid, chr );
|
|
#endif
|
|
}
|
|
|
|
void KoTextParag::setChanged( bool b, bool /*recursive*/ )
|
|
{
|
|
changed = b;
|
|
m_lineChanged = -1; // all
|
|
}
|
|
|
|
void KoTextParag::setLineChanged( short int line )
|
|
{
|
|
if ( m_lineChanged == -1 ) {
|
|
if ( !changed ) // only if the whole parag wasn't "changed" already
|
|
m_lineChanged = line;
|
|
}
|
|
else
|
|
m_lineChanged = TQMIN( m_lineChanged, line ); // also works if line=-1
|
|
changed = true;
|
|
//kdDebug(32500) << "KoTextParag::setLineChanged line=" << line << " -> m_lineChanged=" << m_lineChanged << endl;
|
|
}
|
|
|
|
void KoTextParag::insert( int index, const TQString &s )
|
|
{
|
|
str->insert( index, s, formatCollection()->defaultFormat() );
|
|
tqinvalidate( index );
|
|
//needPreProcess = TRUE;
|
|
}
|
|
|
|
void KoTextParag::truncate( int index )
|
|
{
|
|
str->truncate( index );
|
|
insert( length(), " " );
|
|
//needPreProcess = TRUE;
|
|
}
|
|
|
|
void KoTextParag::remove( int index, int len )
|
|
{
|
|
if ( index + len - str->length() > 0 )
|
|
return;
|
|
for ( int i = index; i < index + len; ++i ) {
|
|
KoTextStringChar *c = at( i );
|
|
if ( doc && c->isCustom() ) {
|
|
doc->unregisterCustomItem( c->customItem(), this );
|
|
//removeCustomItem();
|
|
}
|
|
}
|
|
str->remove( index, len );
|
|
tqinvalidate( 0 );
|
|
//needPreProcess = TRUE;
|
|
}
|
|
|
|
void KoTextParag::join( KoTextParag *s )
|
|
{
|
|
//kdDebug(32500) << "KoTextParag::join this=" << paragId() << " (length " << length() << ") with " << s->paragId() << " (length " << s->length() << ")" << endl;
|
|
int oh = r.height() + s->r.height();
|
|
n = s->n;
|
|
if ( n )
|
|
n->p = this;
|
|
else if ( doc )
|
|
doc->setLastParag( this );
|
|
|
|
int start = str->length();
|
|
if ( length() > 0 && at( length() - 1 )->c == ' ' ) {
|
|
remove( length() - 1, 1 );
|
|
--start;
|
|
}
|
|
append( s->str->toString(), TRUE );
|
|
|
|
for ( int i = 0; i < s->length(); ++i ) {
|
|
if ( doc->useFormatCollection() ) {
|
|
s->str->at( i ).format()->addRef();
|
|
str->setFormat( i + start, s->str->at( i ).format(), TRUE );
|
|
}
|
|
if ( s->str->at( i ).isCustom() ) {
|
|
KoTextCustomItem * item = s->str->at( i ).customItem();
|
|
str->at( i + start ).setCustomItem( item );
|
|
s->str->at( i ).loseCustomItem();
|
|
doc->unregisterCustomItem( item, s ); // ### missing in TQRT
|
|
doc->registerCustomItem( item, this );
|
|
}
|
|
}
|
|
Q_ASSERT(str->at(str->length()-1).c == ' ');
|
|
|
|
/*if ( !extraData() && s->extraData() ) {
|
|
setExtraData( s->extraData() );
|
|
s->setExtraData( 0 );
|
|
} else if ( extraData() && s->extraData() ) {
|
|
extraData()->join( s->extraData() );
|
|
}*/
|
|
delete s;
|
|
tqinvalidate( 0 );
|
|
//// kotext
|
|
invalidateCounters();
|
|
////
|
|
r.setHeight( oh );
|
|
//needPreProcess = TRUE;
|
|
if ( n ) {
|
|
KoTextParag *s = n;
|
|
while ( s ) {
|
|
s->id = s->p->id + 1;
|
|
//s->state = -1;
|
|
//s->needPreProcess = TRUE;
|
|
s->changed = TRUE;
|
|
s = s->n;
|
|
}
|
|
}
|
|
format();
|
|
//state = -1;
|
|
}
|
|
|
|
void KoTextParag::move( int &dy )
|
|
{
|
|
//kdDebug(32500) << "KoTextParag::move paragId=" << paragId() << " dy=" << dy << endl;
|
|
if ( dy == 0 )
|
|
return;
|
|
changed = TRUE;
|
|
r.moveBy( 0, dy );
|
|
if ( mFloatingItems ) {
|
|
for ( KoTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() ) {
|
|
i->finalize();
|
|
}
|
|
}
|
|
//if ( p )
|
|
// p->lastInFrame = TRUE; // TQt does this, but the loop at the end of format() calls move a lot!
|
|
|
|
movedDown = FALSE;
|
|
|
|
// do page breaks if required
|
|
if ( doc && doc->isPageBreakEnabled() ) {
|
|
int shift;
|
|
if ( ( shift = doc->formatter()->formatVertically( doc, this ) ) ) {
|
|
if ( p )
|
|
p->setChanged( TRUE );
|
|
dy += shift;
|
|
}
|
|
}
|
|
}
|
|
|
|
void KoTextParag::format( int start, bool doMove )
|
|
{
|
|
if ( !str || str->length() == 0 || !formatter() )
|
|
return;
|
|
|
|
if ( isValid() )
|
|
return;
|
|
|
|
//kdDebug(32500) << "KoTextParag::format " << this << " id:" << paragId() << endl;
|
|
|
|
r.moveTopLeft( TQPoint( documentX(), p ? p->r.y() + p->r.height() : documentY() ) );
|
|
//if ( p )
|
|
// p->lastInFrame = FALSE;
|
|
|
|
movedDown = FALSE;
|
|
bool formattedAgain = FALSE;
|
|
|
|
formatAgain:
|
|
r.setWidth( documentWidth() );
|
|
|
|
// Not really useful....
|
|
if ( doc && mFloatingItems ) {
|
|
for ( KoTextCustomItem *i = mFloatingItems->first(); i; i = mFloatingItems->next() ) {
|
|
if ( i->placement() == KoTextCustomItem::PlaceRight )
|
|
i->move( r.x() + r.width() - i->width, r.y() );
|
|
else
|
|
i->move( i->x(), r.y() );
|
|
}
|
|
}
|
|
TQMap<int, KoTextParagLineStart*> oldLineStarts = lineStarts;
|
|
lineStarts.clear();
|
|
int y;
|
|
bool formatterWorked = formatter()->format( doc, this, start, oldLineStarts, y, m_wused );
|
|
|
|
// It can't happen that width < minimumWidth -- hopefully.
|
|
//r.setWidth( TQMAX( r.width(), formatter()->minimumWidth() ) );
|
|
//m_minw = formatter()->minimumWidth();
|
|
|
|
TQMap<int, KoTextParagLineStart*>::Iterator it = oldLineStarts.begin();
|
|
|
|
for ( ; it != oldLineStarts.end(); ++it )
|
|
delete *it;
|
|
|
|
/* if ( hasBorder() || str->isRightToLeft() )
|
|
////kotext: border extends to doc width
|
|
//// and, bidi parags might have a counter, which will be right-aligned...
|
|
{
|
|
setWidth( textDocument()->width() - 1 );
|
|
}
|
|
else*/
|
|
{
|
|
if ( lineStarts.count() == 1 ) { //&& ( !doc || doc->flow()->isEmpty() ) ) {
|
|
// kotext: for proper parag borders, we want all parags to be as wide as linestart->w
|
|
/* if ( !str->isBidi() ) {
|
|
KoTextStringChar *c = &str->at( str->length() - 1 );
|
|
r.setWidth( c->x + c->width );
|
|
} else*/ {
|
|
r.setWidth( lineStarts[0]->w );
|
|
}
|
|
}
|
|
if ( newLinesAllowed ) {
|
|
it = lineStarts.begin();
|
|
int usedw = 0; int lineid = 0;
|
|
for ( ; it != lineStarts.end(); ++it, ++lineid ) {
|
|
usedw = TQMAX( usedw, (*it)->w );
|
|
}
|
|
if ( r.width() <= 0 ) {
|
|
// if the user specifies an invalid rect, this means that the
|
|
// bounding box should grow to the width that the text actually
|
|
// needs
|
|
r.setWidth( usedw );
|
|
} else {
|
|
r.setWidth( TQMIN( usedw, r.width() ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( y != r.height() )
|
|
r.setHeight( y );
|
|
|
|
if ( !visible )
|
|
r.setHeight( 0 );
|
|
|
|
// do page breaks if required
|
|
if ( doc && doc->isPageBreakEnabled() ) {
|
|
int shift = doc->formatter()->formatVertically( doc, this );
|
|
//kdDebug(32500) << "formatVertically returned shift=" << shift << endl;
|
|
if ( shift && !formattedAgain ) {
|
|
formattedAgain = TRUE;
|
|
goto formatAgain;
|
|
}
|
|
}
|
|
|
|
if ( doc )
|
|
doc->formatter()->postFormat( this );
|
|
|
|
if ( n && doMove && n->isValid() && r.y() + r.height() != n->r.y() ) {
|
|
//kdDebug(32500) << "r=" << r << " n->r=" << n->r << endl;
|
|
int dy = ( r.y() + r.height() ) - n->r.y();
|
|
KoTextParag *s = n;
|
|
bool makeInvalid = false; //p && p->lastInFrame;
|
|
//kdDebug(32500) << "might move of dy=" << dy << ". previous's lastInFrame (=makeInvalid): " << makeInvalid << endl;
|
|
while ( s && dy ) {
|
|
if ( s->movedDown ) { // (not in TQRT) : moved down -> tqinvalidate and stop moving down
|
|
s->tqinvalidate( 0 ); // (there is no point in moving down a parag that has a frame break...)
|
|
break;
|
|
}
|
|
if ( !s->isFullWidth() )
|
|
makeInvalid = TRUE;
|
|
if ( makeInvalid )
|
|
s->tqinvalidate( 0 );
|
|
s->move( dy );
|
|
//if ( s->lastInFrame )
|
|
// makeInvalid = TRUE;
|
|
s = s->n;
|
|
}
|
|
}
|
|
|
|
//#define DEBUG_CI_PLACEMENT
|
|
if ( mFloatingItems ) {
|
|
#ifdef DEBUG_CI_PLACEMENT
|
|
kdDebug(32500) << lineStarts.count() << " lines" << endl;
|
|
#endif
|
|
// Place custom items - after the formatting is finished
|
|
int len = length();
|
|
int line = -1;
|
|
int lineY = 0; // the one called "cy" in other algos
|
|
int baseLine = 0;
|
|
TQMap<int, KoTextParagLineStart*>::Iterator it = lineStarts.begin();
|
|
for ( int i = 0 ; i < len; ++i ) {
|
|
KoTextStringChar *chr = &str->at( i );
|
|
if ( chr->lineStart ) {
|
|
++line;
|
|
if ( line > 0 )
|
|
++it;
|
|
lineY = (*it)->y;
|
|
baseLine = (*it)->baseLine;
|
|
#ifdef DEBUG_CI_PLACEMENT
|
|
kdDebug(32500) << "New line (" << line << "): lineStart=" << (*it) << " lineY=" << lineY << " baseLine=" << baseLine << " height=" << (*it)->h << endl;
|
|
#endif
|
|
}
|
|
if ( chr->isCustom() ) {
|
|
int x = chr->x;
|
|
KoTextCustomItem* item = chr->customItem();
|
|
Q_ASSERT( baseLine >= item->ascent() ); // something went wrong in KoTextFormatter if this isn't the case
|
|
int y = lineY + baseLine - item->ascent();
|
|
#ifdef DEBUG_CI_PLACEMENT
|
|
kdDebug(32500) << "Custom item: i=" << i << " x=" << x << " lineY=" << lineY << " baseLine=" << baseLine << " ascent=" << item->ascent() << " -> y=" << y << endl;
|
|
#endif
|
|
item->move( x, y );
|
|
item->finalize();
|
|
}
|
|
}
|
|
}
|
|
|
|
//firstFormat = FALSE; //// unused
|
|
if ( formatterWorked ) // only if it worked, i.e. we had some width to format it
|
|
{
|
|
m_invalid = false;
|
|
}
|
|
changed = TRUE;
|
|
//#### str->setTextChanged( FALSE );
|
|
}
|
|
|
|
int KoTextParag::lineHeightOfChar( int i, int *bl, int *y ) const
|
|
{
|
|
if ( !isValid() )
|
|
( (KoTextParag*)this )->format();
|
|
|
|
TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.end();
|
|
--it;
|
|
for ( ;; ) {
|
|
if ( i >= it.key() ) {
|
|
if ( bl )
|
|
*bl = ( *it )->baseLine;
|
|
if ( y )
|
|
*y = ( *it )->y;
|
|
return ( *it )->h;
|
|
}
|
|
if ( it == lineStarts.begin() )
|
|
break;
|
|
--it;
|
|
}
|
|
|
|
kdWarning(32500) << "KoTextParag::lineHeightOfChar: couldn't find lh for " << i << endl;
|
|
return 15;
|
|
}
|
|
|
|
KoTextStringChar *KoTextParag::lineStartOfChar( int i, int *index, int *line ) const
|
|
{
|
|
if ( !isValid() )
|
|
( (KoTextParag*)this )->format();
|
|
|
|
int l = (int)lineStarts.count() - 1;
|
|
TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.end();
|
|
--it;
|
|
for ( ;; ) {
|
|
if ( i >= it.key() ) {
|
|
if ( index )
|
|
*index = it.key();
|
|
if ( line )
|
|
*line = l;
|
|
return &str->at( it.key() );
|
|
}
|
|
if ( it == lineStarts.begin() )
|
|
break;
|
|
--it;
|
|
--l;
|
|
}
|
|
|
|
kdWarning(32500) << "KoTextParag::lineStartOfChar: couldn't find " << i << endl;
|
|
return 0;
|
|
}
|
|
|
|
int KoTextParag::lines() const
|
|
{
|
|
if ( !isValid() )
|
|
( (KoTextParag*)this )->format();
|
|
|
|
return (int)lineStarts.count();
|
|
}
|
|
|
|
KoTextStringChar *KoTextParag::lineStartOfLine( int line, int *index ) const
|
|
{
|
|
if ( !isValid() )
|
|
( (KoTextParag*)this )->format();
|
|
|
|
if ( line >= 0 && line < (int)lineStarts.count() ) {
|
|
TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
|
|
while ( line-- > 0 )
|
|
++it;
|
|
int i = it.key();
|
|
if ( index )
|
|
*index = i;
|
|
return &str->at( i );
|
|
}
|
|
|
|
kdWarning(32500) << "KoTextParag::lineStartOfLine: couldn't find " << line << endl;
|
|
return 0;
|
|
}
|
|
|
|
int KoTextParag::leftGap() const
|
|
{
|
|
if ( !isValid() )
|
|
( (KoTextParag*)this )->format();
|
|
|
|
int line = 0;
|
|
int x = str->at(0).x; /* set x to x of first char */
|
|
if ( str->isBidi() ) {
|
|
for ( int i = 1; i < str->length(); ++i )
|
|
x = TQMIN(x, str->at(i).x);
|
|
return x;
|
|
}
|
|
|
|
TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
|
|
while (line < (int)lineStarts.count()) {
|
|
int i = it.key(); /* char index */
|
|
x = TQMIN(x, str->at(i).x);
|
|
++it;
|
|
++line;
|
|
}
|
|
return x;
|
|
}
|
|
|
|
void KoTextParag::setFormat( int index, int len, const KoTextFormat *_f, bool useCollection, int flags )
|
|
{
|
|
Q_ASSERT( useCollection ); // just for info
|
|
if ( index < 0 )
|
|
index = 0;
|
|
if ( index > str->length() - 1 )
|
|
index = str->length() - 1;
|
|
if ( index + len >= str->length() )
|
|
len = str->length() - index;
|
|
|
|
KoTextFormatCollection *fc = 0;
|
|
if ( useCollection )
|
|
fc = formatCollection();
|
|
KoTextFormat *of;
|
|
for ( int i = 0; i < len; ++i ) {
|
|
of = str->at( i + index ).format();
|
|
if ( !changed && _f->key() != of->key() )
|
|
changed = TRUE;
|
|
// Check things that need the textformatter to run
|
|
// (e.g. not color changes)
|
|
// ######## Is this test exhaustive?
|
|
if ( m_invalid == false &&
|
|
( _f->font().family() != of->font().family() ||
|
|
_f->pointSize() != of->pointSize() ||
|
|
_f->font().weight() != of->font().weight() ||
|
|
_f->font().italic() != of->font().italic() ||
|
|
_f->vAlign() != of->vAlign() ||
|
|
_f->relativeTextSize() != of->relativeTextSize() ||
|
|
_f->offsetFromBaseLine() != of->offsetFromBaseLine() ||
|
|
_f->wordByWord() != of->wordByWord() ||
|
|
_f->attributeFont() != of->attributeFont() ||
|
|
_f->language() != of->language() ||
|
|
_f->hyphenation() != of->hyphenation() ||
|
|
_f->shadowDistanceX() != of->shadowDistanceX() ||
|
|
_f->shadowDistanceY() != of->shadowDistanceY()
|
|
) ) {
|
|
tqinvalidate( 0 );
|
|
}
|
|
if ( flags == -1 || flags == KoTextFormat::Format || !fc ) {
|
|
#ifdef DEBUG_COLLECTION
|
|
kdDebug(32500) << " KoTextParag::setFormat, will use format(f) " << f << " " << _f->key() << endl;
|
|
#endif
|
|
KoTextFormat* f = fc ? fc->format( _f ) : const_cast<KoTextFormat *>( _f );
|
|
str->setFormat( i + index, f, useCollection, true );
|
|
} else {
|
|
#ifdef DEBUG_COLLECTION
|
|
kdDebug(32500) << " KoTextParag::setFormat, will use format(of,f,flags) of=" << of << " " << of->key() << ", f=" << _f << " " << _f->key() << endl;
|
|
#endif
|
|
KoTextFormat *fm = fc->format( of, _f, flags );
|
|
#ifdef DEBUG_COLLECTION
|
|
kdDebug(32500) << " KoTextParag::setFormat, format(of,f,flags) returned " << fm << " " << fm->key() << " " << endl;
|
|
#endif
|
|
str->setFormat( i + index, fm, useCollection );
|
|
}
|
|
}
|
|
}
|
|
|
|
void KoTextParag::drawCursorDefault( TQPainter &painter, KoTextCursor *cursor, int curx, int cury, int curh, const TQColorGroup &cg )
|
|
{
|
|
painter.fillRect( TQRect( curx, cury, 1, curh ), cg.color( TQColorGroup::Text ) );
|
|
painter.save();
|
|
if ( str->isBidi() ) {
|
|
const int d = 4;
|
|
if ( at( cursor->index() )->rightToLeft ) {
|
|
painter.setPen( TQt::black );
|
|
painter.drawLine( curx, cury, curx - d / 2, cury + d / 2 );
|
|
painter.drawLine( curx, cury + d, curx - d / 2, cury + d / 2 );
|
|
} else {
|
|
painter.setPen( TQt::black );
|
|
painter.drawLine( curx, cury, curx + d / 2, cury + d / 2 );
|
|
painter.drawLine( curx, cury + d, curx + d / 2, cury + d / 2 );
|
|
}
|
|
}
|
|
painter.restore();
|
|
}
|
|
|
|
int *KoTextParag::tabArray() const
|
|
{
|
|
int *ta = tArray;
|
|
if ( !ta && doc )
|
|
ta = doc->tabArray();
|
|
return ta;
|
|
}
|
|
|
|
int KoTextParag::nextTabDefault( int, int x )
|
|
{
|
|
int *ta = tArray;
|
|
//if ( doc ) {
|
|
if ( !ta )
|
|
ta = doc->tabArray();
|
|
int tabStopWidth = doc->tabStopWidth();
|
|
//}
|
|
if ( tabStopWidth != 0 )
|
|
return tabStopWidth*(x/tabStopWidth+1);
|
|
else
|
|
return x;
|
|
}
|
|
|
|
KoTextFormatCollection *KoTextParag::formatCollection() const
|
|
{
|
|
if ( doc )
|
|
return doc->formatCollection();
|
|
//if ( !qFormatCollection )
|
|
// qFormatCollection = new KoTextFormatCollection;
|
|
//return qFormatCollection;
|
|
return 0L;
|
|
}
|
|
|
|
void KoTextParag::show()
|
|
{
|
|
if ( visible || !doc )
|
|
return;
|
|
visible = TRUE;
|
|
}
|
|
|
|
void KoTextParag::hide()
|
|
{
|
|
if ( !visible || !doc )
|
|
return;
|
|
visible = FALSE;
|
|
}
|
|
|
|
void KoTextParag::setDirection( TQChar::Direction d )
|
|
{
|
|
if ( str && str->direction() != d ) {
|
|
str->setDirection( d );
|
|
tqinvalidate( 0 );
|
|
//// kotext
|
|
m_layout.direction = d;
|
|
invalidateCounters(); // #47178
|
|
////
|
|
}
|
|
}
|
|
|
|
TQChar::Direction KoTextParag::direction() const
|
|
{
|
|
return (str ? str->direction() : TQChar::DirON );
|
|
}
|
|
|
|
void KoTextParag::setSelection( int id, int start, int end )
|
|
{
|
|
TQMap<int, KoTextParagSelection>::ConstIterator it = selections().find( id );
|
|
if ( it != mSelections->end() ) {
|
|
if ( start == ( *it ).start && end == ( *it ).end )
|
|
return;
|
|
}
|
|
|
|
KoTextParagSelection sel;
|
|
sel.start = start;
|
|
sel.end = end;
|
|
(*mSelections)[ id ] = sel;
|
|
setChanged( TRUE, TRUE );
|
|
}
|
|
|
|
void KoTextParag::removeSelection( int id )
|
|
{
|
|
if ( !hasSelection( id ) )
|
|
return;
|
|
if ( mSelections )
|
|
mSelections->remove( id );
|
|
setChanged( TRUE, TRUE );
|
|
}
|
|
|
|
int KoTextParag::selectionStart( int id ) const
|
|
{
|
|
if ( !mSelections )
|
|
return -1;
|
|
TQMap<int, KoTextParagSelection>::ConstIterator it = mSelections->find( id );
|
|
if ( it == mSelections->end() )
|
|
return -1;
|
|
return ( *it ).start;
|
|
}
|
|
|
|
int KoTextParag::selectionEnd( int id ) const
|
|
{
|
|
if ( !mSelections )
|
|
return -1;
|
|
TQMap<int, KoTextParagSelection>::ConstIterator it = mSelections->find( id );
|
|
if ( it == mSelections->end() )
|
|
return -1;
|
|
return ( *it ).end;
|
|
}
|
|
|
|
bool KoTextParag::hasSelection( int id ) const
|
|
{
|
|
if ( !mSelections )
|
|
return FALSE;
|
|
TQMap<int, KoTextParagSelection>::ConstIterator it = mSelections->find( id );
|
|
if ( it == mSelections->end() )
|
|
return FALSE;
|
|
return ( *it ).start != ( *it ).end || length() == 1;
|
|
}
|
|
|
|
bool KoTextParag::fullSelected( int id ) const
|
|
{
|
|
if ( !mSelections )
|
|
return FALSE;
|
|
TQMap<int, KoTextParagSelection>::ConstIterator it = mSelections->find( id );
|
|
if ( it == mSelections->end() )
|
|
return FALSE;
|
|
return ( *it ).start == 0 && ( *it ).end == str->length() - 1;
|
|
}
|
|
|
|
int KoTextParag::lineY( int l ) const
|
|
{
|
|
if ( l > (int)lineStarts.count() - 1 ) {
|
|
kdWarning(32500) << "KoTextParag::lineY: line " << l << " out of range!" << endl;
|
|
return 0;
|
|
}
|
|
|
|
if ( !isValid() )
|
|
( (KoTextParag*)this )->format();
|
|
|
|
TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
|
|
while ( l-- > 0 )
|
|
++it;
|
|
return ( *it )->y;
|
|
}
|
|
|
|
int KoTextParag::lineBaseLine( int l ) const
|
|
{
|
|
if ( l > (int)lineStarts.count() - 1 ) {
|
|
kdWarning(32500) << "KoTextParag::lineBaseLine: line " << l << " out of range!" << endl;
|
|
return 10;
|
|
}
|
|
|
|
if ( !isValid() )
|
|
( (KoTextParag*)this )->format();
|
|
|
|
TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
|
|
while ( l-- > 0 )
|
|
++it;
|
|
return ( *it )->baseLine;
|
|
}
|
|
|
|
int KoTextParag::lineHeight( int l ) const
|
|
{
|
|
if ( l > (int)lineStarts.count() - 1 ) {
|
|
kdWarning(32500) << "KoTextParag::lineHeight: line " << l << " out of range!" << endl;
|
|
return 15;
|
|
}
|
|
|
|
if ( !isValid() )
|
|
( (KoTextParag*)this )->format();
|
|
|
|
TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
|
|
while ( l-- > 0 )
|
|
++it;
|
|
return ( *it )->h;
|
|
}
|
|
|
|
void KoTextParag::lineInfo( int l, int &y, int &h, int &bl ) const
|
|
{
|
|
if ( l > (int)lineStarts.count() - 1 ) {
|
|
kdWarning(32500) << "KoTextParag::lineInfo: line " << l << " out of range!" << endl;
|
|
kdDebug(32500) << (int)lineStarts.count() - 1 << " " << l << endl;
|
|
y = 0;
|
|
h = 15;
|
|
bl = 10;
|
|
return;
|
|
}
|
|
|
|
if ( !isValid() )
|
|
( (KoTextParag*)this )->format();
|
|
|
|
TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
|
|
while ( l-- > 0 )
|
|
++it;
|
|
y = ( *it )->y;
|
|
h = ( *it )->h;
|
|
bl = ( *it )->baseLine;
|
|
}
|
|
|
|
uint KoTextParag::tqalignment() const
|
|
{
|
|
return align;
|
|
}
|
|
|
|
void KoTextParag::setFormat( KoTextFormat *fm )
|
|
{
|
|
#if 0
|
|
bool doUpdate = FALSE;
|
|
if (defFormat && (defFormat != formatCollection()->defaultFormat()))
|
|
doUpdate = TRUE;
|
|
#endif
|
|
defFormat = formatCollection()->format( fm );
|
|
#if 0
|
|
if ( !doUpdate )
|
|
return;
|
|
for ( int i = 0; i < length(); ++i ) {
|
|
if ( at( i )->format()->styleName() == defFormat->styleName() )
|
|
at( i )->format()->updateStyle();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
KoTextFormatterBase *KoTextParag::formatter() const
|
|
{
|
|
if ( doc )
|
|
return doc->formatter();
|
|
return 0;
|
|
}
|
|
|
|
/*int KoTextParag::minimumWidth() const
|
|
{
|
|
//return doc ? doc->minimumWidth() : 0;
|
|
return m_minw;
|
|
}*/
|
|
|
|
int KoTextParag::widthUsed() const
|
|
{
|
|
return m_wused;
|
|
}
|
|
|
|
void KoTextParag::setTabArray( int *a )
|
|
{
|
|
delete [] tArray;
|
|
tArray = a;
|
|
}
|
|
|
|
void KoTextParag::setTabStops( int tw )
|
|
{
|
|
if ( doc )
|
|
doc->setTabStops( tw );
|
|
//else
|
|
// tabStopWidth = tw;
|
|
}
|
|
|
|
TQMap<int, KoTextParagSelection> &KoTextParag::selections() const
|
|
{
|
|
if ( !mSelections )
|
|
((KoTextParag *)this)->mSelections = new TQMap<int, KoTextParagSelection>;
|
|
return *mSelections;
|
|
}
|
|
|
|
TQPtrList<KoTextCustomItem> &KoTextParag::floatingItems() const
|
|
{
|
|
if ( !mFloatingItems )
|
|
((KoTextParag *)this)->mFloatingItems = new TQPtrList<KoTextCustomItem>;
|
|
return *mFloatingItems;
|
|
}
|
|
|
|
void KoTextCursor::setIndex( int i, bool /*restore*/ )
|
|
{
|
|
// Note: TQRT doesn't allow to position the cursor at string->length
|
|
// However we need it, when applying a style to a paragraph, so that
|
|
// the trailing space gets the style change applied as well.
|
|
// Obviously "right of the trailing space" isn't a good place for a real
|
|
// cursor, but this needs to be checked somewhere else.
|
|
if ( i < 0 || i > string->length() ) {
|
|
#if defined(TQT_CHECK_RANGE)
|
|
kdWarning(32500) << "KoTextCursor::setIndex: " << i << " out of range" << endl;
|
|
//abort();
|
|
#endif
|
|
i = i < 0 ? 0 : string->length() - 1;
|
|
}
|
|
|
|
tmpIndex = -1;
|
|
idx = i;
|
|
}
|
|
|
|
///////////////////////////////////// kotext extensions ///////////////////////
|
|
|
|
// Return the counter associated with this paragraph.
|
|
KoParagCounter *KoTextParag::counter()
|
|
{
|
|
if ( !m_layout.counter )
|
|
return 0L;
|
|
|
|
// Garbage collect un-needed counters.
|
|
if ( m_layout.counter->numbering() == KoParagCounter::NUM_NONE
|
|
// [keep it for unnumbered outlines (the depth is useful)]
|
|
&& ( !m_layout.style || !m_layout.style->isOutline() ) )
|
|
setNoCounter();
|
|
return m_layout.counter;
|
|
}
|
|
|
|
void KoTextParag::setMargin( TQStyleSheetItem::Margin m, double _i )
|
|
{
|
|
//kdDebug(32500) << "KoTextParag::setMargin " << m << " margin " << _i << endl;
|
|
m_layout.margins[m] = _i;
|
|
if ( m == TQStyleSheetItem::MarginTop && prev() )
|
|
prev()->tqinvalidate(0); // for top margin (post-1.1: remove this, not necessary anymore)
|
|
tqinvalidate(0);
|
|
}
|
|
|
|
void KoTextParag::setMargins( const double * margins )
|
|
{
|
|
for ( int i = 0 ; i < 5 ; ++i )
|
|
m_layout.margins[i] = margins[i];
|
|
tqinvalidate(0);
|
|
}
|
|
|
|
void KoTextParag::setAlign( int align )
|
|
{
|
|
Q_ASSERT( align <= TQt::AlignJustify );
|
|
align &= TQt::AlignHorizontal_Mask;
|
|
tqsetAlignment( align );
|
|
m_layout.tqalignment = align;
|
|
}
|
|
|
|
int KoTextParag::resolveAlignment() const
|
|
{
|
|
if ( (int)m_layout.tqalignment == TQt::AlignAuto )
|
|
return str->isRightToLeft() ? TQt::AlignRight : TQt::AlignLeft;
|
|
return m_layout.tqalignment;
|
|
}
|
|
|
|
void KoTextParag::setLineSpacing( double _i )
|
|
{
|
|
m_layout.setLineSpacingValue(_i);
|
|
tqinvalidate(0);
|
|
}
|
|
|
|
void KoTextParag::setLineSpacingType( KoParagLayout::SpacingType _type )
|
|
{
|
|
m_layout.lineSpacingType = _type;
|
|
tqinvalidate(0);
|
|
}
|
|
|
|
void KoTextParag::setTopBorder( const KoBorder & _brd )
|
|
{
|
|
m_layout.topBorder = _brd;
|
|
tqinvalidate(0);
|
|
}
|
|
|
|
void KoTextParag::setBottomBorder( const KoBorder & _brd )
|
|
{
|
|
m_layout.bottomBorder = _brd;
|
|
tqinvalidate(0);
|
|
}
|
|
|
|
void KoTextParag::setJoinBorder( bool join )
|
|
{
|
|
m_layout.joinBorder = join;
|
|
tqinvalidate(0);
|
|
}
|
|
|
|
void KoTextParag::setBackgroundColor ( const TQColor& color )
|
|
{
|
|
m_layout.backgroundColor = color;
|
|
tqinvalidate(0);
|
|
}
|
|
|
|
void KoTextParag::setNoCounter()
|
|
{
|
|
delete m_layout.counter;
|
|
m_layout.counter = 0L;
|
|
invalidateCounters();
|
|
}
|
|
|
|
void KoTextParag::setCounter( const KoParagCounter * pCounter )
|
|
{
|
|
// Preserve footnote numbering when applying a style
|
|
const bool isFootNote = m_layout.counter &&
|
|
m_layout.counter->numbering() == KoParagCounter::NUM_FOOTNOTE;
|
|
if ( isFootNote ) {
|
|
const TQString footNotePrefix = m_layout.counter->prefix(); // this is where the number is
|
|
delete m_layout.counter;
|
|
m_layout.counter = pCounter ? new KoParagCounter( *pCounter ) : new KoParagCounter();
|
|
m_layout.counter->setNumbering( KoParagCounter::NUM_FOOTNOTE );
|
|
m_layout.counter->setStyle( KoParagCounter::STYLE_NONE ); // no number after the 'prefix'
|
|
m_layout.counter->setPrefix( footNotePrefix );
|
|
m_layout.counter->setSuffix( TQString() );
|
|
invalidateCounters();
|
|
} else {
|
|
if ( pCounter )
|
|
setCounter( *pCounter );
|
|
else
|
|
setNoCounter();
|
|
}
|
|
}
|
|
|
|
void KoTextParag::setCounter( const KoParagCounter & counter )
|
|
{
|
|
// Garbage collect unnneeded counters.
|
|
if ( counter.numbering() == KoParagCounter::NUM_NONE
|
|
// [keep it for unnumbered outlines (the depth is useful)]
|
|
&& ( !m_layout.style || !m_layout.style->isOutline() ) )
|
|
{
|
|
setNoCounter();
|
|
}
|
|
else
|
|
{
|
|
delete m_layout.counter;
|
|
m_layout.counter = new KoParagCounter( counter );
|
|
|
|
// Invalidate the counters
|
|
invalidateCounters();
|
|
}
|
|
}
|
|
|
|
void KoTextParag::invalidateCounters()
|
|
{
|
|
// Invalidate this paragraph and all the following ones
|
|
// (Numbering may have changed)
|
|
tqinvalidate( 0 );
|
|
if ( m_layout.counter )
|
|
m_layout.counter->tqinvalidate();
|
|
KoTextParag *s = next();
|
|
// #### Possible optimization: since any invalidation propagates down,
|
|
// it's enough to stop at the first paragraph with an already-invalidated counter, isn't it?
|
|
// This is only true if nobody else calls counter->tqinvalidate...
|
|
while ( s ) {
|
|
if ( s->m_layout.counter )
|
|
s->m_layout.counter->tqinvalidate();
|
|
s->tqinvalidate( 0 );
|
|
s = s->next();
|
|
}
|
|
}
|
|
|
|
int KoTextParag::counterWidth() const
|
|
{
|
|
if ( !m_layout.counter )
|
|
return 0;
|
|
|
|
return m_layout.counter->width( this );
|
|
}
|
|
|
|
// Draw the complete label (i.e. heading/list numbers/bullets) for this paragraph.
|
|
// This is called by KoTextParag::paint.
|
|
void KoTextParag::drawLabel( TQPainter* p, int xLU, int yLU, int /*wLU*/, int /*hLU*/, int baseLU, const TQColorGroup& /*cg*/ )
|
|
{
|
|
if ( !m_layout.counter ) // shouldn't happen
|
|
return;
|
|
|
|
if ( m_layout.counter->numbering() == KoParagCounter::NUM_NONE )
|
|
return;
|
|
|
|
int counterWidthLU = m_layout.counter->width( this );
|
|
|
|
// We use the formatting of the first char as the formatting of the counter
|
|
KoTextFormat counterFormat( *KoParagCounter::counterFormat( this ) );
|
|
if ( !m_layout.style || !m_layout.style->isOutline() )
|
|
{
|
|
// But without bold/italic for normal lists, since some items could be bold and others not.
|
|
// For headings we must keep the bold when the heading is bold.
|
|
counterFormat.setBold( false );
|
|
counterFormat.setItalic( false );
|
|
}
|
|
KoTextFormat* format = &counterFormat;
|
|
p->save();
|
|
|
|
TQColor textColor( format->color() );
|
|
if ( !textColor.isValid() ) // Resolve the color at this point
|
|
textColor = KoTextFormat::defaultTextColor( p );
|
|
p->setPen( TQPen( textColor ) );
|
|
|
|
KoTextZoomHandler * zh = textDocument()->paintingZoomHandler();
|
|
assert( zh );
|
|
//bool forPrint = ( p->device()->devType() == TQInternal::Printer );
|
|
|
|
bool rtl = str->isRightToLeft(); // when true, we put suffix+counter+prefix at the RIGHT of the paragraph.
|
|
int xLeft = zh->layoutUnitToPixelX( xLU - (rtl ? 0 : counterWidthLU) );
|
|
int y = zh->layoutUnitToPixelY( yLU );
|
|
//int h = zh->layoutUnitToPixelY( yLU, hLU );
|
|
int base = zh->layoutUnitToPixelY( yLU, baseLU );
|
|
int counterWidth = zh->layoutUnitToPixelX( xLU, counterWidthLU );
|
|
int height = zh->layoutUnitToPixelY( yLU, format->height() );
|
|
|
|
TQFont font( format->screenFont( zh ) );
|
|
// Footnote numbers are in superscript (in WP and Word, not in OO)
|
|
if ( m_layout.counter->numbering() == KoParagCounter::NUM_FOOTNOTE )
|
|
{
|
|
int pointSize = ( ( font.pointSize() * 2 ) / 3 );
|
|
font.setPointSize( pointSize );
|
|
y -= ( height - TQFontMetrics(font).height() );
|
|
}
|
|
p->setFont( font );
|
|
|
|
// Now draw any bullet that is required over the space left for it.
|
|
if ( m_layout.counter->isBullet() )
|
|
{
|
|
int xBullet = xLeft + zh->layoutUnitToPixelX( m_layout.counter->bulletX() );
|
|
|
|
//kdDebug(32500) << "KoTextParag::drawLabel xLU=" << xLU << " counterWidthLU=" << counterWidthLU << endl;
|
|
// The width and height of the bullet is the width of one space
|
|
int width = zh->layoutUnitToPixelX( xLeft, format->width( ' ' ) );
|
|
|
|
//kdDebug(32500) << "Pix: xLeft=" << xLeft << " counterWidth=" << counterWidth
|
|
// << " xBullet=" << xBullet << " width=" << width << endl;
|
|
|
|
TQString prefix = m_layout.counter->prefix();
|
|
if ( !prefix.isEmpty() )
|
|
{
|
|
if ( rtl )
|
|
prefix.prepend( ' ' /*the space before the bullet in RTL mode*/ );
|
|
KoTextParag::drawFontEffects( p, format, zh, format->screenFont( zh ), textColor, xLeft, base, width, y, height, prefix[0] );
|
|
|
|
int posY =y + base - format->offsetFromBaseLine();
|
|
//we must move to bottom text because we create
|
|
//shadow to 'top'.
|
|
int sy = format->shadowY( zh );
|
|
if ( sy < 0)
|
|
posY -= sy;
|
|
|
|
p->drawText( xLeft, posY, prefix );
|
|
}
|
|
|
|
TQRect er( xBullet + (rtl ? width : 0), y + height / 2 - width / 2, width, width );
|
|
// Draw the bullet.
|
|
int posY = 0;
|
|
switch ( m_layout.counter->style() )
|
|
{
|
|
case KoParagCounter::STYLE_DISCBULLET:
|
|
p->setBrush( TQBrush(textColor) );
|
|
p->drawEllipse( er );
|
|
p->setBrush( TQt::NoBrush );
|
|
break;
|
|
case KoParagCounter::STYLE_SQUAREBULLET:
|
|
p->fillRect( er, TQBrush(textColor) );
|
|
break;
|
|
case KoParagCounter::STYLE_BOXBULLET:
|
|
p->drawRect( er );
|
|
break;
|
|
case KoParagCounter::STYLE_CIRCLEBULLET:
|
|
p->drawEllipse( er );
|
|
break;
|
|
case KoParagCounter::STYLE_CUSTOMBULLET:
|
|
{
|
|
// The user has selected a symbol from a special font. Override the paragraph
|
|
// font with the given family. This conserves the right size etc.
|
|
if ( !m_layout.counter->customBulletFont().isEmpty() )
|
|
{
|
|
TQFont bulletFont( p->font() );
|
|
bulletFont.setFamily( m_layout.counter->customBulletFont() );
|
|
p->setFont( bulletFont );
|
|
}
|
|
KoTextParag::drawFontEffects( p, format, zh, format->screenFont( zh ), textColor, xBullet, base, width, y, height, ' ' );
|
|
|
|
posY = y + base- format->offsetFromBaseLine();
|
|
//we must move to bottom text because we create
|
|
//shadow to 'top'.
|
|
int sy = format->shadowY( zh );
|
|
if ( sy < 0)
|
|
posY -= sy;
|
|
|
|
p->drawText( xBullet, posY, TQString(m_layout.counter->customBulletCharacter()) );
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
TQString suffix = m_layout.counter->suffix();
|
|
if ( !suffix.isEmpty() )
|
|
{
|
|
if ( !rtl )
|
|
suffix += ' ' /*the space after the bullet*/;
|
|
|
|
KoTextParag::drawFontEffects( p, format, zh, format->screenFont( zh ), textColor, xBullet + width, base, counterWidth, y,height, suffix[0] );
|
|
|
|
int posY =y + base- format->offsetFromBaseLine();
|
|
//we must move to bottom text because we create
|
|
//shadow to 'top'.
|
|
int sy = format->shadowY( zh );
|
|
if ( sy < 0)
|
|
posY -= sy;
|
|
|
|
p->drawText( xBullet + width, posY, suffix, -1 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TQString counterText = m_layout.counter->text( this );
|
|
// There are no bullets...any parent bullets have already been suppressed.
|
|
// Just draw the text! Note: one space is always appended.
|
|
if ( !counterText.isEmpty() )
|
|
{
|
|
KoTextParag::drawFontEffects( p, format, zh, format->screenFont( zh ), textColor, xLeft, base, counterWidth, y, height, counterText[0] );
|
|
|
|
counterText += ' ' /*the space after the bullet (before in RTL mode)*/;
|
|
|
|
int posY =y + base - format->offsetFromBaseLine();
|
|
//we must move to bottom text because we create
|
|
//shadow to 'top'.
|
|
int sy = format->shadowY( zh );
|
|
if ( sy < 0)
|
|
posY -= sy;
|
|
|
|
p->drawText( xLeft, posY , counterText, -1 );
|
|
}
|
|
}
|
|
p->restore();
|
|
}
|
|
|
|
int KoTextParag::breakableTopMargin() const
|
|
{
|
|
KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
|
|
return zh->ptToLayoutUnitPixY(
|
|
m_layout.margins[ TQStyleSheetItem::MarginTop ] );
|
|
}
|
|
|
|
int KoTextParag::topMargin() const
|
|
{
|
|
KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
|
|
return zh->ptToLayoutUnitPixY(
|
|
m_layout.margins[ TQStyleSheetItem::MarginTop ]
|
|
+ ( ( prev() && prev()->joinBorder() && prev()->bottomBorder() == m_layout.bottomBorder &&
|
|
prev()->topBorder() == m_layout.topBorder && prev()->leftBorder() == m_layout.leftBorder &&
|
|
prev()->rightBorder() == m_layout.rightBorder) ? 0 : m_layout.topBorder.width() ) );
|
|
}
|
|
|
|
int KoTextParag::bottomMargin() const
|
|
{
|
|
KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
|
|
return zh->ptToLayoutUnitPixY(
|
|
m_layout.margins[ TQStyleSheetItem::MarginBottom ]
|
|
+ ( ( joinBorder() && next() && next()->bottomBorder() == m_layout.bottomBorder &&
|
|
next()->topBorder() == m_layout.topBorder && next()->leftBorder() == m_layout.leftBorder &&
|
|
next()->rightBorder() == m_layout.rightBorder) ? 0 : m_layout.bottomBorder.width() ) );
|
|
}
|
|
|
|
int KoTextParag::leftMargin() const
|
|
{
|
|
KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
|
|
return zh->ptToLayoutUnitPixX(
|
|
m_layout.margins[ TQStyleSheetItem::MarginLeft ]
|
|
+ m_layout.leftBorder.width() );
|
|
}
|
|
|
|
int KoTextParag::rightMargin() const
|
|
{
|
|
KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
|
|
int cw=0;
|
|
if( m_layout.counter && str->isRightToLeft() &&
|
|
(( m_layout.counter->tqalignment() == TQt::AlignRight ) || ( m_layout.counter->tqalignment() == TQt::AlignAuto )))
|
|
cw = counterWidth();
|
|
|
|
return zh->ptToLayoutUnitPixX(
|
|
m_layout.margins[ TQStyleSheetItem::MarginRight ]
|
|
+ m_layout.rightBorder.width() )
|
|
+ cw; /* in tqlayout units already */
|
|
}
|
|
|
|
int KoTextParag::firstLineMargin() const
|
|
{
|
|
KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
|
|
return zh->ptToLayoutUnitPixY(
|
|
m_layout.margins[ TQStyleSheetItem::MarginFirstLine ] );
|
|
}
|
|
|
|
int KoTextParag::lineSpacing( int line ) const
|
|
{
|
|
Q_ASSERT( isValid() );
|
|
if ( m_layout.lineSpacingType == KoParagLayout::LS_SINGLE )
|
|
return 0; // or shadow, see calculateLineSpacing
|
|
else {
|
|
if( line >= (int)lineStarts.count() )
|
|
{
|
|
kdError() << "KoTextParag::lineSpacing assert(line<lines) failed: line=" << line << " lines=" << lineStarts.count() << endl;
|
|
return 0;
|
|
}
|
|
TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
|
|
while ( line-- > 0 )
|
|
++it;
|
|
return (*it)->lineSpacing;
|
|
}
|
|
}
|
|
|
|
// Called by KoTextFormatter
|
|
int KoTextParag::calculateLineSpacing( int line, int startChar, int lastChar ) const
|
|
{
|
|
KoTextZoomHandler * zh = textDocument()->formattingZoomHandler();
|
|
// TODO add shadow in KoTextFormatter!
|
|
int shadow = 0; //TQABS( zh->ptToLayoutUnitPixY( shadowDistanceY() ) );
|
|
if ( m_layout.lineSpacingType == KoParagLayout::LS_SINGLE )
|
|
return shadow;
|
|
else if ( m_layout.lineSpacingType == KoParagLayout::LS_CUSTOM )
|
|
return zh->ptToLayoutUnitPixY( m_layout.lineSpacingValue() ) + shadow;
|
|
else {
|
|
if( line >= (int)lineStarts.count() )
|
|
{
|
|
kdError() << "KoTextParag::lineSpacing assert(line<lines) failed: line=" << line << " lines=" << lineStarts.count() << endl;
|
|
return 0+shadow;
|
|
}
|
|
TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
|
|
while ( line-- > 0 )
|
|
++it;
|
|
|
|
//kdDebug(32500) << " line spacing type: " << m_layout.lineSpacingType << " value:" << m_layout.lineSpacingValue() << " line_height=" << (*it)->h << " startChar=" << startChar << " lastChar=" << lastChar << endl;
|
|
switch ( m_layout.lineSpacingType )
|
|
{
|
|
case KoParagLayout::LS_MULTIPLE:
|
|
{
|
|
double n = m_layout.lineSpacingValue() - 1.0; // yes, can be negative
|
|
return shadow + tqRound( n * heightForLineSpacing( startChar, lastChar ) );
|
|
}
|
|
case KoParagLayout::LS_ONEANDHALF:
|
|
{
|
|
// Special case of LS_MULTIPLE, with n=1.5
|
|
return shadow + heightForLineSpacing( startChar, lastChar ) / 2;
|
|
}
|
|
case KoParagLayout::LS_DOUBLE:
|
|
{
|
|
// Special case of LS_MULTIPLE, with n=1
|
|
return shadow + heightForLineSpacing( startChar, lastChar );
|
|
}
|
|
case KoParagLayout::LS_AT_LEAST:
|
|
{
|
|
int atLeast = zh->ptToLayoutUnitPixY( m_layout.lineSpacingValue() );
|
|
const int lineHeight = ( *it )->h;
|
|
int h = TQMAX( lineHeight, atLeast );
|
|
// height is now the required total height
|
|
return shadow + h - lineHeight;
|
|
}
|
|
case KoParagLayout::LS_FIXED:
|
|
{
|
|
const int lineHeight = ( *it )->h;
|
|
return shadow + zh->ptToLayoutUnitPixY( m_layout.lineSpacingValue() ) - lineHeight;
|
|
}
|
|
// Silence compiler warnings
|
|
case KoParagLayout::LS_SINGLE:
|
|
case KoParagLayout::LS_CUSTOM:
|
|
break;
|
|
}
|
|
}
|
|
kdWarning() << "Unhandled linespacing type : " << m_layout.lineSpacingType << endl;
|
|
return 0+shadow;
|
|
}
|
|
|
|
TQRect KoTextParag::pixelRect( KoTextZoomHandler *zh ) const
|
|
{
|
|
TQRect rct( zh->layoutUnitToPixel( rect() ) );
|
|
//kdDebug(32500) << " pixelRect for parag " << paragId()
|
|
// << ": rect=" << rect() << " pixelRect=" << rct << endl;
|
|
|
|
// After division we almost always end up with the top overwriting the bottom of the parag above
|
|
if ( prev() )
|
|
{
|
|
TQRect prevRect( zh->layoutUnitToPixel( prev()->rect() ) );
|
|
if ( rct.top() < prevRect.bottom() + 1 )
|
|
{
|
|
//kdDebug(32500) << " pixelRect: rct.top() adjusted to " << prevRect.bottom() + 1 << " (was " << rct.top() << ")" << endl;
|
|
rct.setTop( prevRect.bottom() + 1 );
|
|
}
|
|
}
|
|
return rct;
|
|
}
|
|
|
|
// Paint this paragraph. This is called by KoTextDocument::drawParagWYSIWYG
|
|
// (KoTextDocument::drawWithoutDoubleBuffer when printing)
|
|
void KoTextParag::paint( TQPainter &painter, const TQColorGroup &cg, KoTextCursor *cursor, bool drawSelections,
|
|
int clipx, int clipy, int clipw, int cliph )
|
|
{
|
|
#ifdef DEBUG_PAINT
|
|
kdDebug(32500) << "KoTextParag::paint ===== id=" << paragId() << " clipx=" << clipx << " clipy=" << clipy << " clipw=" << clipw << " cliph=" << cliph << endl;
|
|
kdDebug(32500) << " clipw in pix (approx) : " << textDocument()->paintingZoomHandler()->layoutUnitToPixelX( clipw ) << " cliph in pix (approx) : " << textDocument()->paintingZoomHandler()->layoutUnitToPixelX( cliph ) << endl;
|
|
#endif
|
|
|
|
KoTextZoomHandler * zh = textDocument()->paintingZoomHandler();
|
|
assert(zh);
|
|
|
|
TQRect paraRect = pixelRect( zh );
|
|
|
|
// Find left margin size, first line offset and right margin in pixels
|
|
int leftMarginPix = zh->layoutUnitToPixelX( leftMargin() );
|
|
int firstLineOffset = zh->layoutUnitToPixelX( firstLineMargin() );
|
|
|
|
// The furthest left and right x-coords of the paragraph,
|
|
// including the bullet/counter, but not the borders.
|
|
int leftExtent = TQMIN ( leftMarginPix, leftMarginPix + firstLineOffset );
|
|
int rightExtent = paraRect.width() - zh->layoutUnitToPixelX( rightMargin() );
|
|
|
|
// Draw the paragraph background color
|
|
if ( backgroundColor().isValid() )
|
|
{
|
|
// Render background from either left margin indent, or first line indent,
|
|
// whichever is nearer the left.
|
|
int backgroundWidth = rightExtent - leftExtent + 1;
|
|
int backgroundHeight = pixelRect( zh ).height();
|
|
painter.fillRect( leftExtent, 0,
|
|
backgroundWidth, backgroundHeight,
|
|
backgroundColor() );
|
|
}
|
|
|
|
// Let's call drawLabel ourselves, rather than having to deal with TQStyleSheetItem to get paintLines to call it!
|
|
if ( m_layout.counter && m_layout.counter->numbering() != KoParagCounter::NUM_NONE && m_lineChanged <= 0 )
|
|
{
|
|
int cy, h, baseLine;
|
|
lineInfo( 0, cy, h, baseLine );
|
|
int xLabel = at(0)->x;
|
|
if ( str->isRightToLeft() )
|
|
xLabel += at(0)->width;
|
|
drawLabel( &painter, xLabel, cy, 0, 0, baseLine, cg );
|
|
}
|
|
|
|
paintLines( painter, cg, cursor, drawSelections, clipx, clipy, clipw, cliph );
|
|
|
|
// Now draw paragraph border
|
|
if ( m_layout.hasBorder() )
|
|
{
|
|
bool const drawTopBorder = !prev() || !prev()->joinBorder() || prev()->bottomBorder() != bottomBorder() || prev()->topBorder() != topBorder() || prev()->leftBorder() != leftBorder() || prev()->rightBorder() != rightBorder();
|
|
bool const drawBottomBorder = !joinBorder() || !next() || next()->bottomBorder() != bottomBorder() || next()->topBorder() != topBorder() || next()->leftBorder() != leftBorder() || next()->rightBorder() != rightBorder();
|
|
|
|
// Paragraph borders surround the paragraph and its
|
|
// counters/bullets, but they only touch the frame border if
|
|
// the paragraph margins are of non-zero length.
|
|
// This is what OpenOffice does (no really, it is this time).
|
|
//
|
|
// drawBorders paints outside the give rect, so for the
|
|
// x-coords, it just needs to know the left and right extent
|
|
// of the paragraph.
|
|
TQRect r;
|
|
r.setLeft( leftExtent );
|
|
r.setRight( rightExtent );
|
|
r.setTop( zh->layoutUnitToPixelY(lineY( 0 )) );
|
|
|
|
int lastLine = lines() - 1;
|
|
// We need to start from the pixelRect, to make sure the bottom border is entirely painted.
|
|
// This is a case where we DO want to subtract pixels to pixels...
|
|
int paragBottom = pixelRect(zh).height()-1;
|
|
// If we don't have a bottom border, we need go as low as possible ( to touch the next parag's border ).
|
|
// If we have a bottom border, then we rather exclude the linespacing. Looks nicer. OO does that too.
|
|
if ( m_layout.bottomBorder.width() > 0 && drawBottomBorder)
|
|
paragBottom -= zh->layoutUnitToPixelY( lineSpacing( lastLine ) );
|
|
paragBottom -= KoBorder::zoomWidthY( m_layout.bottomBorder.width(), zh, 0 );
|
|
//kdDebug(32500) << "Parag border: paragBottom=" << paragBottom
|
|
// << " bottom border width = " << KoBorder::zoomWidthY( m_layout.bottomBorder.width(), zh, 0 ) << endl;
|
|
r.setBottom( paragBottom );
|
|
|
|
//kdDebug(32500) << "KoTextParag::paint documentWidth=" << documentWidth() << " LU (" << zh->layoutUnitToPixelX(documentWidth()) << " pixels) bordersRect=" << r << endl;
|
|
KoBorder::drawBorders( painter, zh, r,
|
|
m_layout.leftBorder, m_layout.rightBorder, m_layout.topBorder, m_layout.bottomBorder,
|
|
0, TQPen(), drawTopBorder, drawBottomBorder );
|
|
}
|
|
}
|
|
|
|
|
|
void KoTextParag::paintLines( TQPainter &painter, const TQColorGroup &cg, KoTextCursor *cursor, bool drawSelections,
|
|
int clipx, int clipy, int clipw, int cliph )
|
|
{
|
|
if ( !visible )
|
|
return;
|
|
//KoTextStringChar *chr = at( 0 );
|
|
//if (!chr) { kdDebug(32500) << "paragraph " << (void*)this << " " << paragId() << ", can't paint, EMPTY !" << endl;
|
|
|
|
// This is necessary with the current code, but in theory it shouldn't
|
|
// be necessary, if Xft really gives us fully proportionnal chars....
|
|
#define CHECK_PIXELXADJ
|
|
|
|
int curx = -1, cury = 0, curh = 0, curline = 0;
|
|
int xstart, xend = 0;
|
|
|
|
TQString qstr = str->toString();
|
|
qstr.replace( TQChar(0x00a0U), ' ' ); // Not all fonts have non-breakable-space glyph
|
|
|
|
const int nSels = doc ? doc->numSelections() : 1;
|
|
TQMemArray<int> selectionStarts( nSels );
|
|
TQMemArray<int> selectionEnds( nSels );
|
|
if ( drawSelections ) {
|
|
bool hasASelection = FALSE;
|
|
for ( int i = 0; i < nSels; ++i ) {
|
|
if ( !hasSelection( i ) ) {
|
|
selectionStarts[ i ] = -1;
|
|
selectionEnds[ i ] = -1;
|
|
} else {
|
|
hasASelection = TRUE;
|
|
selectionStarts[ i ] = selectionStart( i );
|
|
int end = selectionEnd( i );
|
|
if ( end == length() - 1 && n && n->hasSelection( i ) )
|
|
end++;
|
|
selectionEnds[ i ] = end;
|
|
}
|
|
}
|
|
if ( !hasASelection )
|
|
drawSelections = FALSE;
|
|
}
|
|
|
|
// Draw the lines!
|
|
int line = m_lineChanged;
|
|
if (line<0) line = 0;
|
|
|
|
int numLines = lines();
|
|
#ifdef DEBUG_PAINT
|
|
kdDebug(32500) << " paintLines: from line " << line << " to " << numLines-1 << endl;
|
|
#endif
|
|
for( ; line<numLines ; line++ )
|
|
{
|
|
// get the start and length of the line
|
|
int nextLine;
|
|
int startOfLine;
|
|
lineStartOfLine(line, &startOfLine);
|
|
if (line == numLines-1 )
|
|
nextLine = length();
|
|
else
|
|
lineStartOfLine(line+1, &nextLine);
|
|
|
|
// init this line
|
|
int cy, h, baseLine;
|
|
lineInfo( line, cy, h, baseLine );
|
|
if ( clipy != -1 && cy > clipy - r.y() + cliph ) // outside clip area, leave
|
|
break;
|
|
|
|
// Vars related to the current "run of text"
|
|
int paintStart = startOfLine;
|
|
KoTextStringChar* chr = at(startOfLine);
|
|
KoTextStringChar* nextchr = chr;
|
|
|
|
// okay, paint the line!
|
|
for(int i=startOfLine;i<nextLine;i++)
|
|
{
|
|
chr = nextchr;
|
|
if ( i < nextLine-1 )
|
|
nextchr = at( i+1 );
|
|
|
|
// we flush at end of line
|
|
bool flush = ( i == nextLine - 1 );
|
|
// Optimization note: TQRT uses "flush |=", which doesn't have shortcut optimization
|
|
|
|
// we flush on format changes
|
|
flush = flush || ( nextchr->format() != chr->format() );
|
|
// we flush on link changes
|
|
//flush = flush || ( nextchr->isLink() != chr->isLink() );
|
|
// we flush on small caps changes
|
|
if ( !flush && chr->format()->attributeFont() == KoTextFormat::ATT_SMALL_CAPS )
|
|
{
|
|
bool isLowercase = chr->c.upper() != chr->c;
|
|
bool nextLowercase = nextchr->c.upper() != nextchr->c;
|
|
flush = isLowercase != nextLowercase;
|
|
}
|
|
// we flush on start of run
|
|
flush = flush || nextchr->startOfRun;
|
|
// we flush on bidi changes
|
|
flush = flush || ( nextchr->rightToLeft != chr->rightToLeft );
|
|
#ifdef CHECK_PIXELXADJ
|
|
// we flush when the value of pixelxadj changes
|
|
// [unless inside a ligature]
|
|
flush = flush || ( nextchr->pixelxadj != chr->pixelxadj && nextchr->charStop );
|
|
#endif
|
|
// we flush before and after tabs
|
|
flush = flush || ( chr->c == '\t' || nextchr->c == '\t' );
|
|
// we flush on soft hypens
|
|
flush = flush || ( chr->c.tqunicode() == 0xad );
|
|
// we flush on custom items
|
|
flush = flush || chr->isCustom();
|
|
// we flush before custom items
|
|
flush = flush || nextchr->isCustom();
|
|
// when painting justified we flush on spaces
|
|
if ((tqalignment() & TQt::AlignJustify) == TQt::AlignJustify )
|
|
//flush = flush || TQTextFormatter::isBreakable( str, i );
|
|
flush = flush || chr->whiteSpace;
|
|
// when underlining or striking "word by word" we flush before/after spaces
|
|
if (!flush && chr->format()->wordByWord() && chr->format()->isStrikedOrUnderlined())
|
|
flush = flush || chr->whiteSpace || nextchr->whiteSpace;
|
|
// we flush when the string is getting too long
|
|
flush = flush || ( i - paintStart >= 256 );
|
|
// we flush when the selection state changes
|
|
if ( drawSelections ) {
|
|
// check if selection state changed - TODO update from TQRT
|
|
bool selectionChange = FALSE;
|
|
if ( drawSelections ) {
|
|
for ( int j = 0; j < nSels; ++j ) {
|
|
selectionChange = selectionStarts[ j ] == i+1 || selectionEnds[ j ] == i+1;
|
|
if ( selectionChange )
|
|
break;
|
|
}
|
|
}
|
|
flush = flush || selectionChange;
|
|
}
|
|
|
|
// check for cursor mark
|
|
if ( cursor && this == cursor->parag() && i == cursor->index() ) {
|
|
curx = cursor->x();
|
|
curline = line;
|
|
KoTextStringChar *c = chr;
|
|
if ( i > 0 )
|
|
--c;
|
|
curh = c->height();
|
|
cury = cy + baseLine - c->ascent();
|
|
}
|
|
|
|
if ( flush ) { // something changed, draw what we have so far
|
|
|
|
KoTextStringChar* cStart = at( paintStart );
|
|
if ( chr->rightToLeft ) {
|
|
xstart = chr->x;
|
|
xend = cStart->x + cStart->width;
|
|
} else {
|
|
xstart = cStart->x;
|
|
if ( i < length() - 1 && !str->at( i + 1 ).lineStart &&
|
|
str->at( i + 1 ).rightToLeft == chr->rightToLeft )
|
|
xend = str->at( i + 1 ).x;
|
|
else
|
|
xend = chr->x + chr->width;
|
|
}
|
|
|
|
if ( (clipx == -1 || clipw == -1) || (xend >= clipx && xstart <= clipx + clipw) ) {
|
|
if ( !chr->isCustom() ) {
|
|
drawParagString( painter, qstr, paintStart, i - paintStart + 1, xstart, cy,
|
|
baseLine, xend-xstart, h, drawSelections,
|
|
chr->format(), selectionStarts, selectionEnds,
|
|
cg, chr->rightToLeft, line );
|
|
}
|
|
else
|
|
if ( chr->customItem()->placement() == KoTextCustomItem::PlaceInline ) {
|
|
chr->customItem()->draw( &painter, chr->x, cy + baseLine - chr->customItem()->ascent(),
|
|
clipx - r.x(), clipy - r.y(), clipw, cliph, cg,
|
|
drawSelections && nSels && selectionStarts[ 0 ] <= i && selectionEnds[ 0 ] > i );
|
|
}
|
|
}
|
|
paintStart = i+1;
|
|
}
|
|
} // end of character loop
|
|
} // end of line loop
|
|
|
|
// if we should draw a cursor, draw it now
|
|
if ( curx != -1 && cursor ) {
|
|
drawCursor( painter, cursor, curx, cury, curh, cg );
|
|
}
|
|
}
|
|
|
|
// Called by KoTextParag::paintLines
|
|
// Draw a set of characters with the same formattings.
|
|
// Reimplemented here to convert coordinates first, and call @ref drawFormattingChars.
|
|
void KoTextParag::drawParagString( TQPainter &painter, const TQString &str, int start, int len, int startX,
|
|
int lastY, int baseLine, int bw, int h, bool drawSelections,
|
|
KoTextFormat *format, const TQMemArray<int> &selectionStarts,
|
|
const TQMemArray<int> &selectionEnds, const TQColorGroup &cg, bool rightToLeft, int line )
|
|
{
|
|
KoTextZoomHandler * zh = textDocument()->paintingZoomHandler();
|
|
assert(zh);
|
|
|
|
#ifdef DEBUG_PAINT
|
|
kdDebug(32500) << "KoTextParag::drawParagString drawing from " << start << " to " << start+len << endl;
|
|
kdDebug(32500) << " startX in LU: " << startX << " lastY in LU:" << lastY
|
|
<< " baseLine in LU:" << baseLine << endl;
|
|
#endif
|
|
|
|
// Calculate offset (e.g. due to shadow on left or top)
|
|
// Important: don't use the 2-args methods here, offsets are not heights
|
|
// (0 should be 0, not 1) (#63256)
|
|
int shadowOffsetX_pix = zh->layoutUnitToPixelX( format->offsetX() );
|
|
int shadowOffsetY_pix = zh->layoutUnitToPixelY( format->offsetY() );
|
|
|
|
// Calculate startX in pixels
|
|
int startX_pix = zh->layoutUnitToPixelX( startX ) /* + at( rightToLeft ? start+len-1 : start )->pixelxadj */;
|
|
#ifdef DEBUG_PAINT
|
|
kdDebug(32500) << "KoTextParag::drawParagString startX in pixels : " << startX_pix /*<< " adjustment:" << at( rightToLeft ? start+len-1 : start )->pixelxadj*/ << " bw=" << bw << endl;
|
|
#endif
|
|
|
|
int bw_pix = zh->layoutUnitToPixelX( startX, bw );
|
|
int lastY_pix = zh->layoutUnitToPixelY( lastY );
|
|
int baseLine_pix = zh->layoutUnitToPixelY( lastY, baseLine ); // 2 args=>+1. Is that correct?
|
|
int h_pix = zh->layoutUnitToPixelY( lastY, h );
|
|
#ifdef DEBUG_PAINT
|
|
kdDebug(32500) << "KoTextParag::drawParagString h(LU)=" << h << " lastY(LU)=" << lastY
|
|
<< " h(PIX)=" << h_pix << " lastY(PIX)=" << lastY_pix
|
|
<< " baseLine(PIX)=" << baseLine_pix << endl;
|
|
#endif
|
|
|
|
if ( format->textBackgroundColor().isValid() )
|
|
painter.fillRect( startX_pix, lastY_pix, bw_pix, h_pix, format->textBackgroundColor() );
|
|
|
|
// don't want to draw line breaks but want them when drawing formatting chars
|
|
int draw_len = len;
|
|
int draw_startX = startX;
|
|
int draw_bw = bw_pix;
|
|
if ( at( start + len - 1 )->c == '\n' )
|
|
{
|
|
draw_len--;
|
|
draw_bw -= at( start + len - 1 )->pixelwidth;
|
|
if ( rightToLeft && draw_len > 0 )
|
|
draw_startX = at( start + draw_len - 1 )->x;
|
|
}
|
|
|
|
// Draw selection (moved here to do it before applying the offset from the shadow)
|
|
// (and because it's not part of the shadow drawing)
|
|
if ( drawSelections ) {
|
|
bool inSelection = false;
|
|
const int nSels = doc ? doc->numSelections() : 1;
|
|
for ( int j = 0; j < nSels; ++j ) {
|
|
if ( start >= selectionStarts[ j ] && start < selectionEnds[ j ] ) {
|
|
inSelection = true;
|
|
switch (j) {
|
|
case KoTextDocument::Standard:
|
|
painter.fillRect( startX_pix, lastY_pix, bw_pix, h_pix, cg.color( TQColorGroup::Highlight ) );
|
|
break;
|
|
case KoTextDocument::InputMethodPreedit:
|
|
// no highlight
|
|
break;
|
|
default:
|
|
painter.fillRect( startX_pix, lastY_pix, bw_pix, h_pix, doc ? doc->selectionColor( j ) : cg.color( TQColorGroup::Highlight ) );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( !inSelection )
|
|
drawSelections = false; // save time in drawParagStringInternal
|
|
}
|
|
|
|
// Draw InputMethod Preedit Underline
|
|
const int nSels = doc ? doc->numSelections() : 1;
|
|
if ( KoTextDocument::InputMethodPreedit < nSels
|
|
&& doc->hasSelection( KoTextDocument::InputMethodPreedit )
|
|
&& start >= selectionStarts[ KoTextDocument::InputMethodPreedit ]
|
|
&& start < selectionEnds[ KoTextDocument::InputMethodPreedit ] )
|
|
{
|
|
TQColor textColor( format->color() );
|
|
painter.setPen( TQPen( textColor ) );
|
|
|
|
TQPoint p1( startX_pix, lastY_pix + h_pix - 1 );
|
|
TQPoint p2( startX_pix + bw_pix, lastY_pix + h_pix - 1 );
|
|
painter.drawLine( p1, p2 );
|
|
}
|
|
|
|
if ( draw_len > 0 )
|
|
{
|
|
int draw_startX_pix = zh->layoutUnitToPixelX( draw_startX ) /* + at( rightToLeft ? start+draw_len-1 : start )->pixelxadj*/;
|
|
draw_startX_pix += shadowOffsetX_pix;
|
|
lastY_pix += shadowOffsetY_pix;
|
|
|
|
if ( format->shadowDistanceX() != 0 || format->shadowDistanceY() != 0 ) {
|
|
int sx = format->shadowX( zh );
|
|
int sy = format->shadowY( zh );
|
|
if ( sx != 0 || sy != 0 )
|
|
{
|
|
painter.save();
|
|
painter.translate( sx, sy );
|
|
drawParagStringInternal( painter, str, start, draw_len, draw_startX_pix,
|
|
lastY_pix, baseLine_pix,
|
|
draw_bw,
|
|
h_pix, FALSE /*drawSelections*/,
|
|
format, selectionStarts,
|
|
selectionEnds, cg, rightToLeft, line, zh, true );
|
|
painter.restore();
|
|
}
|
|
}
|
|
|
|
drawParagStringInternal( painter, str, start, draw_len, draw_startX_pix,
|
|
lastY_pix, baseLine_pix,
|
|
draw_bw,
|
|
h_pix, drawSelections, format, selectionStarts,
|
|
selectionEnds, cg, rightToLeft, line, zh, false );
|
|
}
|
|
|
|
bool forPrint = ( painter.device()->devType() == TQInternal::Printer );
|
|
if ( textDocument()->drawFormattingChars() && !forPrint )
|
|
{
|
|
drawFormattingChars( painter, start, len,
|
|
lastY_pix, baseLine_pix, h_pix,
|
|
drawSelections,
|
|
format, selectionStarts,
|
|
selectionEnds, cg, rightToLeft,
|
|
line, zh, AllFormattingChars );
|
|
}
|
|
}
|
|
|
|
// Copied from the original KoTextParag
|
|
// (we have to copy it here, so that color & font changes don't require changing
|
|
// a local copy of the text format)
|
|
// And we have to keep it separate from drawParagString to avoid s/startX/startX_pix/ etc.
|
|
void KoTextParag::drawParagStringInternal( TQPainter &painter, const TQString &s, int start, int len, int startX,
|
|
int lastY, int baseLine, int bw, int h, bool drawSelections,
|
|
KoTextFormat *format, const TQMemArray<int> &selectionStarts,
|
|
const TQMemArray<int> &selectionEnds, const TQColorGroup &cg, bool rightToLeft, int line, KoTextZoomHandler* zh, bool drawingShadow )
|
|
{
|
|
#ifdef DEBUG_PAINT
|
|
kdDebug(32500) << "KoTextParag::drawParagStringInternal start=" << start << " len=" << len << " : '" << s.mid(start,len) << "'" << endl;
|
|
kdDebug(32500) << "In pixels: startX=" << startX << " lastY=" << lastY << " baseLine=" << baseLine
|
|
<< " bw=" << bw << " h=" << h << " rightToLeft=" << rightToLeft << endl;
|
|
#endif
|
|
if ( drawingShadow && format->shadowDistanceX() == 0 && format->shadowDistanceY() == 0 )
|
|
return;
|
|
// 1) Sort out the color
|
|
TQColor textColor( drawingShadow ? format->shadowColor() : format->color() );
|
|
if ( !textColor.isValid() ) // Resolve the color at this point
|
|
textColor = KoTextFormat::defaultTextColor( &painter );
|
|
|
|
// 2) Sort out the font
|
|
TQFont font( format->screenFont( zh ) );
|
|
if ( format->attributeFont() == KoTextFormat::ATT_SMALL_CAPS && s[start].upper() != s[start] )
|
|
font = format->smallCapsFont( zh, true );
|
|
|
|
#if 0
|
|
TQFontInfo fi( font );
|
|
kdDebug(32500) << "KoTextParag::drawParagStringInternal requested font " << font.pointSizeFloat() << " using font " << fi.pointSize() << "pt (format font: " << format->font().pointSizeFloat() << "pt)" << endl;
|
|
TQFontMetrics fm( font );
|
|
kdDebug(32500) << "Real font: " << fi.family() << ". Font height in pixels: " << fm.height() << endl;
|
|
#endif
|
|
|
|
// 3) Paint
|
|
TQString str( s );
|
|
if ( str[ (int)str.length() - 1 ].tqunicode() == 0xad )
|
|
str.remove( str.length() - 1, 1 );
|
|
painter.setPen( TQPen( textColor ) );
|
|
painter.setFont( font );
|
|
|
|
KoTextDocument* doc = document();
|
|
|
|
if ( drawSelections ) {
|
|
const int nSels = doc ? doc->numSelections() : 1;
|
|
for ( int j = 0; j < nSels; ++j ) {
|
|
if ( start >= selectionStarts[ j ] && start < selectionEnds[ j ] ) {
|
|
if ( !doc || doc->invertSelectionText( j ) )
|
|
textColor = cg.color( TQColorGroup::HighlightedText );
|
|
painter.setPen( TQPen( textColor ) );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TQPainter::TextDirection dir = rightToLeft ? TQPainter::RTL : TQPainter::LTR;
|
|
|
|
if ( dir != TQPainter::RTL && start + len == length() ) // don't draw the last character (trailing space)
|
|
{
|
|
len--;
|
|
if ( len <= 0 )
|
|
return;
|
|
bw-=at(length()-1)->pixelwidth;
|
|
}
|
|
KoTextParag::drawFontEffects( &painter, format, zh, font, textColor, startX, baseLine, bw, lastY, h, str[start] );
|
|
|
|
if ( str[ start ] != '\t' && str[ start ].tqunicode() != 0xad ) {
|
|
str = format->displayedString( str ); // #### This converts the whole string, instead of from start to start+len!
|
|
if ( format->vAlign() == KoTextFormat::AlignNormal ) {
|
|
int posY = lastY + baseLine;
|
|
//we must move to bottom text because we create
|
|
//shadow to 'top'.
|
|
int sy = format->shadowY( zh );
|
|
if ( sy < 0)
|
|
posY -= sy;
|
|
painter.drawText( startX, posY, str, start, len, dir );
|
|
#ifdef BIDI_DEBUG
|
|
painter.save();
|
|
painter.setPen ( TQt::red );
|
|
painter.drawLine( startX, lastY, startX, lastY + baseLine );
|
|
painter.drawLine( startX, lastY + baseLine/2, startX + 10, lastY + baseLine/2 );
|
|
int w = 0;
|
|
int i = 0;
|
|
while( i < len )
|
|
w += painter.fontMetrics().charWidth( str, start + i++ );
|
|
painter.setPen ( TQt::blue );
|
|
painter.drawLine( startX + w - 1, lastY, startX + w - 1, lastY + baseLine );
|
|
painter.drawLine( startX + w - 1, lastY + baseLine/2, startX + w - 1 - 10, lastY + baseLine/2 );
|
|
painter.restore();
|
|
#endif
|
|
} else if ( format->vAlign() == KoTextFormat::AlignSuperScript ) {
|
|
int posY =lastY + baseLine - ( painter.fontMetrics().height() / 2 );
|
|
//we must move to bottom text because we create
|
|
//shadow to 'top'.
|
|
int sy = format->shadowY( zh );
|
|
if ( sy < 0)
|
|
posY -= sy;
|
|
painter.drawText( startX, posY, str, start, len, dir );
|
|
} else if ( format->vAlign() == KoTextFormat::AlignSubScript ) {
|
|
int posY =lastY + baseLine + ( painter.fontMetrics().height() / 6 );
|
|
//we must move to bottom text because we create
|
|
//shadow to 'top'.
|
|
int sy = format->shadowY( zh );
|
|
if ( sy < 0)
|
|
posY -= sy;
|
|
painter.drawText( startX, posY, str, start, len, dir );
|
|
} else if ( format->vAlign() == KoTextFormat::AlignCustom ) {
|
|
int posY = lastY + baseLine - format->offsetFromBaseLine();
|
|
//we must move to bottom text because we create
|
|
//shadow to 'top'.
|
|
int sy = format->shadowY( zh );
|
|
if ( sy < 0)
|
|
posY -= sy;
|
|
painter.drawText( startX, posY, str, start, len, dir );
|
|
}
|
|
}
|
|
if ( str[ start ] == '\t' && m_tabCache.contains( start ) ) {
|
|
painter.save();
|
|
KoTextZoomHandler * zh = textDocument()->paintingZoomHandler();
|
|
const KoTabulator& tab = m_layout.tabList()[ m_tabCache[ start ] ];
|
|
int lineWidth = zh->zoomItY( tab.ptWidth );
|
|
switch ( tab.filling ) {
|
|
case TF_DOTS:
|
|
painter.setPen( TQPen( textColor, lineWidth, TQt::DotLine ) );
|
|
painter.drawLine( startX, lastY + baseLine, startX + bw, lastY + baseLine );
|
|
break;
|
|
case TF_LINE:
|
|
painter.setPen( TQPen( textColor, lineWidth, TQt::SolidLine ) );
|
|
painter.drawLine( startX, lastY + baseLine, startX + bw, lastY + baseLine );
|
|
break;
|
|
case TF_DASH:
|
|
painter.setPen( TQPen( textColor, lineWidth, TQt::DashLine ) );
|
|
painter.drawLine( startX, lastY + baseLine, startX + bw, lastY + baseLine );
|
|
break;
|
|
case TF_DASH_DOT:
|
|
painter.setPen( TQPen( textColor, lineWidth, TQt::DashDotLine ) );
|
|
painter.drawLine( startX, lastY + baseLine, startX + bw, lastY + baseLine );
|
|
break;
|
|
case TF_DASH_DOT_DOT:
|
|
painter.setPen( TQPen( textColor, lineWidth, TQt::DashDotDotLine ) );
|
|
painter.drawLine( startX, lastY + baseLine, startX + bw, lastY + baseLine );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
painter.restore();
|
|
}
|
|
|
|
if ( start+len < length() && at( start+len )->lineStart )
|
|
{
|
|
#ifdef DEBUG_PAINT
|
|
//kdDebug(32500) << "we are drawing the end of line " << line << ". Auto-hyphenated: " << lineHyphenated( line ) << endl;
|
|
#endif
|
|
bool drawHyphen = at( start+len-1 )->c.tqunicode() == 0xad;
|
|
drawHyphen = drawHyphen || lineHyphenated( line );
|
|
if ( drawHyphen ) {
|
|
#ifdef DEBUG_PAINT
|
|
kdDebug(32500) << "drawing hyphen at x=" << startX+bw << endl;
|
|
#endif
|
|
painter.drawText( startX + bw, lastY + baseLine, TQString("-") ); // \xad gives squares with some fonts (!?)
|
|
}
|
|
}
|
|
|
|
// Paint a zigzag line for "wrong" background spellchecking checked words:
|
|
if(
|
|
painter.device()->devType() != TQInternal::Printer &&
|
|
format->isMisspelled() &&
|
|
!drawingShadow &&
|
|
textDocument()->drawingMissingSpellLine() )
|
|
{
|
|
painter.save();
|
|
painter.setPen( TQPen( TQt::red, 1 ) );
|
|
|
|
// Draw 3 pixel lines with increasing offset and distance 4:
|
|
for( int zigzag_line = 0; zigzag_line < 3; ++zigzag_line )
|
|
{
|
|
for( int zigzag_x = zigzag_line; zigzag_x < bw; zigzag_x += 4 )
|
|
{
|
|
painter.drawPoint(
|
|
startX + zigzag_x,
|
|
lastY + baseLine + h/12 - 1 + zigzag_line );
|
|
}
|
|
}
|
|
|
|
// "Double" the pixel number for the middle line:
|
|
for( int zigzag_x = 3; zigzag_x < bw; zigzag_x += 4 )
|
|
{
|
|
painter.drawPoint(
|
|
startX + zigzag_x,
|
|
lastY + baseLine + h/12 );
|
|
}
|
|
|
|
painter.restore();
|
|
}
|
|
}
|
|
|
|
bool KoTextParag::lineHyphenated( int l ) const
|
|
{
|
|
if ( l > (int)lineStarts.count() - 1 ) {
|
|
kdWarning() << "KoTextParag::lineHyphenated: line " << l << " out of range!" << endl;
|
|
return false;
|
|
}
|
|
|
|
if ( !isValid() )
|
|
const_cast<KoTextParag*>(this)->format();
|
|
|
|
TQMap<int, KoTextParagLineStart*>::ConstIterator it = lineStarts.begin();
|
|
while ( l-- > 0 )
|
|
++it;
|
|
return ( *it )->hyphenated;
|
|
}
|
|
|
|
/** Draw the cursor mark. Reimplemented from KoTextParag to convert coordinates first. */
|
|
void KoTextParag::drawCursor( TQPainter &painter, KoTextCursor *cursor, int curx, int cury, int curh, const TQColorGroup &cg )
|
|
{
|
|
KoTextZoomHandler * zh = textDocument()->paintingZoomHandler();
|
|
int x = zh->layoutUnitToPixelX( curx ) /*+ cursor->parag()->at( cursor->index() )->pixelxadj*/;
|
|
//kdDebug(32500) << " drawCursor: LU: [cur]x=" << curx << ", cury=" << cury << " -> PIX: x=" << x << ", y=" << zh->layoutUnitToPixelY( cury ) << endl;
|
|
KoTextParag::drawCursorDefault( painter, cursor, x,
|
|
zh->layoutUnitToPixelY( cury ),
|
|
zh->layoutUnitToPixelY( cury, curh ), cg );
|
|
}
|
|
|
|
// Reimplemented from KoTextParag
|
|
void KoTextParag::copyParagData( KoTextParag *parag )
|
|
{
|
|
// Style of the previous paragraph
|
|
KoParagStyle * style = parag->style();
|
|
// Obey "following style" setting
|
|
bool styleApplied = false;
|
|
if ( style )
|
|
{
|
|
KoParagStyle * newStyle = style->followingStyle();
|
|
if ( newStyle && style != newStyle ) // if same style, keep paragraph-specific changes as usual
|
|
{
|
|
setParagLayout( newStyle->paragLayout() );
|
|
KoTextFormat * format = &newStyle->format();
|
|
setFormat( format );
|
|
format->addRef();
|
|
str->setFormat( 0, format, true ); // prepare format for text insertion
|
|
styleApplied = true;
|
|
}
|
|
}
|
|
// This should never happen in KWord, but it happens in KPresenter
|
|
//else
|
|
// kdWarning() << "Paragraph has no style " << paragId() << endl;
|
|
|
|
// No "following style" setting, or same style -> copy tqlayout & format of previous paragraph
|
|
if (!styleApplied)
|
|
{
|
|
setParagLayout( parag->paragLayout() );
|
|
// Remove pagebreak flags from initial parag - they got copied to the new parag
|
|
parag->m_layout.pageBreaking &= ~KoParagLayout::HardFrameBreakBefore;
|
|
parag->m_layout.pageBreaking &= ~KoParagLayout::HardFrameBreakAfter;
|
|
// Remove footnote counter text from second parag
|
|
if ( m_layout.counter && m_layout.counter->numbering() == KoParagCounter::NUM_FOOTNOTE )
|
|
setNoCounter();
|
|
// Do not copy 'restart numbering at this paragraph' option (would be silly)
|
|
if ( m_layout.counter )
|
|
m_layout.counter->setRestartCounter(false);
|
|
|
|
// set parag format to the format of the trailing space of the previous parag
|
|
setFormat( parag->at( parag->length()-1 )->format() );
|
|
// KoTextCursor::splitAndInsertEmptyParag takes care of setting the format
|
|
// for the chars in the new parag
|
|
}
|
|
|
|
// Note: we don't call the original KoTextParag::copyParagData on purpose.
|
|
// We don't want setListStyle to get called - it ruins our stylesheetitems
|
|
// And we don't care about copying the stylesheetitems directly,
|
|
// applying the parag tqlayout will create them
|
|
}
|
|
|
|
void KoTextParag::setTabList( const KoTabulatorList &tabList )
|
|
{
|
|
KoTabulatorList lst( tabList );
|
|
m_layout.setTabList( lst );
|
|
if ( !tabList.isEmpty() )
|
|
{
|
|
KoTextZoomHandler* zh = textDocument()->formattingZoomHandler();
|
|
int * tabs = new int[ tabList.count() + 1 ]; // will be deleted by ~KoTextParag
|
|
KoTabulatorList::Iterator it = lst.begin();
|
|
unsigned int i = 0;
|
|
for ( ; it != lst.end() ; ++it, ++i )
|
|
tabs[i] = zh->ptToLayoutUnitPixX( (*it).ptPos );
|
|
tabs[i] = 0;
|
|
assert( i == tabList.count() );
|
|
setTabArray( tabs );
|
|
} else
|
|
{
|
|
setTabArray( 0 );
|
|
}
|
|
tqinvalidate( 0 );
|
|
}
|
|
|
|
/** "Reimplemented" (compared to nextTabDefault) to implement non-left-aligned tabs */
|
|
int KoTextParag::nextTab( int chnum, int x, int availableWidth )
|
|
{
|
|
if ( !m_layout.tabList().isEmpty() )
|
|
{
|
|
// Fetch the zoomed and sorted tab positions from KoTextParag
|
|
// We stored them there for faster access
|
|
int * tArray = tabArray();
|
|
int i = 0;
|
|
if ( str->isRightToLeft() )
|
|
i = m_layout.tabList().size() - 1;
|
|
KoTextZoomHandler* zh = textDocument()->formattingZoomHandler();
|
|
|
|
while ( i >= 0 && i < (int)m_layout.tabList().size() ) {
|
|
//kdDebug(32500) << "KoTextParag::nextTab tArray[" << i << "]=" << tArray[i] << " type " << m_layout.tabList()[i].type << endl;
|
|
int tab = tArray[ i ];
|
|
|
|
// If a right-aligned tab is after the right edge then assume
|
|
// that it -is- on the right edge, otherwise the last letters will fall off.
|
|
// This is compatible with OOo's behavior.
|
|
if ( tab > availableWidth ) {
|
|
//kdDebug(32500) << "Tab position adjusted to availableWidth=" << availableWidth << endl;
|
|
tab = availableWidth;
|
|
}
|
|
|
|
if ( str->isRightToLeft() )
|
|
tab = availableWidth - tab;
|
|
|
|
if ( tab > x ) {
|
|
int type = m_layout.tabList()[i].type;
|
|
|
|
// fix the tab type for right to left text
|
|
if ( str->isRightToLeft() )
|
|
if ( type == T_RIGHT )
|
|
type = T_LEFT;
|
|
else if ( type == T_LEFT )
|
|
type = T_RIGHT;
|
|
|
|
switch ( type ) {
|
|
case T_RIGHT:
|
|
case T_CENTER:
|
|
{
|
|
// Look for the next tab (or EOL)
|
|
int c = chnum + 1;
|
|
int w = 0;
|
|
while ( c < str->length() - 1 && str->at( c ).c != '\t' && str->at( c ).c != '\n' )
|
|
{
|
|
KoTextStringChar & ch = str->at( c );
|
|
// Determine char width
|
|
// This must be done in the same way as in KoTextFormatter::format() or there can be different rounding errors.
|
|
if ( ch.isCustom() )
|
|
w += ch.customItem()->width;
|
|
else
|
|
{
|
|
KoTextFormat *charFormat = ch.format();
|
|
int ww = charFormat->charWidth( zh, false, &ch, this, c );
|
|
ww = KoTextZoomHandler::ptToLayoutUnitPt( ww );
|
|
w += ww;
|
|
}
|
|
++c;
|
|
}
|
|
|
|
m_tabCache[chnum] = i;
|
|
|
|
if ( type == T_RIGHT )
|
|
return tab - w;
|
|
else // T_CENTER
|
|
return tab - w/2;
|
|
}
|
|
case T_DEC_PNT:
|
|
{
|
|
// Look for the next tab (or EOL), and for alignChar
|
|
// Default to right-aligned if no decimal point found (behavior from msword)
|
|
int c = chnum + 1;
|
|
int w = 0;
|
|
while ( c < str->length()-1 && str->at( c ).c != '\t' && str->at( c ).c != '\n' )
|
|
{
|
|
KoTextStringChar & ch = str->at( c );
|
|
if ( ch.c == m_layout.tabList()[i].alignChar )
|
|
{
|
|
// Can't use ch.width yet, since the formatter hasn't run over those chars
|
|
int ww = ch.format()->charWidth( zh, false, &ch, this, c );
|
|
ww = KoTextZoomHandler::ptToLayoutUnitPt( ww );
|
|
if ( str->isRightToLeft() )
|
|
{
|
|
w = ww / 2; // center around the decimal point
|
|
++c;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
w += ww / 2; // center around the decimal point
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Determine char width
|
|
if ( ch.isCustom() )
|
|
w += ch.customItem()->width;
|
|
else
|
|
{
|
|
int ww = ch.format()->charWidth( zh, false, &ch, this, c );
|
|
w += KoTextZoomHandler::ptToLayoutUnitPt( ww );
|
|
}
|
|
|
|
++c;
|
|
}
|
|
m_tabCache[chnum] = i;
|
|
return tab - w;
|
|
}
|
|
default: // case T_LEFT:
|
|
m_tabCache[chnum] = i;
|
|
return tab;
|
|
}
|
|
}
|
|
if ( str->isRightToLeft() )
|
|
--i;
|
|
else
|
|
++i;
|
|
}
|
|
}
|
|
// No tab list, use tab-stop-width. qrichtext.cpp has the code :)
|
|
return KoTextParag::nextTabDefault( chnum, x );
|
|
}
|
|
|
|
void KoTextParag::applyStyle( KoParagStyle *style )
|
|
{
|
|
setParagLayout( style->paragLayout() );
|
|
KoTextFormat *newFormat = &style->format();
|
|
setFormat( 0, str->length(), newFormat );
|
|
setFormat( newFormat );
|
|
}
|
|
|
|
void KoTextParag::setParagLayout( const KoParagLayout & tqlayout, int flags, int marginIndex )
|
|
{
|
|
//kdDebug(32500) << "KoTextParag::setParagLayout flags=" << flags << endl;
|
|
if ( flags & KoParagLayout::Alignment )
|
|
setAlign( tqlayout.tqalignment );
|
|
if ( flags & KoParagLayout::Margins ) {
|
|
if ( marginIndex == -1 )
|
|
setMargins( tqlayout.margins );
|
|
else
|
|
setMargin( (TQStyleSheetItem::Margin)marginIndex, tqlayout.margins[marginIndex] );
|
|
}
|
|
if ( flags & KoParagLayout::LineSpacing )
|
|
{
|
|
setLineSpacingType( tqlayout.lineSpacingType );
|
|
setLineSpacing( tqlayout.lineSpacingValue() );
|
|
}
|
|
if ( flags & KoParagLayout::Borders )
|
|
{
|
|
setLeftBorder( tqlayout.leftBorder );
|
|
setRightBorder( tqlayout.rightBorder );
|
|
setTopBorder( tqlayout.topBorder );
|
|
setBottomBorder( tqlayout.bottomBorder );
|
|
setJoinBorder( tqlayout.joinBorder );
|
|
}
|
|
if ( flags & KoParagLayout::BackgroundColor )
|
|
{
|
|
setBackgroundColor( tqlayout.backgroundColor );
|
|
}
|
|
if ( flags & KoParagLayout::BulletNumber )
|
|
setCounter( tqlayout.counter );
|
|
if ( flags & KoParagLayout::Tabulator )
|
|
setTabList( tqlayout.tabList() );
|
|
if ( flags == KoParagLayout::All )
|
|
{
|
|
setDirection( static_cast<TQChar::Direction>(tqlayout.direction) );
|
|
// Don't call applyStyle from here, it would overwrite any paragraph-specific settings
|
|
setStyle( tqlayout.style );
|
|
}
|
|
}
|
|
|
|
void KoTextParag::setCustomItem( int index, KoTextCustomItem * custom, KoTextFormat * currentFormat )
|
|
{
|
|
//kdDebug(32500) << "KoTextParag::setCustomItem " << index << " " << (void*)custom
|
|
// << " currentFormat=" << (void*)currentFormat << endl;
|
|
if ( currentFormat )
|
|
setFormat( index, 1, currentFormat );
|
|
at( index )->setCustomItem( custom );
|
|
//addCustomItem();
|
|
document()->registerCustomItem( custom, this );
|
|
custom->recalc(); // calc value (e.g. for variables) and set initial size
|
|
tqinvalidate( 0 );
|
|
setChanged( true );
|
|
}
|
|
|
|
void KoTextParag::removeCustomItem( int index )
|
|
{
|
|
Q_ASSERT( at( index )->isCustom() );
|
|
KoTextCustomItem * item = at( index )->customItem();
|
|
at( index )->loseCustomItem();
|
|
//KoTextParag::removeCustomItem();
|
|
document()->unregisterCustomItem( item, this );
|
|
}
|
|
|
|
|
|
int KoTextParag::findCustomItem( const KoTextCustomItem * custom ) const
|
|
{
|
|
int len = str->length();
|
|
for ( int i = 0; i < len; ++i )
|
|
{
|
|
KoTextStringChar & ch = str->at(i);
|
|
if ( ch.isCustom() && ch.customItem() == custom )
|
|
return i;
|
|
}
|
|
kdWarning() << "KoTextParag::findCustomItem custom item " << (void*)custom
|
|
<< " not found in paragraph " << paragId() << endl;
|
|
return 0;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
void KoTextParag::printRTDebug( int info )
|
|
{
|
|
TQString specialFlags;
|
|
if ( str->needsSpellCheck() )
|
|
specialFlags += " needsSpellCheck=true";
|
|
if ( wasMovedDown() )
|
|
specialFlags += " wasMovedDown=true";
|
|
if ( partOfTableOfContents() )
|
|
specialFlags += " part-of-TOC=true";
|
|
kdDebug(32500) << "Paragraph " << this << " (" << paragId() << ") [changed="
|
|
<< hasChanged() << ", valid=" << isValid()
|
|
<< specialFlags
|
|
<< "] ------------------ " << endl;
|
|
if ( prev() && prev()->paragId() + 1 != paragId() )
|
|
kdWarning() << " Previous paragraph " << prev() << " has ID " << prev()->paragId() << endl;
|
|
if ( next() && next()->paragId() != paragId() + 1 )
|
|
kdWarning() << " Next paragraph " << next() << " has ID " << next()->paragId() << endl;
|
|
//if ( !next() )
|
|
// kdDebug(32500) << " next is 0L" << endl;
|
|
kdDebug(32500) << " Style: " << style() << " " << ( style() ? style()->name().local8Bit().data() : "NO STYLE" ) << endl;
|
|
kdDebug(32500) << " Text: '" << str->toString() << "'" << endl;
|
|
if ( info == 0 ) // paragraph info
|
|
{
|
|
if ( m_layout.counter )
|
|
{
|
|
m_layout.counter->printRTDebug( this );
|
|
}
|
|
static const char * const s_align[] = { "Auto", "Left", "Right", "ERROR", "HCenter", "ERR", "ERR", "ERR", "Justify", };
|
|
static const char * const s_linespacing[] = { "Single", "1.5", "2", "custom", "atLeast", "Multiple", "Fixed" };
|
|
static const char * const s_dir[] = { "DirL", "DirR", "DirEN", "DirES", "DirET", "DirAN", "DirCS", "DirB", "DirS", "DirWS", "DirON", "DirLRE", "DirLRO", "DirAL", "DirRLE", "DirRLO", "DirPDF", "DirNSM", "DirBN" };
|
|
kdDebug(32500) << " align: " << s_align[tqalignment()] << " resolveAlignment: " << s_align[resolveAlignment()]
|
|
<< " isRTL:" << str->isRightToLeft()
|
|
<< " dir: " << s_dir[direction()] << endl;
|
|
TQRect pixr = pixelRect( textDocument()->paintingZoomHandler() );
|
|
kdDebug(32500) << " rect() : " << DEBUGRECT( rect() )
|
|
<< " pixelRect() : " << DEBUGRECT( pixr ) << endl;
|
|
kdDebug(32500) << " topMargin()=" << topMargin()
|
|
<< " breakableTopMargin()=" << breakableTopMargin()
|
|
<< " bottomMargin()=" << bottomMargin()
|
|
<< " leftMargin()=" << leftMargin() << " firstLineMargin()=" << firstLineMargin()
|
|
<< " rightMargin()=" << rightMargin() << endl;
|
|
if ( kwLineSpacingType() != KoParagLayout::LS_SINGLE )
|
|
kdDebug(32500) << " linespacing type=" << s_linespacing[ -kwLineSpacingType() ]
|
|
<< " value=" << kwLineSpacing() << endl;
|
|
const int pageBreaking = m_layout.pageBreaking;
|
|
TQStringList pageBreakingFlags;
|
|
if ( pageBreaking & KoParagLayout::KeepLinesTogether )
|
|
pageBreakingFlags.append( "KeepLinesTogether" );
|
|
if ( pageBreaking & KoParagLayout::HardFrameBreakBefore )
|
|
pageBreakingFlags.append( "HardFrameBreakBefore" );
|
|
if ( pageBreaking & KoParagLayout::HardFrameBreakAfter )
|
|
pageBreakingFlags.append( "HardFrameBreakAfter" );
|
|
if ( pageBreaking & KoParagLayout::KeepWithPrevious )
|
|
pageBreakingFlags.append( "KeepWithPrevious" );
|
|
if ( pageBreaking & KoParagLayout::KeepWithNext )
|
|
pageBreakingFlags.append( "KeepWithNext" );
|
|
if ( !pageBreakingFlags.isEmpty() )
|
|
kdDebug(32500) << " page Breaking: " << pageBreakingFlags.join(",") << endl;
|
|
|
|
static const char * const tabtype[] = { "T_LEFT", "T_CENTER", "T_RIGHT", "T_DEC_PNT", "error!!!" };
|
|
KoTabulatorList tabList = m_layout.tabList();
|
|
if ( tabList.isEmpty() ) {
|
|
if ( str->toString().find( '\t' ) != -1 )
|
|
kdDebug(32500) << "Tab width: " << textDocument()->tabStopWidth() << endl;
|
|
} else {
|
|
KoTabulatorList::Iterator it = tabList.begin();
|
|
for ( ; it != tabList.end() ; it++ )
|
|
kdDebug(32500) << "Tab type:" << tabtype[(*it).type] << " at: " << (*it).ptPos << endl;
|
|
}
|
|
} else if ( info == 1 ) // formatting info
|
|
{
|
|
kdDebug(32500) << " Paragraph format=" << paragFormat() << " " << paragFormat()->key()
|
|
<< " fontsize:" << dynamic_cast<KoTextFormat *>(paragFormat())->pointSize() << endl;
|
|
|
|
for ( int line = 0 ; line < lines(); ++ line ) {
|
|
int y, h, baseLine;
|
|
lineInfo( line, y, h, baseLine );
|
|
int startOfLine;
|
|
lineStartOfLine( line, &startOfLine );
|
|
kdDebug(32500) << " Line " << line << " y=" << y << " height=" << h << " baseLine=" << baseLine << " startOfLine(index)=" << startOfLine << endl;
|
|
}
|
|
kdDebug(32500) << endl;
|
|
KoTextString * s = string();
|
|
int lastX = 0; // pixels
|
|
int lastW = 0; // pixels
|
|
for ( int i = 0 ; i < s->length() ; ++i )
|
|
{
|
|
KoTextStringChar & ch = s->at(i);
|
|
int pixelx = textDocument()->formattingZoomHandler()->layoutUnitToPixelX( ch.x )
|
|
+ ch.pixelxadj;
|
|
if ( ch.lineStart )
|
|
kdDebug(32500) << "LINESTART" << endl;
|
|
TQString attrs = " ";
|
|
if ( ch.whiteSpace )
|
|
attrs += "whitespace ";
|
|
if ( !ch.charStop )
|
|
attrs += "notCharStop ";
|
|
if ( ch.wordStop )
|
|
attrs += "wordStop ";
|
|
attrs.truncate( attrs.length() - 1 );
|
|
|
|
kdDebug(32500) << i << ": '" << TQString(ch.c).rightJustify(2)
|
|
<< "' (" << TQString::number( ch.c.tqunicode() ).rightJustify(3) << ")"
|
|
<< " x(LU)=" << ch.x
|
|
<< " w(LU)=" << ch.width//s->width(i)
|
|
<< " x(PIX)=" << pixelx
|
|
<< " (xadj=" << + ch.pixelxadj << ")"
|
|
<< " w(PIX)=" << ch.pixelwidth
|
|
<< " height=" << ch.height()
|
|
<< attrs
|
|
// << " format=" << ch.format()
|
|
// << " \"" << ch.format()->key() << "\" "
|
|
//<< " fontsize:" << dynamic_cast<KoTextFormat *>(ch.format())->pointSize()
|
|
<< endl;
|
|
|
|
// Check that the format is in the collection (i.e. its defaultFormat or in the dict)
|
|
if ( ch.format() != textDocument()->formatCollection()->defaultFormat() )
|
|
Q_ASSERT( textDocument()->formatCollection()->dict()[ch.format()->key()] );
|
|
|
|
if ( !str->isBidi() && !ch.lineStart )
|
|
Q_ASSERT( lastX + lastW == pixelx ); // looks like some rounding problem with justified spaces
|
|
lastX = pixelx;
|
|
lastW = ch.pixelwidth;
|
|
if ( ch.isCustom() )
|
|
{
|
|
KoTextCustomItem * item = ch.customItem();
|
|
kdDebug(32500) << " - custom item " << item
|
|
<< " ownline=" << item->ownLine()
|
|
<< " size=" << item->width << "x" << item->height
|
|
<< " ascent=" << item->ascent()
|
|
<< endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void KoTextParag::drawFontEffects( TQPainter * p, KoTextFormat *format, KoTextZoomHandler *zh, TQFont font, const TQColor & color, int startX, int baseLine, int bw, int lastY, int /*h*/, TQChar firstChar )
|
|
{
|
|
// This is about drawing underlines and strikeouts
|
|
// So abort immediately if there's none to draw.
|
|
if ( !format->isStrikedOrUnderlined() )
|
|
return;
|
|
//kdDebug(32500) << "drawFontEffects wordByWord=" << format->wordByWord() <<
|
|
// " firstChar='" << TQString(firstChar) << "'" << endl;
|
|
// paintLines ensures that we're called word by word if wordByWord is true.
|
|
if ( format->wordByWord() && firstChar.isSpace() )
|
|
return;
|
|
|
|
double dimd;
|
|
int y;
|
|
int offset = 0;
|
|
if (format->vAlign() == KoTextFormat::AlignSubScript )
|
|
offset = p->fontMetrics().height() / 6;
|
|
else if (format->vAlign() == KoTextFormat::AlignSuperScript )
|
|
offset = -p->fontMetrics().height() / 2;
|
|
|
|
dimd = KoBorder::zoomWidthY( format->underLineWidth(), zh, 1 );
|
|
if((format->vAlign() == KoTextFormat::AlignSuperScript) ||
|
|
(format->vAlign() == KoTextFormat::AlignSubScript ) || (format->vAlign() == KoTextFormat::AlignCustom ))
|
|
dimd*=format->relativeTextSize();
|
|
y = lastY + baseLine + offset - ( (format->vAlign() == KoTextFormat::AlignCustom)?format->offsetFromBaseLine():0 );
|
|
|
|
if ( format->doubleUnderline())
|
|
{
|
|
TQColor col = format->textUnderlineColor().isValid() ? format->textUnderlineColor(): color ;
|
|
int dim=static_cast<int>(0.75*dimd);
|
|
dim=dim?dim:1; //width of line should be at least 1
|
|
p->save();
|
|
|
|
switch( format->underlineStyle())
|
|
{
|
|
case KoTextFormat::U_SOLID:
|
|
p->setPen( TQPen( col, dim, TQt::SolidLine ) );
|
|
break;
|
|
case KoTextFormat::U_DASH:
|
|
p->setPen( TQPen( col, dim, TQt::DashLine ) );
|
|
break;
|
|
case KoTextFormat::U_DOT:
|
|
p->setPen( TQPen( col, dim, TQt::DotLine ) );
|
|
break;
|
|
case KoTextFormat::U_DASH_DOT:
|
|
p->setPen( TQPen( col, dim, TQt::DashDotLine ) );
|
|
break;
|
|
case KoTextFormat::U_DASH_DOT_DOT:
|
|
p->setPen( TQPen( col, dim, TQt::DashDotDotLine ) );
|
|
break;
|
|
default:
|
|
p->setPen( TQPen( color, dim, TQt::SolidLine ) );
|
|
}
|
|
|
|
y += static_cast<int>(1.125*dimd); // slightly under the baseline if possible
|
|
p->drawLine( startX, y, startX + bw, y );
|
|
y += static_cast<int>(1.5*dimd);
|
|
p->drawLine( startX, y, startX + bw, y );
|
|
p->restore();
|
|
if ( font.underline() ) { // can this happen?
|
|
font.setUnderline( FALSE );
|
|
p->setFont( font );
|
|
}
|
|
}
|
|
else if ( format->underline() ||
|
|
format->underlineType() == KoTextFormat::U_SIMPLE_BOLD)
|
|
{
|
|
|
|
TQColor col = format->textUnderlineColor().isValid() ? format->textUnderlineColor(): color ;
|
|
p->save();
|
|
int dim=(format->underlineType() == KoTextFormat::U_SIMPLE_BOLD)?static_cast<int>(2*dimd):static_cast<int>(dimd);
|
|
dim=dim?dim:1; //width of line should be at least 1
|
|
y += static_cast<int>(1.875*dimd);
|
|
|
|
switch( format->underlineStyle() )
|
|
{
|
|
case KoTextFormat::U_SOLID:
|
|
p->setPen( TQPen( col, dim, TQt::SolidLine ) );
|
|
break;
|
|
case KoTextFormat::U_DASH:
|
|
p->setPen( TQPen( col, dim, TQt::DashLine ) );
|
|
break;
|
|
case KoTextFormat::U_DOT:
|
|
p->setPen( TQPen( col, dim, TQt::DotLine ) );
|
|
break;
|
|
case KoTextFormat::U_DASH_DOT:
|
|
p->setPen( TQPen( col, dim, TQt::DashDotLine ) );
|
|
break;
|
|
case KoTextFormat::U_DASH_DOT_DOT:
|
|
p->setPen( TQPen( col, dim, TQt::DashDotDotLine ) );
|
|
break;
|
|
default:
|
|
p->setPen( TQPen( col, dim, TQt::SolidLine ) );
|
|
}
|
|
|
|
p->drawLine( startX, y, startX + bw, y );
|
|
p->restore();
|
|
font.setUnderline( FALSE );
|
|
p->setFont( font );
|
|
}
|
|
else if ( format->waveUnderline() )
|
|
{
|
|
int dim=static_cast<int>(dimd);
|
|
dim=dim?dim:1; //width of line should be at least 1
|
|
y += dim;
|
|
TQColor col = format->textUnderlineColor().isValid() ? format->textUnderlineColor(): color ;
|
|
p->save();
|
|
int offset = 2 * dim;
|
|
TQPen pen(col, dim, TQt::SolidLine);
|
|
pen.setCapStyle(Qt::RoundCap);
|
|
p->setPen(pen);
|
|
Q_ASSERT(offset);
|
|
double anc=acos(1.0-2*(static_cast<double>(offset-(startX)%offset)/static_cast<double>(offset)))/3.1415*180;
|
|
int pos=1;
|
|
//set starting position
|
|
if(2*((startX/offset)/2)==startX/offset)
|
|
pos*=-1;
|
|
//draw first part of wave
|
|
p->drawArc( (startX/offset)*offset, y, offset, offset, 0, -tqRound(pos*anc*16) );
|
|
//now the main part
|
|
int zigzag_x = (startX/offset+1)*offset;
|
|
for ( ; zigzag_x + offset <= bw+startX; zigzag_x += offset)
|
|
{
|
|
p->drawArc( zigzag_x, y, offset, offset, 0, pos*180*16 );
|
|
pos*=-1;
|
|
}
|
|
//and here we finish
|
|
anc=acos(1.0-2*(static_cast<double>((startX+bw)%offset)/static_cast<double>(offset)))/3.1415*180;
|
|
p->drawArc( zigzag_x, y, offset, offset, 180*16, -tqRound(pos*anc*16) );
|
|
p->restore();
|
|
font.setUnderline( FALSE );
|
|
p->setFont( font );
|
|
}
|
|
|
|
dimd = KoBorder::zoomWidthY( static_cast<double>(format->pointSize())/18.0, zh, 1 );
|
|
if((format->vAlign() == KoTextFormat::AlignSuperScript) ||
|
|
(format->vAlign() == KoTextFormat::AlignSubScript ) || (format->vAlign() == KoTextFormat::AlignCustom ))
|
|
dimd*=format->relativeTextSize();
|
|
y = lastY + baseLine + offset - ( (format->vAlign() == KoTextFormat::AlignCustom)?format->offsetFromBaseLine():0 );
|
|
|
|
if ( format->strikeOutType() == KoTextFormat::S_SIMPLE
|
|
|| format->strikeOutType() == KoTextFormat::S_SIMPLE_BOLD)
|
|
{
|
|
unsigned int dim = (format->strikeOutType() == KoTextFormat::S_SIMPLE_BOLD)? static_cast<int>(2*dimd) : static_cast<int>(dimd);
|
|
p->save();
|
|
|
|
switch( format->strikeOutStyle() )
|
|
{
|
|
case KoTextFormat::S_SOLID:
|
|
p->setPen( TQPen( color, dim, TQt::SolidLine ) );
|
|
break;
|
|
case KoTextFormat::S_DASH:
|
|
p->setPen( TQPen( color, dim, TQt::DashLine ) );
|
|
break;
|
|
case KoTextFormat::S_DOT:
|
|
p->setPen( TQPen( color, dim, TQt::DotLine ) );
|
|
break;
|
|
case KoTextFormat::S_DASH_DOT:
|
|
p->setPen( TQPen( color, dim, TQt::DashDotLine ) );
|
|
break;
|
|
case KoTextFormat::S_DASH_DOT_DOT:
|
|
p->setPen( TQPen( color, dim, TQt::DashDotDotLine ) );
|
|
break;
|
|
default:
|
|
p->setPen( TQPen( color, dim, TQt::SolidLine ) );
|
|
}
|
|
|
|
y -= static_cast<int>(5*dimd);
|
|
p->drawLine( startX, y, startX + bw, y );
|
|
p->restore();
|
|
font.setStrikeOut( FALSE );
|
|
p->setFont( font );
|
|
}
|
|
else if ( format->strikeOutType() == KoTextFormat::S_DOUBLE )
|
|
{
|
|
unsigned int dim = static_cast<int>(dimd);
|
|
p->save();
|
|
|
|
switch( format->strikeOutStyle() )
|
|
{
|
|
case KoTextFormat::S_SOLID:
|
|
p->setPen( TQPen( color, dim, TQt::SolidLine ) );
|
|
break;
|
|
case KoTextFormat::S_DASH:
|
|
p->setPen( TQPen( color, dim, TQt::DashLine ) );
|
|
break;
|
|
case KoTextFormat::S_DOT:
|
|
p->setPen( TQPen( color, dim, TQt::DotLine ) );
|
|
break;
|
|
case KoTextFormat::S_DASH_DOT:
|
|
p->setPen( TQPen( color, dim, TQt::DashDotLine ) );
|
|
break;
|
|
case KoTextFormat::S_DASH_DOT_DOT:
|
|
p->setPen( TQPen( color, dim, TQt::DashDotDotLine ) );
|
|
break;
|
|
default:
|
|
p->setPen( TQPen( color, dim, TQt::SolidLine ) );
|
|
}
|
|
|
|
y -= static_cast<int>(4*dimd);
|
|
p->drawLine( startX, y, startX + bw, y);
|
|
y -= static_cast<int>(2*dimd);
|
|
p->drawLine( startX, y, startX + bw, y);
|
|
p->restore();
|
|
font.setStrikeOut( FALSE );
|
|
p->setFont( font );
|
|
}
|
|
|
|
}
|
|
|
|
// ### is this method correct for RTL text?
|
|
TQString KoTextParag::toString( int from, int length ) const
|
|
{
|
|
TQString str;
|
|
if ( from == 0 && m_layout.counter && m_layout.counter->numbering() != KoParagCounter::NUM_FOOTNOTE )
|
|
str += m_layout.counter->text( this ) + ' ';
|
|
if ( length == -1 )
|
|
length = this->length() - 1 /*trailing space*/ - from;
|
|
for ( int i = from ; i < (length+from) ; ++i )
|
|
{
|
|
KoTextStringChar *ch = at( i );
|
|
if ( ch->isCustom() )
|
|
{
|
|
KoVariable * var = dynamic_cast<KoVariable *>(ch->customItem());
|
|
if ( var )
|
|
str += var->text(true);
|
|
else //frame inline
|
|
str +=' ';
|
|
}
|
|
else
|
|
str += ch->c;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
// we cannot use TQString::simplifyWhiteSpace() because it removes
|
|
// leading and trailing whitespace, but such whitespace is significant
|
|
// in ODF -- so we use this function to compress sequences of space characters
|
|
// into single spaces
|
|
static TQString normalizeWhitespace( const TQString& in, bool leadingSpace )
|
|
{
|
|
TQString text = in;
|
|
int r, w = 0;
|
|
int len = text.length();
|
|
|
|
for ( r = 0; r < len; ++r )
|
|
{
|
|
TQCharRef ch = text[r];
|
|
|
|
// check for space, tab, line feed, carriage return
|
|
if ( ch == ' ' || ch == '\t' ||ch == '\r' || ch == '\n')
|
|
{
|
|
// if we were lead by whitespace in some parent or previous sibling element,
|
|
// we completely collapse this space
|
|
if ( r != 0 || !leadingSpace )
|
|
text[w++] = TQChar( ' ' );
|
|
|
|
// find the end of the whitespace run
|
|
while ( r < len && text[r].isSpace() )
|
|
++r;
|
|
|
|
// and then record the next non-whitespace character
|
|
if ( r < len )
|
|
text[w++] = text[r];
|
|
}
|
|
else
|
|
{
|
|
text[w++] = ch;
|
|
}
|
|
}
|
|
|
|
// and now trim off the unused part of the string
|
|
text.truncate(w);
|
|
|
|
return text;
|
|
}
|
|
|
|
void KoTextParag::loadOasisSpan( const TQDomElement& parent, KoOasisContext& context, uint& pos, bool stripLeadingSpace )
|
|
{
|
|
bool dummy;
|
|
|
|
return loadOasisSpan( parent, context, pos, stripLeadingSpace, &dummy );
|
|
}
|
|
|
|
void KoTextParag::loadOasisSpan( const TQDomElement& parent, KoOasisContext& context, uint& pos, bool stripLeadingSpace, bool *hasTrailingSpace )
|
|
{
|
|
// Parse every child node of the parent
|
|
// Can't use forEachElement here since we also care about text nodes
|
|
TQDomNode node;
|
|
for ( node = parent.firstChild(); !node.isNull(); node = node.nextSibling() )
|
|
{
|
|
TQDomElement ts = node.toElement();
|
|
TQString textData;
|
|
const TQString localName( ts.localName() );
|
|
const bool isTextNS = ts.namespaceURI() == KoXmlNS::text;
|
|
KoTextCustomItem* customItem = 0;
|
|
|
|
// allow loadSpanTag to modify the stylestack
|
|
context.styleStack().save();
|
|
|
|
// Try to keep the order of the tag names by probability of happening
|
|
if ( node.isText() )
|
|
{
|
|
textData = normalizeWhitespace( node.toText().data(), stripLeadingSpace );
|
|
*hasTrailingSpace = stripLeadingSpace = textData[textData.length() - 1].isSpace();
|
|
}
|
|
else if ( isTextNS && localName == "span" ) // text:span
|
|
{
|
|
context.styleStack().save();
|
|
context.fillStyleStack( ts, KoXmlNS::text, "style-name", "text" );
|
|
|
|
// the ODF spec states that whitespace is compressed through tags: e.g.
|
|
// "Foo <text:span> Bar </text:span> Baz"
|
|
// should only have one space between each of "Foo", "Bar", and "Baz"
|
|
// so we need to keep track of whether there was any trailing whitespace
|
|
// in sub-spans so that we can propogate the whitespace compression state
|
|
// back up to the parent element
|
|
loadOasisSpan( ts, context, pos, stripLeadingSpace, hasTrailingSpace ); // recurse
|
|
stripLeadingSpace = *hasTrailingSpace;
|
|
context.styleStack().restore();
|
|
}
|
|
else if ( isTextNS && localName == "s" ) // text:s
|
|
{
|
|
int howmany = 1;
|
|
if (ts.hasAttributeNS( KoXmlNS::text, "c"))
|
|
howmany = ts.attributeNS( KoXmlNS::text, "c", TQString()).toInt();
|
|
|
|
textData.fill(32, howmany);
|
|
}
|
|
else if ( isTextNS && localName == "tab" ) // text:tab (it's tab-stop in OO-1.1 but tab in oasis)
|
|
{
|
|
textData = '\t';
|
|
}
|
|
else if ( isTextNS && localName == "line-break" ) // text:line-break
|
|
{
|
|
textData = '\n';
|
|
}
|
|
else if ( isTextNS && localName == "number" ) // text:number
|
|
{
|
|
// This is the number in front of a numbered paragraph,
|
|
// written out to help export filters. We can ignore it.
|
|
}
|
|
else if ( node.isProcessingInstruction() )
|
|
{
|
|
TQDomProcessingInstruction pi = node.toProcessingInstruction();
|
|
if ( pi.target() == "opendocument" && pi.data().startsWith( "cursor-position" ) )
|
|
{
|
|
context.setCursorPosition( this, pos );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool handled = false;
|
|
// Check if it's a variable
|
|
KoVariable* var = context.variableCollection().loadOasisField( textDocument(), ts, context );
|
|
if ( var )
|
|
{
|
|
textData = "#"; // field placeholder
|
|
customItem = var;
|
|
handled = true;
|
|
}
|
|
if ( !handled )
|
|
{
|
|
handled = textDocument()->loadSpanTag( ts, context,
|
|
this, pos,
|
|
textData, customItem );
|
|
if ( !handled )
|
|
{
|
|
kdWarning(32500) << "Ignoring tag " << ts.tagName() << endl;
|
|
context.styleStack().restore();
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
const uint length = textData.length();
|
|
if ( length )
|
|
{
|
|
insert( pos, textData );
|
|
if ( customItem )
|
|
setCustomItem( pos, customItem, 0 );
|
|
KoTextFormat f;
|
|
f.load( context );
|
|
//kdDebug(32500) << "loadOasisSpan: applying formatting from " << pos << " to " << pos+length << "\n format=" << f.key() << endl;
|
|
setFormat( pos, length, document()->formatCollection()->format( &f ), TRUE );
|
|
pos += length;
|
|
}
|
|
context.styleStack().restore();
|
|
}
|
|
}
|
|
|
|
KoParagLayout KoTextParag::loadParagLayout( KoOasisContext& context, KoStyleCollection *styleCollection, bool findStyle )
|
|
{
|
|
KoParagLayout tqlayout;
|
|
|
|
// Only when loading paragraphs, not when loading styles
|
|
if ( findStyle )
|
|
{
|
|
KoParagStyle *style;
|
|
// Name of the style. If there is no style, then we do not supply
|
|
// any default!
|
|
TQString styleName = context.styleStack().userStyleName( "paragraph" );
|
|
if ( !styleName.isEmpty() )
|
|
{
|
|
style = styleCollection->findStyle( styleName );
|
|
// When pasting the style names are random, the display names matter
|
|
if (!style)
|
|
style = styleCollection->findStyleByDisplayName( context.styleStack().userStyleDisplayName( "paragraph" ) );
|
|
if (!style)
|
|
{
|
|
kdError(32500) << "Cannot find style \"" << styleName << "\" - using Standard" << endl;
|
|
style = styleCollection->findStyle( "Standard" );
|
|
}
|
|
//else kdDebug() << "KoParagLayout::KoParagLayout setting style to " << style << " " << style->name() << endl;
|
|
}
|
|
else
|
|
{
|
|
kdError(32500) << "No style name !? - using Standard" << endl;
|
|
style = styleCollection->findStyle( "Standard" );
|
|
}
|
|
Q_ASSERT(style);
|
|
tqlayout.style = style;
|
|
}
|
|
|
|
KoParagLayout::loadOasisParagLayout( tqlayout, context );
|
|
|
|
return tqlayout;
|
|
}
|
|
|
|
void KoTextParag::loadOasis( const TQDomElement& parent, KoOasisContext& context, KoStyleCollection *styleCollection, uint& pos )
|
|
{
|
|
// First load tqlayout from style
|
|
KoParagLayout paragLayout = loadParagLayout( context, styleCollection, true );
|
|
setParagLayout( paragLayout );
|
|
|
|
// Load paragraph format
|
|
KoTextFormat defaultFormat;
|
|
defaultFormat.load( context );
|
|
setFormat( document()->formatCollection()->format( &defaultFormat ) );
|
|
|
|
// Load text
|
|
// OO.o compatibility: ignore leading whitespace in <p> and <h> elements
|
|
loadOasisSpan( parent, context, pos, true );
|
|
|
|
// Apply default format to trailing space
|
|
const int len = str->length();
|
|
Q_ASSERT( len >= 1 );
|
|
setFormat( len - 1, 1, paragFormat(), TRUE );
|
|
|
|
setChanged( true );
|
|
tqinvalidate( 0 );
|
|
}
|
|
|
|
void KoTextParag::saveOasis( KoXmlWriter& writer, KoSavingContext& context,
|
|
int from /* default 0 */, int to /* usually length()-2 */,
|
|
bool /*saveAnchorsFramesets*/ /* default false */ ) const
|
|
{
|
|
KoGenStyles& mainStyles = context.mainStyles();
|
|
|
|
// Write paragtqlayout to styles (with parent == the parag's style)
|
|
TQString parentStyleName;
|
|
if ( m_layout.style )
|
|
parentStyleName = m_layout.style->name();
|
|
|
|
KoGenStyle autoStyle( KoGenStyle::STYLE_AUTO, "paragraph", parentStyleName );
|
|
paragFormat()->save( autoStyle, context );
|
|
m_layout.saveOasis( autoStyle, context, false );
|
|
|
|
// First paragraph is special, it includes page-tqlayout info (for word-processing at least)
|
|
if ( !prev() ) {
|
|
if ( context.variableSettings() )
|
|
autoStyle.addProperty( "style:page-number", context.variableSettings()->startingPageNumber() );
|
|
// Well we support only one page tqlayout, so the first parag always points to "Standard".
|
|
autoStyle.addAttribute( "style:master-page-name", "Standard" );
|
|
}
|
|
|
|
|
|
TQString autoParagStyleName = mainStyles.lookup( autoStyle, "P", KoGenStyles::ForceNumbering );
|
|
|
|
KoParagCounter* paragCounter = m_layout.counter;
|
|
// outline (text:h) assumes paragCounter != 0 (because depth is mandatory)
|
|
bool outline = m_layout.style && m_layout.style->isOutline() && paragCounter;
|
|
bool normalList = paragCounter && paragCounter->style() != KoParagCounter::STYLE_NONE && !outline;
|
|
if ( normalList ) // non-heading list
|
|
{
|
|
writer.startElement( "text:numbered-paragraph" );
|
|
writer.addAttribute( "text:level", (int)paragCounter->depth() + 1 );
|
|
if ( paragCounter->restartCounter() )
|
|
writer.addAttribute( "text:start-value", paragCounter->startNumber() );
|
|
|
|
KoGenStyle listStyle( KoGenStyle::STYLE_AUTO_LIST /*, no family*/ );
|
|
paragCounter->saveOasis( listStyle );
|
|
|
|
TQString autoListStyleName = mainStyles.lookup( listStyle, "L", KoGenStyles::ForceNumbering );
|
|
writer.addAttribute( "text:style-name", autoListStyleName );
|
|
|
|
TQString textNumber = m_layout.counter->text( this );
|
|
if ( !textNumber.isEmpty() )
|
|
{
|
|
// This is to help export filters
|
|
writer.startElement( "text:number" );
|
|
writer.addTextNode( textNumber );
|
|
writer.endElement();
|
|
}
|
|
}
|
|
else if ( outline ) // heading
|
|
{
|
|
writer.startElement( "text:h", false /*no indent inside this tag*/ );
|
|
writer.addAttribute( "text:style-name", autoParagStyleName );
|
|
writer.addAttribute( "text:outline-level", (int)paragCounter->depth() + 1 );
|
|
if ( paragCounter->numbering() == KoParagCounter::NUM_NONE )
|
|
writer.addAttribute( "text:is-list-header", "true" );
|
|
|
|
TQString textNumber = paragCounter->text( this );
|
|
if ( !textNumber.isEmpty() )
|
|
{
|
|
// This is to help export filters
|
|
writer.startElement( "text:number" );
|
|
writer.addTextNode( textNumber );
|
|
writer.endElement();
|
|
}
|
|
}
|
|
|
|
if ( !outline ) // normal (non-numbered) paragraph, or normalList
|
|
{
|
|
writer.startElement( "text:p", false /*no indent inside this tag*/ );
|
|
writer.addAttribute( "text:style-name", autoParagStyleName );
|
|
}
|
|
|
|
TQString text = str->toString();
|
|
Q_ASSERT( text.right(1)[0] == ' ' );
|
|
|
|
const int cursorIndex = context.cursorTextParagraph() == this ? context.cursorTextIndex() : -1;
|
|
|
|
//kdDebug() << k_funcinfo << "'" << text << "' from=" << from << " to=" << to << " cursorIndex=" << cursorIndex << endl;
|
|
|
|
// A helper method would need no less than 7 params...
|
|
#define WRITESPAN( next ) { \
|
|
if ( curFormat == paragFormat() ) { \
|
|
writer.addTextSpan( text.mid( startPos, next - startPos ), m_tabCache ); \
|
|
} else { \
|
|
KoGenStyle gs( KoGenStyle::STYLE_AUTO, "text" ); \
|
|
curFormat->save( gs, context, paragFormat() ); \
|
|
writer.startElement( "text:span" ); \
|
|
if ( !gs.isEmpty() ) { \
|
|
const TQString autoStyleName = mainStyles.lookup( gs, "T" ); \
|
|
writer.addAttribute( "text:style-name", autoStyleName ); \
|
|
} \
|
|
writer.addTextSpan( text.mid( startPos, next - startPos ), m_tabCache ); \
|
|
writer.endElement(); \
|
|
} \
|
|
}
|
|
#define ISSTARTBOOKMARK( i ) bkStartIter != bookmarkStarts.end() && (*bkStartIter).pos == i
|
|
#define ISENDBOOKMARK( i ) bkEndIter != bookmarkEnds.end() && (*bkEndIter).pos == i
|
|
#define CHECKPOS( i ) \
|
|
if ( cursorIndex == i ) { \
|
|
writer.addProcessingInstruction( "opendocument cursor-position" ); \
|
|
} \
|
|
if ( ISSTARTBOOKMARK( i ) ) { \
|
|
if ( (*bkStartIter).startEqualsEnd ) \
|
|
writer.startElement( "text:bookmark" ); \
|
|
else \
|
|
writer.startElement( "text:bookmark-start" ); \
|
|
writer.addAttribute( "text:name", (*bkStartIter).name ); \
|
|
writer.endElement(); \
|
|
++bkStartIter; \
|
|
} \
|
|
if ( ISENDBOOKMARK( i ) ) { \
|
|
writer.startElement( "text:bookmark-end" ); \
|
|
writer.addAttribute( "text:name", (*bkEndIter).name ); \
|
|
writer.endElement(); \
|
|
++bkEndIter; \
|
|
}
|
|
|
|
|
|
|
|
// Make (shallow) copy of bookmark list, since saving an inline frame might overwrite it
|
|
// from the context while we're saving this paragraph.
|
|
typedef KoSavingContext::BookmarkPositions BookmarkPositions;
|
|
BookmarkPositions bookmarkStarts = context.bookmarkStarts();
|
|
BookmarkPositions::const_iterator bkStartIter = bookmarkStarts.begin();
|
|
while ( bkStartIter != bookmarkStarts.end() && (*bkStartIter).pos < from )
|
|
++bkStartIter;
|
|
//int nextBookmarkStart = bkStartIter == bookmarkStarts.end() ? -1 : (*bkStartIter).pos;
|
|
BookmarkPositions bookmarkEnds = context.bookmarkEnds();
|
|
BookmarkPositions::const_iterator bkEndIter = bookmarkEnds.begin();
|
|
while ( bkEndIter != bookmarkEnds.end() && (*bkEndIter).pos < from )
|
|
++bkEndIter;
|
|
|
|
KoTextFormat *curFormat = 0;
|
|
KoTextFormat *lastFormatRaw = 0; // this is for speeding up "removing misspelled" from each char
|
|
KoTextFormat *lastFormatFixed = 0; // raw = as stored in the chars; fixed = after removing misspelled
|
|
int startPos = from;
|
|
for ( int i = from; i <= to; ++i ) {
|
|
KoTextStringChar & ch = str->at(i);
|
|
KoTextFormat * newFormat = static_cast<KoTextFormat *>( ch.format() );
|
|
if ( newFormat->isMisspelled() ) {
|
|
if ( newFormat == lastFormatRaw )
|
|
newFormat = lastFormatFixed; // the fast way
|
|
else
|
|
{
|
|
lastFormatRaw = newFormat;
|
|
// Remove isMisspelled from format, to avoid useless derived styles
|
|
// (which would be indentical to their parent style)
|
|
KoTextFormat tmpFormat( *newFormat );
|
|
tmpFormat.setMisspelled( false );
|
|
newFormat = formatCollection()->format( &tmpFormat );
|
|
lastFormatFixed = newFormat;
|
|
}
|
|
}
|
|
if ( !curFormat )
|
|
curFormat = newFormat;
|
|
if ( newFormat != curFormat // Format changed, save previous one.
|
|
|| ch.isCustom() || cursorIndex == i || ISSTARTBOOKMARK( i ) || ISENDBOOKMARK( i ) )
|
|
{
|
|
WRITESPAN( i ) // write text up to i-1
|
|
startPos = i;
|
|
curFormat = newFormat;
|
|
}
|
|
CHECKPOS( i ) // do cursor position and bookmarks
|
|
if ( ch.isCustom() ) {
|
|
KoGenStyle gs( KoGenStyle::STYLE_AUTO, "text" );
|
|
curFormat->save( gs, context, paragFormat() );
|
|
writer.startElement( "text:span" );
|
|
if ( !gs.isEmpty() ) {
|
|
const TQString autoStyleName = mainStyles.lookup( gs, "T" );
|
|
writer.addAttribute( "text:style-name", autoStyleName );
|
|
}
|
|
KoTextCustomItem* customItem = ch.customItem();
|
|
customItem->saveOasis( writer, context );
|
|
writer.endElement();
|
|
startPos = i + 1;
|
|
}
|
|
}
|
|
|
|
//kdDebug() << k_funcinfo << "startPos=" << startPos << " to=" << to << " curFormat=" << curFormat << endl;
|
|
|
|
if ( to >= startPos ) { // Save last format
|
|
WRITESPAN( to + 1 )
|
|
}
|
|
CHECKPOS( to + 1 ) // do cursor position and bookmarks
|
|
|
|
writer.endElement(); // text:p or text:h
|
|
if ( normalList )
|
|
writer.endElement(); // text:numbered-paragraph (englobing a text:p)
|
|
}
|
|
|
|
void KoTextParag::applyListStyle( KoOasisContext& context, int restartNumbering, bool orderedList, bool heading, int level )
|
|
{
|
|
//kdDebug(32500) << k_funcinfo << "applyListStyle to parag " << this << " heading=" << heading << endl;
|
|
delete m_layout.counter;
|
|
m_layout.counter = new KoParagCounter;
|
|
m_layout.counter->loadOasis( context, restartNumbering, orderedList, heading, level );
|
|
// We emulate space-before with a left paragraph indent (#109223)
|
|
const TQDomElement listStyleProperties = context.listStyleStack().currentListStyleProperties();
|
|
if ( listStyleProperties.hasAttributeNS( KoXmlNS::text, "space-before" ) )
|
|
{
|
|
double spaceBefore = KoUnit::parseValue( listStyleProperties.attributeNS( KoXmlNS::text, "space-before", TQString() ) );
|
|
m_layout.margins[ TQStyleSheetItem::MarginLeft ] += spaceBefore; // added to left-margin, see 15.12 in spec.
|
|
}
|
|
// need to call invalidateCounters() ? Not during the initial loading at least.
|
|
}
|
|
|
|
int KoTextParag::documentWidth() const
|
|
{
|
|
return doc ? doc->width() : 0; //docRect.width();
|
|
}
|
|
|
|
//int KoTextParag::documentVisibleWidth() const
|
|
//{
|
|
// return doc ? doc->visibleWidth() : 0; //docRect.width();
|
|
//}
|
|
|
|
int KoTextParag::documentX() const
|
|
{
|
|
return doc ? doc->x() : 0; //docRect.x();
|
|
}
|
|
|
|
int KoTextParag::documentY() const
|
|
{
|
|
return doc ? doc->y() : 0; //docRect.y();
|
|
}
|
|
|
|
void KoTextParag::fixParagWidth( bool viewFormattingChars )
|
|
{
|
|
// Fixing the parag rect for the formatting chars (only CR here, KWord handles framebreak).
|
|
if ( viewFormattingChars && lineStartList().count() == 1 ) // don't use lines() here, parag not formatted yet
|
|
{
|
|
KoTextFormat * lastFormat = at( length() - 1 )->format();
|
|
setWidth( TQMIN( rect().width() + lastFormat->width('x'), doc->width() ) );
|
|
}
|
|
// Warning, if adding anything else here, adjust KWTextFrameSet::fixParagWidth
|
|
}
|
|
|
|
// Called by KoTextParag::drawParagString - all params are in pixel coordinates
|
|
void KoTextParag::drawFormattingChars( TQPainter &painter, int start, int len,
|
|
int lastY_pix, int baseLine_pix, int h_pix, // in pixels
|
|
bool /*drawSelections*/,
|
|
KoTextFormat * /*lastFormat*/, const TQMemArray<int> &/*selectionStarts*/,
|
|
const TQMemArray<int> &/*selectionEnds*/, const TQColorGroup & /*cg*/,
|
|
bool rightToLeft, int /*line*/, KoTextZoomHandler* zh,
|
|
int whichFormattingChars )
|
|
{
|
|
if ( !whichFormattingChars )
|
|
return;
|
|
painter.save();
|
|
//TQPen pen( cg.color( TQColorGroup::Highlight ) );
|
|
TQPen pen( KGlobalSettings::linkColor() ); // #101820
|
|
painter.setPen( pen );
|
|
//kdDebug() << "KWTextParag::drawFormattingChars start=" << start << " len=" << len << " length=" << length() << endl;
|
|
if ( start + len == length() && ( whichFormattingChars & FormattingEndParag ) )
|
|
{
|
|
// drawing the end of the parag
|
|
KoTextStringChar &ch = str->at( length() - 1 );
|
|
KoTextFormat* format = static_cast<KoTextFormat *>( ch.format() );
|
|
int w = format->charWidth( zh, true, &ch, this, 'X' );
|
|
int size = TQMIN( w, h_pix * 3 / 4 );
|
|
// x,y is the bottom right corner of the
|
|
//kdDebug() << "startX=" << startX << " bw=" << bw << " w=" << w << endl;
|
|
int x;
|
|
if ( rightToLeft )
|
|
x = zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ + ch.pixelwidth - 1;
|
|
else
|
|
x = zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ + w;
|
|
int y = lastY_pix + baseLine_pix;
|
|
//kdDebug() << "KWTextParag::drawFormattingChars drawing CR at " << x << "," << y << endl;
|
|
painter.drawLine( (int)(x - size * 0.2), y - size, (int)(x - size * 0.2), y );
|
|
painter.drawLine( (int)(x - size * 0.5), y - size, (int)(x - size * 0.5), y );
|
|
painter.drawLine( x, y, (int)(x - size * 0.7), y );
|
|
painter.drawLine( x, y - size, (int)(x - size * 0.5), y - size);
|
|
painter.drawArc( x - size, y - size, size, (int)(size / 2), -90*16, -180*16 );
|
|
#ifdef DEBUG_FORMATTING
|
|
painter.setPen( TQt::blue );
|
|
painter.drawRect( zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ - 1, lastY_pix, ch.pixelwidth, baseLine_pix );
|
|
TQPen pen( cg.color( TQColorGroup::Highlight ) );
|
|
painter.setPen( pen );
|
|
#endif
|
|
}
|
|
|
|
// Now draw spaces, tabs and newlines
|
|
if ( (whichFormattingChars & FormattingSpace) ||
|
|
(whichFormattingChars & FormattingTabs) ||
|
|
(whichFormattingChars & FormattingBreak) )
|
|
{
|
|
int end = TQMIN( start + len, length() - 1 ); // don't look at the trailing space
|
|
for ( int i = start ; i < end ; ++i )
|
|
{
|
|
KoTextStringChar &ch = str->at(i);
|
|
#ifdef DEBUG_FORMATTING
|
|
painter.setPen( (i % 2)? TQt::red: TQt::green );
|
|
painter.drawRect( zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ - 1, lastY_pix, ch.pixelwidth, baseLine_pix );
|
|
TQPen pen( cg.color( TQColorGroup::Highlight ) );
|
|
painter.setPen( pen );
|
|
#endif
|
|
if ( ch.isCustom() )
|
|
continue;
|
|
if ( (ch.c == ' ' || ch.c.tqunicode() == 0x00a0U)
|
|
&& (whichFormattingChars & FormattingSpace))
|
|
{
|
|
// Don't use ch.pixelwidth here. We want a square with
|
|
// the same size for all spaces, even the justified ones
|
|
int w = zh->layoutUnitToPixelX( ch.format()->width( ' ' ) );
|
|
int height = zh->layoutUnitToPixelY( ch.ascent() );
|
|
int size = TQMAX( 2, TQMIN( w/2, height/3 ) ); // Enfore that it's a square, and that it's visible
|
|
int x = zh->layoutUnitToPixelX( ch.x ); // + ch.pixelxadj;
|
|
TQRect spcRect( x + (ch.pixelwidth - size) / 2, lastY_pix + baseLine_pix - (height - size) / 2, size, size );
|
|
if ( ch.c == ' ' )
|
|
painter.drawRect( spcRect );
|
|
else // nbsp
|
|
painter.fillRect( spcRect, pen.color() );
|
|
}
|
|
else if ( ch.c == '\t' && (whichFormattingChars & FormattingTabs) )
|
|
{
|
|
/*KoTextStringChar &nextch = str->at(i+1);
|
|
int nextx = (nextch.x > ch.x) ? nextch.x : rect().width();
|
|
//kdDebug() << "tab x=" << ch.x << " nextch.x=" << nextch.x
|
|
// << " nextx=" << nextx << " startX=" << startX << " bw=" << bw << endl;
|
|
int availWidth = nextx - ch.x - 1;
|
|
availWidth=zh->layoutUnitToPixelX(availWidth);*/
|
|
|
|
int availWidth = ch.pixelwidth;
|
|
|
|
KoTextFormat* format = ch.format();
|
|
int x = zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ + availWidth / 2;
|
|
int charWidth = format->screenFontMetrics( zh ).width( 'W' );
|
|
int size = TQMIN( availWidth, charWidth ) / 2 ; // actually the half size
|
|
int y = lastY_pix + baseLine_pix - zh->layoutUnitToPixelY( ch.ascent()/2 );
|
|
int arrowsize = zh->zoomItY( 2 );
|
|
painter.drawLine( x - size, y, x + size, y );
|
|
if ( rightToLeft )
|
|
{
|
|
painter.drawLine( x - size, y, x - size + arrowsize, y - arrowsize );
|
|
painter.drawLine( x - size, y, x - size + arrowsize, y + arrowsize );
|
|
}
|
|
else
|
|
{
|
|
painter.drawLine( x + size, y, x + size - arrowsize, y - arrowsize );
|
|
painter.drawLine( x + size, y, x + size - arrowsize, y + arrowsize );
|
|
}
|
|
}
|
|
else if ( ch.c == '\n' && (whichFormattingChars & FormattingBreak) )
|
|
{
|
|
// draw line break
|
|
KoTextFormat* format = static_cast<KoTextFormat *>( ch.format() );
|
|
int w = format->charWidth( zh, true, &ch, this, 'X' );
|
|
int size = TQMIN( w, h_pix * 3 / 4 );
|
|
int arrowsize = zh->zoomItY( 2 );
|
|
// x,y is the bottom right corner of the reversed L
|
|
//kdDebug() << "startX=" << startX << " bw=" << bw << " w=" << w << endl;
|
|
int y = lastY_pix + baseLine_pix - arrowsize;
|
|
//kdDebug() << "KWTextParag::drawFormattingChars drawing Line Break at " << x << "," << y << endl;
|
|
if ( rightToLeft )
|
|
{
|
|
int x = zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ + ch.pixelwidth - 1;
|
|
painter.drawLine( x - size, y - size, x - size, y );
|
|
painter.drawLine( x - size, y, (int)(x - size * 0.3), y );
|
|
// Now the arrow
|
|
painter.drawLine( (int)(x - size * 0.3), y, (int)(x - size * 0.3 - arrowsize), y - arrowsize );
|
|
painter.drawLine( (int)(x - size * 0.3), y, (int)(x - size * 0.3 - arrowsize), y + arrowsize );
|
|
}
|
|
else
|
|
{
|
|
int x = zh->layoutUnitToPixelX( ch.x ) /*+ ch.pixelxadj*/ + w - 1;
|
|
painter.drawLine( x, y - size, x, y );
|
|
painter.drawLine( x, y, (int)(x - size * 0.7), y );
|
|
// Now the arrow
|
|
painter.drawLine( (int)(x - size * 0.7), y, (int)(x - size * 0.7 + arrowsize), y - arrowsize );
|
|
painter.drawLine( (int)(x - size * 0.7), y, (int)(x - size * 0.7 + arrowsize), y + arrowsize );
|
|
}
|
|
}
|
|
}
|
|
painter.restore();
|
|
}
|
|
}
|
|
|
|
int KoTextParag::heightForLineSpacing( int startChar, int lastChar ) const
|
|
{
|
|
int h = 0;
|
|
int end = TQMIN( lastChar, length() - 1 ); // don't look at the trailing space
|
|
for( int i = startChar; i <= end; ++i )
|
|
{
|
|
const KoTextStringChar &chr = str->at( i );
|
|
if ( !chr.isCustom() )
|
|
h = TQMAX( h, chr.format()->height() );
|
|
}
|
|
return h;
|
|
}
|