diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
commit | 8362bf63dea22bbf6736609b0f49c152f975eb63 (patch) | |
tree | 0eea3928e39e50fae91d4e68b21b1e6cbae25604 /lib/kotext/KoParagCounter.cpp | |
download | koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.tar.gz koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.zip |
Added old abandoned KDE3 version of koffice
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/koffice@1077364 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'lib/kotext/KoParagCounter.cpp')
-rw-r--r-- | lib/kotext/KoParagCounter.cpp | 987 |
1 files changed, 987 insertions, 0 deletions
diff --git a/lib/kotext/KoParagCounter.cpp b/lib/kotext/KoParagCounter.cpp new file mode 100644 index 00000000..e163f757 --- /dev/null +++ b/lib/kotext/KoParagCounter.cpp @@ -0,0 +1,987 @@ +/* This file is part of the KDE project + Copyright (C) 2001 Shaheed Haque <srhaque@iee.org> + + 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 "KoParagCounter.h" +#include "KoTextParag.h" +#include "KoTextZoomHandler.h" +#include "KoTextFormat.h" +#include "KoTextDocument.h" +#include "KoOasisContext.h" +#include <KoXmlWriter.h> +#include <KoGenStyles.h> +#include <KoXmlNS.h> +#include <kdebug.h> +#include <qdom.h> +#include <qbuffer.h> + +static KoTextParag * const INVALID_PARAG = (KoTextParag *)-1; + +KoParagCounter::KoParagCounter() +{ + m_numbering = NUM_NONE; + m_style = STYLE_NONE; + m_depth = 0; + m_startNumber = 1; + m_displayLevels = 1; + m_restartCounter = false; + m_customBulletChar = QChar( '-' ); + m_customBulletFont = QString::null; + m_align = Qt::AlignAuto; + invalidate(); +} + +bool KoParagCounter::operator==( const KoParagCounter & c2 ) const +{ + // ## This is kinda wrong. Unused fields (depending on the counter style) shouldn't be compared. + return (m_numbering==c2.m_numbering && + m_style==c2.m_style && + m_depth==c2.m_depth && + m_startNumber==c2.m_startNumber && + m_displayLevels==c2.m_displayLevels && + m_restartCounter==c2.m_restartCounter && + m_prefix==c2.m_prefix && + m_suffix==c2.m_suffix && + m_customBulletChar==c2.m_customBulletChar && + m_customBulletFont==c2.m_customBulletFont && + m_align==c2.m_align && + m_custom==c2.m_custom); +} + +QString KoParagCounter::custom() const +{ + return m_custom; +} + +QChar KoParagCounter::customBulletCharacter() const +{ + return m_customBulletChar; +} + +QString KoParagCounter::customBulletFont() const +{ + return m_customBulletFont; +} + +unsigned int KoParagCounter::depth() const +{ + return m_depth; +} + +void KoParagCounter::invalidate() +{ + m_cache.number = -1; + m_cache.text = QString::null; + m_cache.width = -1; + m_cache.parent = INVALID_PARAG; + m_cache.counterFormat = 0; +} + +bool KoParagCounter::isBullet( Style style ) // static +{ + switch ( style ) + { + case STYLE_DISCBULLET: + case STYLE_SQUAREBULLET: + case STYLE_BOXBULLET: + case STYLE_CIRCLEBULLET: + case STYLE_CUSTOMBULLET: + return true; + default: + return false; + } +} + +bool KoParagCounter::isBullet() const +{ + return isBullet( m_style ); +} + +void KoParagCounter::load( QDomElement & element ) +{ + m_numbering = static_cast<Numbering>( element.attribute("numberingtype", "2").toInt() ); + m_style = static_cast<Style>( element.attribute("type").toInt() ); + // Old docs have this: + if ( m_numbering == NUM_LIST && m_style == STYLE_NONE ) + m_numbering = NUM_NONE; + m_depth = element.attribute("depth").toInt(); + m_customBulletChar = QChar( element.attribute("bullet").toInt() ); + m_prefix = element.attribute("lefttext"); + if ( m_prefix.lower() == "(null)" ) // very old kword thing + m_prefix = QString::null; + m_suffix = element.attribute("righttext"); + if ( m_suffix.lower() == "(null)" ) + m_suffix = QString::null; + QString s = element.attribute("start"); + if ( s.isEmpty() ) + m_startNumber = 1; + else if ( s[0].isDigit() ) + m_startNumber = s.toInt(); + else // support for very-old files + m_startNumber = s.lower()[0].latin1() - 'a' + 1; + s = element.attribute("display-levels"); + if ( !s.isEmpty() ) + m_displayLevels = QMIN( s.toInt(), m_depth+1 ); // can't be > depth+1 + else // Not specified -> compat with koffice-1.2: make equal to depth+1 + m_displayLevels = m_depth+1; + m_customBulletFont = element.attribute("bulletfont"); + m_custom = element.attribute("customdef"); + m_align = element.attribute("align", "0").toInt(); //AlignAuto as defeult + QString restart = element.attribute("restart"); + m_restartCounter = (restart == "true") || (restart == "1"); + invalidate(); +} + +static int importCounterType( QChar numFormat ) +{ + if ( numFormat == '1' ) + return KoParagCounter::STYLE_NUM; + if ( numFormat == 'a' ) + return KoParagCounter::STYLE_ALPHAB_L; + if ( numFormat == 'A' ) + return KoParagCounter::STYLE_ALPHAB_U; + if ( numFormat == 'i' ) + return KoParagCounter::STYLE_ROM_NUM_L; + if ( numFormat == 'I' ) + return KoParagCounter::STYLE_ROM_NUM_U; + return KoParagCounter::STYLE_NONE; +} + +// should only be called with style != none and != a bullet. +static QChar exportCounterType( KoParagCounter::Style style ) +{ + static const int s_oasisCounterTypes[] = + { '\0', '1', 'a', 'A', 'i', 'I', + '\0', '\0', // custombullet, custom + 0x2022, // circle -> small disc + 0xE00A, // square + 0x25CF, // disc -> large disc + 0x27A2 // box -> right-pointing triangle + }; + return QChar( s_oasisCounterTypes[ style ] ); +} + +void KoParagCounter::loadOasis( KoOasisContext& context, int restartNumbering, + bool orderedList, bool heading, int level, bool loadingStyle ) +{ + const QDomElement listStyle = context.listStyleStack().currentListStyle(); + const QDomElement listStyleProperties = context.listStyleStack().currentListStyleProperties(); + const QDomElement listStyleTextProperties = context.listStyleStack().currentListStyleTextProperties(); + loadOasisListStyle( listStyle, listStyleProperties, listStyleTextProperties, + restartNumbering, orderedList, heading, level, loadingStyle ); +} + +void KoParagCounter::loadOasisListStyle( const QDomElement& listStyle, + const QDomElement& listStyleProperties, + const QDomElement& listStyleTextProperties, + int restartNumbering, + bool orderedList, bool heading, int level, + bool loadingStyle ) +{ + m_numbering = heading ? NUM_CHAPTER : NUM_LIST; + m_depth = level - 1; // depth start at 0 + // restartNumbering can either be provided by caller, or taken from the style + if ( restartNumbering == -1 && listStyle.hasAttributeNS( KoXmlNS::text, "start-value" ) ) + restartNumbering = listStyle.attributeNS( KoXmlNS::text, "start-value", QString::null ).toInt(); + + // styles have a start-value, but that doesn't mean restartNumbering, as it does for paragraphs + m_restartCounter = loadingStyle ? false : ( restartNumbering != -1 ); + m_startNumber = ( restartNumbering != -1 ) ? restartNumbering : 1; + //kdDebug() << k_funcinfo << "IN: restartNumbering=" << restartNumbering << " OUT: m_restartCounter=" << m_restartCounter << " m_startNumber=" << m_startNumber << endl; + + m_prefix = listStyle.attributeNS( KoXmlNS::style, "num-prefix", QString::null ); + m_suffix = listStyle.attributeNS( KoXmlNS::style, "num-suffix", QString::null ); + + if ( orderedList || heading ) { + m_style = static_cast<Style>( importCounterType( listStyle.attributeNS( KoXmlNS::style, "num-format", QString::null)[0] ) ); + QString dl = listStyle.attributeNS( KoXmlNS::text, "display-levels", QString::null ); + m_displayLevels = dl.isEmpty() ? 1 : dl.toInt(); + } else { // bullets, see 3.3.6 p138 + m_style = STYLE_CUSTOMBULLET; + QString bulletChar = listStyle.attributeNS( KoXmlNS::text, "bullet-char", QString::null ); + if ( !bulletChar.isEmpty() ) { + // Reverse engineering, I found those codes: + switch( bulletChar[0].unicode() ) { + case 0x2022: // small disc -> circle + m_style = STYLE_CIRCLEBULLET; + break; + case 0x25CF: // large disc -> disc + case 0xF0B7: // #113361 + m_style = STYLE_DISCBULLET; + break; + case 0xE00C: // losange - TODO in kotext. Not in OASIS either (reserved Unicode area!) + m_style = STYLE_BOXBULLET; + break; + case 0xE00A: // square. Not in OASIS (reserved Unicode area!), but used in both OOo and kotext. + m_style = STYLE_SQUAREBULLET; + break; + case 0x27A2: // two-colors right-pointing triangle + // mapping (both ways) to box for now. + m_style = STYLE_BOXBULLET; + break; + default: + kdDebug() << "Unhandled bullet code 0x" << QString::number( (uint)m_customBulletChar.unicode(), 16 ) << endl; + // fallback + case 0x2794: // arrow + case 0x2717: // cross + case 0x2714: // checkmark + m_customBulletChar = bulletChar[0]; + // often StarSymbol when it comes from OO; doesn't matter, Qt finds it in another font if needed. + if ( listStyleProperties.hasAttributeNS( KoXmlNS::style, "font-name" ) ) + { + m_customBulletFont = listStyleProperties.attributeNS( KoXmlNS::style, "font-name", QString::null ); + kdDebug() << "m_customBulletFont style:font-name = " << listStyleProperties.attributeNS( KoXmlNS::style, "font-name", QString::null ) << endl; + } + else if ( listStyleTextProperties.hasAttributeNS( KoXmlNS::fo, "font-family" ) ) + { + m_customBulletFont = listStyleTextProperties.attributeNS( KoXmlNS::fo, "font-family", QString::null ); + kdDebug() << "m_customBulletFont fo:font-family = " << listStyleTextProperties.attributeNS( KoXmlNS::fo, "font-family", QString::null ) << endl; + } + // ## TODO in fact we're supposed to read it from the style pointed to by text:style-name + break; + } + } else { // can never happen + m_style = STYLE_DISCBULLET; + } + } + invalidate(); +} + +void KoParagCounter::saveOasis( KoGenStyle& listStyle, bool savingStyle ) const +{ + Q_ASSERT( (Style)m_style != STYLE_NONE ); + + // Prepare a sub-xmlwriter for the list-level-style-* element + QBuffer buffer; + buffer.open( IO_WriteOnly ); + KoXmlWriter listLevelWriter( &buffer, 3 /*indentation*/ ); + const char* tagName = isBullet() ? "text:list-level-style-bullet" : "text:list-level-style-number"; + listLevelWriter.startElement( tagName ); + + saveOasisListLevel( listLevelWriter, true, savingStyle ); + + listLevelWriter.endElement(); + const QString listLevelContents = QString::fromUtf8( buffer.buffer(), buffer.buffer().size() ); + listStyle.addChildElement( tagName, listLevelContents ); +} + +void KoParagCounter::saveOasisListLevel( KoXmlWriter& listLevelWriter, bool includeLevelAndProperties, bool savingStyle ) const +{ + if ( includeLevelAndProperties ) // false when called for footnotes-configuration + listLevelWriter.addAttribute( "text:level", (int)m_depth + 1 ); + // OASIS allows to specify a text:style, the character style to use for the numbering... + // We currently always format as per the first character of the paragraph, but that's not perfect. + + if ( isBullet() ) + { + QChar bulletChar; + if ( (Style)m_style == STYLE_CUSTOMBULLET ) + { + bulletChar = m_customBulletChar; + // TODO font (text style) + } + else + { + bulletChar = exportCounterType( (Style)m_style ); + } + listLevelWriter.addAttribute( "text:bullet-char", QString( bulletChar ) ); + } + else + { + if ( includeLevelAndProperties ) // not for KWVariableSettings + listLevelWriter.addAttribute( "text:display-levels", m_displayLevels ); + if ( (Style)m_style == STYLE_CUSTOM ) + ; // not implemented + else + listLevelWriter.addAttribute( "style:num-format", QString( exportCounterType( (Style)m_style ) ) ); + + // m_startNumber/m_restartCounter is saved by kotextparag itself, except for styles. + if ( savingStyle && m_restartCounter ) { + listLevelWriter.addAttribute( "text:start-value", m_startNumber ); + } + + } + // m_numbering isn't saved, it's set depending on context (NUM_CHAPTER for headings). + + listLevelWriter.addAttribute( "style:num-prefix", m_prefix ); + listLevelWriter.addAttribute( "style:num-suffix", m_suffix ); + + if ( includeLevelAndProperties ) // false when called for footnotes-configuration + { + listLevelWriter.startElement( "style:list-level-properties" ); + listLevelWriter.addAttribute( "fo:text-align", KoParagLayout::saveOasisAlignment( (Qt::AlignmentFlags)m_align ) ); + // OASIS has other style properties: text:space-before (indent), text:min-label-width (TODO), + // text:min-label-distance, style:font-name (for bullets), image size and vertical alignment. + listLevelWriter.endElement(); // style:list-level-properties + } +} + +int KoParagCounter::number( const KoTextParag *paragraph ) +{ + // Return cached value if possible. + if ( m_cache.number != -1 ) + return m_cache.number; + + // Should we start a new list? + if ( m_restartCounter ) { + Q_ASSERT( m_startNumber != -1 ); + m_cache.number = m_startNumber; + return m_startNumber; + } + + // Go looking for another paragraph at the same level or higher level. + // (This code shares logic with parent()) + KoTextParag *otherParagraph = paragraph->prev(); + KoParagCounter *otherCounter; + + switch ( m_numbering ) + { + case NUM_NONE: + // This should not occur! + case NUM_FOOTNOTE: + m_cache.number = 0; + break; + case NUM_CHAPTER: + m_cache.number = m_startNumber; + // Go upwards... + while ( otherParagraph ) + { + otherCounter = otherParagraph->counter(); + if ( otherCounter && // ...look at numbered paragraphs only + ( otherCounter->m_numbering == NUM_CHAPTER ) && // ...same number type. + ( otherCounter->m_depth <= m_depth ) ) // ...same or higher level. + { + if ( ( otherCounter->m_depth == m_depth ) && + ( otherCounter->m_style == m_style ) ) + { + // Found a preceding paragraph of exactly our type! + m_cache.number = otherCounter->number( otherParagraph ) + 1; + } + else + { + // Found a preceding paragraph of higher level! + m_cache.number = m_startNumber; + } + break; + } + otherParagraph = otherParagraph->prev(); + } + break; + case NUM_LIST: + m_cache.number = m_startNumber; + // Go upwards... + while ( otherParagraph ) + { + otherCounter = otherParagraph->counter(); + if ( otherCounter ) // look at numbered paragraphs only + { + if ( ( otherCounter->m_numbering == NUM_LIST ) && // ...same number type. + !isBullet( otherCounter->m_style ) && // ...not a bullet + ( otherCounter->m_depth <= m_depth ) ) // ...same or higher level. + { + if ( ( otherCounter->m_depth == m_depth ) && + ( otherCounter->m_style == m_style ) ) + { + // Found a preceding paragraph of exactly our type! + m_cache.number = otherCounter->number( otherParagraph ) + 1; + } + else + { + // Found a preceding paragraph of higher level! + m_cache.number = m_startNumber; + } + break; + } + else + if ( otherCounter->m_numbering == NUM_CHAPTER ) // ...heading number type. + { + m_cache.number = m_startNumber; + break; + } + } +/* else + { + // There is no counter at all. + m_cache.number = m_startNumber; + break; + }*/ + otherParagraph = otherParagraph->prev(); + } + break; + } + Q_ASSERT( m_cache.number != -1 ); + return m_cache.number; +} + +KoParagCounter::Numbering KoParagCounter::numbering() const +{ + return m_numbering; +} + +// Go looking for another paragraph at a higher level. +KoTextParag *KoParagCounter::parent( const KoTextParag *paragraph ) +{ + // Return cached value if possible. + if ( m_cache.parent != INVALID_PARAG ) + return m_cache.parent; + + KoTextParag *otherParagraph = paragraph->prev(); + KoParagCounter *otherCounter; + + // (This code shares logic with number()) + switch ( m_numbering ) + { + case NUM_NONE: + // This should not occur! + case NUM_FOOTNOTE: + otherParagraph = 0L; + break; + case NUM_CHAPTER: + // Go upwards while... + while ( otherParagraph ) + { + otherCounter = otherParagraph->counter(); + if ( otherCounter && // ...numbered paragraphs. + ( otherCounter->m_numbering == NUM_CHAPTER ) && // ...same number type. + ( otherCounter->m_depth < m_depth ) ) // ...higher level. + { + break; + } + otherParagraph = otherParagraph->prev(); + } + break; + case NUM_LIST: + // Go upwards while... + while ( otherParagraph ) + { + otherCounter = otherParagraph->counter(); + if ( otherCounter ) // ...numbered paragraphs. + { + if ( ( otherCounter->m_numbering == NUM_LIST ) && // ...same number type. + !isBullet( otherCounter->m_style ) && // ...not a bullet + ( otherCounter->m_depth < m_depth ) ) // ...higher level. + { + break; + } + else + if ( otherCounter->m_numbering == NUM_CHAPTER ) // ...heading number type. + { + otherParagraph = 0L; + break; + } + } + otherParagraph = otherParagraph->prev(); + } + break; + } + m_cache.parent = otherParagraph; + return m_cache.parent; +} + +QString KoParagCounter::prefix() const +{ + return m_prefix; +} + +void KoParagCounter::save( QDomElement & element ) +{ + element.setAttribute( "type", static_cast<int>( m_style ) ); + element.setAttribute( "depth", m_depth ); + if ( (Style)m_style == STYLE_CUSTOMBULLET ) + { + element.setAttribute( "bullet", m_customBulletChar.unicode() ); + if ( !m_customBulletFont.isEmpty() ) + element.setAttribute( "bulletfont", m_customBulletFont ); + } + if ( !m_prefix.isEmpty() ) + element.setAttribute( "lefttext", m_prefix ); + if ( !m_suffix.isEmpty() ) + element.setAttribute( "righttext", m_suffix ); + if ( m_startNumber != 1 ) + element.setAttribute( "start", m_startNumber ); + //if ( m_displayLevels != m_depth ) // see load() + element.setAttribute( "display-levels", m_displayLevels ); + // Don't need to save NUM_FOOTNOTE, it's updated right after loading + if ( m_numbering != NUM_NONE && m_numbering != NUM_FOOTNOTE ) + element.setAttribute( "numberingtype", static_cast<int>( m_numbering ) ); + if ( !m_custom.isEmpty() ) + element.setAttribute( "customdef", m_custom ); + if ( m_restartCounter ) + element.setAttribute( "restart", "true" ); + if ( !m_cache.text.isEmpty() ) + element.setAttribute( "text", m_cache.text ); + element.setAttribute( "align", m_align ); +} + +void KoParagCounter::setCustom( QString c ) +{ + m_custom = c; + invalidate(); +} + +void KoParagCounter::setCustomBulletCharacter( QChar c ) +{ + m_customBulletChar = c; + invalidate(); +} + +void KoParagCounter::setCustomBulletFont( QString f ) +{ + m_customBulletFont = f; + invalidate(); +} + +void KoParagCounter::setDepth( unsigned int d ) +{ + m_depth = d; + invalidate(); +} + +void KoParagCounter::setNumbering( Numbering n ) +{ + m_numbering = n; + invalidate(); +} + +void KoParagCounter::setPrefix( QString p ) +{ + m_prefix = p; + invalidate(); +} +void KoParagCounter::setStartNumber( int s ) +{ + m_startNumber = s; + invalidate(); +} + +void KoParagCounter::setDisplayLevels( int l ) +{ + m_displayLevels = l; + invalidate(); +} + +void KoParagCounter::setAlignment( int a ) +{ + m_align = a; + invalidate(); +} + +void KoParagCounter::setStyle( Style s ) +{ + m_style = s; + invalidate(); +} + +void KoParagCounter::setSuffix( QString s ) +{ + m_suffix = s; + invalidate(); +} + +int KoParagCounter::startNumber() const +{ + return m_startNumber; +} + +int KoParagCounter::displayLevels() const +{ + return m_displayLevels; +} + +int KoParagCounter::alignment() const +{ + return m_align; +} + +KoParagCounter::Style KoParagCounter::style() const +{ + return m_style; +} + +QString KoParagCounter::suffix() const +{ + return m_suffix; +} + +bool KoParagCounter::restartCounter() const +{ + return m_restartCounter; +} + +void KoParagCounter::setRestartCounter( bool restart ) +{ + m_restartCounter = restart; + invalidate(); +} + +// Return the text for that level only +QString KoParagCounter::levelText( const KoTextParag *paragraph ) +{ + if ( m_numbering == NUM_NONE ) + return ""; + + bool bullet = isBullet( m_style ); + + if ( bullet && m_numbering == NUM_CHAPTER ) { + // Shome mishtake surely! (not sure how it can happen though) + m_style = STYLE_NUM; + bullet = false; + } + + QString text; + if ( !bullet ) + { + // Ensure paragraph number is valid. + number( paragraph ); + + switch ( m_style ) + { + case STYLE_NONE: + if ( m_numbering == NUM_LIST ) + text = ' '; + break; + case STYLE_NUM: + text.setNum( m_cache.number ); + break; + case STYLE_ALPHAB_L: + text = makeAlphaLowerNumber( m_cache.number ); + break; + case STYLE_ALPHAB_U: + text = makeAlphaUpperNumber( m_cache.number ); + break; + case STYLE_ROM_NUM_L: + text = makeRomanNumber( m_cache.number ).lower(); + break; + case STYLE_ROM_NUM_U: + text = makeRomanNumber( m_cache.number ).upper(); + break; + case STYLE_CUSTOM: + ////// TODO + default: // shut up compiler + text.setNum( m_cache.number ); + break; + } + } + else + { + switch ( m_style ) + { + // --- these are used in export filters but are ignored by KoTextParag::drawLabel (for bulleted lists - which they are :)) --- + case KoParagCounter::STYLE_DISCBULLET: + text = '*'; + break; + case KoParagCounter::STYLE_SQUAREBULLET: + text = '#'; + break; + case KoParagCounter::STYLE_BOXBULLET: + text = '='; // think up a better character + break; + case KoParagCounter::STYLE_CIRCLEBULLET: + text = 'o'; + break; + case KoParagCounter::STYLE_CUSTOMBULLET: + text = m_customBulletChar; + break; + default: // shut up compiler + break; + } + } + return text; +} + +// Return the full text to be displayed +QString KoParagCounter::text( const KoTextParag *paragraph ) +{ + // Return cached value if possible. + if ( !m_cache.text.isNull() ) + return m_cache.text; + + // If necessary, grab the text of the preceding levels. + if ( m_displayLevels > 1 && m_numbering != NUM_NONE ) + { + KoTextParag* p = parent( paragraph ); + int displayLevels = QMIN( m_displayLevels, m_depth+1 ); // can't be >depth+1 + for ( int level = 1 ; level < displayLevels ; ++level ) { + //kdDebug() << "additional level=" << level << "/" << displayLevels-1 << endl; + if ( p ) + { + KoParagCounter* counter = p->counter(); + QString str = counter->levelText( p ); + // If the preceding level is a bullet, replace it with blanks. + if ( counter->isBullet() ) + for ( unsigned i = 0; i < str.length(); i++ ) + str[i] = ' '; + + str.append('.'); // hardcoded on purpose (like OO) until anyone complains + + // Find the number of missing parents, and add dummy text for them. + int missingParents = m_depth - level - p->counter()->m_depth; + //kdDebug() << "levelText = " << str << " missingParents=" << missingParents << endl; + level += missingParents; + for ( ; missingParents > 0 ; --missingParents ) + // Each missing level adds a "0" + str.append( "0." ); + + m_cache.text.prepend( str ); + // Prepare next iteration + if ( level < displayLevels ) // no need to calc it if we won't use it + p = counter->parent( p ); + } + else // toplevel parents are missing + { + // Special case for one-paragraph-documents like preview widgets + KoTextDocument* textdoc = paragraph->textDocument(); + if ( paragraph == textdoc->firstParag() && paragraph == textdoc->lastParag() ) + m_cache.text.prepend( "1." ); + else + m_cache.text.prepend( "0." ); + } + } + + } + + //kdDebug() << "result: " << m_cache.text << " + " << levelText( paragraph ) << endl; + // Now add text for this level. + m_cache.text.append( levelText( paragraph ) ); + + // Now apply prefix and suffix + // We want the '.' to be before the number in a RTL parag, + // but we can't paint the whole string using QPainter::RTL direction, otherwise + // '10' becomes '01'. + m_cache.text.prepend( paragraph->string()->isRightToLeft() ? suffix() : prefix() ); + m_cache.text.append( paragraph->string()->isRightToLeft() ? prefix() : suffix() ); + return m_cache.text; +} + +int KoParagCounter::width( const KoTextParag *paragraph ) +{ + // Return cached value if possible. + if ( m_cache.width != -1 && counterFormat( paragraph ) == m_cache.counterFormat ) + return m_cache.width; + + // Ensure paragraph text is valid. + if ( m_cache.text.isNull() ) + text( paragraph ); + + // Now calculate width. + if ( m_cache.counterFormat ) + m_cache.counterFormat->removeRef(); + m_cache.counterFormat = counterFormat( paragraph ); + m_cache.counterFormat->addRef(); + m_cache.width = 0; + if ( m_style != STYLE_NONE || m_numbering == NUM_FOOTNOTE) + { + QString text = m_cache.text; + if ( m_style == STYLE_CUSTOMBULLET && !text.isEmpty() ) + { + text.append( " " ); // append two trailing spaces, see KoTextParag::drawLabel + } + else if ( !text.isEmpty() ) + text.append( ' ' ); // append a trailing space, see KoTextParag::drawLabel + QFontMetrics fm = m_cache.counterFormat->refFontMetrics(); + for ( unsigned int i = 0; i < text.length(); i++ ) + //m_cache.width += m_cache.counterFormat->width( text, i ); + m_cache.width += fm.width( text[i] ); + } + // Now go from 100%-zoom to LU + m_cache.width = KoTextZoomHandler::ptToLayoutUnitPt( m_cache.width ); + + //kdDebug(32500) << "KoParagCounter::width recalculated parag=" << paragraph << " text='" << text << "' width=" << m_cache.width << endl; + return m_cache.width; +} + +int KoParagCounter::bulletX() +{ + // width() must have been called first + Q_ASSERT( m_cache.width != -1 ); + Q_ASSERT( m_cache.counterFormat ); + int x = 0; + QFontMetrics fm = m_cache.counterFormat->refFontMetrics(); + QString text = prefix(); + for ( unsigned int i = 0; i < text.length(); i++ ) + x += fm.width( text[i] ); + // Now go from 100%-zoom to LU + return KoTextZoomHandler::ptToLayoutUnitPt( x ); +} + +// Only exists to centralize code. Does no caching. +KoTextFormat* KoParagCounter::counterFormat( const KoTextParag *paragraph ) +{ + KoTextFormat* refFormat = paragraph->at( 0 )->format(); + KoTextFormat format( *refFormat ); + format.setVAlign( KoTextFormat::AlignNormal ); + return paragraph->textDocument()->formatCollection()->format( &format ); + /*paragraph->paragFormat()*/ +} + +/// + +const QCString RNUnits[] = {"", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"}; +const QCString RNTens[] = {"", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"}; +const QCString RNHundreds[] = {"", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"}; +const QCString RNThousands[] = {"", "m", "mm", "mmm"}; + +QString KoParagCounter::makeRomanNumber( int n ) +{ + if ( n >= 0 ) + return QString::fromLatin1( RNThousands[ ( n / 1000 ) ] + + RNHundreds[ ( n / 100 ) % 10 ] + + RNTens[ ( n / 10 ) % 10 ] + + RNUnits[ ( n ) % 10 ] ); + else { // should never happen, but better not crash if it does + kdWarning(32500) << "makeRomanNumber: n=" << n << endl; + return QString::number( n ); + } +} + +QString KoParagCounter::makeAlphaUpperNumber( int n ) +{ + QString tmp; + char bottomDigit; + while ( n > 26 ) + { + bottomDigit = (n-1) % 26; + n = (n-1) / 26; + tmp.prepend( QChar( 'A' + bottomDigit ) ); + } + tmp.prepend( QChar( 'A' + n -1 ) ); + return tmp; +} + +QString KoParagCounter::makeAlphaLowerNumber( int n ) +{ + QString tmp; + char bottomDigit; + while ( n > 26 ) + { + bottomDigit = (n-1) % 26; + n = (n-1) / 26; + tmp.prepend( QChar( 'a' + bottomDigit ) ); + } + tmp.prepend( QChar( 'a' + n - 1 ) ); + return tmp; +} + +int KoParagCounter::fromRomanNumber( const QString &string ) +{ + int ret = 0; + int stringStart = 0; + const int stringLen = string.length(); + + for (int base = 1000; base >= 1 && stringStart < stringLen; base /= 10) + { + const QCString *rn; + int rnNum; + switch (base) + { + case 1000: + rn = RNThousands; + rnNum = sizeof (RNThousands) / sizeof (const QCString); + break; + case 100: + rn = RNHundreds; + rnNum = sizeof (RNHundreds) / sizeof (const QCString); + break; + case 10: + rn = RNTens; + rnNum = sizeof (RNTens) / sizeof (const QCString); + break; + case 1: + default: + rn = RNUnits; + rnNum = sizeof (RNUnits) / sizeof (const QCString); + break; + } + + // I _think_ this will work :) - Clarence + for (int i = rnNum - 1; i >= 1; i--) + { + const int rnLength = rn[i].length(); + if (string.mid(stringStart,rnLength) == (const char*)rn[i]) + { + ret += i * base; + stringStart += rnLength; + break; + } + } + } + + return (ret == 0 || stringStart != stringLen) ? -1 /*invalid value*/ : ret; +} + +int KoParagCounter::fromAlphaUpperNumber( const QString &string ) +{ + int ret = 0; + + const int len = string.length(); + for (int i = 0; i < len; i++) + { + const int add = char(string[i]) - 'A' + 1; + + if (add >= 1 && add <= 26) // _not_ < 26 + ret = ret * 26 + add; + else + { + ret = -1; // invalid character + break; + } + } + + return (ret == 0) ? -1 /*invalid value*/ : ret; +} + +int KoParagCounter::fromAlphaLowerNumber( const QString &string ) +{ + int ret = 0; + + const int len = string.length(); + for (int i = 0; i < len; i++) + { + const int add = char(string[i]) - 'a' + 1; + + if (add >= 1 && add <= 26) // _not_ < 26 + ret = ret * 26 + add; + else + { + ret = -1; // invalid character + break; + } + } + + return (ret == 0) ? -1 /*invalid value*/ : ret; +} + +#ifndef NDEBUG +void KoParagCounter::printRTDebug( KoTextParag* parag ) +{ + QString additionalInfo; + if ( restartCounter() ) + additionalInfo = "[restartCounter]"; + if ( m_style == STYLE_CUSTOMBULLET ) + additionalInfo += " [customBullet: " + QString::number( m_customBulletChar.unicode() ) + + " in font '" + m_customBulletFont + "']"; + static const char * const s_numbering[] = { "List", "Chapter", "None", "Footnote" }; + kdDebug(32500) << " Counter style=" << style() + << " numbering=" << s_numbering[ numbering() ] + << " depth=" << depth() + << " number=" << number( parag ) + << " text='" << text( parag ) << "'" + << " width=" << width( parag ) + << additionalInfo << endl; +} +#endif |