source: trunk/third/gtkhtml3/src/htmlclueflow.c @ 21460

Revision 21460, 74.8 KB checked in by ghudson, 20 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r21459, which included commits to RCS files with non-trunk default branches.
Line 
1/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2/* This file is part of the GtkHTML library.
3
4   Copyright (C) 1997 Martin Jones (mjones@kde.org)
5   Copyright (C) 1997 Torben Weis (weis@kde.org)
6   Copyright (C) 1999, 2000 Helix Code, Inc.
7
8   This library is free software; you can redistribute it and/or
9   modify it under the terms of the GNU Library General Public
10   License as published by the Free Software Foundation; either
11   version 2 of the License, or (at your option) any later version.
12
13   This library is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16   Library General Public License for more details.
17
18   You should have received a copy of the GNU Library General Public License
19   along with this library; see the file COPYING.LIB.  If not, write to
20   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21   Boston, MA 02111-1307, USA.
22*/
23
24/* This is the object that defines a paragraph in the HTML document.  */
25
26/* WARNING: it must always be the child of a clue.  */
27
28#include <config.h>
29#include <ctype.h>
30#include <string.h>
31#include <stdlib.h>
32
33#include "gtkhtml-properties.h"
34
35#include "htmlcolor.h"
36#include "htmlcolorset.h"
37#include "htmlclue.h"
38#include "htmlclueflow.h"
39#include "htmlcluealigned.h"
40#include "htmlentity.h"
41#include "htmlengine-edit.h"
42#include "htmlengine-save.h"
43#include "htmlpainter.h"
44#include "htmlplainpainter.h"
45#include "htmlsearch.h"
46#include "htmlselection.h"
47#include "htmlsettings.h"
48#include "htmltable.h"
49#include "htmltablecell.h"
50#include "htmltext.h"
51#include "htmltextslave.h"      /* FIXME */
52
53HTMLClueFlowClass html_clueflow_class;
54static HTMLClueClass *parent_class = NULL;
55
56#define HCF_CLASS(x) HTML_CLUEFLOW_CLASS (HTML_OBJECT (x)->klass)
57
58inline HTMLHAlignType html_clueflow_get_halignment          (HTMLClueFlow *flow);
59static gchar *        get_item_marker_str                   (HTMLClueFlow *flow, gboolean ascii_only);
60static guint          get_post_padding                      (HTMLClueFlow *flow,
61                                                             guint pad);
62static int            get_similar_depth                     (HTMLClueFlow *self,
63                                                             HTMLClueFlow *neighbor);
64
65static void
66copy_levels (GByteArray *dst, GByteArray *src)
67{
68        int i;
69
70        g_byte_array_set_size (dst, src->len);
71
72        for (i = 0; i < src->len; i++)
73                dst->data[i] = src->data[i];
74}
75
76static gboolean
77is_levels_equal (HTMLClueFlow *me, HTMLClueFlow *you)
78{
79        if (!you)
80                return FALSE;
81
82        if (me->levels->len != you->levels->len)
83                return FALSE;
84
85        if (me->levels->len == 0)
86                return TRUE;
87
88        return !memcmp (me->levels->data, you->levels->data, you->levels->len);
89}
90
91static inline gboolean
92is_item (HTMLClueFlow *flow)
93{
94        return flow && flow->style == HTML_CLUEFLOW_STYLE_LIST_ITEM;
95}
96
97static void
98destroy (HTMLObject *self)
99{
100        HTMLClueFlow *flow = HTML_CLUEFLOW (self);
101
102        g_byte_array_free (flow->levels, TRUE);
103        if (flow->item_color) {
104                html_color_unref (flow->item_color);
105                flow->item_color = NULL;
106        }
107
108        (* HTML_OBJECT_CLASS (parent_class)->destroy) (self);
109}
110
111static void
112copy (HTMLObject *self,
113      HTMLObject *dest)
114{
115        (* HTML_OBJECT_CLASS (parent_class)->copy) (self, dest);
116
117        HTML_CLUEFLOW (dest)->levels = html_clueflow_dup_levels (HTML_CLUEFLOW (self));
118        HTML_CLUEFLOW (dest)->style = HTML_CLUEFLOW (self)->style;
119        HTML_CLUEFLOW (dest)->item_type = HTML_CLUEFLOW (self)->item_type;
120        HTML_CLUEFLOW (dest)->item_number = HTML_CLUEFLOW (self)->item_number;
121        HTML_CLUEFLOW (dest)->clear = HTML_CLUEFLOW (self)->clear;
122        HTML_CLUEFLOW (dest)->item_color = HTML_CLUEFLOW (self)->item_color;
123        HTML_CLUEFLOW (dest)->indent_width = HTML_CLUEFLOW (self)->indent_width;
124       
125        if (HTML_CLUEFLOW (dest)->item_color)
126                html_color_ref (HTML_CLUEFLOW (dest)->item_color);
127}
128
129static inline gboolean
130is_blockquote (HTMLListType type)
131{
132        if ((type == HTML_LIST_TYPE_BLOCKQUOTE_CITE)
133            || (type == HTML_LIST_TYPE_BLOCKQUOTE))
134                return TRUE;
135
136        return FALSE;
137}
138
139static inline gboolean
140items_are_relative (HTMLObject *self, HTMLObject *next_object)
141{
142        HTMLClueFlow *flow, *next;
143
144        if (!self || !next_object)
145                return FALSE;
146        flow = HTML_CLUEFLOW (self);
147        next = HTML_CLUEFLOW (next_object);
148
149        if (!is_item (flow)
150            || !is_item (next)
151            || !is_levels_equal (flow, next)
152            || next->item_type != flow->item_type)
153                return FALSE;
154
155        return TRUE;
156}
157
158static HTMLObject *
159get_prev_relative_item (HTMLObject *self)
160{
161        HTMLObject *prev;
162       
163        prev = self->prev;
164        while (prev
165               && HTML_IS_CLUEFLOW (prev)
166               && (HTML_CLUEFLOW (prev)->levels->len > HTML_CLUEFLOW (self)->levels->len
167                   || (HTML_CLUEFLOW (prev)->levels->len == HTML_CLUEFLOW (self)->levels->len
168                       && !is_item (HTML_CLUEFLOW (prev))))
169               && !memcmp (HTML_CLUEFLOW (prev)->levels->data,
170                           HTML_CLUEFLOW (self)->levels->data,
171                           HTML_CLUEFLOW (self)->levels->len))
172               
173                prev = prev->prev;
174
175        return prev;
176}
177
178static HTMLObject *
179get_next_relative_item (HTMLObject *self)
180{
181        HTMLObject *next;
182
183        next = self->next;
184        while (next
185               && HTML_IS_CLUEFLOW (next)
186               && (HTML_CLUEFLOW (next)->levels->len > HTML_CLUEFLOW (self)->levels->len
187                   || (HTML_CLUEFLOW (next)->levels->len == HTML_CLUEFLOW (self)->levels->len
188                       && !is_item (HTML_CLUEFLOW (next))))
189               && !memcmp (HTML_CLUEFLOW (next)->levels->data,
190                           HTML_CLUEFLOW (self)->levels->data,
191                           HTML_CLUEFLOW (self)->levels->len))
192               
193                next = next->next;
194
195        return next;
196}
197
198static void
199update_item_number (HTMLObject *self, HTMLEngine *e)
200{
201        HTMLObject *prev, *next;
202
203        if (!self || !is_item (HTML_CLUEFLOW (self)))
204                return;
205
206        /* printf ("update_item_number\n"); */
207
208        prev = get_prev_relative_item (self);
209        if (items_are_relative (prev, self))
210                HTML_CLUEFLOW (self)->item_number = HTML_CLUEFLOW (prev)->item_number + 1;
211        else if (is_item (HTML_CLUEFLOW (self)))
212                HTML_CLUEFLOW (self)->item_number = 1;
213        html_engine_queue_draw (e, self);
214
215        next = self;
216        while ((next = get_next_relative_item (next)) && items_are_relative (self, next)) {
217                HTML_CLUEFLOW (next)->item_number = HTML_CLUEFLOW (self)->item_number + 1;
218                html_engine_queue_draw (e, next);
219                self = next;
220        }
221}
222
223static guint
224get_recursive_length (HTMLObject *self)
225{
226        return (*HTML_OBJECT_CLASS (parent_class)->get_recursive_length) (self) + (self->next ? 1 : 0);
227}
228
229static HTMLObject *
230op_helper (HTMLObject *self, HTMLEngine *e, GList *from, GList *to, GList *left, GList *right, guint *len, gboolean cut)
231{
232        HTMLObject *o;
233
234        /* if (!from && to && HTML_IS_TABLE (to->data) && to->next && GPOINTER_TO_INT (to->next->data) == 0)
235                return NULL;
236        if (!to && from && HTML_IS_TABLE (from->data) && from->next && GPOINTER_TO_INT (from->next->data) == 1)
237                return NULL;
238        if (!from && (*len || !(self->prev && HTML_IS_CLUEFLOW (self->prev) && HTML_IS_TABLE (HTML_CLUE (self->prev)->tail))))
239        (*len) ++; */
240        if (!from && self->prev) {
241                (*len) ++;
242                /* if (cut)
243                   e->cursor->position --; */
244        }
245        if (cut)
246                html_clue_remove_text_slaves (HTML_CLUE (self));
247        o = cut
248                ? (*HTML_OBJECT_CLASS (parent_class)->op_cut) (self, e, from, to, left, right, len)
249                : (*HTML_OBJECT_CLASS (parent_class)->op_copy) (self, NULL, e, from, to, len);
250
251        if (!cut && o) {
252                html_clue_remove_text_slaves (HTML_CLUE (o));
253        }
254
255        return o;
256}
257
258static HTMLObject *
259op_copy (HTMLObject *self, HTMLObject *parent, HTMLEngine *e, GList *from, GList *to, guint *len)
260{
261        return op_helper (self, e, from, to, NULL, NULL, len, FALSE);
262}
263
264static HTMLObject *
265op_cut (HTMLObject *self, HTMLEngine *e, GList *from, GList *to, GList *left, GList *right, guint *len)
266{
267        HTMLObject *rv, *prev, *next;
268
269        prev = self->prev;
270        next = self->next;
271
272        rv = op_helper (self, e, from, to, left, right, len, TRUE);
273
274        if (prev && from) {
275                update_item_number (prev, e);
276                if (prev->next == self)
277                        update_item_number (self, e);
278        }
279        if (next && to) {
280                if (next->prev == self)
281                        update_item_number (self, e);
282                update_item_number (next, e);
283        }
284
285        return rv;
286}
287
288static void
289set_head_size (HTMLObject *o)
290{
291        if (o && HTML_CLUE (o)->head)
292                HTML_CLUE (o)->head->change |= HTML_CHANGE_SIZE;
293}
294
295static void
296set_tail_size (HTMLObject *o)
297{
298        if (o && HTML_CLUE (o)->tail) {
299                HTML_CLUE (o)->tail->change |= HTML_CHANGE_SIZE;
300        }
301}
302
303static void
304set_around_size (HTMLObject *o) {
305        if (o) {
306                o->change |= HTML_CHANGE_SIZE;
307                if (o->next)
308                        o->next->change |= HTML_CHANGE_SIZE;
309                if (o->prev)
310                        o->prev->change |= HTML_CHANGE_SIZE;
311        }
312}
313
314static void
315split (HTMLObject *self, HTMLEngine *e, HTMLObject *child, gint offset, gint level, GList **left, GList **right)
316{
317        set_around_size (child);
318        html_clue_remove_text_slaves (HTML_CLUE (self));
319        (*HTML_OBJECT_CLASS (parent_class)->split) (self, e, child, offset, level, left, right);
320
321        update_item_number (self, e);
322}
323
324static gboolean
325merge (HTMLObject *self, HTMLObject *with, HTMLEngine *e, GList **left, GList **right, HTMLCursor *cursor)
326{
327        HTMLClueFlow *cf1, *cf2;
328        HTMLObject *cf2_next_relative;
329        gboolean rv;
330
331        cf1 = HTML_CLUEFLOW (self);
332        cf2 = HTML_CLUEFLOW (with);
333
334        html_clue_remove_text_slaves (HTML_CLUE (cf1));
335        html_clue_remove_text_slaves (HTML_CLUE (cf2));
336
337        cf2_next_relative = get_next_relative_item (with);
338
339        set_tail_size (self);
340        set_head_size (with);
341
342        if (html_clueflow_is_empty (cf1)) {
343                self->x = with->x;
344                self->y = with->y;
345                self->width = with->width;
346                self->ascent = with->ascent;
347                self->descent = with->descent;
348                HTML_CLUE  (cf1)->halign = HTML_CLUE (cf2)->halign;
349                HTML_CLUE  (cf1)->valign = HTML_CLUE (cf2)->valign;
350                html_object_copy_data_from_object (self, with);
351        }
352
353        rv = (* HTML_OBJECT_CLASS (parent_class)->merge) (self, with, e, left, right, cursor);
354
355        if (rv) {
356                if (is_item (cf1)) {
357                        /* cf2 will be removed, update item numbers around
358                           as if it was already removed - it has to have
359                           the same item style as cf1 to not break item lists*/
360                        g_byte_array_free (cf2->levels, TRUE);
361                        cf2->levels = html_clueflow_dup_levels (cf1);
362                        cf2->style = cf1->style;
363                        cf2->item_type = cf1->item_type;
364
365                        update_item_number (self, e);
366                        cf1->item_number --;
367                        update_item_number (with, e);
368                        cf1->item_number ++;
369
370                        if (cf2_next_relative)
371                                update_item_number (cf2_next_relative, e);
372                }
373       
374        }
375               
376        return rv;
377}
378
379static guint
380calc_padding (HTMLPainter *painter)
381{
382        if (!HTML_IS_PLAIN_PAINTER (painter)) {
383                return 2 * html_painter_get_space_width (painter, GTK_HTML_FONT_STYLE_SIZE_3, NULL);
384        }
385        return 0;
386}
387
388static gboolean
389is_cite (HTMLClueFlow *flow, gint level)
390{
391        if (flow->levels->data[level] == HTML_LIST_TYPE_BLOCKQUOTE_CITE)
392                return TRUE;
393
394        return FALSE;
395}
396 
397static gboolean
398is_header (HTMLClueFlow *flow)
399{
400        switch (flow->style) {
401        case HTML_CLUEFLOW_STYLE_H1:
402        case HTML_CLUEFLOW_STYLE_H2:
403        case HTML_CLUEFLOW_STYLE_H3:
404        case HTML_CLUEFLOW_STYLE_H4:
405        case HTML_CLUEFLOW_STYLE_H5:
406        case HTML_CLUEFLOW_STYLE_H6:
407                return TRUE;
408        default:
409                return FALSE;
410        }
411}
412
413static gboolean
414need_blockquote_padding  (HTMLClueFlow *flow, HTMLClueFlow *prev)
415{
416        int i = get_similar_depth (flow, prev);
417       
418        /*
419         * If the levels don't match up the the current flow
420         * level the padding should be handled the other way.
421         */
422        if (i < flow->levels->len || flow->levels->len == 0) {
423                if (i < prev->levels->len)
424                        return TRUE;
425                else
426                        return FALSE;
427        }
428       
429        i = prev->levels->len - i;
430       
431        /*
432         * now we check each level greater than the current flow level
433         * and see if it is a blockquote and therefore needs padding
434         */
435        while (i > 0) {
436                HTMLListType type;
437               
438                type = prev->levels->data [prev->levels->len - i];
439
440                if (is_blockquote (type)) {
441                        return TRUE;
442                }
443                i--;
444        }
445
446        /*
447         * If all the levels were items we don't need padding
448         */
449        return FALSE;
450}
451
452static guint
453get_pre_padding (HTMLClueFlow *flow, guint pad)
454{
455        HTMLObject *prev_object;
456
457        prev_object = HTML_OBJECT (flow)->prev;
458        if (prev_object == NULL)
459                return 0;
460
461        if (HTML_OBJECT_TYPE (prev_object) == HTML_TYPE_CLUEFLOW) {
462                HTMLClueFlow *prev;
463
464                if (get_post_padding (HTML_CLUEFLOW (prev_object), 1))
465                        return 0;
466
467                prev = HTML_CLUEFLOW (prev_object);
468
469                if (!is_levels_equal (flow, prev)) {
470                        if (need_blockquote_padding (flow, prev))
471                                return pad;
472                        else
473                                return 0;
474                }
475
476                if (flow->style == HTML_CLUEFLOW_STYLE_PRE
477                    && prev->style != HTML_CLUEFLOW_STYLE_PRE
478                    && ! is_header (prev))
479                        return pad;
480
481                if (is_header (flow) && ! is_header (prev))
482                        return pad;
483
484                return 0;
485        }
486
487        if (! is_header (flow) && flow->levels->len == 0)
488                return 0;
489       
490        return pad;
491}
492
493static guint
494get_post_padding (HTMLClueFlow *flow,
495                  guint pad)
496{
497        HTMLObject *next_object;
498
499        next_object = HTML_OBJECT (flow)->next;
500        if (next_object == NULL)
501                return 0;
502
503        if (HTML_OBJECT_TYPE (next_object) == HTML_TYPE_CLUEFLOW) {
504                HTMLClueFlow *next;
505
506                next = HTML_CLUEFLOW (next_object);
507
508                if (!is_levels_equal (next, flow)) {
509                        if (need_blockquote_padding (flow, next))
510                                return pad;
511                        else
512                                return 0;
513                }
514
515                if (flow->style == HTML_CLUEFLOW_STYLE_PRE
516                    && next->style != HTML_CLUEFLOW_STYLE_PRE
517                    && ! is_header (next))
518                        return pad;
519
520                if (is_header (flow))
521                        return pad;
522
523                return 0;
524        }
525
526        if (! is_header (flow) && flow->levels->len == 0)
527                return 0;
528
529        return pad;
530}
531
532static void
533add_pre_padding (HTMLClueFlow *flow,
534                 guint pad)
535{
536        guint real_pad;
537
538        real_pad = get_pre_padding (flow, pad);
539
540        HTML_OBJECT (flow)->ascent += real_pad;
541        HTML_OBJECT (flow)->y += real_pad;
542}
543
544static void
545add_post_padding (HTMLClueFlow *flow,
546                  guint pad)
547{
548        guint real_pad;
549
550        real_pad = get_post_padding (flow, pad);
551
552        HTML_OBJECT (flow)->ascent += real_pad;
553        HTML_OBJECT (flow)->y += real_pad;
554}
555
556static guint
557get_level_indent (HTMLClueFlow *flow,
558                  gint level,
559                  HTMLPainter *painter)
560{
561        guint indent = 0;
562        gint i = 0;
563
564        if (flow->levels->len > 0 || ! is_item (flow)) {
565                guint cite_width, indent_width;
566
567                cite_width   = html_painter_get_block_cite_width (painter, GTK_HTML_FONT_STYLE_SIZE_3, NULL);
568                indent_width = html_painter_get_block_indent_width (painter, GTK_HTML_FONT_STYLE_SIZE_3, NULL);
569               
570                while (i <= level) {
571                        switch (flow->levels->data[i]) {
572                        case HTML_LIST_TYPE_BLOCKQUOTE_CITE:
573                                indent += cite_width;
574                                break;
575                        case HTML_LIST_TYPE_GLOSSARY_DL:
576                                indent += 0;
577                                break;
578                        default:
579                                indent += indent_width;
580                                break;
581                        }
582                        i++;
583                }
584        } else {
585                GtkHTMLFontStyle style;
586                style = html_clueflow_get_default_font_style (flow);
587
588                indent = 4 * html_painter_get_space_width (painter, style, NULL);
589        }
590
591        return indent;
592}
593
594static void
595set_painter (HTMLObject *o, HTMLPainter *painter)
596{
597        HTML_CLUEFLOW (o)->indent_width = -1;
598}
599
600static guint
601get_indent (HTMLClueFlow *flow,
602            HTMLPainter *painter)
603{
604        if (flow->indent_width < 0 )
605                flow->indent_width = get_level_indent (flow, flow->levels->len -1, painter);
606
607        return flow->indent_width;
608}
609 
610/* HTMLObject methods.  */
611
612static void
613set_max_width (HTMLObject *o,
614               HTMLPainter *painter,
615               gint max_width)
616{
617        HTMLObject *obj;
618        guint indent;
619
620        o->max_width = max_width;
621
622        indent = get_indent (HTML_CLUEFLOW (o), painter);
623       
624        for (obj = HTML_CLUE (o)->head; obj != 0; obj = obj->next) {
625                html_object_set_max_width (obj, painter, o->max_width - indent);
626        }
627}
628
629static gint
630calc_min_width (HTMLObject *o,
631                HTMLPainter *painter)
632{
633        HTMLObject *cur;
634        gint min_width = 0;
635        gint aligned_min_width = 0;
636        gint w = 0;
637        gboolean add;
638
639        add = HTML_CLUEFLOW (o)->style == HTML_CLUEFLOW_STYLE_PRE;
640
641        cur = HTML_CLUE (o)->head;
642        while (cur) {
643                if (cur->flags & HTML_OBJECT_FLAG_ALIGNED)
644                        aligned_min_width = MAX (aligned_min_width, html_object_calc_min_width (cur, painter));
645                else {
646                        w += add
647                                ? html_object_calc_preferred_width (cur, painter)
648                                : html_object_calc_min_width (cur, painter);
649                        if (!add || !cur->next) {
650                                if (min_width < w) min_width = w;
651                                w = 0;
652                        }
653                }
654                cur = cur->next;
655        }
656
657        return MAX (aligned_min_width, min_width) + get_indent (HTML_CLUEFLOW (o), painter);
658}
659
660static gint
661pref_right_margin (HTMLPainter *p, HTMLClueFlow *clueflow, HTMLObject *o, gint y, gboolean with_aligned)
662{
663        gint fixed_margin = html_object_get_right_margin (o, p, y, with_aligned);
664
665        /* FIXME: this hack lets us wrap the display at 72 characters when we are using
666           a plain painter */
667         
668        if (clueflow->style == HTML_CLUEFLOW_STYLE_PRE || ! HTML_IS_PLAIN_PAINTER(p))
669                return fixed_margin;
670
671        return MIN (fixed_margin, 72 * (MAX (html_painter_get_space_width (p, GTK_HTML_FONT_STYLE_SIZE_3 | GTK_HTML_FONT_STYLE_FIXED, NULL),
672                                             html_painter_get_e_width (p, GTK_HTML_FONT_STYLE_SIZE_3 | GTK_HTML_FONT_STYLE_FIXED, NULL))));
673}
674
675static void
676add_clear_area (GList **changed_objs, HTMLObject *o, gint x, gint w)
677{
678        HTMLObjectClearRectangle *cr;
679
680        cr = g_new (HTMLObjectClearRectangle, 1);
681
682        cr->object = o;
683        cr->x = x;
684        cr->y = 0;
685        cr->width = w;
686        cr->height = o->ascent + o->descent;
687
688        *changed_objs = g_list_prepend (*changed_objs, cr);
689        /* NULL means: clear following rectangle */
690        *changed_objs = g_list_prepend (*changed_objs, NULL);
691}
692
693static void
694calc_margins (HTMLObject *o, HTMLPainter *painter, gint indent, gint *lmargin, gint *rmargin)
695{
696        *lmargin = html_object_get_left_margin (o->parent, painter, o->y, TRUE);
697        if (indent > *lmargin)
698                *lmargin = indent;
699        *rmargin = pref_right_margin (painter, HTML_CLUEFLOW (o), o->parent, o->y, TRUE);
700}
701
702static inline gint
703width_left (HTMLObject *o, gint x, gint rmargin)
704{
705        return HTML_CLUEFLOW (o)->style == HTML_CLUEFLOW_STYLE_PRE ? G_MAXINT : rmargin - x;
706}
707
708static gint
709object_nb_width (HTMLObject *o, HTMLPainter *painter, gboolean lineBegin)
710{
711        if (HTML_IS_TEXT_SLAVE (o))
712                return html_text_slave_get_nb_width (HTML_TEXT_SLAVE (o), painter, lineBegin);
713               
714        return html_object_calc_min_width (o, painter);
715}
716
717static inline gboolean
718is_top_aligned (HTMLVAlignType valign)
719{
720        return valign == HTML_VALIGN_TOP;
721}
722
723static inline void
724update_leafs_children_changed_size (HTMLObject *o, gboolean *leaf_children_changed_size)
725{
726        if (o && o->change & HTML_CHANGE_SIZE
727            && HTML_OBJECT_TYPE (o) != HTML_TYPE_TEXTSLAVE && !html_object_is_container (o))
728                *leaf_children_changed_size = TRUE;
729}
730
731static inline void
732update_height (HTMLObject *o, HTMLVAlignType valign, gint *a, gint *d, gint *height, gboolean *top)
733{
734        switch (valign) {
735        case HTML_VALIGN_TOP:
736                *top = TRUE;
737                *height = MAX (*height, o->ascent + o->descent);
738                break;
739        case HTML_VALIGN_NONE:
740        case HTML_VALIGN_BOTTOM:
741                *a = MAX (*a, o->ascent);
742                *d = MAX (*d, o->descent);
743                *height = MAX (*height, *a + *d);
744                break;
745        case HTML_VALIGN_MIDDLE: {
746                gint h, h2;
747
748                h = o->ascent + o->descent;
749                h2 = h / 2;
750                *a = MAX (*a, h2);
751                *d = MAX (*d, h - h2);
752                *height = MAX (*height, *a + *d);
753                break;
754        }
755        }
756}
757
758static inline void
759update_top_height (HTMLObject *begin, HTMLObject *end, gint *a, gint *d, gint *height)
760{
761        while (begin && begin != end) {
762                if (html_object_get_valign (begin) == HTML_VALIGN_TOP) {
763                        gint rest = begin->ascent + begin->descent - *a;
764
765                        if (rest > *d) {
766                                *d = rest;
767                                *height = MAX (*height, *a + *d);
768                        }
769                }
770                begin = begin->next;
771        }
772}
773
774static inline void
775update_line_positions (HTMLObject *clue, HTMLObject *begin, HTMLObject *end, gint left, gint a, gint d, gint height)
776{
777        gint xinc = 0;
778
779        switch (html_clueflow_get_halignment (HTML_CLUEFLOW (clue))) {
780        case HTML_HALIGN_NONE:
781        case HTML_HALIGN_LEFT:
782                xinc = 0;
783                break;
784        case HTML_HALIGN_CENTER:
785                xinc = left / 2;
786                break;
787        case HTML_HALIGN_RIGHT:
788                xinc = left;
789                break;
790        }
791
792        while (begin && begin != end) {
793                begin->x += xinc;
794
795                switch (html_object_get_valign (begin)) {
796                case HTML_VALIGN_NONE:
797                case HTML_VALIGN_BOTTOM:
798                        begin->y = clue->ascent + a;
799                        break;
800                case HTML_VALIGN_MIDDLE:
801                        begin->y = clue->ascent + (height - begin->ascent - begin->descent) / 2 + begin->ascent;
802                        break;
803                case HTML_VALIGN_TOP:
804                        begin->y = clue->ascent + begin->ascent;
805                        break;
806                }
807                begin = begin->next;
808        }
809}
810
811
812static HTMLObject *
813layout_line (HTMLObject *o, HTMLPainter *painter, HTMLObject *begin,
814             GList **changed_objs, gboolean *leaf_children_changed_size,
815             gint *lmargin, gint *rmargin, gint indent)
816{
817        HTMLObject *cur;
818        gboolean first = TRUE;
819        gboolean top_align = FALSE;
820        gboolean need_update_height = FALSE;
821        gint old_y;
822        gint x;
823        gint start_lmargin;
824        gint a, d, height;
825        gint nb_width;
826
827        if (html_object_is_text (begin)) {
828                update_leafs_children_changed_size (begin, leaf_children_changed_size);
829                /* this ever succeds and creates slaves */
830                html_object_calc_size (begin, painter, changed_objs);
831                html_object_fit_line (begin, painter, first, first, FALSE, 0);
832                begin = begin->next;
833        }
834        cur = begin;
835
836        old_y = o->y;
837        if (!HTML_IS_TEXT_SLAVE (begin) || HTML_IS_TEXT (begin->prev))
838                html_object_calc_size (begin, painter, changed_objs);
839
840        a = d = height = 0;
841        update_height (begin, html_object_get_valign (begin), &a, &d, &height, &top_align);
842
843        nb_width = object_nb_width (begin, painter, first);
844        if (*rmargin - *lmargin < nb_width)
845                html_clue_find_free_area (HTML_CLUE (o->parent), painter, o->y,
846                                          nb_width, height,
847                                          indent, &o->y, lmargin, rmargin);
848
849        x = start_lmargin = *lmargin;
850        o->ascent += o->y - old_y;
851
852        while (cur && !(cur->flags & HTML_OBJECT_FLAG_ALIGNED)) {
853                HTMLFitType fit;
854                HTMLVAlignType valign;
855
856                update_leafs_children_changed_size (cur, leaf_children_changed_size);
857
858                cur->x = x;
859                if (cur != begin)
860                        html_object_calc_size (cur, painter, changed_objs);
861
862                valign = html_object_get_valign (cur);
863                if ((!is_top_aligned (valign) && (cur->ascent > a || cur->descent > d))
864                    || cur->ascent + cur->descent > height) {
865                        nb_width = object_nb_width (cur, painter, first);
866                        old_y = o->y;
867                        html_clue_find_free_area (HTML_CLUE (o->parent), painter, o->y,
868                                                  nb_width, height,
869                                                  indent, &o->y, lmargin, rmargin);
870
871                        /* is there enough space for this object? */
872                        if (HTML_CLUEFLOW (o)->style != HTML_CLUEFLOW_STYLE_PRE && o->y != old_y && *rmargin - x < nb_width)
873                                break;
874                        need_update_height = TRUE;
875                }
876
877                cur->y = o->ascent + a;
878                fit = html_object_fit_line (cur, painter, first, first, FALSE, width_left (o, x, *rmargin));
879                first = FALSE;
880                if (fit == HTML_FIT_NONE)
881                        break;
882
883                if (need_update_height)
884                        update_height (cur, valign, &a, &d, &height, &top_align);
885                need_update_height = FALSE;
886                x += cur->width;
887                cur = cur->next;
888
889                if (fit == HTML_FIT_PARTIAL)
890                        break;
891        }
892
893        if (top_align)
894                update_top_height (begin, cur, &a, &d, &height);
895        update_line_positions (o, begin, cur, MAX (0, *rmargin - start_lmargin - x), a, d, height);
896
897        o->y += height;
898        o->ascent += height;
899
900        calc_margins (o, painter, indent, lmargin, rmargin);
901
902        return cur;
903}
904
905static HTMLObject *
906layout_aligned (HTMLObject *o, HTMLPainter *painter, HTMLObject *cur,
907                GList **changed_objs, gboolean *leaf_children_changed_size,
908                gint *lmargin, gint *rmargin, gint indent, gboolean *changed)
909{
910        if (! html_clue_appended (HTML_CLUE (o->parent), HTML_CLUE (cur))) {
911                html_object_calc_size (cur, painter, changed_objs);
912
913                if (HTML_CLUE (cur)->halign == HTML_HALIGN_LEFT)
914                        html_clue_append_left_aligned (HTML_CLUE (o->parent), painter,
915                                                       HTML_CLUE (cur), lmargin, rmargin, indent);
916                else
917                        html_clue_append_right_aligned (HTML_CLUE (o->parent), painter,
918                                                        HTML_CLUE (cur), lmargin, rmargin, indent);
919                *changed = TRUE;
920        }
921
922        return cur->next;
923}
924
925static gboolean
926html_clue_flow_layout (HTMLObject *o, HTMLPainter *painter, GList **changed_objs, gboolean *leaf_children_changed_size)
927{
928        HTMLClueFlow *cf = HTML_CLUEFLOW (o);
929        HTMLObject *cur = HTML_CLUE (o)->head;
930        gint indent, lmargin, rmargin;
931        gboolean changed = FALSE;
932
933        /* prepare margins */
934        indent = get_indent (cf, painter);
935        calc_margins (o, painter, indent, &lmargin, &rmargin);
936
937        while (cur) {
938                if (cur->flags & HTML_OBJECT_FLAG_ALIGNED)
939                        cur = layout_aligned (o, painter, cur, changed_objs, leaf_children_changed_size,
940                                              &lmargin, &rmargin, indent, &changed);
941                else
942                        cur = layout_line (o, painter, cur, changed_objs, leaf_children_changed_size,
943                                           &lmargin, &rmargin, indent);
944        }
945
946        return changed;
947}
948
949static gboolean
950html_clue_flow_real_calc_size (HTMLObject *o, HTMLPainter *painter, GList **changed_objs)
951{
952        HTMLClueFlow *cf = HTML_CLUEFLOW (o);
953        gint oa, od, ow, padding;
954        gboolean leaf_children_changed_size = FALSE;
955        gboolean changed, changed_size = FALSE;
956
957        /* reset size */
958        oa = o->ascent;
959        od = o->descent;
960        ow = o->width;
961
962        cf->indent_width = -1;
963
964        o->ascent = 0;
965        o->descent = 0;
966        o->width = MAX (o->max_width, html_object_calc_min_width (o, painter));
967
968        /* calc size */
969        padding = calc_padding (painter);
970        add_pre_padding (cf, padding);
971        changed = html_clue_flow_layout (o, painter, changed_objs, &leaf_children_changed_size);
972        add_post_padding (cf, padding);
973
974        /* take care about changes */
975        if (o->ascent != oa || o->descent != od || o->width != ow)
976                changed = changed_size = TRUE;
977
978        if (changed_size || leaf_children_changed_size)
979                if (changed_objs) {
980                        if (ow > o->max_width && o->width < ow)
981                                add_clear_area (changed_objs, o, o->width, ow - o->width);
982                        html_object_add_to_changed (changed_objs, o);
983                }
984
985        return changed;
986}
987
988static void
989set_max_height (HTMLObject *o, HTMLPainter *painter, gint max_height)
990{
991}
992
993static HTMLClearType
994get_clear (HTMLObject *self)
995{
996        return HTML_CLUEFLOW (self)->clear;
997}
998
999static gint
1000calc_preferred_width (HTMLObject *o,
1001                      HTMLPainter *painter)
1002{
1003        HTMLObject *obj, *next;
1004        gint maxw = 0, w = 0;
1005
1006        next = NULL;
1007
1008        for (obj = HTML_CLUE (o)->head; obj != 0; obj = obj->next) {
1009                w += html_object_calc_preferred_width (obj, painter);
1010
1011                if (!(next = html_object_next_not_slave (obj))) {
1012
1013                        /* remove trailing space width on the end of line which is not on end of paragraph */
1014                        if (next && html_object_is_text (obj))
1015                                w -= html_text_trail_space_width (HTML_TEXT (obj), painter);
1016
1017                        if (w > maxw)
1018                                maxw = w;
1019                        w = 0;
1020                }
1021        }
1022
1023        return maxw + get_indent (HTML_CLUEFLOW (o), painter);
1024}
1025
1026static gchar *
1027get_alpha_value (gint value, gboolean lower)
1028{
1029        GString *str;
1030        gchar *rv;
1031        gint add = lower ? 'a' : 'A';
1032
1033        str = g_string_new (". ");
1034
1035        do {
1036                g_string_prepend_c (str, ((value - 1) % 26) + add);
1037                value = (value - 1) / 26;
1038        } while (value);
1039
1040        rv = str->str;
1041        g_string_free (str, FALSE);
1042
1043        return rv;
1044}
1045
1046#define BASES 7
1047
1048static gchar *
1049get_roman_value (gint value, gboolean lower)
1050{
1051        GString *str;
1052        gchar *rv, *base = "IVXLCDM";
1053        gint b, r, add = lower ? 'a' - 'A' : 0;
1054
1055        if (value > 3999)
1056                return g_strdup ("?. ");
1057
1058        str = g_string_new (". ");
1059
1060        for (b = 0; value > 0 && b < BASES - 1; b += 2, value /= 10) {
1061                r = value % 10;
1062                if (r != 0) {
1063                        if (r < 4) {
1064                                for (; r; r--)
1065                                        g_string_prepend_c (str, base [b] + add);
1066                        } else if (r == 4) {
1067                                g_string_prepend_c (str, base [b + 1] + add);
1068                                g_string_prepend_c (str, base [b] + add);
1069                        } else if (r == 5) {
1070                                g_string_prepend_c (str, base [b + 1] + add);
1071                        } else if (r < 9) {
1072                                for (; r > 5; r--)
1073                                        g_string_prepend_c (str, base [b] + add);
1074                                g_string_prepend_c (str, base [b + 1] + add);
1075                        } else if (r == 9) {
1076                                g_string_prepend_c (str, base [b + 2] + add);
1077                                g_string_prepend_c (str, base [b] + add);
1078                        }
1079                }
1080        }
1081
1082        rv = str->str;
1083        g_string_free (str, FALSE);
1084
1085        return rv;
1086}
1087
1088static gchar *
1089get_item_marker_str (HTMLClueFlow *flow, gboolean ascii_only)
1090{
1091        HTMLListType type = flow->item_type;
1092
1093        if (type == HTML_LIST_TYPE_BLOCKQUOTE && flow->levels->len > 0) {
1094                int i;
1095
1096                for (i = flow->levels->len - 1; i >= 0; i --) {
1097                        if (flow->levels->data [i] != HTML_LIST_TYPE_BLOCKQUOTE) {
1098                                type = flow->levels->data [i];
1099                                break;
1100                        }
1101                }
1102        }
1103
1104        switch (type) {
1105        case HTML_LIST_TYPE_ORDERED_ARABIC:
1106                return g_strdup_printf ("%d. ", flow->item_number);
1107        case HTML_LIST_TYPE_ORDERED_LOWER_ALPHA:
1108        case HTML_LIST_TYPE_ORDERED_UPPER_ALPHA:
1109                return get_alpha_value (flow->item_number, flow->item_type == HTML_LIST_TYPE_ORDERED_LOWER_ALPHA);
1110        case HTML_LIST_TYPE_ORDERED_LOWER_ROMAN:
1111        case HTML_LIST_TYPE_ORDERED_UPPER_ROMAN:
1112                return get_roman_value (flow->item_number, flow->item_type == HTML_LIST_TYPE_ORDERED_LOWER_ROMAN);
1113        case HTML_LIST_TYPE_UNORDERED:
1114                if (ascii_only)
1115                        return g_strdup ("* ");
1116                else if (flow->levels->len == 0 || flow->levels->len & 1)
1117                        return g_strdup ("\342\227\217 "); /* U+25CF BLACK CIRCLE */
1118                else
1119                        return g_strdup ("\342\227\213 "); /* U+25CB WHITE CIRCLE */
1120        default:
1121                return NULL;
1122        }
1123}
1124
1125static void
1126draw_gt_line (HTMLObject *cur, HTMLPainter *p, gint offset, gint x, gint y)
1127{
1128        gint cy, w, a, d, line_offset = 0;
1129
1130        /* FIXME: cache items and glyphs? */
1131        html_painter_calc_text_size (p, HTML_BLOCK_CITE,
1132                                     strlen (HTML_BLOCK_CITE), NULL, NULL, NULL, 0, &line_offset,
1133                                     GTK_HTML_FONT_STYLE_SIZE_3, NULL,
1134                                     &w, &a, &d);
1135
1136        cy = offset;
1137        while (cy + a <= cur->ascent) {
1138                /* FIXME: cache items and glyphs? */
1139                html_painter_draw_text (p, x, y + cur->y - cy,
1140                                        HTML_BLOCK_CITE, 1, NULL, NULL, NULL, 0, 0);
1141                cy += a + d;
1142        }
1143
1144        cy = - offset + a + d;
1145        while (cy + d <= cur->descent) {
1146                /* FIXME: cache items and glyphs? */
1147                html_painter_draw_text (p, x, y + cur->y + cy,
1148                                        HTML_BLOCK_CITE, 1, NULL, NULL, NULL, 0, 0);
1149                cy += a + d;
1150        }
1151}
1152               
1153static void
1154draw_quotes (HTMLObject *self, HTMLPainter *painter,
1155             gint x, gint y, gint width, gint height,
1156             gint tx, gint ty)
1157{
1158        HTMLClueFlow *flow;
1159        GdkRectangle paint, area, clip;
1160        int i;
1161        int indent = 0;
1162        int last_indent = 0;
1163        gint pixel_size = html_painter_get_pixel_size (painter);
1164        gboolean is_plain = HTML_IS_PLAIN_PAINTER (painter);
1165        HTMLEngine *e;
1166
1167        if (painter->widget && GTK_IS_HTML (painter->widget))
1168                e = GTK_HTML (painter->widget)->engine;
1169        else
1170                return;
1171       
1172        flow = HTML_CLUEFLOW (self);
1173
1174        for (i = 0; i < flow->levels->len; i++, last_indent = indent) {
1175                indent = get_level_indent (flow, i, painter);
1176
1177                html_painter_set_pen (painter, &html_colorset_get_color_allocated (e->settings->color_set,
1178                                                                                   painter, HTMLLinkColor)->color);
1179                if (is_cite (flow, i)) {
1180                        if (!is_plain) {
1181                                area.x = self->x + indent - 5 * pixel_size;
1182                                area.width = 2 * pixel_size;
1183                                area.y = self->y - self->ascent;
1184                                area.height = self->ascent + self->descent;
1185                               
1186                                clip.x = x;
1187                                clip.width = width;
1188                                clip.y = y;
1189                                clip.height = height;
1190                               
1191                                if (!gdk_rectangle_intersect (&clip, &area, &paint))
1192                                        return;
1193
1194                                html_painter_fill_rect (painter,
1195                                                        paint.x + tx, paint.y + ty,
1196                                                        paint.width, paint.height);
1197                        } else {
1198                                HTMLObject *cur = HTML_CLUE (self)->head;
1199                                gint baseline = 0;
1200                                while (cur) {
1201                                        if (cur->y != 0) {
1202                                                baseline = cur->y;
1203                                                break;
1204                                        }
1205                                        cur = cur->next;
1206                                }
1207
1208
1209                                /* draw "> " quote characters in the plain case */
1210                                html_painter_set_font_style (painter,
1211                                                             html_clueflow_get_default_font_style (flow));
1212                                               
1213                                html_painter_set_font_face  (painter, NULL);
1214                                draw_gt_line (self, painter, self->ascent - baseline,
1215                                              self->x + tx + last_indent, ty);
1216                        }
1217                }
1218        }
1219}               
1220
1221static void
1222draw_item (HTMLObject *self, HTMLPainter *painter, gint x, gint y, gint width, gint height, gint tx, gint ty)
1223{
1224        HTMLClueFlow *flow;
1225        HTMLObject *first;
1226        gchar *marker;
1227        HTMLEngine *e;
1228
1229        if (painter->widget && GTK_IS_HTML (painter->widget))
1230                e = GTK_HTML (painter->widget)->engine;
1231        else
1232                return;
1233
1234        first = HTML_CLUE (self)->head;
1235        if (html_object_is_text (first) && first->next)
1236                first = first->next;
1237
1238        flow = HTML_CLUEFLOW (self);
1239
1240        if (flow->item_color) {
1241                html_color_alloc (flow->item_color, painter);
1242                html_painter_set_pen (painter, &flow->item_color->color);
1243        } else
1244                html_painter_set_pen (painter, &html_colorset_get_color_allocated (e->settings->color_set,
1245                                                                                   painter, HTMLTextColor)->color);
1246
1247        marker = get_item_marker_str (flow, HTML_IS_PLAIN_PAINTER (painter));
1248        if (marker) {
1249                gint width, len, line_offset = 0, asc, dsc;
1250               
1251                len   = g_utf8_strlen (marker, -1);
1252                /* FIXME: cache items and glyphs? */
1253                html_painter_calc_text_size (painter, marker, len, NULL, NULL, NULL, 0, &line_offset,
1254                                             html_clueflow_get_default_font_style (flow), NULL, &width, &asc, &dsc);
1255                width += html_painter_get_space_width (painter, html_clueflow_get_default_font_style (flow), NULL);
1256                html_painter_set_font_style (painter, html_clueflow_get_default_font_style (flow));
1257                html_painter_set_font_face  (painter, NULL);
1258                /* FIXME: cache items and glyphs? */
1259                html_painter_draw_text (painter, self->x + first->x - width + tx,
1260                                        self->y - self->ascent + first->y + ty,
1261                                        marker, len, NULL, NULL, NULL, 0, 0);
1262        }
1263        g_free (marker);
1264}
1265
1266static void
1267draw (HTMLObject *self,
1268      HTMLPainter *painter,
1269      gint x, gint y,
1270      gint width, gint height,
1271      gint tx, gint ty)
1272{
1273        if (y > self->y + self->descent || y + height < self->y - self->ascent)
1274                return;
1275
1276        if (HTML_CLUE (self)->head != NULL && is_item (HTML_CLUEFLOW (self)))
1277                draw_item (self, painter, x, y, width, height, tx, ty);
1278
1279        if (HTML_CLUE (self)->head != NULL)
1280                draw_quotes (self, painter, x, y, width, height, tx, ty);
1281
1282        (* HTML_OBJECT_CLASS (&html_clue_class)->draw) (self, painter, x, y, width, height, tx, ty);
1283}
1284
1285static HTMLObject*
1286check_point (HTMLObject *self,
1287             HTMLPainter *painter,
1288             gint x, gint y,
1289             guint *offset_return,
1290             gboolean for_cursor)
1291{
1292        HTMLObject *obj, *p, *pnext, *eol, *cur;
1293        HTMLClue *clue;
1294        gint line_ly = 0;
1295        gint line_y;
1296
1297        if (x < self->x || x >= self->x + self->width
1298            || y < self->y - self->ascent || y >= self->y + self->descent)
1299                return NULL;
1300
1301        clue = HTML_CLUE (self);
1302
1303        x = x - self->x;
1304        y = y - self->y + self->ascent;
1305       
1306        /*
1307         * shift any selection inside the indent block to the
1308         * left edge of the flow.
1309         */
1310        if (for_cursor)
1311                x = MAX (x, get_indent (HTML_CLUEFLOW (self), painter));
1312
1313        for (p = clue->head; p; ) {
1314
1315                if (html_object_is_text (p))
1316                        p = p->next;
1317                if (!p)
1318                        break;
1319
1320                line_y  = line_ly;
1321                line_ly = p->y + p->descent;
1322
1323                for (eol = p; eol && (eol->y - eol->ascent < line_ly || eol->ascent + eol->y == line_y); ) {
1324                        line_ly = MAX (line_ly, eol->y + eol->descent);
1325                        do
1326                                eol = eol->next;
1327                        while (eol && html_object_is_text (eol));
1328                }
1329
1330                if (y >= line_y && y < line_ly)
1331                        for (cur = p; cur && cur != eol; ) {
1332                                obj = html_object_check_point (cur, painter, x, for_cursor
1333                                                               ? MIN (MAX (y, cur->y - cur->ascent),
1334                                                                      cur->y + cur->descent - 1) : y,
1335                                                               offset_return, for_cursor);
1336
1337                                if (obj != NULL)
1338                                        return obj;
1339                                do
1340                                        cur = cur->next;
1341                                while (cur && cur != eol && html_object_is_text (cur));
1342                        }
1343                p = eol;
1344        }
1345
1346        if (!for_cursor)
1347                return NULL;
1348
1349        /* before */
1350        p = clue->head;
1351        if (p && html_object_is_text (p))
1352                p = p->next;
1353        if (p && (x < p->x || y < p->y - p->ascent)) {
1354                obj = html_object_check_point (p, painter, p->x, p->y - p->ascent, offset_return, for_cursor);
1355                if (obj != NULL)
1356                        return obj;
1357        }
1358        for (p = clue->head; p != NULL; p = pnext) {
1359                if (html_object_is_text (p))
1360                        p = p->next;
1361                if (!p)
1362                        break;
1363                pnext = p->next;
1364                while (pnext && html_object_is_text (pnext))
1365                        pnext = pnext->next;
1366
1367                /* new line */
1368                if (pnext == NULL || (pnext->y - pnext->ascent >= p->y + p->descent
1369                                      && y >= p->y - p->ascent
1370                                      && y <  p->y + p->descent)) {
1371                        obj = html_object_check_point (p, painter, MAX (0, p->x + p->width - 1), p->y + p->descent - 1,
1372                                                       offset_return, for_cursor);
1373                        if (obj != NULL)
1374                                return obj;
1375                }
1376        }
1377
1378        /* after */
1379        p = clue->tail;
1380        if (p && ((x >= p->x + p->width) || (y >= p->y + p->descent))) {
1381                obj = html_object_check_point (p, painter, MAX (0, p->x + p->width - 1), p->y + p->descent - 1,
1382                                               offset_return, for_cursor);
1383                if (obj != NULL)
1384                        return obj;
1385        }
1386        return NULL;
1387}
1388
1389
1390/* Saving support.  */
1391
1392static gboolean
1393write_indent (HTMLEngineSaveState *state, gint level)
1394{
1395        while (level > 0) {
1396                if (!html_engine_save_output_string (state, "    "))
1397                        return FALSE;
1398                level --;
1399        }
1400
1401        return TRUE;
1402}
1403
1404inline static gint
1405get_level (HTMLClueFlow *cf)
1406{
1407        return cf->levels->len;
1408}
1409
1410static gchar *
1411get_list_start_tag (HTMLClueFlow *self)
1412{
1413        switch (self->item_type) {
1414        case HTML_LIST_TYPE_UNORDERED:
1415        case HTML_LIST_TYPE_MENU:
1416        case HTML_LIST_TYPE_DIR:
1417                return g_strdup ("LI");
1418        case HTML_LIST_TYPE_ORDERED_ARABIC:
1419                return g_strdup_printf ("LI TYPE=1 VALUE=%d", HTML_CLUEFLOW (self)->item_number);
1420        case HTML_LIST_TYPE_ORDERED_UPPER_ROMAN:
1421                return g_strdup_printf ("LI TYPE=I VALUE=%d", HTML_CLUEFLOW (self)->item_number);
1422        case HTML_LIST_TYPE_ORDERED_LOWER_ROMAN:
1423                return g_strdup_printf ("LI TYPE=i VALUE=%d", HTML_CLUEFLOW (self)->item_number);
1424        case HTML_LIST_TYPE_ORDERED_UPPER_ALPHA:
1425                return g_strdup_printf ("LI TYPE=A VALUE=%d", HTML_CLUEFLOW (self)->item_number);
1426        case HTML_LIST_TYPE_ORDERED_LOWER_ALPHA:
1427                return g_strdup_printf ("LI TYPE=a VALUE=%d", HTML_CLUEFLOW (self)->item_number);
1428        case HTML_LIST_TYPE_GLOSSARY_DL:
1429                return g_strdup_printf ("DT");
1430        case HTML_LIST_TYPE_GLOSSARY_DD:
1431                return g_strdup_printf ("DD");
1432        default:
1433                return NULL;
1434        }
1435
1436        return NULL;
1437}
1438
1439
1440static gchar *
1441get_start_tag (HTMLClueFlow *self)
1442{
1443        switch (self->style) {
1444        case HTML_CLUEFLOW_STYLE_H1:
1445                return "H1";
1446        case HTML_CLUEFLOW_STYLE_H2:
1447                return "H2";
1448        case HTML_CLUEFLOW_STYLE_H3:
1449                return "H3";
1450        case HTML_CLUEFLOW_STYLE_H4:
1451                return "H4";
1452        case HTML_CLUEFLOW_STYLE_H5:
1453                return "H5";
1454        case HTML_CLUEFLOW_STYLE_H6:
1455                return "H6";
1456        case HTML_CLUEFLOW_STYLE_ADDRESS:
1457                return "ADDRESS";
1458        case HTML_CLUEFLOW_STYLE_PRE:
1459                return "PRE";
1460        case HTML_CLUEFLOW_STYLE_LIST_ITEM:
1461                g_warning ("This should not be reached");
1462        case HTML_CLUEFLOW_STYLE_NORMAL:
1463        default:
1464                return NULL;
1465        }
1466}
1467
1468static const char *
1469get_start_indent_item (HTMLListType type)
1470{
1471        switch (type) {
1472        case HTML_LIST_TYPE_UNORDERED:
1473        case HTML_LIST_TYPE_MENU:
1474        case HTML_LIST_TYPE_DIR:
1475                return "UL";
1476        case HTML_LIST_TYPE_ORDERED_LOWER_ALPHA:
1477                return "OL TYPE=a";
1478        case HTML_LIST_TYPE_ORDERED_UPPER_ALPHA:
1479                return "OL TYPE=A";
1480        case HTML_LIST_TYPE_ORDERED_LOWER_ROMAN:
1481                return "OL TYPE=i";
1482        case HTML_LIST_TYPE_ORDERED_UPPER_ROMAN:
1483                return "OL TYPE=I";
1484        case HTML_LIST_TYPE_ORDERED_ARABIC:
1485                return "OL TYPE=1";
1486        case HTML_LIST_TYPE_GLOSSARY_DD:
1487        case HTML_LIST_TYPE_GLOSSARY_DL:
1488                return "DL";
1489        case HTML_LIST_TYPE_BLOCKQUOTE_CITE:
1490                return "BLOCKQUOTE TYPE=CITE";
1491        case HTML_LIST_TYPE_BLOCKQUOTE:
1492                return "BLOCKQUOTE";
1493        }
1494        return "";             
1495}
1496
1497static const char *
1498get_end_indent_item (HTMLListType type)
1499{
1500        switch (type) {
1501        case HTML_LIST_TYPE_BLOCKQUOTE:
1502        case HTML_LIST_TYPE_BLOCKQUOTE_CITE:
1503                return "BLOCKQUOTE";
1504        case HTML_LIST_TYPE_GLOSSARY_DD:
1505        case HTML_LIST_TYPE_GLOSSARY_DL:
1506                return "DL";
1507        case HTML_LIST_TYPE_UNORDERED:
1508        case HTML_LIST_TYPE_MENU:
1509        case HTML_LIST_TYPE_DIR:
1510                return "UL";
1511        default:
1512                return "OL";
1513        }
1514        return "";
1515}
1516
1517static int
1518get_similar_depth (HTMLClueFlow *self, HTMLClueFlow *neighbor)
1519{
1520        int i;
1521        int max_depth;
1522
1523        if (neighbor == NULL)
1524                return 0;
1525
1526        max_depth = MIN (self->levels->len, neighbor->levels->len);
1527
1528        for (i = 0; i < max_depth; i++) {
1529                if (self->levels->data[i] != neighbor->levels->data[i])
1530                        break;
1531        }
1532
1533        return i;
1534}
1535
1536static gboolean
1537save_indent_string (HTMLClueFlow *self, HTMLEngineSaveState *state, const char *format, ...)
1538{
1539        va_list args;
1540        gboolean retval;
1541
1542        if (self->style != HTML_CLUEFLOW_STYLE_PRE)
1543                if (!write_indent (state, self->levels->len))
1544                        return FALSE;
1545
1546        va_start (args, format);
1547        retval = html_engine_save_output_stringv (state, format, args);
1548        va_end (args);
1549
1550        return retval;
1551}
1552
1553static gboolean
1554write_flow_tag (HTMLClueFlow *self, HTMLEngineSaveState *state)
1555{
1556        int d;
1557        HTMLClueFlow *next = NULL;
1558        HTMLClueFlow *prev = NULL;
1559        HTMLHAlignType halign;
1560       
1561        if (HTML_IS_CLUEFLOW (HTML_OBJECT (self)->next))
1562                next = HTML_CLUEFLOW (HTML_OBJECT (self)->next);
1563           
1564        if (HTML_IS_CLUEFLOW (HTML_OBJECT (self)->prev))
1565                prev = HTML_CLUEFLOW (HTML_OBJECT (self)->prev);
1566
1567        d = get_similar_depth (self, prev);
1568        if (is_item (self)) {
1569                char *li = get_list_start_tag (self);
1570
1571                if (li && !save_indent_string (self, state, "<%s>", li)) {
1572                        g_free (li);
1573                        return FALSE;
1574                }
1575        } else if (is_levels_equal (self, prev) && prev->style == self->style) {
1576                if (!save_indent_string (self, state, ""))
1577                        return FALSE;
1578        } else {
1579                char *start = get_start_tag (self);
1580
1581                if (start) {
1582                        if (!save_indent_string (self, state, "<%s>\n", start))
1583                                return FALSE;
1584                } else {
1585                        if (!save_indent_string (self, state, ""))
1586                                return FALSE;
1587                }
1588        }
1589
1590        halign = HTML_CLUE (self)->halign;
1591        /* Alignment tag.  */
1592        if (halign != HTML_HALIGN_NONE && halign != HTML_HALIGN_LEFT) {
1593                if (! html_engine_save_output_string
1594                    (state, "<DIV ALIGN=%s>",
1595                     html_engine_save_get_paragraph_align (html_alignment_to_paragraph (halign))))
1596                        return FALSE;
1597        }
1598
1599        if (!html_object_save_data (HTML_OBJECT (self), state))
1600                return FALSE;
1601
1602        /* Paragraph's content.  */
1603        if (! HTML_OBJECT_CLASS (&html_clue_class)->save (HTML_OBJECT (self), state))
1604                return FALSE;
1605
1606        /* Close alignment tag.  */
1607        if (halign != HTML_HALIGN_NONE && halign != HTML_HALIGN_LEFT) {
1608                if (! html_engine_save_output_string (state, "</DIV>"))
1609                        return FALSE;
1610        }
1611
1612        if (is_item (self)) {
1613                if (next && is_levels_equal (self, next) && !is_item (next) && !html_clueflow_contains_table (self)) {
1614                        if (!html_engine_save_output_string (state, "<BR>\n"))
1615                                return FALSE;
1616                } else if (!html_engine_save_output_string (state, "\n"))
1617                        return FALSE;
1618        } else if (is_levels_equal (self, next) && self->style == next->style) {
1619                if (self->style != HTML_CLUEFLOW_STYLE_PRE && !html_clueflow_contains_table (self)) {
1620                        if (!html_engine_save_output_string (state, "<BR>\n"))
1621                                return FALSE;
1622                } else {
1623                        if (!html_engine_save_output_string (state, "\n"))
1624                                return FALSE;
1625                }
1626        } else {
1627                char *end = get_start_tag (self);
1628
1629                if (self->style != HTML_CLUEFLOW_STYLE_PRE) {
1630                        if ((!html_clueflow_contains_table (self) && !end && next && self->style == next->style) || html_clueflow_is_empty (self)) {
1631                                if (!html_engine_save_output_string (state, "<BR>\n"))
1632                                        return FALSE;
1633                        } else {
1634                                if (!html_engine_save_output_string (state, "\n"))
1635                                        return FALSE;
1636                        }
1637                } else {
1638                        if (!html_engine_save_output_string (state, "\n"))
1639                                return FALSE;
1640                }
1641
1642                if (end) {
1643                        if (!html_engine_save_output_string (state, "</%s>\n", end))
1644                                return FALSE;
1645                }
1646        }
1647       
1648        return TRUE;
1649}
1650
1651static gboolean
1652save (HTMLObject *s,
1653      HTMLEngineSaveState *state)
1654{
1655        HTMLClueFlow *self = HTML_CLUEFLOW (s);
1656        HTMLClueFlow *next = NULL;
1657        HTMLClueFlow *prev = NULL;
1658        int d;
1659        int i;
1660       
1661        if (HTML_IS_CLUEFLOW (HTML_OBJECT (self)->next))
1662                next = HTML_CLUEFLOW (HTML_OBJECT (self)->next);
1663           
1664        if (HTML_IS_CLUEFLOW (HTML_OBJECT (self)->prev))
1665                prev = HTML_CLUEFLOW (HTML_OBJECT (self)->prev);
1666
1667        d = i = get_similar_depth (self, prev);
1668        while (i < self->levels->len) {
1669                const char *stag = get_start_indent_item (self->levels->data[i]);
1670               
1671                if (!write_indent (state, i)
1672                    || !html_engine_save_output_string (state, "<%s>\n", stag))
1673                        return FALSE;
1674               
1675                i++;
1676        }
1677
1678        if (!write_flow_tag (self, state))
1679            return FALSE;
1680
1681        i = self->levels->len - 1;
1682        d = get_similar_depth (self, next);
1683        while (i >= d) {
1684                const char *stag = get_end_indent_item (self->levels->data[i]);
1685
1686                if (!write_indent (state, i)
1687                    || !html_engine_save_output_string (state, "</%s>\n", stag))
1688                        return FALSE;
1689
1690                i--;
1691        }
1692
1693        return TRUE;
1694}
1695
1696static void
1697write_item_marker (GString *pad_string, HTMLClueFlow *flow)
1698{
1699        char *marker;
1700
1701        marker = get_item_marker_str (flow, TRUE);
1702
1703        if (marker) {
1704                gint marker_len = strlen (marker);
1705                gint len = pad_string->len - 1;
1706                char *str = pad_string->str;
1707
1708                while (len > 0) {
1709                        if ((str[len - 1] != ' ') || (pad_string->len - len >= marker_len))
1710                                break;
1711                        else
1712                                len--;
1713                }
1714
1715                if (len > 0)
1716                        g_string_truncate (pad_string, len);
1717
1718                g_string_append (pad_string, marker);
1719        }
1720}
1721
1722static gint
1723plain_padding (HTMLClueFlow *flow, GString *out, gboolean firstline)
1724{
1725        GString *pad_string = NULL;
1726        gint pad_len = 0;
1727        gint i;
1728
1729        pad_string = g_string_new ("");
1730
1731#define APPEND_PLAIN(w) \
1732        pad_len += strlen (w); \
1733        if (out) g_string_append (pad_string, w);
1734
1735        if (flow->levels->len) {
1736                for (i = 0; i < flow->levels->len; i++) {
1737                        switch (flow->levels->data[i]) {
1738                        case HTML_LIST_TYPE_BLOCKQUOTE_CITE:
1739                                APPEND_PLAIN (HTML_BLOCK_CITE);
1740                                break;
1741                        case HTML_LIST_TYPE_GLOSSARY_DL:
1742                                break;
1743                        default:
1744                                APPEND_PLAIN (HTML_BLOCK_INDENT);
1745                                break;
1746                        }
1747                }
1748        } else if (is_item (flow)) {
1749                /* item without a list block give it a little pading */
1750                APPEND_PLAIN ("    ");
1751        }
1752
1753        if (is_item (flow) && firstline) {
1754                write_item_marker (pad_string, flow);
1755        }
1756
1757        if (out)
1758                g_string_append (out, pad_string->str);
1759
1760        g_string_free (pad_string, TRUE);
1761        return pad_len;
1762}
1763
1764static void
1765append_selection_string (HTMLObject *self,
1766                         GString *buffer)
1767{
1768        (*HTML_OBJECT_CLASS (parent_class)->append_selection_string) (self, buffer);
1769
1770        if (self->selected) {
1771                g_string_append_c (buffer, '\n');
1772                plain_padding (HTML_CLUEFLOW (self), buffer, TRUE);
1773        }
1774}
1775
1776static gboolean
1777save_plain (HTMLObject *self,
1778            HTMLEngineSaveState *state,
1779            gint requested_width)
1780{
1781        HTMLClueFlow *flow;
1782        HTMLEngineSaveState *buffer_state;
1783        GString *out = g_string_new ("");
1784        gint pad;
1785        gint align_pad;
1786        gboolean firstline = TRUE;
1787        gint max_len;
1788
1789        flow = HTML_CLUEFLOW (self);
1790
1791        pad = plain_padding (flow, NULL, FALSE);
1792        buffer_state = html_engine_save_buffer_new (state->engine,
1793                                                    state->inline_frames);
1794        max_len = MAX (requested_width - pad, 0);
1795        /* buffer the paragraph's content into the save buffer */
1796        if (HTML_OBJECT_CLASS (&html_clue_class)->save_plain (self,
1797                                                              buffer_state,
1798                                                              max_len)) {
1799                guchar *s;
1800                int offset;
1801               
1802                if (get_pre_padding (flow, calc_padding (state->engine->painter)) > 0) {
1803                        plain_padding (flow, out, FALSE);
1804                        g_string_append (out, "\n");
1805                }
1806
1807                s = html_engine_save_buffer_peek_text (buffer_state);
1808
1809                if (*s == 0) {
1810                        plain_padding (flow, out, TRUE);
1811                        g_string_append (out, "\n");
1812                } else {
1813                        PangoAttrList *attrs = pango_attr_list_new ();
1814                        gint bytes = html_engine_save_buffer_peek_text_bytes (buffer_state), slen = g_utf8_strlen (s, -1), i, clen, n_items;
1815                        GList *items_list, *cur;
1816                        PangoContext *pc = gtk_widget_get_pango_context (GTK_WIDGET (state->engine->widget));
1817                        PangoLogAttr *lattrs;
1818                        PangoItem **items;
1819                        gint len, skip;
1820
1821                        items_list = pango_itemize (pc, s, 0, bytes, attrs, NULL);
1822                        lattrs = g_new (PangoLogAttr, slen + 1);
1823                        n_items = g_list_length (items_list);
1824                        items = g_new (PangoItem *, n_items);
1825                        for (i = 0, cur = items_list; i < n_items; i ++, cur = cur->next)
1826                                items [i] = (PangoItem *) cur->data;
1827
1828                        offset = 0;
1829                        for (i = 0; i < n_items; i ++) {
1830                                PangoItem tmp_item;
1831                                int start_i, start_offset;
1832
1833                                start_i = i;
1834                                start_offset = offset;
1835                                offset += items [i]->num_chars;
1836                                tmp_item = *items [i];
1837                                while (i < n_items - 1) {
1838                                        if (tmp_item.analysis.lang_engine == items [i + 1]->analysis.lang_engine) {
1839                                                tmp_item.length += items [i + 1]->length;
1840                                                tmp_item.num_chars += items [i + 1]->num_chars;
1841                                                offset += items [i + 1]->num_chars;
1842                                                i ++;
1843                                        } else
1844                                                break;
1845                                }
1846
1847                                pango_break (s + tmp_item.offset, tmp_item.length, &tmp_item.analysis, lattrs + start_offset, tmp_item.num_chars + 1);
1848                        }
1849
1850                        html_text_remove_unwanted_line_breaks (s, slen, lattrs);
1851
1852                        g_list_free (items_list);
1853                        for (i = 0; i < n_items; i ++)
1854                                pango_item_free (items [i]);
1855                        g_free (items);
1856                        pango_attr_list_unref (attrs);
1857
1858                        clen = 0;
1859                        while (*s) {
1860                                len = strcspn (s, "\n");
1861                                len = g_utf8_strlen (s, len);
1862                                skip = 0;
1863                       
1864                                if ((flow->style != HTML_CLUEFLOW_STYLE_PRE)
1865                                    && !HTML_IS_TABLE (HTML_CLUE (flow)->head)) {
1866                                        if (len > max_len) {
1867                                                gboolean look_backward = TRUE;
1868                                                gint wi, wl;
1869
1870                                                wl = clen + max_len;
1871
1872                                                if (lattrs [wl].is_white) {
1873
1874                                                        while (lattrs [wl].is_white && wl < slen)
1875                                                                wl ++;
1876
1877                                                        if (wl < slen && html_text_is_line_break (lattrs [wl]))
1878                                                                look_backward = FALSE;
1879                                                        else
1880                                                                wl = clen + max_len;
1881                                                }
1882
1883                                                if (look_backward) {
1884                                                        while (wl > 0) {
1885                                                                if (html_text_is_line_break (lattrs [wl]))
1886                                                                        break;
1887                                                                wl --;
1888                                                        }
1889                                                }
1890
1891                                                if (wl > clen && wl < slen && html_text_is_line_break (lattrs [wl])) {
1892                                                        wi = MIN (wl, clen + max_len);
1893                                                        while (wi > clen && lattrs [wi - 1].is_white)
1894                                                                wi --;
1895                                                        len = wi - clen;
1896                                                        skip = wl - wi;
1897                                                }
1898                                        }
1899                                }
1900
1901                                /* FIXME plain padding doesn't work properly with tables aligment
1902                                 * at the moment.
1903                                 */
1904                                plain_padding (flow, out, firstline);
1905
1906                                switch (html_clueflow_get_halignment (flow)) {
1907                                case HTML_HALIGN_RIGHT:
1908                                        align_pad = max_len - len;
1909                                        break;
1910                                case HTML_HALIGN_CENTER:
1911                                        align_pad = (max_len - len) / 2;
1912                                        break;
1913                                default:
1914                                        align_pad = 0;
1915                                        break;
1916                                }
1917                       
1918                                while (align_pad > 0) {
1919                                        g_string_append_c (out, ' ');
1920                                        align_pad--;
1921                                }
1922
1923                                bytes = ((guchar *) g_utf8_offset_to_pointer (s, len)) - s;
1924                                html_engine_save_string_append_nonbsp (out, s, bytes);
1925                                s += bytes;
1926                                s = g_utf8_offset_to_pointer (s, skip);
1927                                clen += len + skip;
1928
1929                                if (*s == '\n') {
1930                                        s++;
1931                                        clen ++;
1932                                }
1933                       
1934                                g_string_append_c (out, '\n');
1935                                firstline = FALSE;
1936                        }
1937                        g_free (lattrs);
1938                }
1939
1940                if (get_post_padding (flow, calc_padding (state->engine->painter)) > 0) {
1941                        plain_padding (flow, out, FALSE);
1942                        g_string_append (out, "\n");
1943                }
1944        }
1945        html_engine_save_buffer_free (buffer_state);
1946               
1947        if (!html_engine_save_output_string (state, "%s", out->str)) {
1948                g_string_free (out, TRUE);
1949                return FALSE;
1950        }               
1951       
1952        g_string_free (out, TRUE);
1953        return TRUE;
1954}
1955
1956
1957static GtkHTMLFontStyle
1958get_default_font_style (const HTMLClueFlow *self)
1959{
1960        GtkHTMLFontStyle style = 0;
1961
1962        if (HTML_OBJECT (self)->parent && HTML_IS_TABLE_CELL (HTML_OBJECT (self)->parent)
1963            && HTML_TABLE_CELL (HTML_OBJECT (self)->parent)->heading)
1964                style = GTK_HTML_FONT_STYLE_BOLD;
1965
1966        switch (self->style) {
1967        case HTML_CLUEFLOW_STYLE_NORMAL:
1968        case HTML_CLUEFLOW_STYLE_LIST_ITEM:
1969                return style | GTK_HTML_FONT_STYLE_SIZE_3;
1970        case HTML_CLUEFLOW_STYLE_ADDRESS:
1971                return style | GTK_HTML_FONT_STYLE_SIZE_3 | GTK_HTML_FONT_STYLE_ITALIC;
1972        case HTML_CLUEFLOW_STYLE_PRE:
1973                return style | GTK_HTML_FONT_STYLE_SIZE_3 | GTK_HTML_FONT_STYLE_FIXED;
1974        case HTML_CLUEFLOW_STYLE_H1:
1975                return style | GTK_HTML_FONT_STYLE_SIZE_6 | GTK_HTML_FONT_STYLE_BOLD;
1976        case HTML_CLUEFLOW_STYLE_H2:
1977                return style | GTK_HTML_FONT_STYLE_SIZE_5 | GTK_HTML_FONT_STYLE_BOLD;
1978        case HTML_CLUEFLOW_STYLE_H3:
1979                return style | GTK_HTML_FONT_STYLE_SIZE_4 | GTK_HTML_FONT_STYLE_BOLD;
1980        case HTML_CLUEFLOW_STYLE_H4:
1981                return style | GTK_HTML_FONT_STYLE_SIZE_3 | GTK_HTML_FONT_STYLE_BOLD;
1982        case HTML_CLUEFLOW_STYLE_H5:
1983                return style | GTK_HTML_FONT_STYLE_SIZE_2 | GTK_HTML_FONT_STYLE_BOLD;
1984        case HTML_CLUEFLOW_STYLE_H6:
1985                return style | GTK_HTML_FONT_STYLE_SIZE_1 | GTK_HTML_FONT_STYLE_BOLD;
1986        default:
1987                g_warning ("Unexpected HTMLClueFlow style %d", self->style);
1988                return style | GTK_HTML_FONT_STYLE_DEFAULT;
1989        }
1990}
1991
1992static void
1993search_set_info (HTMLObject *cur, HTMLSearch *info, guchar *text, guint index, guint bytes)
1994{
1995        guint text_bytes = 0;
1996        guint cur_bytes;
1997
1998        info->found_bytes = bytes;
1999
2000        if (info->found) {
2001                g_list_free (info->found);
2002                info->found = NULL;
2003        }
2004
2005        while (cur) {
2006                if (html_object_is_text (cur)) {
2007                        cur_bytes = HTML_TEXT (cur)->text_bytes;
2008                        if (text_bytes + cur_bytes > index) {
2009                                if (!info->found) {
2010                                        info->start_pos = g_utf8_pointer_to_offset (text + text_bytes,
2011                                                                                    text + index);
2012                                }
2013                                info->found = g_list_append (info->found, cur);
2014                        }
2015                        text_bytes += cur_bytes;
2016                        if (text_bytes >= index + info->found_bytes) {
2017                                info->stop_pos = info->start_pos + g_utf8_pointer_to_offset (text + index,
2018                                                                                             text + index + info->found_bytes);
2019                                info->last     = HTML_OBJECT (cur);
2020                                return;
2021                        }
2022                } else if (HTML_OBJECT_TYPE (cur) != HTML_TYPE_TEXTSLAVE) {
2023                        break;
2024                }               
2025                cur = cur->next;
2026        }
2027
2028        g_assert_not_reached ();
2029}
2030
2031/* search text objects ([TextMaster, LinkTextMaster], TextSlave*) */
2032static gboolean
2033search_text (HTMLObject **beg, HTMLSearch *info)
2034{
2035        HTMLObject *cur = *beg;
2036        HTMLObject *end = cur;
2037        HTMLObject *head;
2038        guchar *par, *pp;
2039        guint text_bytes;
2040        guint eq_bytes;
2041        gint index;
2042        gboolean retval = FALSE;
2043
2044        /* printf ("search flow look for \"text\" %s\n", info->text); */
2045
2046        /* first get flow text_bytes */
2047        text_bytes = 0;
2048        while (cur) {
2049                if (html_object_is_text (cur)) {
2050                        text_bytes += HTML_TEXT (cur)->text_bytes;
2051                        end = cur;
2052                } else if (HTML_OBJECT_TYPE (cur) != HTML_TYPE_TEXTSLAVE) {
2053                        break;
2054                }
2055                cur = (info->forward) ? cur->next : cur->prev;
2056        }
2057
2058        if (text_bytes > 0) {
2059                par = g_new (gchar, text_bytes + 1);
2060                par [text_bytes] = 0;
2061
2062                pp = (info->forward) ? par : par + text_bytes;
2063
2064                /* now fill par with text */
2065                head = cur = (info->forward) ? *beg : end;
2066                cur = *beg;
2067                while (cur) {
2068                        if (html_object_is_text (cur)) {
2069                                if (!info->forward) {
2070                                        pp -= HTML_TEXT (cur)->text_bytes;
2071                                }
2072                                strncpy (pp, HTML_TEXT (cur)->text, HTML_TEXT (cur)->text_bytes);
2073                                if (info->forward) {
2074                                        pp += HTML_TEXT (cur)->text_bytes;
2075                                }
2076                        } else if (HTML_OBJECT_TYPE (cur) != HTML_TYPE_TEXTSLAVE) {
2077                                break;
2078                        }               
2079                        cur = (info->forward) ? cur->next : cur->prev;
2080                }
2081
2082                /* set eq_bytes and pos counters */
2083                eq_bytes = 0;
2084                if (info->found) {
2085                        index = ((guchar *)g_utf8_offset_to_pointer (par, info->start_pos + ((info->forward) ? 1 : -1))) - par;
2086                } else {
2087                        index = (info->forward) ? 0 : text_bytes - 1;
2088                }
2089
2090                /* FIXME make shorter text instead */
2091                if (!info->forward)
2092                        par [index+1] = 0;
2093
2094                if ((info->forward && index < text_bytes)
2095                    || (!info->forward && index > 0)) {
2096                        if (info->reb) {
2097                                /* regex search */
2098                                gint rv;
2099#ifndef HAVE_GNU_REGEX
2100                                regmatch_t match;
2101                                /* guchar *p=par+pos; */
2102
2103                                /* FIXME UTF8
2104                                   replace &nbsp;'s with spaces
2105                                while (*p) {
2106                                        if (*p == ENTITY_NBSP) {
2107                                                *p = ' ';
2108                                        }
2109                                        p += (info->forward) ? 1 : -1;
2110                                        } */
2111
2112                                while ((info->forward && index < text_bytes)
2113                                       || (!info->forward && index >= 0)) {
2114                                        rv = regexec (info->reb,
2115                                                      par + index,
2116                                                      1, &match, 0);
2117                                        if (rv == 0) {
2118                                                search_set_info (head, info, par,
2119                                                                 index + match.rm_so, match.rm_eo - match.rm_so);
2120                                                retval = TRUE;
2121                                                break;
2122                                        }
2123                                        index += (info->forward)
2124                                                ? (((guchar *) g_utf8_next_char (par + index)) - par - index)
2125                                                : (((guchar *) g_utf8_prev_char (par + index)) - par - index);
2126                                }
2127#else
2128                                rv = re_search (info->reb, par, text_bytes, index,
2129                                                (info->forward) ? text_bytes - index : -index, NULL);
2130                                if (rv >= 0) {
2131                                        guint found_index = rv;
2132                                        rv = re_match (info->reb, par, text_bytes, found_index, NULL);
2133                                        if (rv < 0) {
2134                                                g_warning ("re_match (...) error");
2135                                        }
2136                                        search_set_info (head, info, par, found_index, rv);
2137                                        retval = TRUE;
2138                                } else {
2139                                        if (rv < -1) {
2140                                                g_warning ("re_search (...) error");
2141                                        }
2142                                }
2143#endif
2144                        } else {
2145                                /* substring search - simple one - could be improved
2146                                   go thru par and look for info->text */
2147                                while (par [index]) {
2148                                        if (info->trans [(guchar) info->text
2149                                                        [(info->forward) ? eq_bytes : info->text_bytes - eq_bytes - 1]]
2150                                            == info->trans [par [index]]) {
2151                                                eq_bytes ++;
2152                                                if (eq_bytes == info->text_bytes) {
2153                                                        search_set_info (head, info, par,
2154                                                                         index - (info->forward
2155                                                                                  ? -(((guchar *) g_utf8_next_char (par + index - eq_bytes)) - par - index)
2156                                                                                  : 0),
2157                                                                         info->text_bytes);
2158                                                        retval=TRUE;
2159                                                        break;
2160                                                }
2161                                        } else {
2162                                                index += (info->forward) ? -eq_bytes : eq_bytes;
2163                                                eq_bytes = 0;
2164                                        }
2165                                        index += (info->forward)
2166                                                ? (((guchar *) g_utf8_next_char (par + index)) - par - index)
2167                                                : (((guchar *) g_utf8_prev_char (par + index)) - par - index);
2168                                }
2169                        }
2170                }
2171                g_free (par);
2172        }
2173
2174        *beg = cur;
2175
2176        return retval;
2177}
2178
2179static gboolean
2180search (HTMLObject *obj, HTMLSearch *info)
2181{
2182        HTMLClue *clue = HTML_CLUE (obj);
2183        HTMLObject *cur;
2184        gboolean next = FALSE;
2185
2186        /* does last search end here? */
2187        if (info->found) {
2188                cur  = HTML_OBJECT (info->found->data);
2189                next = TRUE;
2190        } else {
2191                /* search_next? */
2192                if (html_search_child_on_stack (info, obj)) {
2193                        cur  = html_search_pop (info);
2194                        cur  = (info->forward) ? cur->next : cur->prev;
2195                        next = TRUE;
2196                } else {
2197                        /* normal search */
2198                        cur  = (info->forward) ? clue->head : clue->tail;
2199                }
2200        }
2201        while (cur) {
2202                gboolean found = FALSE;
2203                gboolean is_text;
2204
2205                is_text = html_object_is_text (cur);
2206
2207                if (is_text) {
2208                        if (search_text (&cur, info))
2209                                return TRUE;
2210                }
2211
2212                if (info->found) {
2213                        g_list_free (info->found);
2214                        info->found = NULL;
2215                        info->start_pos = 0;
2216                        found = TRUE;
2217                }
2218
2219                if (!is_text) {
2220                        if (!found || (info->start_pos < 0 && info->forward) || (info->start_pos >= 0 && !info->forward)) {
2221                                html_search_push (info, cur);
2222                                if (html_object_search (cur, info))
2223                                        return TRUE;
2224                                html_search_pop (info);
2225                        }
2226                        cur = (info->forward) ? cur->next : cur->prev;
2227                }
2228        }
2229
2230        if (next) {
2231                return html_search_next_parent (info);
2232        }
2233
2234        return FALSE;
2235}
2236
2237static gboolean
2238search_next (HTMLObject *obj, HTMLSearch *info)
2239{
2240        return FALSE;
2241}
2242
2243static gboolean
2244relayout (HTMLObject *self,
2245          HTMLEngine *engine,
2246          HTMLObject *child)
2247{
2248        gint mw;
2249
2250        mw = html_object_calc_min_width (self, engine->painter);
2251        if (mw <= self->max_width)
2252                return (*HTML_OBJECT_CLASS (parent_class)->relayout) (self, engine, child);
2253        html_engine_calc_size (engine, FALSE);
2254        html_engine_draw (engine, engine->x_offset, engine->y_offset, engine->width, engine->height);
2255
2256        return TRUE;
2257}
2258
2259
2260void
2261html_clueflow_type_init (void)
2262{
2263        html_clueflow_class_init (&html_clueflow_class, HTML_TYPE_CLUEFLOW, sizeof (HTMLClueFlow));
2264}
2265
2266void
2267html_clueflow_class_init (HTMLClueFlowClass *klass,
2268                          HTMLType type,
2269                          guint size)
2270{
2271        HTMLClueClass *clue_class;
2272        HTMLObjectClass *object_class;
2273
2274        clue_class = HTML_CLUE_CLASS (klass);
2275        object_class = HTML_OBJECT_CLASS (klass);
2276
2277        html_clue_class_init (clue_class, type, size);
2278
2279        object_class->destroy = destroy;
2280        object_class->copy = copy;
2281        object_class->op_cut = op_cut;
2282        object_class->op_copy = op_copy;
2283        object_class->split = split;
2284        object_class->merge = merge;
2285        object_class->calc_size = html_clue_flow_real_calc_size;
2286        object_class->set_max_width = set_max_width;
2287        object_class->set_max_height = set_max_height;
2288        object_class->calc_min_width = calc_min_width;
2289        object_class->calc_preferred_width = calc_preferred_width;
2290        object_class->draw = draw;
2291        object_class->save = save;
2292        object_class->save_plain = save_plain;
2293        object_class->check_point = check_point;
2294        object_class->append_selection_string = append_selection_string;
2295        object_class->search = search;
2296        object_class->search_next = search_next;
2297        object_class->relayout = relayout;
2298        object_class->get_recursive_length = get_recursive_length;
2299        object_class->get_clear = get_clear;
2300        object_class->set_painter = set_painter;
2301
2302        klass->get_default_font_style = get_default_font_style;
2303
2304        parent_class = &html_clue_class;
2305}
2306
2307void
2308html_clueflow_init (HTMLClueFlow *clueflow, HTMLClueFlowClass *klass,
2309                    HTMLClueFlowStyle style, GByteArray *levels, HTMLListType item_type, gint item_number,
2310                    HTMLClearType clear)
2311{
2312        HTMLObject *object;
2313        HTMLClue *clue;
2314
2315        object = HTML_OBJECT (clueflow);
2316        clue = HTML_CLUE (clueflow);
2317
2318        html_clue_init (clue, HTML_CLUE_CLASS (klass));
2319
2320        object->flags &= ~HTML_OBJECT_FLAG_FIXEDWIDTH;
2321
2322        clue->valign = HTML_VALIGN_BOTTOM;
2323        clue->halign = HTML_HALIGN_NONE;
2324
2325        clueflow->style = style;
2326        clueflow->levels = levels;
2327        clueflow->indent_width = -1;
2328
2329        clueflow->item_type   = item_type;
2330        clueflow->item_number = item_number;
2331        clueflow->item_color = NULL;
2332
2333        clueflow->clear = clear;
2334}
2335
2336HTMLObject *
2337html_clueflow_new (HTMLClueFlowStyle style, GByteArray *levels, HTMLListType item_type, gint item_number, HTMLClearType clear)
2338{
2339        HTMLClueFlow *clueflow;
2340
2341        clueflow = g_new (HTMLClueFlow, 1);
2342        html_clueflow_init (clueflow, &html_clueflow_class, style, levels, item_type, item_number, clear);
2343
2344        return HTML_OBJECT (clueflow);
2345}
2346
2347HTMLObject *
2348html_clueflow_new_from_flow (HTMLClueFlow *flow)
2349{
2350        HTMLObject *o;
2351
2352        o = html_clueflow_new (flow->style, html_clueflow_dup_levels (flow),
2353                               flow->item_type, flow->item_number, flow->clear);
2354        html_object_copy_data_from_object (o, HTML_OBJECT (flow));
2355
2356        return o;
2357}
2358
2359
2360/* Virtual methods.  */
2361
2362GtkHTMLFontStyle
2363html_clueflow_get_default_font_style (const HTMLClueFlow *self)
2364{
2365        g_return_val_if_fail (self != NULL, GTK_HTML_FONT_STYLE_DEFAULT);
2366
2367        return (* HCF_CLASS (self)->get_default_font_style) (self);
2368}
2369
2370
2371/* Clue splitting (for editing).  */
2372
2373/**
2374 * html_clue_split:
2375 * @clue:
2376 * @child:
2377 *
2378 * Remove @child and its successors from @clue, and create a new clue
2379 * containing them.  The new clue has the same properties as the original clue.
2380 *
2381 * Return value: A pointer to the new clue.
2382 **/
2383HTMLClueFlow *
2384html_clueflow_split (HTMLClueFlow *clue,
2385                     HTMLObject *child)
2386{
2387        HTMLClueFlow *new;
2388        HTMLObject *prev;
2389
2390        g_return_val_if_fail (clue != NULL, NULL);
2391        g_return_val_if_fail (child != NULL, NULL);
2392
2393        /* Create the new clue.  */
2394
2395        new = HTML_CLUEFLOW (html_clueflow_new_from_flow (clue));
2396
2397        /* Remove the children from the original clue.  */
2398
2399        prev = child->prev;
2400        if (prev != NULL) {
2401                prev->next = NULL;
2402                HTML_CLUE (clue)->tail = prev;
2403        } else {
2404                HTML_CLUE (clue)->head = NULL;
2405                HTML_CLUE (clue)->tail = NULL;
2406        }
2407
2408        child->prev = NULL;
2409        html_object_change_set (HTML_OBJECT (clue), HTML_CHANGE_ALL_CALC);
2410
2411        /* Put the children into the new clue.  */
2412
2413        html_clue_append (HTML_CLUE (new), child);
2414
2415        /* Return the new clue.  */
2416
2417        return new;
2418}
2419
2420
2421static void
2422relayout_and_draw (HTMLObject *object,
2423                   HTMLEngine *engine)
2424{
2425        if (engine == NULL)
2426                return;
2427
2428        html_object_relayout (object, engine, NULL);
2429        html_engine_queue_draw (engine, object);
2430}
2431
2432/* This performs a relayout of the object when the indentation level
2433   has changed.  In this case, we need to relayout the previous
2434   paragraph and the following one, because their padding might change
2435   after the level change. */
2436static void
2437relayout_with_siblings (HTMLClueFlow *flow,
2438                        HTMLEngine *engine)
2439{
2440        if (engine == NULL)
2441                return;
2442
2443        /* FIXME this is ugly and inefficient.  */
2444
2445        if (HTML_OBJECT (flow)->prev != NULL)
2446                relayout_and_draw (HTML_OBJECT (flow)->prev, engine);
2447
2448        relayout_and_draw (HTML_OBJECT (flow), engine);
2449
2450        if (HTML_OBJECT (flow)->next != NULL)
2451                relayout_and_draw (HTML_OBJECT (flow)->next, engine);
2452}
2453
2454
2455void
2456html_clueflow_set_style (HTMLClueFlow *flow,
2457                         HTMLEngine *engine,
2458                         HTMLClueFlowStyle style)
2459{
2460        g_return_if_fail (flow != NULL);
2461        g_return_if_fail (engine != NULL);
2462        g_return_if_fail (HTML_IS_ENGINE (engine));
2463
2464        html_object_change_set_down (HTML_OBJECT (flow), HTML_CHANGE_ALL);
2465        flow->style = style;
2466        if (style != HTML_CLUEFLOW_STYLE_LIST_ITEM)
2467                flow->item_number = 0;
2468
2469        html_engine_schedule_update (engine);
2470        /* FIXME - make it more effective: relayout_with_siblings (flow, engine); */
2471}
2472
2473GByteArray *
2474html_clueflow_dup_levels (HTMLClueFlow *flow)
2475{
2476        GByteArray *levels;
2477       
2478        levels = g_byte_array_new ();
2479        copy_levels (levels, flow->levels);
2480       
2481        return levels;
2482}
2483
2484void
2485html_clueflow_set_levels (HTMLClueFlow *flow,
2486                          HTMLEngine *engine,
2487                          GByteArray *levels)
2488{
2489        HTMLObject *next_relative;
2490
2491        next_relative = get_next_relative_item (HTML_OBJECT (flow));
2492        copy_levels (flow->levels, levels);
2493
2494        update_item_number (HTML_OBJECT (flow), engine);
2495        if (next_relative)
2496                update_item_number (next_relative, engine);
2497
2498        relayout_with_siblings (flow, engine);
2499}
2500
2501void
2502html_clueflow_set_item_type (HTMLClueFlow *flow,
2503                             HTMLEngine *engine,
2504                             HTMLListType item_type)
2505{
2506        g_return_if_fail (flow != NULL);
2507        g_return_if_fail (engine != NULL);
2508        g_return_if_fail (HTML_IS_ENGINE (engine));
2509
2510        html_object_change_set (HTML_OBJECT (flow), HTML_CHANGE_ALL);
2511
2512        if ((is_blockquote (item_type) != is_blockquote (flow->item_type)) && flow->levels->len)
2513                flow->levels->data[flow->levels->len - 1] = item_type;
2514
2515        flow->item_type = item_type;
2516
2517        update_item_number (HTML_OBJECT (flow), engine);
2518        if (!items_are_relative (HTML_OBJECT (flow), HTML_OBJECT (flow)->next) && HTML_OBJECT (flow)->next)
2519                update_item_number (HTML_OBJECT (flow)->next, engine);
2520
2521        html_engine_schedule_update (engine);
2522        /* FIXME - make it more effective: relayout_with_siblings (flow, engine); */
2523}
2524
2525HTMLClueFlowStyle
2526html_clueflow_get_style (HTMLClueFlow *flow)
2527{
2528        g_return_val_if_fail (flow != NULL, HTML_CLUEFLOW_STYLE_NORMAL);
2529
2530        return flow->style;
2531}
2532
2533HTMLListType
2534html_clueflow_get_item_type (HTMLClueFlow *flow)
2535{
2536        g_return_val_if_fail (flow != NULL, HTML_LIST_TYPE_BLOCKQUOTE);
2537
2538        return flow->item_type;
2539}
2540
2541void
2542html_clueflow_set_halignment (HTMLClueFlow *flow,
2543                              HTMLEngine *engine,
2544                              HTMLHAlignType alignment)
2545{
2546        g_return_if_fail (flow != NULL);
2547        g_return_if_fail (engine != NULL);
2548        g_return_if_fail (HTML_IS_ENGINE (engine));
2549
2550        HTML_CLUE (flow)->halign = alignment;
2551
2552        relayout_and_draw (HTML_OBJECT (flow), engine);
2553}
2554
2555inline HTMLHAlignType
2556html_clueflow_get_halignment (HTMLClueFlow *flow)
2557{
2558        g_return_val_if_fail (flow != NULL, HTML_HALIGN_NONE);
2559
2560        if (HTML_CLUE (flow)->halign == HTML_HALIGN_NONE) {
2561                if (HTML_OBJECT (flow)->parent && HTML_IS_TABLE_CELL (HTML_OBJECT (flow)->parent))
2562                        return HTML_CLUE (HTML_OBJECT (flow)->parent)->halign == HTML_HALIGN_NONE
2563                                ? HTML_TABLE_CELL (HTML_OBJECT (flow)->parent)->heading ? HTML_HALIGN_CENTER : HTML_HALIGN_LEFT
2564                                : HTML_CLUE (HTML_OBJECT (flow)->parent)->halign;
2565                else
2566                        return HTML_CLUE (HTML_OBJECT (flow)->parent)->halign == HTML_HALIGN_NONE
2567                                ? HTML_HALIGN_LEFT : HTML_CLUE (HTML_OBJECT (flow)->parent)->halign;
2568        } else
2569                return HTML_CLUE (flow)->halign;
2570}
2571
2572void
2573html_clueflow_modify_indentation_by_delta (HTMLClueFlow *flow,
2574                                           HTMLEngine *engine,
2575                                           gint indentation_delta,
2576                                           guint8 *indentation_levels)
2577{
2578        HTMLObject *next_relative;
2579        gint indentation;
2580        g_return_if_fail (flow != NULL);
2581        g_return_if_fail (engine != NULL);
2582        g_return_if_fail (HTML_IS_ENGINE (engine));
2583
2584        next_relative = get_next_relative_item (HTML_OBJECT (flow));
2585
2586        indentation = flow->levels->len + indentation_delta;
2587        indentation = indentation < 0 ? 0 : indentation;
2588
2589        if (indentation_delta > 0)
2590                g_byte_array_append (flow->levels, indentation_levels, indentation_delta);
2591        else {
2592                g_byte_array_set_size (flow->levels, indentation);
2593                if (is_item (flow) && indentation < 1 && indentation_delta < 0) {
2594                        html_clueflow_set_style (flow, engine, HTML_CLUEFLOW_STYLE_NORMAL);
2595                        html_clueflow_set_item_type (flow, engine, HTML_LIST_TYPE_BLOCKQUOTE);
2596                        html_object_change_set_down (HTML_OBJECT (flow), HTML_CHANGE_ALL);
2597                }
2598        }
2599
2600        update_item_number (HTML_OBJECT (flow), engine);
2601        if (next_relative)
2602                update_item_number (next_relative, engine);
2603        relayout_with_siblings (flow, engine);
2604}
2605
2606void
2607html_clueflow_set_indentation (HTMLClueFlow *flow,
2608                               HTMLEngine *engine,
2609                               gint indentation,
2610                               guint8 *indentation_levels)
2611{
2612        HTMLObject *next_relative;
2613        int i;
2614        g_return_if_fail (flow != NULL);
2615        g_return_if_fail (engine != NULL);
2616        g_return_if_fail (HTML_IS_ENGINE (engine));
2617
2618        if (indentation < 0)
2619                indentation = 0;
2620
2621        next_relative = get_next_relative_item (HTML_OBJECT (flow));
2622
2623        g_byte_array_set_size (flow->levels, indentation);
2624
2625        i = indentation;
2626        while (i--)
2627                flow->levels->data[i] = indentation_levels[i];
2628
2629        update_item_number (HTML_OBJECT (flow), engine);
2630        if (next_relative)
2631                update_item_number (next_relative, engine);
2632        relayout_with_siblings (flow, engine);
2633}
2634
2635guint8
2636html_clueflow_get_indentation (HTMLClueFlow *flow)
2637{
2638        g_return_val_if_fail (flow != NULL, 0);
2639
2640        // FIXME levels
2641        return flow->levels->len;
2642}
2643
2644#if 0
2645void
2646html_clueflow_set_properties (HTMLClueFlow *flow,
2647                              HTMLEngine *engine,
2648                              HTMLClueFlowStyle style,
2649                              guint8 indentation,
2650                              HTMLHAlignType alignment)
2651{
2652        g_return_if_fail (flow != NULL);
2653        g_return_if_fail (engine != NULL);
2654        g_return_if_fail (HTML_IS_ENGINE (engine));
2655
2656        HTML_CLUE (flow)->halign = alignment;
2657
2658        flow->style = style;
2659        html_clueflow_set_indentation (flow, engine, indentation);
2660
2661        relayout_and_draw (HTML_OBJECT (flow), engine);
2662}
2663
2664void
2665html_clueflow_get_properties (HTMLClueFlow *flow,
2666                              HTMLClueFlowStyle *style_return,
2667                              guint8 *indentation_return,
2668                              HTMLHAlignType *alignment_return)
2669{
2670        g_return_if_fail (flow != NULL);
2671
2672        if (style_return != NULL)
2673                *style_return = flow->style;
2674        if (indentation_return != NULL)
2675                // FIXME levels
2676                *indentation_return = flow->levels->len;
2677        if (alignment_return != NULL)
2678                *alignment_return = HTML_CLUE (flow)->halign;
2679}
2680#endif
2681/* spell checking */
2682
2683#include "htmlinterval.h"
2684
2685static guint
2686get_text_bytes (HTMLClue *clue, HTMLInterval *i)
2687{
2688        HTMLObject *obj;
2689        guint bytes;
2690
2691        g_assert (i);
2692        g_assert (i->from.object);
2693        g_assert (i->to.object);
2694
2695        bytes = 0;
2696        obj = html_interval_get_head (i, HTML_OBJECT (clue));
2697        while (obj) {
2698                bytes += html_interval_get_bytes (i, obj);
2699                if (obj == i->to.object)
2700                        break;
2701                obj = html_object_next_not_slave (obj);
2702        }
2703
2704        return bytes;
2705}
2706
2707static gchar *
2708get_text (HTMLClue *clue, HTMLInterval *i)
2709{
2710        HTMLObject *obj;
2711        guint cb, bytes = 0;
2712        gchar *text, *ct;
2713
2714        bytes        = get_text_bytes (clue, i);
2715        ct           = text = g_malloc (bytes + 1);
2716        text [bytes] = 0;
2717
2718        obj = html_interval_get_head (i, HTML_OBJECT (clue));
2719        while (obj) {
2720                cb = html_interval_get_bytes (i, obj);
2721                if (html_object_is_text (obj))
2722                        strncpy (ct, HTML_TEXT (obj)->text + html_interval_get_start_index (i, obj), cb);
2723                else
2724                        if (cb == 1) *ct = ' ';
2725                        else memset (ct, ' ', cb);
2726                ct += cb;
2727                if (obj == i->to.object)
2728                        break;
2729                obj = html_object_next_not_slave (obj);
2730        }
2731
2732        /* printf ("get_text: %d \"%s\"\n", bytes, text); */
2733
2734        return text;
2735}
2736
2737static HTMLObject *
2738next_obj_and_clear (HTMLObject *obj, guint *off, gboolean *is_text, HTMLInterval *i)
2739{
2740        *off += html_object_get_length (obj) - html_interval_get_start (i, obj);
2741        obj = obj->next;
2742        if (obj && (*is_text = html_object_is_text (obj)))
2743                html_text_spell_errors_clear_interval (HTML_TEXT (obj), i);
2744
2745        return obj;
2746}
2747
2748static HTMLObject *
2749spell_check_word_mark (HTMLObject *obj, const gchar *text, const gchar *word, guint *off, HTMLInterval *i)
2750{
2751        guint w_off, ioff;
2752        guint len = g_utf8_strlen (word, -1);
2753        gboolean is_text;
2754
2755        /* printf ("[not in dictionary word off: %d off: %d]\n", word - text, *off); */
2756        is_text = html_object_is_text (obj);
2757        w_off   = g_utf8_pointer_to_offset (text, word);
2758        while (obj && (!is_text || (is_text && *off + html_interval_get_length (i, obj) <= w_off)))
2759                obj = next_obj_and_clear (obj, off, &is_text, i);
2760
2761        /* printf ("is_text: %d len: %d obj: %p off: %d\n", is_text, len, obj, *off); */
2762        if (obj && is_text) {
2763                gchar *t;
2764                guint tlen;
2765                guint toff;
2766
2767                while (len) {
2768                        toff  = w_off - *off;
2769                        ioff  = html_interval_get_start (i, obj);
2770                        tlen  = MIN (HTML_TEXT (obj)->text_len - toff - ioff, len);
2771                        t     = HTML_TEXT (obj)->text;
2772                        g_assert (!strncmp (word, g_utf8_offset_to_pointer (t, toff + ioff),
2773                                            g_utf8_offset_to_pointer (t, toff + ioff + tlen)
2774                                            - g_utf8_offset_to_pointer (t, toff + ioff)));
2775                        /* printf ("add spell error - word: %s off: %d beg: %s len: %d\n",
2776                           word, *off, HTML_TEXT (obj)->text + toff, tlen); */
2777                        html_text_spell_errors_add (HTML_TEXT (obj),
2778                                                    ioff + toff, tlen);
2779                        len     -= tlen;
2780                        w_off   += tlen;
2781                        word     = g_utf8_offset_to_pointer (word, tlen);
2782                        if (len)
2783                                do obj = next_obj_and_clear (obj, off, &is_text, i); while (obj && !is_text);
2784                        /* printf ("off: %d\n", *off); */
2785                        g_assert (!len || obj);
2786                }
2787        }
2788
2789        return obj;
2790}
2791
2792static gchar *
2793begin_of_word (gchar *text, gchar *ct, gboolean *cited)
2794{
2795        gunichar uc;
2796
2797        *cited = FALSE;
2798        do
2799                uc = g_utf8_get_char (ct);
2800        while (!html_selection_spell_word (uc, cited) && (ct = g_utf8_next_char (ct)) && *ct);
2801
2802        return ct;
2803}
2804
2805static gchar *
2806end_of_word (gchar *ct, gboolean cited)
2807{
2808        gunichar uc, ucn;
2809        gchar *cn;
2810        gboolean cited2;
2811
2812        cited2 = FALSE;
2813        while (*ct
2814               && (uc = g_utf8_get_char (ct))
2815               && (cn = g_utf8_next_char (ct))
2816               && (html_selection_spell_word (uc, &cited2)
2817                   || (!cited && cited2)
2818                   || (cited && cited2 && (ucn = g_utf8_get_char (cn)) && g_unichar_isalpha (ucn)))) {
2819                ct = cn;
2820                cited2 = FALSE;
2821        }
2822
2823        return ct;
2824}
2825
2826static void
2827queue_draw (HTMLObject *o, HTMLEngine *e, HTMLInterval *i)
2828{
2829        if (html_object_is_text (o))
2830                html_text_queue_draw (HTML_TEXT (o), e, html_interval_get_start (i, o), html_interval_get_length (i, o));
2831}
2832
2833void
2834html_clueflow_spell_check (HTMLClueFlow *flow, HTMLEngine *e, HTMLInterval *interval)
2835{
2836        HTMLObject *obj;
2837        HTMLClue *clue;
2838        guint off;
2839        gchar *text, *ct, *word;
2840        HTMLInterval *new_interval = NULL;
2841
2842        g_return_if_fail (flow != NULL);
2843        g_return_if_fail (HTML_OBJECT_TYPE (flow) == HTML_TYPE_CLUEFLOW);
2844
2845        /* if (interval)
2846           printf ("html_clueflow_spell_check %p %p %d %d\n", i->from, i->to, i->from_offset, i->to_offset); */
2847
2848        clue = HTML_CLUE (flow);
2849        if (!e->widget->editor_api || !gtk_html_get_inline_spelling (e->widget) || !clue || !clue->tail)
2850                return;
2851
2852        off  = 0;
2853
2854        if (!interval) {
2855                new_interval = html_interval_new (clue->head, clue->tail, 0, html_object_get_length (clue->tail));
2856                interval = new_interval;
2857        }
2858
2859        text = get_text (clue, interval);
2860        obj  = html_interval_get_head (interval, HTML_OBJECT (flow));
2861        if (obj && html_object_is_text (obj))
2862                html_text_spell_errors_clear_interval (HTML_TEXT (obj), interval);
2863
2864        if (text) {
2865                ct = text;
2866                while (*ct) {
2867                        gboolean cited;
2868
2869                        word = ct = begin_of_word (text, ct, &cited);
2870                        ct        =   end_of_word (ct, cited);
2871
2872                        /* test if we have found word */
2873                        if (word != ct) {
2874                                gint result;
2875                                gchar bak;
2876
2877                                bak = *ct;
2878                                *ct = 0;
2879                                /* printf ("off %d going to test word: \"%s\"\n", off, word); */
2880                                result = (*e->widget->editor_api->check_word) (e->widget, word, e->widget->editor_data);
2881
2882                                if (result == 1) {
2883                                        gboolean is_text = (obj) ? html_object_is_text (obj) : FALSE;
2884                                        while (obj && (!is_text
2885                                                       || (off + html_interval_get_length (interval, obj)
2886                                                           < g_utf8_pointer_to_offset (text, ct))))
2887                                                obj = next_obj_and_clear (obj, &off, &is_text, interval);
2888                                } else if (obj)
2889                                                obj = spell_check_word_mark (obj, text, word, &off, interval);
2890
2891                                *ct = bak;
2892                                if (*ct)
2893                                        ct = g_utf8_next_char (ct);
2894                        }
2895                }
2896                g_free (text);
2897
2898                if (!html_engine_frozen (e)) {
2899                        /* html_engine_queue_clear (e); */
2900                        html_interval_forall (interval, e, (HTMLObjectForallFunc) queue_draw, interval);
2901                        html_engine_flush_draw_queue (e);
2902                }
2903                html_interval_destroy (new_interval);
2904        }
2905}
2906
2907gboolean
2908html_clueflow_is_empty (HTMLClueFlow *flow)
2909{
2910        HTMLClue *clue;
2911        g_return_val_if_fail (HTML_IS_CLUEFLOW (flow), TRUE);
2912
2913        clue = HTML_CLUE (flow);
2914
2915        if (!clue->head
2916            || (clue->head && html_object_is_text (clue->head)
2917                && HTML_TEXT (clue->head)->text_len == 0 && !html_object_next_not_slave (clue->head)))
2918                return TRUE;
2919        return FALSE;
2920}
2921
2922gboolean
2923html_clueflow_contains_table (HTMLClueFlow *flow)
2924{
2925        HTMLClue *clue;
2926        g_return_val_if_fail (HTML_IS_CLUEFLOW (flow), FALSE);
2927
2928        clue = HTML_CLUE (flow);
2929
2930        if (clue->head && HTML_IS_TABLE (clue->head))
2931                return TRUE;
2932
2933        return FALSE;
2934}
2935
2936gint
2937html_clueflow_get_line_offset (HTMLClueFlow *flow, HTMLPainter *painter, HTMLObject *child)
2938{
2939        HTMLObject *o, *head;
2940        gint line_offset;
2941
2942        g_assert (HTML_IS_CLUEFLOW (flow));
2943
2944        if (!html_clueflow_tabs (flow, painter))
2945                return -1;
2946
2947        line_offset = 0;
2948
2949        /* find head */
2950        o = head = child;
2951        while (o) {
2952                o = head->prev;
2953                if (o) {
2954                        if (o->y + o->descent - 1 < child->y - child->ascent)
2955                                break;
2956                        else
2957                                head = o;
2958                }
2959        }
2960
2961        if (HTML_IS_TEXT_SLAVE (head)) {
2962                HTMLTextSlave *bol = HTML_TEXT_SLAVE (head);
2963
2964                html_text_text_line_length (html_text_get_text (bol->owner, bol->posStart),
2965                                            &line_offset, bol->owner->text_len - bol->posStart, NULL);
2966                head = html_object_next_not_slave (head);
2967        }
2968
2969        while (head) {
2970                if (head == child)
2971                        break;
2972                line_offset += html_object_get_line_length (head, painter, line_offset);
2973                head = html_object_next_not_slave (head);
2974        }
2975        /* printf ("lo: %d\n", line_offset); */
2976        return line_offset;
2977}
2978
2979gboolean
2980html_clueflow_tabs (HTMLClueFlow *flow, HTMLPainter *p)
2981{
2982        return (flow && HTML_IS_CLUEFLOW (flow) && flow->style == HTML_CLUEFLOW_STYLE_PRE) || HTML_IS_PLAIN_PAINTER (p)
2983                ? TRUE : FALSE;
2984}
2985
2986void
2987html_clueflow_set_item_color (HTMLClueFlow *flow, HTMLColor *color)
2988{
2989        if (flow->item_color)
2990                html_color_unref (flow->item_color);
2991        if (color)
2992                html_color_ref (color);
2993        flow->item_color = color;
2994}
2995
2996gboolean
2997html_clueflow_style_equals (HTMLClueFlow *cf1, HTMLClueFlow *cf2)
2998{
2999        if (!cf1 || !cf2
3000            || !HTML_IS_CLUEFLOW (cf1) || !HTML_IS_CLUEFLOW (cf2)
3001            || cf1->style != cf2->style
3002            || (cf1->style == HTML_CLUEFLOW_STYLE_LIST_ITEM && cf1->item_type != cf2->item_type)
3003            || !is_levels_equal (cf1, cf2))
3004                return FALSE;
3005        return TRUE;
3006}
Note: See TracBrowser for help on using the repository browser.