lib

KoParagCounter.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2001 Shaheed Haque <srhaque@iee.org>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017  * Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "KoParagCounter.h"
00021 #include "KoTextParag.h"
00022 #include "KoTextZoomHandler.h"
00023 #include "KoTextFormat.h"
00024 #include "KoTextDocument.h"
00025 #include "KoOasisContext.h"
00026 #include <KoXmlWriter.h>
00027 #include <KoGenStyles.h>
00028 #include <KoXmlNS.h>
00029 #include <kdebug.h>
00030 #include <qdom.h>
00031 #include <qbuffer.h>
00032 
00033 static KoTextParag * const INVALID_PARAG = (KoTextParag *)-1;
00034 
00035 KoParagCounter::KoParagCounter()
00036 {
00037     m_numbering = NUM_NONE;
00038     m_style = STYLE_NONE;
00039     m_depth = 0;
00040     m_startNumber = 1;
00041     m_displayLevels = 1;
00042     m_restartCounter = false;
00043     m_customBulletChar = QChar( '-' );
00044     m_customBulletFont = QString::null;
00045     m_align = Qt::AlignAuto;
00046     invalidate();
00047 }
00048 
00049 bool KoParagCounter::operator==( const KoParagCounter & c2 ) const
00050 {
00051     // ## This is kinda wrong. Unused fields (depending on the counter style) shouldn't be compared.
00052     return (m_numbering==c2.m_numbering &&
00053             m_style==c2.m_style &&
00054             m_depth==c2.m_depth &&
00055             m_startNumber==c2.m_startNumber &&
00056             m_displayLevels==c2.m_displayLevels &&
00057             m_restartCounter==c2.m_restartCounter &&
00058             m_prefix==c2.m_prefix &&
00059             m_suffix==c2.m_suffix &&
00060             m_customBulletChar==c2.m_customBulletChar &&
00061             m_customBulletFont==c2.m_customBulletFont &&
00062             m_align==c2.m_align &&
00063             m_custom==c2.m_custom);
00064 }
00065 
00066 QString KoParagCounter::custom() const
00067 {
00068     return m_custom;
00069 }
00070 
00071 QChar KoParagCounter::customBulletCharacter() const
00072 {
00073     return m_customBulletChar;
00074 }
00075 
00076 QString KoParagCounter::customBulletFont() const
00077 {
00078     return m_customBulletFont;
00079 }
00080 
00081 unsigned int KoParagCounter::depth() const
00082 {
00083     return m_depth;
00084 }
00085 
00086 void KoParagCounter::invalidate()
00087 {
00088     m_cache.number = -1;
00089     m_cache.text = QString::null;
00090     m_cache.width = -1;
00091     m_cache.parent = INVALID_PARAG;
00092     m_cache.counterFormat = 0;
00093 }
00094 
00095 bool KoParagCounter::isBullet( Style style ) // static
00096 {
00097     switch ( style )
00098     {
00099     case STYLE_DISCBULLET:
00100     case STYLE_SQUAREBULLET:
00101     case STYLE_BOXBULLET:
00102     case STYLE_CIRCLEBULLET:
00103     case STYLE_CUSTOMBULLET:
00104         return true;
00105     default:
00106         return false;
00107     }
00108 }
00109 
00110 bool KoParagCounter::isBullet() const
00111 {
00112     return isBullet( m_style );
00113 }
00114 
00115 void KoParagCounter::load( QDomElement & element )
00116 {
00117     m_numbering = static_cast<Numbering>( element.attribute("numberingtype", "2").toInt() );
00118     m_style = static_cast<Style>( element.attribute("type").toInt() );
00119     // Old docs have this:
00120     if ( m_numbering == NUM_LIST && m_style == STYLE_NONE )
00121         m_numbering = NUM_NONE;
00122     m_depth = element.attribute("depth").toInt();
00123     m_customBulletChar = QChar( element.attribute("bullet").toInt() );
00124     m_prefix = element.attribute("lefttext");
00125     if ( m_prefix.lower() == "(null)" ) // very old kword thing
00126         m_prefix = QString::null;
00127     m_suffix = element.attribute("righttext");
00128     if ( m_suffix.lower() == "(null)" )
00129         m_suffix = QString::null;
00130     QString s = element.attribute("start");
00131     if ( s.isEmpty() )
00132         m_startNumber = 1;
00133     else if ( s[0].isDigit() )
00134         m_startNumber = s.toInt();
00135     else // support for very-old files
00136         m_startNumber = s.lower()[0].latin1() - 'a' + 1;
00137     s = element.attribute("display-levels");
00138     if ( !s.isEmpty() )
00139         m_displayLevels = QMIN( s.toInt(), m_depth+1 ); // can't be > depth+1
00140     else // Not specified -> compat with koffice-1.2: make equal to depth+1
00141         m_displayLevels = m_depth+1;
00142     m_customBulletFont = element.attribute("bulletfont");
00143     m_custom = element.attribute("customdef");
00144     m_align = element.attribute("align", "0").toInt(); //AlignAuto as defeult
00145     QString restart = element.attribute("restart");
00146     m_restartCounter = (restart == "true") || (restart == "1");
00147     invalidate();
00148 }
00149 
00150 static int importCounterType( QChar numFormat )
00151 {
00152     if ( numFormat == '1' )
00153         return KoParagCounter::STYLE_NUM;
00154     if ( numFormat == 'a' )
00155         return KoParagCounter::STYLE_ALPHAB_L;
00156     if ( numFormat == 'A' )
00157         return KoParagCounter::STYLE_ALPHAB_U;
00158     if ( numFormat == 'i' )
00159         return KoParagCounter::STYLE_ROM_NUM_L;
00160     if ( numFormat == 'I' )
00161         return KoParagCounter::STYLE_ROM_NUM_U;
00162     return KoParagCounter::STYLE_NONE;
00163 }
00164 
00165 // should only be called with style != none and != a bullet.
00166 static QChar exportCounterType( KoParagCounter::Style style )
00167 {
00168     static const int s_oasisCounterTypes[] =
00169         { '\0', '1', 'a', 'A', 'i', 'I',
00170           '\0', '\0', // custombullet, custom
00171           0x2022, // circle -> small disc
00172           0xE00A, // square
00173           0x25CF, // disc -> large disc
00174           0x27A2  // box -> right-pointing triangle
00175         };
00176     return QChar( s_oasisCounterTypes[ style ] );
00177 }
00178 
00179 void KoParagCounter::loadOasis( KoOasisContext& context, int restartNumbering,
00180                                 bool orderedList, bool heading, int level, bool loadingStyle )
00181 {
00182     const QDomElement listStyle = context.listStyleStack().currentListStyle();
00183     const QDomElement listStyleProperties = context.listStyleStack().currentListStyleProperties();
00184     const QDomElement listStyleTextProperties = context.listStyleStack().currentListStyleTextProperties();
00185     loadOasisListStyle( listStyle, listStyleProperties, listStyleTextProperties,
00186                         restartNumbering, orderedList, heading, level, loadingStyle );
00187 }
00188 
00189 void KoParagCounter::loadOasisListStyle( const QDomElement& listStyle,
00190                                          const QDomElement& listStyleProperties,
00191                                          const QDomElement& listStyleTextProperties,
00192                                          int restartNumbering,
00193                                          bool orderedList, bool heading, int level,
00194                                          bool loadingStyle )
00195 {
00196     m_numbering = heading ? NUM_CHAPTER : NUM_LIST;
00197     m_depth = level - 1; // depth start at 0
00198     // restartNumbering can either be provided by caller, or taken from the style
00199     if ( restartNumbering == -1 && listStyle.hasAttributeNS( KoXmlNS::text, "start-value" ) )
00200         restartNumbering = listStyle.attributeNS( KoXmlNS::text, "start-value", QString::null ).toInt();
00201 
00202     // styles have a start-value, but that doesn't mean restartNumbering, as it does for paragraphs
00203     m_restartCounter = loadingStyle ? false : ( restartNumbering != -1 );
00204     m_startNumber = ( restartNumbering != -1 ) ? restartNumbering : 1;
00205     //kdDebug() << k_funcinfo << "IN: restartNumbering=" << restartNumbering << " OUT: m_restartCounter=" << m_restartCounter << " m_startNumber=" << m_startNumber << endl;
00206 
00207     m_prefix = listStyle.attributeNS( KoXmlNS::style, "num-prefix", QString::null );
00208     m_suffix = listStyle.attributeNS( KoXmlNS::style, "num-suffix", QString::null );
00209 
00210     if ( orderedList || heading ) {
00211         m_style = static_cast<Style>( importCounterType( listStyle.attributeNS( KoXmlNS::style, "num-format", QString::null)[0] ) );
00212         QString dl = listStyle.attributeNS( KoXmlNS::text, "display-levels", QString::null );
00213         m_displayLevels = dl.isEmpty() ? 1 : dl.toInt();
00214     } else { // bullets, see 3.3.6 p138
00215         m_style = STYLE_CUSTOMBULLET;
00216         QString bulletChar = listStyle.attributeNS( KoXmlNS::text, "bullet-char", QString::null );
00217         if ( !bulletChar.isEmpty() ) {
00218             // Reverse engineering, I found those codes:
00219             switch( bulletChar[0].unicode() ) {
00220             case 0x2022: // small disc -> circle
00221                 m_style = STYLE_CIRCLEBULLET;
00222                 break;
00223             case 0x25CF: // large disc -> disc
00224             case 0xF0B7: // #113361
00225                 m_style = STYLE_DISCBULLET;
00226                 break;
00227             case 0xE00C: // losange - TODO in kotext. Not in OASIS either (reserved Unicode area!)
00228                 m_style = STYLE_BOXBULLET;
00229                 break;
00230             case 0xE00A: // square. Not in OASIS (reserved Unicode area!), but used in both OOo and kotext.
00231                 m_style = STYLE_SQUAREBULLET;
00232                 break;
00233             case 0x27A2: // two-colors right-pointing triangle
00234                          // mapping (both ways) to box for now.
00235                 m_style = STYLE_BOXBULLET;
00236                 break;
00237             default:
00238                 kdDebug() << "Unhandled bullet code 0x" << QString::number( (uint)m_customBulletChar.unicode(), 16 ) << endl;
00239                 // fallback
00240             case 0x2794: // arrow
00241             case 0x2717: // cross
00242             case 0x2714: // checkmark
00243                 m_customBulletChar = bulletChar[0];
00244                 // often StarSymbol when it comes from OO; doesn't matter, Qt finds it in another font if needed.
00245                 if ( listStyleProperties.hasAttributeNS( KoXmlNS::style, "font-name" ) )
00246                 {
00247                     m_customBulletFont = listStyleProperties.attributeNS( KoXmlNS::style, "font-name", QString::null );
00248                     kdDebug() << "m_customBulletFont style:font-name = " << listStyleProperties.attributeNS( KoXmlNS::style, "font-name", QString::null ) << endl;
00249                 }
00250                 else if ( listStyleTextProperties.hasAttributeNS( KoXmlNS::fo, "font-family" ) )
00251                 {
00252                     m_customBulletFont = listStyleTextProperties.attributeNS( KoXmlNS::fo, "font-family", QString::null );
00253                     kdDebug() << "m_customBulletFont fo:font-family = " << listStyleTextProperties.attributeNS( KoXmlNS::fo, "font-family", QString::null ) << endl;
00254                 }
00255                 // ## TODO in fact we're supposed to read it from the style pointed to by text:style-name
00256                 break;
00257             }
00258         } else { // can never happen
00259             m_style = STYLE_DISCBULLET;
00260         }
00261     }
00262     invalidate();
00263 }
00264 
00265 void KoParagCounter::saveOasis( KoGenStyle& listStyle, bool savingStyle ) const
00266 {
00267     Q_ASSERT( (Style)m_style != STYLE_NONE );
00268 
00269     // Prepare a sub-xmlwriter for the list-level-style-* element
00270     QBuffer buffer;
00271     buffer.open( IO_WriteOnly );
00272     KoXmlWriter listLevelWriter( &buffer, 3 /*indentation*/ );
00273     const char* tagName = isBullet() ? "text:list-level-style-bullet" : "text:list-level-style-number";
00274     listLevelWriter.startElement( tagName );
00275 
00276     saveOasisListLevel( listLevelWriter, true, savingStyle );
00277 
00278     listLevelWriter.endElement();
00279     const QString listLevelContents = QString::fromUtf8( buffer.buffer(), buffer.buffer().size() );
00280     listStyle.addChildElement( tagName, listLevelContents );
00281 }
00282 
00283 void KoParagCounter::saveOasisListLevel( KoXmlWriter& listLevelWriter, bool includeLevelAndProperties, bool savingStyle ) const
00284 {
00285     if ( includeLevelAndProperties ) // false when called for footnotes-configuration
00286         listLevelWriter.addAttribute( "text:level", (int)m_depth + 1 );
00287     // OASIS allows to specify a text:style, the character style to use for the numbering...
00288     // We currently always format as per the first character of the paragraph, but that's not perfect.
00289 
00290     if ( isBullet() )
00291     {
00292         QChar bulletChar;
00293         if ( (Style)m_style == STYLE_CUSTOMBULLET )
00294         {
00295             bulletChar = m_customBulletChar;
00296             // TODO font (text style)
00297         }
00298         else
00299         {
00300             bulletChar = exportCounterType( (Style)m_style );
00301         }
00302         listLevelWriter.addAttribute( "text:bullet-char", QString( bulletChar ) );
00303     }
00304     else
00305     {
00306         if ( includeLevelAndProperties ) // not for KWVariableSettings
00307             listLevelWriter.addAttribute( "text:display-levels", m_displayLevels );
00308         if ( (Style)m_style == STYLE_CUSTOM )
00309             ; // not implemented
00310         else
00311             listLevelWriter.addAttribute( "style:num-format", QString( exportCounterType( (Style)m_style ) ) );
00312 
00313         // m_startNumber/m_restartCounter is saved by kotextparag itself, except for styles.
00314         if ( savingStyle && m_restartCounter ) {
00315             listLevelWriter.addAttribute( "text:start-value", m_startNumber );
00316         }
00317 
00318     }
00319     // m_numbering isn't saved, it's set depending on context (NUM_CHAPTER for headings).
00320 
00321     listLevelWriter.addAttribute( "style:num-prefix", m_prefix );
00322     listLevelWriter.addAttribute( "style:num-suffix", m_suffix );
00323 
00324     if ( includeLevelAndProperties ) // false when called for footnotes-configuration
00325     {
00326         listLevelWriter.startElement( "style:list-level-properties" );
00327         listLevelWriter.addAttribute( "fo:text-align", KoParagLayout::saveOasisAlignment( (Qt::AlignmentFlags)m_align ) );
00328         // OASIS has other style properties: text:space-before (indent), text:min-label-width (TODO),
00329         // text:min-label-distance, style:font-name (for bullets), image size and vertical alignment.
00330         listLevelWriter.endElement(); // style:list-level-properties
00331     }
00332 }
00333 
00334 int KoParagCounter::number( const KoTextParag *paragraph )
00335 {
00336     // Return cached value if possible.
00337     if ( m_cache.number != -1 )
00338         return m_cache.number;
00339 
00340     // Should we start a new list?
00341     if ( m_restartCounter ) {
00342         Q_ASSERT( m_startNumber != -1 );
00343         m_cache.number = m_startNumber;
00344         return m_startNumber;
00345     }
00346 
00347     // Go looking for another paragraph at the same level or higher level.
00348     // (This code shares logic with parent())
00349     KoTextParag *otherParagraph = paragraph->prev();
00350     KoParagCounter *otherCounter;
00351 
00352     switch ( m_numbering )
00353     {
00354     case NUM_NONE:
00355         // This should not occur!
00356     case NUM_FOOTNOTE:
00357         m_cache.number = 0;
00358         break;
00359     case NUM_CHAPTER:
00360         m_cache.number = m_startNumber;
00361         // Go upwards...
00362         while ( otherParagraph )
00363         {
00364             otherCounter = otherParagraph->counter();
00365             if ( otherCounter &&               // ...look at numbered paragraphs only
00366                 ( otherCounter->m_numbering == NUM_CHAPTER ) &&     // ...same number type.
00367                 ( otherCounter->m_depth <= m_depth ) )        // ...same or higher level.
00368             {
00369                 if ( ( otherCounter->m_depth == m_depth ) &&
00370                    ( otherCounter->m_style == m_style ) )
00371                 {
00372                     // Found a preceding paragraph of exactly our type!
00373                     m_cache.number = otherCounter->number( otherParagraph ) + 1;
00374                 }
00375                 else
00376                 {
00377                     // Found a preceding paragraph of higher level!
00378                     m_cache.number = m_startNumber;
00379                 }
00380                 break;
00381             }
00382             otherParagraph = otherParagraph->prev();
00383         }
00384         break;
00385     case NUM_LIST:
00386         m_cache.number = m_startNumber;
00387         // Go upwards...
00388         while ( otherParagraph )
00389         {
00390             otherCounter = otherParagraph->counter();
00391             if ( otherCounter )                                         // look at numbered paragraphs only
00392             {
00393                 if ( ( otherCounter->m_numbering == NUM_LIST ) &&       // ...same number type.
00394                      !isBullet( otherCounter->m_style ) &&    // ...not a bullet
00395                     ( otherCounter->m_depth <= m_depth ) )    // ...same or higher level.
00396                 {
00397                     if ( ( otherCounter->m_depth == m_depth ) &&
00398                        ( otherCounter->m_style == m_style ) )
00399                     {
00400                         // Found a preceding paragraph of exactly our type!
00401                         m_cache.number = otherCounter->number( otherParagraph ) + 1;
00402                     }
00403                     else
00404                     {
00405                         // Found a preceding paragraph of higher level!
00406                         m_cache.number = m_startNumber;
00407                     }
00408                     break;
00409                 }
00410                 else
00411                 if ( otherCounter->m_numbering == NUM_CHAPTER )        // ...heading number type.
00412                 {
00413                     m_cache.number = m_startNumber;
00414                     break;
00415                 }
00416             }
00417 /*            else
00418             {
00419                 // There is no counter at all.
00420                 m_cache.number = m_startNumber;
00421                 break;
00422             }*/
00423             otherParagraph = otherParagraph->prev();
00424         }
00425         break;
00426     }
00427     Q_ASSERT( m_cache.number != -1 );
00428     return m_cache.number;
00429 }
00430 
00431 KoParagCounter::Numbering KoParagCounter::numbering() const
00432 {
00433     return m_numbering;
00434 }
00435 
00436 // Go looking for another paragraph at a higher level.
00437 KoTextParag *KoParagCounter::parent( const KoTextParag *paragraph )
00438 {
00439     // Return cached value if possible.
00440     if ( m_cache.parent != INVALID_PARAG )
00441         return m_cache.parent;
00442 
00443     KoTextParag *otherParagraph = paragraph->prev();
00444     KoParagCounter *otherCounter;
00445 
00446     // (This code shares logic with number())
00447     switch ( m_numbering )
00448     {
00449     case NUM_NONE:
00450         // This should not occur!
00451     case NUM_FOOTNOTE:
00452         otherParagraph = 0L;
00453         break;
00454     case NUM_CHAPTER:
00455         // Go upwards while...
00456         while ( otherParagraph )
00457         {
00458             otherCounter = otherParagraph->counter();
00459             if ( otherCounter &&                                        // ...numbered paragraphs.
00460                 ( otherCounter->m_numbering == NUM_CHAPTER ) &&         // ...same number type.
00461                 ( otherCounter->m_depth < m_depth ) )         // ...higher level.
00462             {
00463                 break;
00464             }
00465             otherParagraph = otherParagraph->prev();
00466         }
00467         break;
00468     case NUM_LIST:
00469         // Go upwards while...
00470         while ( otherParagraph )
00471         {
00472             otherCounter = otherParagraph->counter();
00473             if ( otherCounter )                                         // ...numbered paragraphs.
00474             {
00475                 if ( ( otherCounter->m_numbering == NUM_LIST ) &&       // ...same number type.
00476                      !isBullet( otherCounter->m_style ) &&    // ...not a bullet
00477                     ( otherCounter->m_depth < m_depth ) )     // ...higher level.
00478                 {
00479                     break;
00480                 }
00481                 else
00482                 if ( otherCounter->m_numbering == NUM_CHAPTER )         // ...heading number type.
00483                 {
00484                     otherParagraph = 0L;
00485                     break;
00486                 }
00487             }
00488             otherParagraph = otherParagraph->prev();
00489         }
00490         break;
00491     }
00492     m_cache.parent = otherParagraph;
00493     return m_cache.parent;
00494 }
00495 
00496 QString KoParagCounter::prefix() const
00497 {
00498     return m_prefix;
00499 }
00500 
00501 void KoParagCounter::save( QDomElement & element )
00502 {
00503     element.setAttribute( "type", static_cast<int>( m_style ) );
00504     element.setAttribute( "depth", m_depth );
00505     if ( (Style)m_style == STYLE_CUSTOMBULLET )
00506     {
00507         element.setAttribute( "bullet", m_customBulletChar.unicode() );
00508         if ( !m_customBulletFont.isEmpty() )
00509             element.setAttribute( "bulletfont", m_customBulletFont );
00510     }
00511     if ( !m_prefix.isEmpty() )
00512         element.setAttribute( "lefttext", m_prefix );
00513     if ( !m_suffix.isEmpty() )
00514         element.setAttribute( "righttext", m_suffix );
00515     if ( m_startNumber != 1 )
00516         element.setAttribute( "start", m_startNumber );
00517     //if ( m_displayLevels != m_depth ) // see load()
00518         element.setAttribute( "display-levels", m_displayLevels );
00519     // Don't need to save NUM_FOOTNOTE, it's updated right after loading
00520     if ( m_numbering != NUM_NONE && m_numbering != NUM_FOOTNOTE )
00521         element.setAttribute( "numberingtype", static_cast<int>( m_numbering ) );
00522     if ( !m_custom.isEmpty() )
00523         element.setAttribute( "customdef", m_custom );
00524     if ( m_restartCounter )
00525         element.setAttribute( "restart", "true" );
00526     if ( !m_cache.text.isEmpty() )
00527         element.setAttribute( "text", m_cache.text );
00528     element.setAttribute( "align", m_align );
00529 }
00530 
00531 void KoParagCounter::setCustom( QString c )
00532 {
00533     m_custom = c;
00534     invalidate();
00535 }
00536 
00537 void KoParagCounter::setCustomBulletCharacter( QChar c )
00538 {
00539     m_customBulletChar = c;
00540     invalidate();
00541 }
00542 
00543 void KoParagCounter::setCustomBulletFont( QString f )
00544 {
00545     m_customBulletFont = f;
00546     invalidate();
00547 }
00548 
00549 void KoParagCounter::setDepth( unsigned int d )
00550 {
00551     m_depth = d;
00552     invalidate();
00553 }
00554 
00555 void KoParagCounter::setNumbering( Numbering n )
00556 {
00557     m_numbering = n;
00558     invalidate();
00559 }
00560 
00561 void KoParagCounter::setPrefix( QString p )
00562 {
00563     m_prefix = p;
00564     invalidate();
00565 }
00566 void KoParagCounter::setStartNumber( int s )
00567 {
00568     m_startNumber = s;
00569     invalidate();
00570 }
00571 
00572 void KoParagCounter::setDisplayLevels( int l )
00573 {
00574     m_displayLevels = l;
00575     invalidate();
00576 }
00577 
00578 void KoParagCounter::setAlignment( int a )
00579 {
00580     m_align = a;
00581     invalidate();
00582 }
00583 
00584 void KoParagCounter::setStyle( Style s )
00585 {
00586     m_style = s;
00587     invalidate();
00588 }
00589 
00590 void KoParagCounter::setSuffix( QString s )
00591 {
00592     m_suffix = s;
00593     invalidate();
00594 }
00595 
00596 int KoParagCounter::startNumber() const
00597 {
00598     return m_startNumber;
00599 }
00600 
00601 int KoParagCounter::displayLevels() const
00602 {
00603     return m_displayLevels;
00604 }
00605 
00606 int KoParagCounter::alignment() const
00607 {
00608     return m_align;
00609 }
00610 
00611 KoParagCounter::Style KoParagCounter::style() const
00612 {
00613     return m_style;
00614 }
00615 
00616 QString KoParagCounter::suffix() const
00617 {
00618     return m_suffix;
00619 }
00620 
00621 bool KoParagCounter::restartCounter() const
00622 {
00623     return m_restartCounter;
00624 }
00625 
00626 void KoParagCounter::setRestartCounter( bool restart )
00627 {
00628     m_restartCounter = restart;
00629     invalidate();
00630 }
00631 
00632 // Return the text for that level only
00633 QString KoParagCounter::levelText( const KoTextParag *paragraph )
00634 {
00635     if ( m_numbering == NUM_NONE )
00636         return "";
00637 
00638     bool bullet = isBullet( m_style );
00639 
00640     if ( bullet && m_numbering == NUM_CHAPTER ) {
00641         // Shome mishtake surely! (not sure how it can happen though)
00642         m_style = STYLE_NUM;
00643         bullet = false;
00644     }
00645 
00646     QString text;
00647     if ( !bullet )
00648     {
00649         // Ensure paragraph number is valid.
00650         number( paragraph );
00651 
00652         switch ( m_style )
00653         {
00654         case STYLE_NONE:
00655         if ( m_numbering == NUM_LIST )
00656             text = ' ';
00657         break;
00658         case STYLE_NUM:
00659             text.setNum( m_cache.number );
00660             break;
00661         case STYLE_ALPHAB_L:
00662             text = makeAlphaLowerNumber( m_cache.number );
00663             break;
00664         case STYLE_ALPHAB_U:
00665             text = makeAlphaUpperNumber( m_cache.number );
00666             break;
00667         case STYLE_ROM_NUM_L:
00668             text = makeRomanNumber( m_cache.number ).lower();
00669             break;
00670         case STYLE_ROM_NUM_U:
00671             text = makeRomanNumber( m_cache.number ).upper();
00672             break;
00673         case STYLE_CUSTOM:
00675         default: // shut up compiler
00676             text.setNum( m_cache.number );
00677             break;
00678         }
00679     }
00680     else
00681     {
00682         switch ( m_style )
00683         {
00684             // --- these are used in export filters but are ignored by KoTextParag::drawLabel (for bulleted lists - which they are :))  ---
00685         case KoParagCounter::STYLE_DISCBULLET:
00686             text = '*';
00687             break;
00688         case KoParagCounter::STYLE_SQUAREBULLET:
00689             text = '#';
00690             break;
00691         case KoParagCounter::STYLE_BOXBULLET:
00692             text = '=';  // think up a better character
00693             break;
00694         case KoParagCounter::STYLE_CIRCLEBULLET:
00695             text = 'o';
00696             break;
00697         case KoParagCounter::STYLE_CUSTOMBULLET:
00698             text = m_customBulletChar;
00699             break;
00700         default: // shut up compiler
00701             break;
00702         }
00703     }
00704     return text;
00705 }
00706 
00707 // Return the full text to be displayed
00708 QString KoParagCounter::text( const KoTextParag *paragraph )
00709 {
00710     // Return cached value if possible.
00711     if ( !m_cache.text.isNull() )
00712         return m_cache.text;
00713 
00714     // If necessary, grab the text of the preceding levels.
00715     if ( m_displayLevels > 1 && m_numbering != NUM_NONE )
00716     {
00717         KoTextParag* p = parent( paragraph );
00718         int displayLevels = QMIN( m_displayLevels, m_depth+1 ); // can't be >depth+1
00719         for ( int level = 1 ; level < displayLevels ; ++level ) {
00720             //kdDebug() << "additional level=" << level << "/" << displayLevels-1 << endl;
00721             if ( p )
00722             {
00723                 KoParagCounter* counter = p->counter();
00724                 QString str = counter->levelText( p );
00725                 // If the preceding level is a bullet, replace it with blanks.
00726                 if ( counter->isBullet() )
00727                     for ( unsigned i = 0; i < str.length(); i++ )
00728                         str[i] = ' ';
00729 
00730                 str.append('.'); // hardcoded on purpose (like OO) until anyone complains
00731 
00732                 // Find the number of missing parents, and add dummy text for them.
00733                 int missingParents = m_depth - level - p->counter()->m_depth;
00734                 //kdDebug() << "levelText = " << str << " missingParents=" << missingParents << endl;
00735                 level += missingParents;
00736                 for ( ; missingParents > 0 ; --missingParents )
00737                     // Each missing level adds a "0"
00738                     str.append( "0." );
00739 
00740                 m_cache.text.prepend( str );
00741                 // Prepare next iteration
00742                 if ( level < displayLevels ) // no need to calc it if we won't use it
00743                     p = counter->parent( p );
00744             }
00745             else // toplevel parents are missing
00746             {
00747                 // Special case for one-paragraph-documents like preview widgets
00748                 KoTextDocument* textdoc = paragraph->textDocument();
00749                 if ( paragraph == textdoc->firstParag() && paragraph == textdoc->lastParag() )
00750                     m_cache.text.prepend( "1." );
00751                 else
00752                     m_cache.text.prepend( "0." );
00753             }
00754         }
00755 
00756     }
00757 
00758     //kdDebug() << "result: " << m_cache.text << " + " << levelText( paragraph ) << endl;
00759     // Now add text for this level.
00760     m_cache.text.append( levelText( paragraph ) );
00761 
00762     // Now apply prefix and suffix
00763     // We want the '.' to be before the number in a RTL parag,
00764     // but we can't paint the whole string using QPainter::RTL direction, otherwise
00765     // '10' becomes '01'.
00766     m_cache.text.prepend( paragraph->string()->isRightToLeft() ? suffix() : prefix() );
00767     m_cache.text.append( paragraph->string()->isRightToLeft() ? prefix() : suffix() );
00768     return m_cache.text;
00769 }
00770 
00771 int KoParagCounter::width( const KoTextParag *paragraph )
00772 {
00773     // Return cached value if possible.
00774     if ( m_cache.width != -1 && counterFormat( paragraph ) == m_cache.counterFormat )
00775         return m_cache.width;
00776 
00777     // Ensure paragraph text is valid.
00778     if ( m_cache.text.isNull() )
00779         text( paragraph );
00780 
00781     // Now calculate width.
00782     if ( m_cache.counterFormat )
00783         m_cache.counterFormat->removeRef();
00784     m_cache.counterFormat = counterFormat( paragraph );
00785     m_cache.counterFormat->addRef();
00786     m_cache.width = 0;
00787     if ( m_style != STYLE_NONE || m_numbering == NUM_FOOTNOTE)
00788     {
00789         QString text = m_cache.text;
00790         if ( m_style == STYLE_CUSTOMBULLET && !text.isEmpty() )
00791         {
00792             text.append( "  " ); // append two trailing spaces, see KoTextParag::drawLabel
00793         }
00794         else if ( !text.isEmpty() )
00795             text.append( ' ' ); // append a trailing space, see KoTextParag::drawLabel
00796         QFontMetrics fm = m_cache.counterFormat->refFontMetrics();
00797         for ( unsigned int i = 0; i < text.length(); i++ )
00798             //m_cache.width += m_cache.counterFormat->width( text, i );
00799             m_cache.width += fm.width( text[i] );
00800     }
00801     // Now go from 100%-zoom to LU
00802     m_cache.width = KoTextZoomHandler::ptToLayoutUnitPt( m_cache.width );
00803 
00804     //kdDebug(32500) << "KoParagCounter::width recalculated parag=" << paragraph << " text='" << text << "' width=" << m_cache.width << endl;
00805     return m_cache.width;
00806 }
00807 
00808 int KoParagCounter::bulletX()
00809 {
00810     // width() must have been called first
00811     Q_ASSERT( m_cache.width != -1 );
00812     Q_ASSERT( m_cache.counterFormat );
00813     int x = 0;
00814     QFontMetrics fm = m_cache.counterFormat->refFontMetrics();
00815     QString text = prefix();
00816     for (  unsigned int i = 0; i < text.length(); i++ )
00817         x += fm.width( text[i] );
00818     // Now go from 100%-zoom to LU
00819     return KoTextZoomHandler::ptToLayoutUnitPt( x );
00820 }
00821 
00822 // Only exists to centralize code. Does no caching.
00823 KoTextFormat* KoParagCounter::counterFormat( const KoTextParag *paragraph )
00824 {
00825     KoTextFormat* refFormat = paragraph->at( 0 )->format();
00826     KoTextFormat format( *refFormat );
00827     format.setVAlign( KoTextFormat::AlignNormal );
00828     return paragraph->textDocument()->formatCollection()->format( &format );
00829     /*paragraph->paragFormat()*/
00830 }
00831 
00833 
00834 const QCString RNUnits[] = {"", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"};
00835 const QCString RNTens[] = {"", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"};
00836 const QCString RNHundreds[] = {"", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"};
00837 const QCString RNThousands[] = {"", "m", "mm", "mmm"};
00838 
00839 QString KoParagCounter::makeRomanNumber( int n )
00840 {
00841     if ( n >= 0 )
00842         return QString::fromLatin1( RNThousands[ ( n / 1000 ) ] +
00843                                     RNHundreds[ ( n / 100 ) % 10 ] +
00844                                     RNTens[ ( n / 10 ) % 10 ] +
00845                                     RNUnits[ ( n ) % 10 ] );
00846     else { // should never happen, but better not crash if it does
00847         kdWarning(32500) << "makeRomanNumber: n=" << n << endl;
00848         return QString::number( n );
00849     }
00850 }
00851 
00852 QString KoParagCounter::makeAlphaUpperNumber( int n )
00853 {
00854     QString tmp;
00855     char bottomDigit;
00856     while ( n > 26 )
00857     {
00858         bottomDigit = (n-1) % 26;
00859         n = (n-1) / 26;
00860         tmp.prepend( QChar( 'A' + bottomDigit  ) );
00861     }
00862     tmp.prepend( QChar( 'A' + n -1 ) );
00863     return tmp;
00864 }
00865 
00866 QString KoParagCounter::makeAlphaLowerNumber( int n )
00867 {
00868     QString tmp;
00869     char bottomDigit;
00870     while ( n > 26 )
00871     {
00872         bottomDigit = (n-1) % 26;
00873         n = (n-1) / 26;
00874         tmp.prepend( QChar( 'a' + bottomDigit  ) );
00875     }
00876     tmp.prepend( QChar( 'a' + n - 1 ) );
00877     return tmp;
00878 }
00879 
00880 int KoParagCounter::fromRomanNumber( const QString &string )
00881 {
00882     int ret = 0;
00883     int stringStart = 0;
00884     const int stringLen = string.length();
00885 
00886     for (int base = 1000; base >= 1 && stringStart < stringLen; base /= 10)
00887     {
00888         const QCString *rn;
00889         int rnNum;
00890         switch (base)
00891         {
00892             case 1000:
00893                 rn = RNThousands;
00894                 rnNum = sizeof (RNThousands) / sizeof (const QCString);
00895                 break;
00896             case 100:
00897                 rn = RNHundreds;
00898                 rnNum = sizeof (RNHundreds) / sizeof (const QCString);
00899                 break;
00900             case 10:
00901                 rn = RNTens;
00902                 rnNum = sizeof (RNTens) / sizeof (const QCString);
00903                 break;
00904             case 1:
00905             default:
00906                 rn = RNUnits;
00907                 rnNum = sizeof (RNUnits) / sizeof (const QCString);
00908                 break;
00909         }
00910 
00911         // I _think_ this will work :) - Clarence
00912         for (int i = rnNum - 1; i >= 1; i--)
00913         {
00914             const int rnLength = rn[i].length();
00915             if (string.mid(stringStart,rnLength) == (const char*)rn[i])
00916             {
00917                 ret += i * base;
00918                 stringStart += rnLength;
00919                 break;
00920             }
00921         }
00922     }
00923 
00924     return (ret == 0 || stringStart != stringLen) ? -1 /*invalid value*/ : ret;
00925 }
00926 
00927 int KoParagCounter::fromAlphaUpperNumber( const QString &string )
00928 {
00929     int ret = 0;
00930 
00931     const int len = string.length();
00932     for (int i = 0; i < len; i++)
00933     {
00934         const int add = char(string[i]) - 'A' + 1;
00935 
00936         if (add >= 1 && add <= 26) // _not_ < 26
00937             ret = ret * 26 + add;
00938         else
00939         {
00940             ret = -1; // invalid character
00941             break;
00942         }
00943     }
00944 
00945     return (ret == 0) ? -1 /*invalid value*/ : ret;
00946 }
00947 
00948 int KoParagCounter::fromAlphaLowerNumber( const QString &string )
00949 {
00950     int ret = 0;
00951 
00952     const int len = string.length();
00953     for (int i = 0; i < len; i++)
00954     {
00955         const int add = char(string[i]) - 'a' + 1;
00956 
00957         if (add >= 1 && add <= 26) // _not_ < 26
00958             ret = ret * 26 + add;
00959         else
00960         {
00961             ret = -1; // invalid character
00962             break;
00963         }
00964     }
00965 
00966     return (ret == 0) ? -1 /*invalid value*/ : ret;
00967 }
00968 
00969 #ifndef NDEBUG
00970 void KoParagCounter::printRTDebug( KoTextParag* parag )
00971 {
00972     QString additionalInfo;
00973     if ( restartCounter() )
00974         additionalInfo = "[restartCounter]";
00975     if ( m_style == STYLE_CUSTOMBULLET )
00976         additionalInfo += " [customBullet: " + QString::number( m_customBulletChar.unicode() )
00977                           + " in font '" + m_customBulletFont + "']";
00978     static const char * const s_numbering[] = { "List", "Chapter", "None", "Footnote" };
00979     kdDebug(32500) << "  Counter style=" << style()
00980                    << " numbering=" << s_numbering[ numbering() ]
00981                    << " depth=" << depth()
00982                    << " number=" << number( parag )
00983                    << " text='" << text( parag ) << "'"
00984                    << " width=" << width( parag )
00985                    << additionalInfo << endl;
00986 }
00987 #endif
KDE Home | KDE Accessibility Home | Description of Access Keys