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.
TQDomElement currentListStyleProperties() const
TQDomElement currentListStyle() const
TQDomElement currentListStyleTextProperties() const
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