lib

KoParagCounter.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2001 Shaheed Haque <srhaque@iee.org>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18*/
19
20#include "KoParagCounter.h"
21#include "KoTextParag.h"
22#include "KoTextZoomHandler.h"
23#include "KoTextFormat.h"
24#include "KoTextDocument.h"
25#include "KoOasisContext.h"
26#include <KoXmlWriter.h>
27#include <KoGenStyles.h>
28#include <KoXmlNS.h>
29#include <kdebug.h>
30#include <tqdom.h>
31#include <tqbuffer.h>
32
33static KoTextParag * const INVALID_PARAG = (KoTextParag *)-1;
34
35KoParagCounter::KoParagCounter()
36{
37 m_numbering = NUM_NONE;
38 m_style = STYLE_NONE;
39 m_depth = 0;
40 m_startNumber = 1;
41 m_displayLevels = 1;
42 m_restartCounter = false;
43 m_customBulletChar = TQChar( '-' );
44 m_customBulletFont = TQString();
45 m_align = TQt::AlignAuto;
46 invalidate();
47}
48
49bool KoParagCounter::operator==( const KoParagCounter & c2 ) const
50{
51 // ## This is kinda wrong. Unused fields (depending on the counter style) shouldn't be compared.
52 return (m_numbering==c2.m_numbering &&
53 m_style==c2.m_style &&
54 m_depth==c2.m_depth &&
55 m_startNumber==c2.m_startNumber &&
56 m_displayLevels==c2.m_displayLevels &&
57 m_restartCounter==c2.m_restartCounter &&
58 m_prefix==c2.m_prefix &&
59 m_suffix==c2.m_suffix &&
60 m_customBulletChar==c2.m_customBulletChar &&
61 m_customBulletFont==c2.m_customBulletFont &&
62 m_align==c2.m_align &&
63 m_custom==c2.m_custom);
64}
65
66TQString KoParagCounter::custom() const
67{
68 return m_custom;
69}
70
72{
73 return m_customBulletChar;
74}
75
76TQString KoParagCounter::customBulletFont() const
77{
78 return m_customBulletFont;
79}
80
81unsigned int KoParagCounter::depth() const
82{
83 return m_depth;
84}
85
87{
88 m_cache.number = -1;
89 m_cache.text = TQString();
90 m_cache.width = -1;
91 m_cache.parent = INVALID_PARAG;
92 m_cache.counterFormat = 0;
93}
94
95bool KoParagCounter::isBullet( Style style ) // static
96{
97 switch ( style )
98 {
99 case STYLE_DISCBULLET:
100 case STYLE_SQUAREBULLET:
101 case STYLE_BOXBULLET:
102 case STYLE_CIRCLEBULLET:
103 case STYLE_CUSTOMBULLET:
104 return true;
105 default:
106 return false;
107 }
108}
109
111{
112 return isBullet( m_style );
113}
114
115void KoParagCounter::load( TQDomElement & element )
116{
117 m_numbering = static_cast<Numbering>( element.attribute("numberingtype", "2").toInt() );
118 m_style = static_cast<Style>( element.attribute("type").toInt() );
119 // Old docs have this:
120 if ( m_numbering == NUM_LIST && m_style == STYLE_NONE )
121 m_numbering = NUM_NONE;
122 m_depth = element.attribute("depth").toInt();
123 m_customBulletChar = TQChar( element.attribute("bullet").toInt() );
124 m_prefix = element.attribute("lefttext");
125 if ( m_prefix.lower() == "(null)" ) // very old kword thing
126 m_prefix = TQString();
127 m_suffix = element.attribute("righttext");
128 if ( m_suffix.lower() == "(null)" )
129 m_suffix = TQString();
130 TQString s = element.attribute("start");
131 if ( s.isEmpty() )
132 m_startNumber = 1;
133 else if ( s[0].isDigit() )
134 m_startNumber = s.toInt();
135 else // support for very-old files
136 m_startNumber = s.lower()[0].latin1() - 'a' + 1;
137 s = element.attribute("display-levels");
138 if ( !s.isEmpty() )
139 m_displayLevels = TQMIN( s.toInt(), m_depth+1 ); // can't be > depth+1
140 else // Not specified -> compat with koffice-1.2: make equal to depth+1
141 m_displayLevels = m_depth+1;
142 m_customBulletFont = element.attribute("bulletfont");
143 m_custom = element.attribute("customdef");
144 m_align = element.attribute("align", "0").toInt(); //AlignAuto as defeult
145 TQString restart = element.attribute("restart");
146 m_restartCounter = (restart == "true") || (restart == "1");
147 invalidate();
148}
149
150static int importCounterType( TQChar numFormat )
151{
152 if ( numFormat == '1' )
153 return KoParagCounter::STYLE_NUM;
154 if ( numFormat == 'a' )
155 return KoParagCounter::STYLE_ALPHAB_L;
156 if ( numFormat == 'A' )
157 return KoParagCounter::STYLE_ALPHAB_U;
158 if ( numFormat == 'i' )
159 return KoParagCounter::STYLE_ROM_NUM_L;
160 if ( numFormat == 'I' )
161 return KoParagCounter::STYLE_ROM_NUM_U;
162 return KoParagCounter::STYLE_NONE;
163}
164
165// should only be called with style != none and != a bullet.
166static TQChar exportCounterType( KoParagCounter::Style style )
167{
168 static const int s_oasisCounterTypes[] =
169 { '\0', '1', 'a', 'A', 'i', 'I',
170 '\0', '\0', // custombullet, custom
171 0x2022, // circle -> small disc
172 0xE00A, // square
173 0x25CF, // disc -> large disc
174 0x27A2 // box -> right-pointing triangle
175 };
176 return TQChar( s_oasisCounterTypes[ style ] );
177}
178
179void KoParagCounter::loadOasis( KoOasisContext& context, int restartNumbering,
180 bool orderedList, bool heading, int level, bool loadingStyle )
181{
182 const TQDomElement listStyle = context.listStyleStack().currentListStyle();
183 const TQDomElement listStyleProperties = context.listStyleStack().currentListStyleProperties();
184 const TQDomElement listStyleTextProperties = context.listStyleStack().currentListStyleTextProperties();
185 loadOasisListStyle( listStyle, listStyleProperties, listStyleTextProperties,
186 restartNumbering, orderedList, heading, level, loadingStyle );
187}
188
189void KoParagCounter::loadOasisListStyle( const TQDomElement& listStyle,
190 const TQDomElement& listStyleProperties,
191 const TQDomElement& listStyleTextProperties,
192 int restartNumbering,
193 bool orderedList, bool heading, int level,
194 bool loadingStyle )
195{
196 m_numbering = heading ? NUM_CHAPTER : NUM_LIST;
197 m_depth = level - 1; // depth start at 0
198 // restartNumbering can either be provided by caller, or taken from the style
199 if ( restartNumbering == -1 && listStyle.hasAttributeNS( KoXmlNS::text, "start-value" ) )
200 restartNumbering = listStyle.attributeNS( KoXmlNS::text, "start-value", TQString() ).toInt();
201
202 // styles have a start-value, but that doesn't mean restartNumbering, as it does for paragraphs
203 m_restartCounter = loadingStyle ? false : ( restartNumbering != -1 );
204 m_startNumber = ( restartNumbering != -1 ) ? restartNumbering : 1;
205 //kdDebug() << k_funcinfo << "IN: restartNumbering=" << restartNumbering << " OUT: m_restartCounter=" << m_restartCounter << " m_startNumber=" << m_startNumber << endl;
206
207 m_prefix = listStyle.attributeNS( KoXmlNS::style, "num-prefix", TQString() );
208 m_suffix = listStyle.attributeNS( KoXmlNS::style, "num-suffix", TQString() );
209
210 if ( orderedList || heading ) {
211 m_style = static_cast<Style>( importCounterType( listStyle.attributeNS( KoXmlNS::style, "num-format", TQString())[0] ) );
212 TQString dl = listStyle.attributeNS( KoXmlNS::text, "display-levels", TQString() );
213 m_displayLevels = dl.isEmpty() ? 1 : dl.toInt();
214 } else { // bullets, see 3.3.6 p138
215 m_style = STYLE_CUSTOMBULLET;
216 TQString bulletChar = listStyle.attributeNS( KoXmlNS::text, "bullet-char", TQString() );
217 if ( !bulletChar.isEmpty() ) {
218 // Reverse engineering, I found those codes:
219 switch( bulletChar[0].unicode() ) {
220 case 0x2022: // small disc -> circle
221 m_style = STYLE_CIRCLEBULLET;
222 break;
223 case 0x25CF: // large disc -> disc
224 case 0xF0B7: // #113361
225 m_style = STYLE_DISCBULLET;
226 break;
227 case 0xE00C: // losange - TODO in kotext. Not in OASIS either (reserved Unicode area!)
228 m_style = STYLE_BOXBULLET;
229 break;
230 case 0xE00A: // square. Not in OASIS (reserved Unicode area!), but used in both OOo and kotext.
231 m_style = STYLE_SQUAREBULLET;
232 break;
233 case 0x27A2: // two-colors right-pointing triangle
234 // mapping (both ways) to box for now.
235 m_style = STYLE_BOXBULLET;
236 break;
237 default:
238 kdDebug() << "Unhandled bullet code 0x" << TQString::number( (uint)m_customBulletChar.unicode(), 16 ) << endl;
239 // fallback
240 case 0x2794: // arrow
241 case 0x2717: // cross
242 case 0x2714: // checkmark
243 m_customBulletChar = bulletChar[0];
244 // often StarSymbol when it comes from OO; doesn't matter, TQt finds it in another font if needed.
245 if ( listStyleProperties.hasAttributeNS( KoXmlNS::style, "font-name" ) )
246 {
247 m_customBulletFont = listStyleProperties.attributeNS( KoXmlNS::style, "font-name", TQString() );
248 kdDebug() << "m_customBulletFont style:font-name = " << listStyleProperties.attributeNS( KoXmlNS::style, "font-name", TQString() ) << endl;
249 }
250 else if ( listStyleTextProperties.hasAttributeNS( KoXmlNS::fo, "font-family" ) )
251 {
252 m_customBulletFont = listStyleTextProperties.attributeNS( KoXmlNS::fo, "font-family", TQString() );
253 kdDebug() << "m_customBulletFont fo:font-family = " << listStyleTextProperties.attributeNS( KoXmlNS::fo, "font-family", TQString() ) << endl;
254 }
255 // ## TODO in fact we're supposed to read it from the style pointed to by text:style-name
256 break;
257 }
258 } else { // can never happen
259 m_style = STYLE_DISCBULLET;
260 }
261 }
262 invalidate();
263}
264
265void KoParagCounter::saveOasis( KoGenStyle& listStyle, bool savingStyle ) const
266{
267 Q_ASSERT( (Style)m_style != STYLE_NONE );
268
269 // Prepare a sub-xmlwriter for the list-level-style-* element
270 TQBuffer buffer;
271 buffer.open( IO_WriteOnly );
272 KoXmlWriter listLevelWriter( &buffer, 3 /*indentation*/ );
273 const char* tagName = isBullet() ? "text:list-level-style-bullet" : "text:list-level-style-number";
274 listLevelWriter.startElement( tagName );
275
276 saveOasisListLevel( listLevelWriter, true, savingStyle );
277
278 listLevelWriter.endElement();
279 const TQString listLevelContents = TQString::fromUtf8( buffer.buffer(), buffer.buffer().size() );
280 listStyle.addChildElement( tagName, listLevelContents );
281}
282
283void KoParagCounter::saveOasisListLevel( KoXmlWriter& listLevelWriter, bool includeLevelAndProperties, bool savingStyle ) const
284{
285 if ( includeLevelAndProperties ) // false when called for footnotes-configuration
286 listLevelWriter.addAttribute( "text:level", (int)m_depth + 1 );
287 // OASIS allows to specify a text:style, the character style to use for the numbering...
288 // We currently always format as per the first character of the paragraph, but that's not perfect.
289
290 if ( isBullet() )
291 {
292 TQChar bulletChar;
293 if ( (Style)m_style == STYLE_CUSTOMBULLET )
294 {
295 bulletChar = m_customBulletChar;
296 // TODO font (text style)
297 }
298 else
299 {
300 bulletChar = exportCounterType( (Style)m_style );
301 }
302 listLevelWriter.addAttribute( "text:bullet-char", TQString( bulletChar ) );
303 }
304 else
305 {
306 if ( includeLevelAndProperties ) // not for KWVariableSettings
307 listLevelWriter.addAttribute( "text:display-levels", m_displayLevels );
308 if ( (Style)m_style == STYLE_CUSTOM )
309 ; // not implemented
310 else
311 listLevelWriter.addAttribute( "style:num-format", TQString( exportCounterType( (Style)m_style ) ) );
312
313 // m_startNumber/m_restartCounter is saved by kotextparag itself, except for styles.
314 if ( savingStyle && m_restartCounter ) {
315 listLevelWriter.addAttribute( "text:start-value", m_startNumber );
316 }
317
318 }
319 // m_numbering isn't saved, it's set depending on context (NUM_CHAPTER for headings).
320
321 listLevelWriter.addAttribute( "style:num-prefix", m_prefix );
322 listLevelWriter.addAttribute( "style:num-suffix", m_suffix );
323
324 if ( includeLevelAndProperties ) // false when called for footnotes-configuration
325 {
326 listLevelWriter.startElement( "style:list-level-properties" );
327 listLevelWriter.addAttribute( "fo:text-align", KoParagLayout::saveOasisAlignment( (TQt::AlignmentFlags)m_align ) );
328 // OASIS has other style properties: text:space-before (indent), text:min-label-width (TODO),
329 // text:min-label-distance, style:font-name (for bullets), image size and vertical alignment.
330 listLevelWriter.endElement(); // style:list-level-properties
331 }
332}
333
334int KoParagCounter::number( const KoTextParag *paragraph )
335{
336 // Return cached value if possible.
337 if ( m_cache.number != -1 )
338 return m_cache.number;
339
340 // Should we start a new list?
341 if ( m_restartCounter ) {
342 Q_ASSERT( m_startNumber != -1 );
343 m_cache.number = m_startNumber;
344 return m_startNumber;
345 }
346
347 // Go looking for another paragraph at the same level or higher level.
348 // (This code shares logic with parent())
349 KoTextParag *otherParagraph = paragraph->prev();
350 KoParagCounter *otherCounter;
351
352 switch ( m_numbering )
353 {
354 case NUM_NONE:
355 // This should not occur!
356 case NUM_FOOTNOTE:
357 m_cache.number = 0;
358 break;
359 case NUM_CHAPTER:
360 m_cache.number = m_startNumber;
361 // Go upwards...
362 while ( otherParagraph )
363 {
364 otherCounter = otherParagraph->counter();
365 if ( otherCounter && // ...look at numbered paragraphs only
366 ( otherCounter->m_numbering == NUM_CHAPTER ) && // ...same number type.
367 ( otherCounter->m_depth <= m_depth ) ) // ...same or higher level.
368 {
369 if ( ( otherCounter->m_depth == m_depth ) &&
370 ( otherCounter->m_style == m_style ) )
371 {
372 // Found a preceding paragraph of exactly our type!
373 m_cache.number = otherCounter->number( otherParagraph ) + 1;
374 }
375 else
376 {
377 // Found a preceding paragraph of higher level!
378 m_cache.number = m_startNumber;
379 }
380 break;
381 }
382 otherParagraph = otherParagraph->prev();
383 }
384 break;
385 case NUM_LIST:
386 m_cache.number = m_startNumber;
387 // Go upwards...
388 while ( otherParagraph )
389 {
390 otherCounter = otherParagraph->counter();
391 if ( otherCounter ) // look at numbered paragraphs only
392 {
393 if ( ( otherCounter->m_numbering == NUM_LIST ) && // ...same number type.
394 !isBullet( otherCounter->m_style ) && // ...not a bullet
395 ( otherCounter->m_depth <= m_depth ) ) // ...same or higher level.
396 {
397 if ( ( otherCounter->m_depth == m_depth ) &&
398 ( otherCounter->m_style == m_style ) )
399 {
400 // Found a preceding paragraph of exactly our type!
401 m_cache.number = otherCounter->number( otherParagraph ) + 1;
402 }
403 else
404 {
405 // Found a preceding paragraph of higher level!
406 m_cache.number = m_startNumber;
407 }
408 break;
409 }
410 else
411 if ( otherCounter->m_numbering == NUM_CHAPTER ) // ...heading number type.
412 {
413 m_cache.number = m_startNumber;
414 break;
415 }
416 }
417/* else
418 {
419 // There is no counter at all.
420 m_cache.number = m_startNumber;
421 break;
422 }*/
423 otherParagraph = otherParagraph->prev();
424 }
425 break;
426 }
427 Q_ASSERT( m_cache.number != -1 );
428 return m_cache.number;
429}
430
431KoParagCounter::Numbering KoParagCounter::numbering() const
432{
433 return m_numbering;
434}
435
436// Go looking for another paragraph at a higher level.
437KoTextParag *KoParagCounter::parent( const KoTextParag *paragraph )
438{
439 // Return cached value if possible.
440 if ( m_cache.parent != INVALID_PARAG )
441 return m_cache.parent;
442
443 KoTextParag *otherParagraph = paragraph->prev();
444 KoParagCounter *otherCounter;
445
446 // (This code shares logic with number())
447 switch ( m_numbering )
448 {
449 case NUM_NONE:
450 // This should not occur!
451 case NUM_FOOTNOTE:
452 otherParagraph = 0L;
453 break;
454 case NUM_CHAPTER:
455 // Go upwards while...
456 while ( otherParagraph )
457 {
458 otherCounter = otherParagraph->counter();
459 if ( otherCounter && // ...numbered paragraphs.
460 ( otherCounter->m_numbering == NUM_CHAPTER ) && // ...same number type.
461 ( otherCounter->m_depth < m_depth ) ) // ...higher level.
462 {
463 break;
464 }
465 otherParagraph = otherParagraph->prev();
466 }
467 break;
468 case NUM_LIST:
469 // Go upwards while...
470 while ( otherParagraph )
471 {
472 otherCounter = otherParagraph->counter();
473 if ( otherCounter ) // ...numbered paragraphs.
474 {
475 if ( ( otherCounter->m_numbering == NUM_LIST ) && // ...same number type.
476 !isBullet( otherCounter->m_style ) && // ...not a bullet
477 ( otherCounter->m_depth < m_depth ) ) // ...higher level.
478 {
479 break;
480 }
481 else
482 if ( otherCounter->m_numbering == NUM_CHAPTER ) // ...heading number type.
483 {
484 otherParagraph = 0L;
485 break;
486 }
487 }
488 otherParagraph = otherParagraph->prev();
489 }
490 break;
491 }
492 m_cache.parent = otherParagraph;
493 return m_cache.parent;
494}
495
497{
498 return m_prefix;
499}
500
501void KoParagCounter::save( TQDomElement & element )
502{
503 element.setAttribute( "type", static_cast<int>( m_style ) );
504 element.setAttribute( "depth", m_depth );
505 if ( (Style)m_style == STYLE_CUSTOMBULLET )
506 {
507 element.setAttribute( "bullet", m_customBulletChar.unicode() );
508 if ( !m_customBulletFont.isEmpty() )
509 element.setAttribute( "bulletfont", m_customBulletFont );
510 }
511 if ( !m_prefix.isEmpty() )
512 element.setAttribute( "lefttext", m_prefix );
513 if ( !m_suffix.isEmpty() )
514 element.setAttribute( "righttext", m_suffix );
515 if ( m_startNumber != 1 )
516 element.setAttribute( "start", m_startNumber );
517 //if ( m_displayLevels != m_depth ) // see load()
518 element.setAttribute( "display-levels", m_displayLevels );
519 // Don't need to save NUM_FOOTNOTE, it's updated right after loading
520 if ( m_numbering != NUM_NONE && m_numbering != NUM_FOOTNOTE )
521 element.setAttribute( "numberingtype", static_cast<int>( m_numbering ) );
522 if ( !m_custom.isEmpty() )
523 element.setAttribute( "customdef", m_custom );
524 if ( m_restartCounter )
525 element.setAttribute( "restart", "true" );
526 if ( !m_cache.text.isEmpty() )
527 element.setAttribute( "text", m_cache.text );
528 element.setAttribute( "align", m_align );
529}
530
531void KoParagCounter::setCustom( TQString c )
532{
533 m_custom = c;
534 invalidate();
535}
536
537void KoParagCounter::setCustomBulletCharacter( TQChar c )
538{
539 m_customBulletChar = c;
540 invalidate();
541}
542
543void KoParagCounter::setCustomBulletFont( TQString f )
544{
545 m_customBulletFont = f;
546 invalidate();
547}
548
549void KoParagCounter::setDepth( unsigned int d )
550{
551 m_depth = d;
552 invalidate();
553}
554
555void KoParagCounter::setNumbering( Numbering n )
556{
557 m_numbering = n;
558 invalidate();
559}
560
561void KoParagCounter::setPrefix( TQString p )
562{
563 m_prefix = p;
564 invalidate();
565}
566void KoParagCounter::setStartNumber( int s )
567{
568 m_startNumber = s;
569 invalidate();
570}
571
572void KoParagCounter::setDisplayLevels( int l )
573{
574 m_displayLevels = l;
575 invalidate();
576}
577
578void KoParagCounter::setAlignment( int a )
579{
580 m_align = a;
581 invalidate();
582}
583
584void KoParagCounter::setStyle( Style s )
585{
586 m_style = s;
587 invalidate();
588}
589
590void KoParagCounter::setSuffix( TQString s )
591{
592 m_suffix = s;
593 invalidate();
594}
595
597{
598 return m_startNumber;
599}
600
602{
603 return m_displayLevels;
604}
605
607{
608 return m_align;
609}
610
611KoParagCounter::Style KoParagCounter::style() const
612{
613 return m_style;
614}
615
616TQString KoParagCounter::suffix() const
617{
618 return m_suffix;
619}
620
622{
623 return m_restartCounter;
624}
625
626void KoParagCounter::setRestartCounter( bool restart )
627{
628 m_restartCounter = restart;
629 invalidate();
630}
631
632// Return the text for that level only
633TQString KoParagCounter::levelText( const KoTextParag *paragraph )
634{
635 if ( m_numbering == NUM_NONE )
636 return "";
637
638 bool bullet = isBullet( m_style );
639
640 if ( bullet && m_numbering == NUM_CHAPTER ) {
641 // Shome mishtake surely! (not sure how it can happen though)
642 m_style = STYLE_NUM;
643 bullet = false;
644 }
645
646 TQString text;
647 if ( !bullet )
648 {
649 // Ensure paragraph number is valid.
650 number( paragraph );
651
652 switch ( m_style )
653 {
654 case STYLE_NONE:
655 if ( m_numbering == NUM_LIST )
656 text = ' ';
657 break;
658 case STYLE_NUM:
659 text.setNum( m_cache.number );
660 break;
661 case STYLE_ALPHAB_L:
662 text = makeAlphaLowerNumber( m_cache.number );
663 break;
664 case STYLE_ALPHAB_U:
665 text = makeAlphaUpperNumber( m_cache.number );
666 break;
667 case STYLE_ROM_NUM_L:
668 text = makeRomanNumber( m_cache.number ).lower();
669 break;
670 case STYLE_ROM_NUM_U:
671 text = makeRomanNumber( m_cache.number ).upper();
672 break;
673 case STYLE_CUSTOM:
675 default: // shut up compiler
676 text.setNum( m_cache.number );
677 break;
678 }
679 }
680 else
681 {
682 switch ( m_style )
683 {
684 // --- these are used in export filters but are ignored by KoTextParag::drawLabel (for bulleted lists - which they are :)) ---
685 case KoParagCounter::STYLE_DISCBULLET:
686 text = '*';
687 break;
688 case KoParagCounter::STYLE_SQUAREBULLET:
689 text = '#';
690 break;
691 case KoParagCounter::STYLE_BOXBULLET:
692 text = '='; // think up a better character
693 break;
694 case KoParagCounter::STYLE_CIRCLEBULLET:
695 text = 'o';
696 break;
697 case KoParagCounter::STYLE_CUSTOMBULLET:
698 text = m_customBulletChar;
699 break;
700 default: // shut up compiler
701 break;
702 }
703 }
704 return text;
705}
706
707// Return the full text to be displayed
708TQString KoParagCounter::text( const KoTextParag *paragraph )
709{
710 // Return cached value if possible.
711 if ( !m_cache.text.isNull() )
712 return m_cache.text;
713
714 // If necessary, grab the text of the preceding levels.
715 if ( m_displayLevels > 1 && m_numbering != NUM_NONE )
716 {
717 KoTextParag* p = parent( paragraph );
718 int displayLevels = TQMIN( m_displayLevels, m_depth+1 ); // can't be >depth+1
719 for ( int level = 1 ; level < displayLevels ; ++level ) {
720 //kdDebug() << "additional level=" << level << "/" << displayLevels-1 << endl;
721 if ( p )
722 {
723 KoParagCounter* counter = p->counter();
724 TQString str = counter->levelText( p );
725 // If the preceding level is a bullet, replace it with blanks.
726 if ( counter->isBullet() )
727 for ( unsigned i = 0; i < str.length(); i++ )
728 str[i] = ' ';
729
730 str.append('.'); // hardcoded on purpose (like OO) until anyone complains
731
732 // Find the number of missing parents, and add dummy text for them.
733 int missingParents = m_depth - level - p->counter()->m_depth;
734 //kdDebug() << "levelText = " << str << " missingParents=" << missingParents << endl;
735 level += missingParents;
736 for ( ; missingParents > 0 ; --missingParents )
737 // Each missing level adds a "0"
738 str.append( "0." );
739
740 m_cache.text.prepend( str );
741 // Prepare next iteration
742 if ( level < displayLevels ) // no need to calc it if we won't use it
743 p = counter->parent( p );
744 }
745 else // toplevel parents are missing
746 {
747 // Special case for one-paragraph-documents like preview widgets
748 KoTextDocument* textdoc = paragraph->textDocument();
749 if ( paragraph == textdoc->firstParag() && paragraph == textdoc->lastParag() )
750 m_cache.text.prepend( "1." );
751 else
752 m_cache.text.prepend( "0." );
753 }
754 }
755
756 }
757
758 //kdDebug() << "result: " << m_cache.text << " + " << levelText( paragraph ) << endl;
759 // Now add text for this level.
760 m_cache.text.append( levelText( paragraph ) );
761
762 // Now apply prefix and suffix
763 // We want the '.' to be before the number in a RTL parag,
764 // but we can't paint the whole string using TQPainter::RTL direction, otherwise
765 // '10' becomes '01'.
766 m_cache.text.prepend( paragraph->string()->isRightToLeft() ? suffix() : prefix() );
767 m_cache.text.append( paragraph->string()->isRightToLeft() ? prefix() : suffix() );
768 return m_cache.text;
769}
770
771int KoParagCounter::width( const KoTextParag *paragraph )
772{
773 // Return cached value if possible.
774 if ( m_cache.width != -1 && counterFormat( paragraph ) == m_cache.counterFormat )
775 return m_cache.width;
776
777 // Ensure paragraph text is valid.
778 if ( m_cache.text.isNull() )
779 text( paragraph );
780
781 // Now calculate width.
782 if ( m_cache.counterFormat )
783 m_cache.counterFormat->removeRef();
784 m_cache.counterFormat = counterFormat( paragraph );
785 m_cache.counterFormat->addRef();
786 m_cache.width = 0;
787 if ( m_style != STYLE_NONE || m_numbering == NUM_FOOTNOTE)
788 {
789 TQString text = m_cache.text;
790 if ( m_style == STYLE_CUSTOMBULLET && !text.isEmpty() )
791 {
792 text.append( " " ); // append two trailing spaces, see KoTextParag::drawLabel
793 }
794 else if ( !text.isEmpty() )
795 text.append( ' ' ); // append a trailing space, see KoTextParag::drawLabel
796 TQFontMetrics fm = m_cache.counterFormat->refFontMetrics();
797 for ( unsigned int i = 0; i < text.length(); i++ )
798 //m_cache.width += m_cache.counterFormat->width( text, i );
799 m_cache.width += fm.width( text[i] );
800 }
801 // Now go from 100%-zoom to LU
802 m_cache.width = KoTextZoomHandler::ptToLayoutUnitPt( m_cache.width );
803
804 //kdDebug(32500) << "KoParagCounter::width recalculated parag=" << paragraph << " text='" << text << "' width=" << m_cache.width << endl;
805 return m_cache.width;
806}
807
809{
810 // width() must have been called first
811 Q_ASSERT( m_cache.width != -1 );
812 Q_ASSERT( m_cache.counterFormat );
813 int x = 0;
814 TQFontMetrics fm = m_cache.counterFormat->refFontMetrics();
815 TQString text = prefix();
816 for ( unsigned int i = 0; i < text.length(); i++ )
817 x += fm.width( text[i] );
818 // Now go from 100%-zoom to LU
820}
821
822// Only exists to centralize code. Does no caching.
823KoTextFormat* KoParagCounter::counterFormat( const KoTextParag *paragraph )
824{
825 KoTextFormat* refFormat = paragraph->at( 0 )->format();
826 KoTextFormat format( *refFormat );
827 format.setVAlign( KoTextFormat::AlignNormal );
828 return paragraph->textDocument()->formatCollection()->format( &format );
829 /*paragraph->paragFormat()*/
830}
831
833
834const TQCString RNUnits[] = {"", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"};
835const TQCString RNTens[] = {"", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"};
836const TQCString RNHundreds[] = {"", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"};
837const TQCString RNThousands[] = {"", "m", "mm", "mmm"};
838
839TQString KoParagCounter::makeRomanNumber( int n )
840{
841 if ( n >= 0 )
842 return TQString::fromLatin1( RNThousands[ ( n / 1000 ) ] +
843 RNHundreds[ ( n / 100 ) % 10 ] +
844 RNTens[ ( n / 10 ) % 10 ] +
845 RNUnits[ ( n ) % 10 ] );
846 else { // should never happen, but better not crash if it does
847 kdWarning(32500) << "makeRomanNumber: n=" << n << endl;
848 return TQString::number( n );
849 }
850}
851
852TQString KoParagCounter::makeAlphaUpperNumber( int n )
853{
854 TQString tmp;
855 char bottomDigit;
856 while ( n > 26 )
857 {
858 bottomDigit = (n-1) % 26;
859 n = (n-1) / 26;
860 tmp.prepend( TQChar( 'A' + bottomDigit ) );
861 }
862 tmp.prepend( TQChar( 'A' + n -1 ) );
863 return tmp;
864}
865
866TQString KoParagCounter::makeAlphaLowerNumber( int n )
867{
868 TQString tmp;
869 char bottomDigit;
870 while ( n > 26 )
871 {
872 bottomDigit = (n-1) % 26;
873 n = (n-1) / 26;
874 tmp.prepend( TQChar( 'a' + bottomDigit ) );
875 }
876 tmp.prepend( TQChar( 'a' + n - 1 ) );
877 return tmp;
878}
879
880int KoParagCounter::fromRomanNumber( const TQString &string )
881{
882 int ret = 0;
883 int stringStart = 0;
884 const int stringLen = string.length();
885
886 for (int base = 1000; base >= 1 && stringStart < stringLen; base /= 10)
887 {
888 const TQCString *rn;
889 int rnNum;
890 switch (base)
891 {
892 case 1000:
893 rn = RNThousands;
894 rnNum = sizeof (RNThousands) / sizeof (const TQCString);
895 break;
896 case 100:
897 rn = RNHundreds;
898 rnNum = sizeof (RNHundreds) / sizeof (const TQCString);
899 break;
900 case 10:
901 rn = RNTens;
902 rnNum = sizeof (RNTens) / sizeof (const TQCString);
903 break;
904 case 1:
905 default:
906 rn = RNUnits;
907 rnNum = sizeof (RNUnits) / sizeof (const TQCString);
908 break;
909 }
910
911 // I _think_ this will work :) - Clarence
912 for (int i = rnNum - 1; i >= 1; i--)
913 {
914 const int rnLength = rn[i].length();
915 if (string.mid(stringStart,rnLength) == (const char*)rn[i])
916 {
917 ret += i * base;
918 stringStart += rnLength;
919 break;
920 }
921 }
922 }
923
924 return (ret == 0 || stringStart != stringLen) ? -1 /*invalid value*/ : ret;
925}
926
927int KoParagCounter::fromAlphaUpperNumber( const TQString &string )
928{
929 int ret = 0;
930
931 const int len = string.length();
932 for (int i = 0; i < len; i++)
933 {
934 const int add = char(string[i]) - 'A' + 1;
935
936 if (add >= 1 && add <= 26) // _not_ < 26
937 ret = ret * 26 + add;
938 else
939 {
940 ret = -1; // invalid character
941 break;
942 }
943 }
944
945 return (ret == 0) ? -1 /*invalid value*/ : ret;
946}
947
948int KoParagCounter::fromAlphaLowerNumber( const TQString &string )
949{
950 int ret = 0;
951
952 const int len = string.length();
953 for (int i = 0; i < len; i++)
954 {
955 const int add = char(string[i]) - 'a' + 1;
956
957 if (add >= 1 && add <= 26) // _not_ < 26
958 ret = ret * 26 + add;
959 else
960 {
961 ret = -1; // invalid character
962 break;
963 }
964 }
965
966 return (ret == 0) ? -1 /*invalid value*/ : ret;
967}
968
969#ifndef NDEBUG
970void KoParagCounter::printRTDebug( KoTextParag* parag )
971{
972 TQString additionalInfo;
973 if ( restartCounter() )
974 additionalInfo = "[restartCounter]";
975 if ( m_style == STYLE_CUSTOMBULLET )
976 additionalInfo += " [customBullet: " + TQString::number( m_customBulletChar.unicode() )
977 + " in font '" + m_customBulletFont + "']";
978 static const char * const s_numbering[] = { "List", "Chapter", "None", "Footnote" };
979 kdDebug(32500) << " Counter style=" << style()
980 << " numbering=" << s_numbering[ numbering() ]
981 << " depth=" << depth()
982 << " number=" << number( parag )
983 << " text='" << text( parag ) << "'"
984 << " width=" << width( parag )
985 << additionalInfo << endl;
986}
987#endif
A generic style, i.e.
void addChildElement(const TQString &elementName, const TQString &elementContents)
Add a child element to the style properties.
Used during loading of Oasis format (and discarded at the end of the loading).
This is the structure associated with a paragraph (KoTextParag), to define the bullet or numbering of...
TQString custom() const
The string STYLE_CUSTOM.
TQChar customBulletCharacter() const
The character and font for STYLE_CUSTOMBULLET.
unsigned int depth() const
The level of the numbering.
int alignment() const
Counter alignment.
void loadOasis(KoOasisContext &context, int restartNumbering, bool orderedList, bool heading, int level, bool loadingStyle=false)
Load from OASIS XML.
void invalidate()
Invalidate the internal cache.
TQString prefix() const
Prefix and suffix strings.
void saveOasis(KoGenStyle &listStyle, bool savingStyle=false) const
Save as OASIS XML.
void load(TQDomElement &element)
KOffice-1.3 loading code.
void saveOasisListLevel(KoXmlWriter &listLevelWriter, bool includeLevelAndProperties, bool savingStyle=false) const
Part of saveOasis that is shared with KoStyleCollection::saveOasisOutlineStyles and KWVariableSetting...
Numbering numbering() const
Numbering type and style.
void save(TQDomElement &element)
KOffice-1.3 saving code.
int bulletX()
X position of the bullet ( i.e.
void loadOasisListStyle(const TQDomElement &listStyle, const TQDomElement &listStyleProperties, const TQDomElement &listStyleTextProperties, int restartNumbering, bool orderedList, bool heading, int level, bool loadingStyle)
Part of loadOasis that is shared with KWVariableSettings::loadOasis for footnotes/endnotes.
TQString levelText(const KoTextParag *paragraph)
Return the current value of the counter as a text.
int startNumber() const
Starting number.
int displayLevels() const
Number of levels whose numbers are displayed at the current level.
bool isBullet() const
Does this counter have a bullet style?
bool restartCounter() const
Should this counter start at "startNumber" (instead of being the 'last counter of the same type + 1')...
static TQCString saveOasisAlignment(TQt::AlignmentFlags alignment)
Convert an alignment flag into an alignment string (save).
Each character (KoTextStringChar) points to a KoTextFormat that defines the formatting of that charac...
static double ptToLayoutUnitPt(double pt)
Not zoom dependent.
A class for writing out XML (to any TQIODevice), with a special attention on performance.
Definition KoXmlWriter.h:37
void startElement(const char *tagName, bool indentInside=true)
Start a new element, as a child of the current element.
void endElement()
Terminate the current element.
void addAttribute(const char *attrName, const TQString &value)
Overloaded version of addAttribute( const char*, const char* ), which is a bit slower because it need...
Definition KoXmlWriter.h:76