source: trunk/third/gtkhtml/src/htmlcursor.c @ 18136

Revision 18136, 18.5 KB checked in by ghudson, 22 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r18135, 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 1999, 2000 Helix Code, Inc.
5
6   This library is free software; you can redistribute it and/or
7   modify it under the terms of the GNU Library General Public
8   License as published by the Free Software Foundation; either
9   version 2 of the License, or (at your option) any later version.
10
11   This library is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   Library General Public License for more details.
15
16   You should have received a copy of the GNU Library General Public License
17   along with this library; see the file COPYING.LIB.  If not, write to
18   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19   Boston, MA 02111-1307, USA.
20*/
21
22/* This file is a bit of a hack.  To make things work in a really nice way, we
23   should have some extra methods in the various subclasses to implement cursor
24   movement.  But for now, I think this is a reasonable way to get things to
25   work.  */
26
27#include <config.h>
28#include <glib.h>
29
30#include "htmlclue.h"
31#include "htmlengine.h"
32#include "htmlengine-edit.h"
33#include "htmltext.h"
34#include "htmltextslave.h"
35#include "htmltype.h"
36
37#include "htmlcursor.h"
38
39/* #define _HTML_CURSOR_DEBUG */
40
41#ifdef _HTML_CURSOR_DEBUG
42static void
43debug_location (const HTMLCursor *cursor)
44{
45        HTMLObject *object;
46
47        object = cursor->object;
48        if (object == NULL) {
49                g_print ("Cursor has no position.\n");
50                return;
51        }
52
53        g_print ("Cursor in %s (%p), offset %d, position %d\n",
54                 html_type_name (HTML_OBJECT_TYPE (object)),
55                 object, cursor->offset, cursor->position);
56}
57#else
58#define debug_location(cursor)
59#endif
60
61
62static void
63normalize (HTMLObject **object,
64           guint *offset)
65{
66        if (*offset == 0 && (*object)->prev != NULL) {
67                *object = html_object_prev_not_slave (*object);
68                *offset = html_object_get_length (*object);
69        }
70}
71
72
73
74inline void
75html_cursor_init (HTMLCursor *cursor, HTMLObject *o, guint offset)
76{
77        cursor->object = o;
78        cursor->offset = offset;
79
80        cursor->target_x = 0;
81        cursor->have_target_x = FALSE;
82
83        cursor->position = 0;
84}
85
86HTMLCursor *
87html_cursor_new (void)
88{
89        HTMLCursor *new_cursor;
90
91        new_cursor = g_new (HTMLCursor, 1);
92        html_cursor_init (new_cursor, NULL, 0);
93
94        return new_cursor;
95}
96
97void
98html_cursor_destroy (HTMLCursor *cursor)
99{
100        g_return_if_fail (cursor != NULL);
101
102        g_free (cursor);
103}
104
105/**
106 * html_cursor_copy:
107 * @dest: A cursor object to copy into
108 * @src: A cursor object to copy from
109 *
110 * Copy @src into @dest.  @dest does not need to be an initialized cursor, so
111 * for example declaring a cursor as a local variable and then calling
112 * html_cursor_copy() to initialize it from another cursor's position works.
113 **/
114void
115html_cursor_copy (HTMLCursor *dest,
116                  const HTMLCursor *src)
117{
118        g_return_if_fail (dest != NULL);
119        g_return_if_fail (src != NULL);
120
121        dest->object = src->object;
122        dest->offset = src->offset;
123        dest->target_x = src->target_x;
124        dest->have_target_x = src->have_target_x;
125        dest->position = src->position;
126}
127
128HTMLCursor *
129html_cursor_dup (const HTMLCursor *cursor)
130{
131        HTMLCursor *new;
132
133        new = html_cursor_new ();
134        html_cursor_copy (new, cursor);
135
136        return new;
137}
138
139void
140html_cursor_normalize (HTMLCursor *cursor)
141{
142        g_return_if_fail (cursor != NULL);
143
144        normalize (&cursor->object, &cursor->offset);
145}
146
147void
148html_cursor_home (HTMLCursor *cursor,
149                  HTMLEngine *engine)
150{
151        HTMLObject *obj;
152
153        g_return_if_fail (cursor != NULL);
154        g_return_if_fail (engine != NULL);
155
156        if (engine->clue == NULL) {
157                cursor->object = NULL;
158                cursor->offset = 0;
159                return;
160        }
161
162        if (engine->need_spell_check)
163                html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
164
165        obj = engine->clue;
166        while (!html_object_accepts_cursor (obj)) {
167                HTMLObject *head = html_object_head (obj);
168                if (obj)
169                        obj = head;
170                else
171                        break;
172        }
173
174        cursor->object = obj;
175        cursor->offset = 0;
176
177        if (!html_object_accepts_cursor (obj))
178                html_cursor_forward (cursor, engine);
179
180        cursor->position = 0;
181
182        debug_location (cursor);
183}
184
185
186
187static gboolean
188forward (HTMLCursor *cursor)
189{
190        gboolean retval;
191
192        retval = TRUE;
193        if (!html_object_cursor_forward (cursor->object, cursor)) {
194                HTMLObject *next;
195
196                next = html_object_next_cursor (cursor->object, &cursor->offset);
197                if (next) {
198                        if (!html_object_is_container (next))
199                                cursor->offset = (next->parent == cursor->object->parent) ? 1 : 0;
200                        cursor->object = next;
201                        cursor->position ++;
202                } else
203                        retval = FALSE;
204        }
205        return retval;
206}
207
208static gboolean
209forward_in_flow (HTMLCursor *cursor)
210{
211        gboolean retval;
212
213        retval = TRUE;
214        if (cursor->offset != html_object_get_length (cursor->object)) {
215                if (html_object_is_container (cursor->object)) {
216                        HTMLObject *obj;
217
218                        obj = cursor->object;
219                        while ((retval = forward (cursor)) && cursor->object != obj)
220                                ;
221                } else
222                        retval = html_object_cursor_forward (cursor->object, cursor);
223        } else {
224                if (html_object_next_not_slave (cursor->object))
225                        retval = forward (cursor);
226                else
227                        retval = FALSE;
228        }
229
230        return retval;
231}
232
233gboolean
234html_cursor_forward (HTMLCursor *cursor, HTMLEngine *engine)
235{
236        gboolean retval;
237
238        g_return_val_if_fail (cursor != NULL, FALSE);
239        g_return_val_if_fail (engine != NULL, FALSE);
240
241        if (engine->need_spell_check)
242                html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
243
244        cursor->have_target_x = FALSE;
245        retval = forward (cursor);
246
247        debug_location (cursor);
248
249        return retval;
250}
251
252static gboolean
253backward (HTMLCursor *cursor)
254{
255        gboolean retval;
256
257        retval = TRUE;
258        if (!html_object_cursor_backward (cursor->object, cursor)) {
259                HTMLObject *prev;
260
261                prev = html_object_prev_cursor (cursor->object, &cursor->offset);
262                if (prev) {
263                        if (!html_object_is_container (prev))
264                                cursor->offset = html_object_get_length (prev);
265                        cursor->object = prev;
266                        cursor->position --;
267                } else
268                        retval = FALSE;
269        }
270        return retval;
271}
272
273static gboolean
274backward_in_flow (HTMLCursor *cursor)
275{
276        gboolean retval;
277
278        retval = TRUE;
279        if (cursor->offset  && html_object_is_container (cursor->object)) {
280                HTMLObject *obj;
281
282                obj = cursor->object;
283                while ((retval = backward (cursor)) && cursor->object != obj)
284                        ;
285        } else {
286                if (cursor->offset > 1 || !cursor->object->prev)
287                        retval = html_object_cursor_backward (cursor->object, cursor);
288                else if (cursor->object->prev)
289                        retval = backward (cursor);
290                else
291                        retval = FALSE;
292        }
293
294        return retval;
295}
296
297gboolean
298html_cursor_backward (HTMLCursor *cursor,
299                      HTMLEngine *engine)
300{
301        gboolean retval;
302
303        g_return_val_if_fail (cursor != NULL, FALSE);
304        g_return_val_if_fail (engine != NULL, FALSE);
305
306        if (engine->need_spell_check)
307                html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
308
309        cursor->have_target_x = FALSE;
310        retval = backward (cursor);
311
312        debug_location (cursor);
313
314        return retval;
315}
316
317
318gboolean
319html_cursor_up (HTMLCursor *cursor,
320                HTMLEngine *engine)
321{
322        HTMLCursor orig_cursor;
323        HTMLCursor prev_cursor;
324        gint prev_x, prev_y;
325        gint x, y;
326        gint target_x;
327        gint orig_y;
328        gboolean new_line;
329
330        if (cursor->object == NULL) {
331                g_warning ("The cursor is in a NULL position: going home.");
332                html_cursor_home (cursor, engine);
333                return TRUE;
334        }
335
336        if (engine->need_spell_check)
337                html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
338
339        html_cursor_copy (&orig_cursor, cursor);
340
341        html_object_get_cursor_base (cursor->object,
342                                     engine->painter, cursor->offset,
343                                     &x, &y);
344
345        if (! cursor->have_target_x) {
346                cursor->target_x = x;
347                cursor->have_target_x = TRUE;
348        }
349
350        target_x = cursor->target_x;
351
352        orig_y = y;
353
354        new_line = FALSE;
355
356        while (1) {
357                html_cursor_copy (&prev_cursor, cursor);
358
359                prev_x = x;
360                prev_y = y;
361
362                if (! backward (cursor))
363                        return FALSE;
364
365                html_object_get_cursor_base (cursor->object,
366                                             engine->painter, cursor->offset,
367                                             &x, &y);
368
369                if (html_cursor_equal (&prev_cursor, cursor)) {
370                        html_cursor_copy (cursor, &orig_cursor);
371                        return FALSE;
372                }
373
374                if (y + cursor->object->descent - 1 < prev_y - prev_cursor.object->ascent) {
375                        if (new_line) {
376                                html_cursor_copy (cursor, &prev_cursor);
377                                return FALSE;
378                        }
379
380                        new_line = TRUE;
381                }
382
383                if (new_line && x <= target_x) {
384                        if (! cursor->have_target_x) {
385                                cursor->have_target_x = TRUE;
386                                cursor->target_x = target_x;
387                        }
388
389                        /* Choose the character which is the nearest to the
390                           target X.  */
391                        if (prev_y == y && target_x - x >= prev_x - target_x) {
392                                cursor->object = prev_cursor.object;
393                                cursor->offset = prev_cursor.offset;
394                                cursor->position = prev_cursor.position;
395                        }
396
397                        debug_location (cursor);
398                        return TRUE;
399                }
400        }
401}
402
403
404gboolean
405html_cursor_down (HTMLCursor *cursor,
406                  HTMLEngine *engine)
407{
408        HTMLCursor orig_cursor;
409        HTMLCursor prev_cursor;
410        gint prev_x, prev_y;
411        gint x, y;
412        gint target_x;
413        gint orig_y;
414        gboolean new_line;
415
416        if (cursor->object == NULL) {
417                g_warning ("The cursor is in a NULL position: going home.");
418                html_cursor_home (cursor, engine);
419                return TRUE;
420        }
421
422        if (engine->need_spell_check)
423                html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
424
425        html_object_get_cursor_base (cursor->object,
426                                     engine->painter, cursor->offset,
427                                     &x, &y);
428
429        if (! cursor->have_target_x) {
430                cursor->target_x = x;
431                cursor->have_target_x = TRUE;
432        }
433
434        target_x = cursor->target_x;
435
436        orig_y = y;
437
438        new_line = FALSE;
439
440        while (1) {
441                prev_cursor = *cursor;
442                prev_x = x;
443                prev_y = y;
444
445                if (! forward (cursor))
446                        return FALSE;
447
448                html_object_get_cursor_base (cursor->object,
449                                             engine->painter, cursor->offset,
450                                             &x, &y);
451
452                if (html_cursor_equal (&prev_cursor, cursor)) {
453                        html_cursor_copy (cursor, &orig_cursor);
454                        return FALSE;
455                }
456
457                if (y - cursor->object->ascent > prev_y + prev_cursor.object->descent - 1) {
458                        if (new_line) {
459                                html_cursor_copy (cursor, &prev_cursor);
460                                return FALSE;
461                        }
462
463                        new_line = TRUE;
464                }
465
466                if (new_line && x >= target_x) {
467                        if (! cursor->have_target_x) {
468                                cursor->have_target_x = TRUE;
469                                cursor->target_x = target_x;
470                        }
471
472                        /* Choose the character which is the nearest to the
473                           target X.  */
474                        if (prev_y == y && x - target_x >= target_x - prev_x) {
475                                cursor->object = prev_cursor.object;
476                                cursor->offset = prev_cursor.offset;
477                                cursor->position = prev_cursor.position;
478                        }
479
480                        debug_location (cursor);
481                        return TRUE;
482                }
483        }
484}
485
486
487/**
488 * html_cursor_jump_to:
489 * @cursor:
490 * @object:
491 * @offset:
492 *
493 * Move the cursor to the specified @offset in the specified @object.
494 *
495 * Return value: %TRUE if successfull, %FALSE if failed.
496 **/
497gboolean
498html_cursor_jump_to (HTMLCursor *cursor,
499                     HTMLEngine *engine,
500                     HTMLObject *object,
501                     guint offset)
502{
503        HTMLCursor original;
504
505        g_return_val_if_fail (cursor != NULL, FALSE);
506        g_return_val_if_fail (object != NULL, FALSE);
507
508        if (engine->need_spell_check)
509                html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
510
511        html_cursor_normalize (cursor);
512        normalize (&object, &offset);
513
514        if (cursor->object == object && cursor->offset == offset)
515                return TRUE;
516
517        html_cursor_copy (&original, cursor);
518
519        while (forward (cursor)) {
520                if (cursor->object == object && cursor->offset == offset)
521                        return TRUE;
522        }
523
524        html_cursor_copy (cursor, &original);
525
526        while (backward (cursor)) {
527                if (cursor->object == object && cursor->offset == offset)
528                        return TRUE;
529        }
530
531        return FALSE;
532}
533
534
535/* Complex cursor movement commands.  */
536
537void
538html_cursor_beginning_of_document (HTMLCursor *cursor,
539                                   HTMLEngine *engine)
540{
541        g_return_if_fail (cursor != NULL);
542        g_return_if_fail (engine != NULL);
543        g_return_if_fail (HTML_IS_ENGINE (engine));
544
545        if (engine->need_spell_check)
546                html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
547
548        while (backward (cursor))
549                ;
550}
551
552void
553html_cursor_end_of_document (HTMLCursor *cursor,
554                             HTMLEngine *engine)
555{
556        g_return_if_fail (cursor != NULL);
557        g_return_if_fail (engine != NULL);
558        g_return_if_fail (HTML_IS_ENGINE (engine));
559
560        if (engine->need_spell_check)
561                html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
562
563        while (forward (cursor))
564                ;
565}
566
567gboolean
568html_cursor_end_of_line (HTMLCursor *cursor,
569                         HTMLEngine *engine)
570{
571        HTMLCursor prev_cursor;
572        gint x, y, prev_y;
573
574        g_return_val_if_fail (cursor != NULL, FALSE);
575        g_return_val_if_fail (engine != NULL, FALSE);
576        g_return_val_if_fail (HTML_IS_ENGINE (engine), FALSE);
577
578        cursor->have_target_x = FALSE;
579
580        if (engine->need_spell_check)
581                html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
582
583        html_cursor_copy (&prev_cursor, cursor);
584        html_object_get_cursor_base (cursor->object, engine->painter, cursor->offset,
585                                     &x, &prev_y);
586
587        while (1) {
588                if (! forward_in_flow (cursor))
589                        return TRUE;
590
591                html_object_get_cursor_base (cursor->object, engine->painter, cursor->offset,
592                                             &x, &y);
593
594                if (y - cursor->object->ascent > prev_y + prev_cursor.object->descent - 1) {
595                        html_cursor_copy (cursor, &prev_cursor);
596                        return TRUE;
597                }
598                prev_y = y;
599                html_cursor_copy (&prev_cursor, cursor);
600        }
601}
602
603gboolean
604html_cursor_beginning_of_line (HTMLCursor *cursor,
605                               HTMLEngine *engine)
606{
607        HTMLCursor prev_cursor;
608        gint x, y, prev_y;
609
610        g_return_val_if_fail (cursor != NULL, FALSE);
611        g_return_val_if_fail (engine != NULL, FALSE);
612        g_return_val_if_fail (HTML_IS_ENGINE (engine), FALSE);
613
614        cursor->have_target_x = FALSE;
615
616        if (engine->need_spell_check)
617                html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
618
619        html_cursor_copy (&prev_cursor, cursor);
620        html_object_get_cursor_base (cursor->object, engine->painter, cursor->offset,
621                                     &x, &prev_y);
622
623        while (1) {
624                if (! backward_in_flow (cursor))
625                        return TRUE;
626
627                html_object_get_cursor_base (cursor->object, engine->painter, cursor->offset,
628                                             &x, &y);
629
630                if (y + cursor->object->descent - 1 < prev_y - prev_cursor.object->ascent) {
631                        html_cursor_copy (cursor, &prev_cursor);
632                        return TRUE;
633                }
634
635                prev_y = y;
636                html_cursor_copy (&prev_cursor, cursor);
637        }
638}
639
640
641gint
642html_cursor_get_position (HTMLCursor *cursor)
643{
644        g_return_val_if_fail (cursor != NULL, 0);
645
646        return cursor->position;
647}
648
649void
650html_cursor_jump_to_position_no_spell (HTMLCursor *cursor, HTMLEngine *engine, gint position)
651{
652        gboolean need_spell_check;
653
654        need_spell_check = engine->need_spell_check;
655        engine->need_spell_check = FALSE;
656        html_cursor_jump_to_position (cursor, engine, position);
657        engine->need_spell_check = need_spell_check;
658}
659
660void
661html_cursor_jump_to_position (HTMLCursor *cursor, HTMLEngine *engine, gint position)
662{
663        g_return_if_fail (cursor != NULL);
664        g_return_if_fail (position >= 0);
665
666        if (engine->need_spell_check)
667                html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
668
669        if (cursor->position < position) {
670                while (cursor->position < position) {
671                        if (! forward (cursor))
672                                break;
673                }
674        } else if (cursor->position > position) {
675                while (cursor->position > position) {
676                        if (! backward (cursor))
677                                break;
678                }
679        }
680}
681
682
683/* Comparison.  */
684
685gboolean
686html_cursor_equal (const HTMLCursor *a,
687                   const HTMLCursor *b)
688{
689        g_return_val_if_fail (a != NULL, FALSE);
690        g_return_val_if_fail (b != NULL, FALSE);
691
692        return a->object == b->object && a->offset == b->offset;
693}
694
695gboolean
696html_cursor_precedes (const HTMLCursor *a,
697                      const HTMLCursor *b)
698{
699        g_return_val_if_fail (a != NULL, FALSE);
700        g_return_val_if_fail (b != NULL, FALSE);
701
702        return a->position < b->position;
703}
704
705gboolean
706html_cursor_follows (const HTMLCursor *a,
707                     const HTMLCursor *b)
708{
709        g_return_val_if_fail (a != NULL, FALSE);
710        g_return_val_if_fail (b != NULL, FALSE);
711
712        return a->position > b->position;
713}
714
715
716gunichar
717html_cursor_get_current_char (const HTMLCursor *cursor)
718{
719        HTMLObject *next;
720
721        g_return_val_if_fail (cursor != NULL, 0);
722
723        if (! html_object_is_text (cursor->object)) {
724                if (cursor->offset < html_object_get_length (cursor->object))
725                        return 0;
726
727                next = html_object_next_not_slave (cursor->object);
728                if (next != NULL && html_object_is_text (next))
729                        return html_text_get_char (HTML_TEXT (next), 0);
730
731                return 0;
732        }
733
734        if (cursor->offset < HTML_TEXT (cursor->object)->text_len)
735                return html_text_get_char (HTML_TEXT (cursor->object), cursor->offset);
736
737        next = html_object_next_not_slave (cursor->object);
738        if (next == NULL || ! html_object_is_text (next))
739                return 0;
740
741        return html_text_get_char (HTML_TEXT (next), 0);
742}
743
744gunichar
745html_cursor_get_prev_char (const HTMLCursor *cursor)
746{
747        HTMLObject *prev;
748
749        g_return_val_if_fail (cursor != NULL, 0);
750
751        if (cursor->offset)
752                return (html_object_is_text (cursor->object))
753                        ? html_text_get_char (HTML_TEXT (cursor->object), cursor->offset - 1)
754                        : 0;
755        prev = html_object_prev_not_slave (cursor->object);
756        return (prev && html_object_is_text (prev))
757                ? html_text_get_char (HTML_TEXT (prev), HTML_TEXT (prev)->text_len - 1)
758                : 0;
759}
760
761gboolean
762html_cursor_beginning_of_paragraph (HTMLCursor *cursor, HTMLEngine *engine)
763{
764        HTMLCursor *copy;
765        HTMLObject *flow;
766        gboolean rv = FALSE;
767        gint level, new_level;
768
769        level = html_object_get_parent_level (cursor->object);
770        flow  = cursor->object->parent;
771
772        if (engine->need_spell_check)
773                html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
774
775        while (1) {
776                if (!cursor->offset) {
777                        copy = html_cursor_dup (cursor);
778                        if (backward (cursor)) {
779                                new_level = html_object_get_parent_level (cursor->object);
780                                if (new_level < level
781                                    || (new_level == level && flow != cursor->object->parent)) {
782                                        html_cursor_copy (cursor, copy);
783                                        break;
784                                }
785                                html_cursor_destroy (copy);
786                        } else
787                                break;
788                }
789                        else
790                                if (!backward (cursor))
791                                        break;
792                rv = TRUE;
793        }
794
795        return rv;
796}
797
798gboolean
799html_cursor_end_of_paragraph (HTMLCursor *cursor, HTMLEngine *engine)
800{
801        HTMLCursor *copy;
802        HTMLObject *flow;
803        gboolean rv = FALSE;
804        gint level, new_level;
805
806        level = html_object_get_parent_level (cursor->object);
807        flow  = cursor->object->parent;
808
809        if (engine->need_spell_check)
810                html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
811
812        while (1) {
813                if (cursor->offset == html_object_get_length (cursor->object)) {
814                        copy = html_cursor_dup (cursor);
815                        if (forward (cursor)) {
816                                new_level = html_object_get_parent_level (cursor->object);
817                                if (new_level < level
818                                    || (new_level == level && flow != cursor->object->parent)) {
819                                        html_cursor_copy (cursor, copy);
820                                        break;
821                                }
822                                html_cursor_destroy (copy);
823                        } else
824                                break;
825                }
826                        else
827                                if (!forward (cursor))
828                                        break;
829                rv = TRUE;
830        }
831
832        return rv;
833}
834
835gboolean
836html_cursor_forward_n (HTMLCursor *cursor, HTMLEngine *e, guint n)
837{
838        gboolean rv = FALSE;
839
840        while (n && html_cursor_forward (cursor, e)) {
841                n --;
842                rv = TRUE;
843        }
844
845        return rv;
846}
847
848gboolean
849html_cursor_backward_n (HTMLCursor *cursor, HTMLEngine *e, guint n)
850{
851        gboolean rv = FALSE;
852
853        while (n && html_cursor_backward (cursor, e)) {
854                n --;
855                rv = TRUE;
856        }
857
858        return rv;
859}
Note: See TracBrowser for help on using the repository browser.