F:/KPlato/koffice/libs/kotext/KoParagCounter.cpp

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

Généré le Wed Nov 22 23:41:08 2006 pour KPlato par  doxygen 1.5.1-p1