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., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, 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 <stdlib.h>
29 #include <glib.h>
30
31 #include "gtkhtml-private.h"
32 #include "htmlclue.h"
33 #include "htmlengine.h"
34 #include "htmlengine-edit.h"
35 #include "htmltext.h"
36 #include "htmltextslave.h"
37 #include "htmltype.h"
38
39 #include "htmlcursor.h"
40
41 static gboolean move_right (HTMLCursor *cursor, HTMLEngine *e);
42 static gboolean move_left (HTMLCursor *cursor, HTMLEngine *e);
43
44 #define _HTML_CURSOR_DEBUG
45
46 #ifdef _HTML_CURSOR_DEBUG
47 static gint gtk_html_cursor_debug_flag = -1;
48
49 static void
debug_location(const HTMLCursor * cursor)50 debug_location (const HTMLCursor *cursor)
51 {
52 HTMLObject *object;
53
54 if (gtk_html_cursor_debug_flag == -1) {
55 if (getenv ("GTK_HTML_DEBUG_CURSOR") != NULL)
56 gtk_html_cursor_debug_flag = 1;
57 else
58 gtk_html_cursor_debug_flag = 0;
59 }
60
61 if (!gtk_html_cursor_debug_flag)
62 return;
63
64 object = cursor->object;
65 if (object == NULL) {
66 g_print ("Cursor has no position.\n");
67 return;
68 }
69
70 g_print ("Cursor in %s (%p), offset %d, position %d\n",
71 html_type_name (HTML_OBJECT_TYPE (object)),
72 (gpointer) object, cursor->offset, cursor->position);
73 }
74 #else
75 #define debug_location(cursor)
76 #endif
77
78
79 static void
normalize(HTMLObject ** object,guint * offset)80 normalize (HTMLObject **object,
81 guint *offset)
82 {
83 if (*offset == 0 && (*object)->prev != NULL) {
84 *object = html_object_prev_not_slave (*object);
85 *offset = html_object_get_length (*object);
86 }
87 }
88
89
90
91 inline void
html_cursor_init(HTMLCursor * cursor,HTMLObject * o,guint offset)92 html_cursor_init (HTMLCursor *cursor,
93 HTMLObject *o,
94 guint offset)
95 {
96 cursor->object = o;
97 cursor->offset = offset;
98
99 cursor->target_x = 0;
100 cursor->have_target_x = FALSE;
101
102 cursor->position = 0;
103 }
104
105 HTMLCursor *
html_cursor_new(void)106 html_cursor_new (void)
107 {
108 HTMLCursor *new_cursor;
109
110 new_cursor = g_new (HTMLCursor, 1);
111 html_cursor_init (new_cursor, NULL, 0);
112
113 return new_cursor;
114 }
115
116 void
html_cursor_destroy(HTMLCursor * cursor)117 html_cursor_destroy (HTMLCursor *cursor)
118 {
119 g_return_if_fail (cursor != NULL);
120
121 g_free (cursor);
122 }
123
124 /**
125 * html_cursor_copy:
126 * @dest: A cursor object to copy into
127 * @src: A cursor object to copy from
128 *
129 * Copy @src into @dest. @dest does not need to be an initialized cursor, so
130 * for example declaring a cursor as a local variable and then calling
131 * html_cursor_copy() to initialize it from another cursor's position works.
132 **/
133 void
html_cursor_copy(HTMLCursor * dest,const HTMLCursor * src)134 html_cursor_copy (HTMLCursor *dest,
135 const HTMLCursor *src)
136 {
137 g_return_if_fail (dest != NULL);
138 g_return_if_fail (src != NULL);
139
140 dest->object = src->object;
141 dest->offset = src->offset;
142 dest->target_x = src->target_x;
143 dest->have_target_x = src->have_target_x;
144 dest->position = src->position;
145 }
146
147 HTMLCursor *
html_cursor_dup(const HTMLCursor * cursor)148 html_cursor_dup (const HTMLCursor *cursor)
149 {
150 HTMLCursor *new;
151
152 new = html_cursor_new ();
153 html_cursor_copy (new, cursor);
154
155 return new;
156 }
157
158 void
html_cursor_normalize(HTMLCursor * cursor)159 html_cursor_normalize (HTMLCursor *cursor)
160 {
161 g_return_if_fail (cursor != NULL);
162
163 normalize (&cursor->object, &cursor->offset);
164 }
165
166 void
html_cursor_home(HTMLCursor * cursor,HTMLEngine * engine)167 html_cursor_home (HTMLCursor *cursor,
168 HTMLEngine *engine)
169 {
170 HTMLObject *obj;
171
172 g_return_if_fail (cursor != NULL);
173 g_return_if_fail (engine != NULL);
174
175 gtk_html_im_reset (engine->widget);
176
177 if (engine->clue == NULL) {
178 cursor->object = NULL;
179 cursor->offset = 0;
180 return;
181 }
182
183 if (engine->need_spell_check)
184 html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
185
186 obj = engine->clue;
187 while (!html_object_accepts_cursor (obj)) {
188 HTMLObject *head = html_object_head (obj);
189 if (head)
190 obj = head;
191 else
192 break;
193 }
194
195 cursor->object = obj;
196 cursor->offset = 0;
197
198 if (!html_object_accepts_cursor (obj))
199 html_cursor_forward (cursor, engine);
200
201 cursor->position = 0;
202
203 debug_location (cursor);
204 }
205
206
207
208 static gboolean
forward(HTMLCursor * cursor,HTMLEngine * engine,gboolean exact_position)209 forward (HTMLCursor *cursor,
210 HTMLEngine *engine,
211 gboolean exact_position)
212 {
213 gboolean retval;
214 gboolean (*forward_func) (HTMLObject *self, HTMLCursor *cursor, HTMLEngine *engine);
215
216 retval = TRUE;
217 if (exact_position)
218 forward_func = html_object_cursor_forward_one;
219 else
220 forward_func = html_object_cursor_forward;
221
222 if (!forward_func (cursor->object, cursor, engine)) {
223 HTMLObject *next;
224
225 next = html_object_next_cursor (cursor->object, (gint *) &cursor->offset);
226 if (next) {
227 if (!html_object_is_container (next))
228 cursor->offset = (next->parent == cursor->object->parent) ? 1 : 0;
229 cursor->object = next;
230 cursor->position++;
231 } else
232 retval = FALSE;
233 }
234 return retval;
235 }
236
237 static gboolean
html_cursor_real_forward(HTMLCursor * cursor,HTMLEngine * engine,gboolean exact_position)238 html_cursor_real_forward (HTMLCursor *cursor,
239 HTMLEngine *engine,
240 gboolean exact_position)
241 {
242 gboolean retval;
243
244 g_return_val_if_fail (cursor != NULL, FALSE);
245 g_return_val_if_fail (engine != NULL, FALSE);
246
247 gtk_html_im_reset (engine->widget);
248
249 if (engine->need_spell_check)
250 html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
251
252 cursor->have_target_x = FALSE;
253 retval = forward (cursor, engine, exact_position);
254
255 debug_location (cursor);
256
257 return retval;
258 }
259
260 gboolean
html_cursor_forward(HTMLCursor * cursor,HTMLEngine * engine)261 html_cursor_forward (HTMLCursor *cursor,
262 HTMLEngine *engine)
263 {
264 return html_cursor_real_forward (cursor, engine, FALSE);
265 }
266
267 gboolean
html_cursor_forward_one(HTMLCursor * cursor,HTMLEngine * engine)268 html_cursor_forward_one (HTMLCursor *cursor,
269 HTMLEngine *engine)
270 {
271 return html_cursor_real_forward (cursor, engine, TRUE);
272 }
273
274 static gboolean
backward(HTMLCursor * cursor,HTMLEngine * engine,gboolean exact_position)275 backward (HTMLCursor *cursor,
276 HTMLEngine *engine,
277 gboolean exact_position)
278 {
279 gboolean retval;
280 gboolean (*backward_func) (HTMLObject *self, HTMLCursor *cursor, HTMLEngine *engine);
281
282 retval = TRUE;
283 if (exact_position)
284 backward_func = html_object_cursor_backward_one;
285 else
286 backward_func = html_object_cursor_backward;
287 if (!backward_func (cursor->object, cursor, engine)) {
288 HTMLObject *prev;
289
290 prev = html_object_prev_cursor (cursor->object, (gint *) &cursor->offset);
291 if (prev) {
292 if (!html_object_is_container (prev))
293 cursor->offset = html_object_get_length (prev);
294 cursor->object = prev;
295 cursor->position--;
296 } else
297 retval = FALSE;
298 }
299 return retval;
300 }
301
302 static gboolean
html_cursor_real_backward(HTMLCursor * cursor,HTMLEngine * engine,gboolean exact_position)303 html_cursor_real_backward (HTMLCursor *cursor,
304 HTMLEngine *engine,
305 gboolean exact_position)
306 {
307 gboolean retval;
308
309 g_return_val_if_fail (cursor != NULL, FALSE);
310 g_return_val_if_fail (engine != NULL, FALSE);
311
312 gtk_html_im_reset (engine->widget);
313
314 if (engine->need_spell_check)
315 html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
316
317 cursor->have_target_x = FALSE;
318 retval = backward (cursor, engine, exact_position);
319
320 debug_location (cursor);
321
322 return retval;
323 }
324
325 gboolean
html_cursor_backward(HTMLCursor * cursor,HTMLEngine * engine)326 html_cursor_backward (HTMLCursor *cursor,
327 HTMLEngine *engine)
328 {
329 return html_cursor_real_backward (cursor, engine, FALSE);
330 }
331
332 gboolean
html_cursor_backward_one(HTMLCursor * cursor,HTMLEngine * engine)333 html_cursor_backward_one (HTMLCursor *cursor,
334 HTMLEngine *engine)
335 {
336 return html_cursor_real_backward (cursor, engine, TRUE);
337 }
338
339
340 gboolean
html_cursor_up(HTMLCursor * cursor,HTMLEngine * engine)341 html_cursor_up (HTMLCursor *cursor,
342 HTMLEngine *engine)
343 {
344 HTMLCursor orig_cursor;
345 HTMLCursor prev_cursor;
346 HTMLDirection dir;
347 gint prev_x, prev_y;
348 gint x, y;
349 gint target_x;
350 gboolean new_line;
351
352 gtk_html_im_reset (engine->widget);
353
354 if (cursor->object == NULL) {
355 g_warning ("The cursor is in a NULL position: going home.");
356 html_cursor_home (cursor, engine);
357 return TRUE;
358 }
359
360 if (engine->need_spell_check)
361 html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
362
363 if (cursor->object->parent)
364 dir = html_object_get_direction (cursor->object->parent);
365 else
366 dir = HTML_DIRECTION_LTR;
367
368 html_cursor_copy (&orig_cursor, cursor);
369
370 html_object_get_cursor_base (cursor->object,
371 engine->painter, cursor->offset,
372 &x, &y);
373
374 if (!cursor->have_target_x) {
375 cursor->target_x = x;
376 cursor->have_target_x = TRUE;
377 }
378
379 target_x = cursor->target_x;
380
381 new_line = FALSE;
382
383 while (1) {
384 html_cursor_copy (&prev_cursor, cursor);
385
386 prev_x = x;
387 prev_y = y;
388
389 if (!backward (cursor, engine, FALSE))
390 return FALSE;
391
392 html_object_get_cursor_base (cursor->object,
393 engine->painter, cursor->offset,
394 &x, &y);
395
396 if (html_cursor_equal (&prev_cursor, cursor)) {
397 html_cursor_copy (cursor, &orig_cursor);
398 return FALSE;
399 }
400
401 if (y + cursor->object->descent - 1 < prev_y - prev_cursor.object->ascent) {
402 if (new_line) {
403 html_cursor_copy (cursor, &prev_cursor);
404 return TRUE;
405 }
406
407 new_line = TRUE;
408 if (cursor->object->parent)
409 dir = html_object_get_direction (cursor->object->parent);
410 else
411 dir = HTML_DIRECTION_LTR;
412 }
413
414 if (dir == HTML_DIRECTION_RTL) {
415 if (new_line && x >= target_x) {
416 if (!cursor->have_target_x) {
417 cursor->have_target_x = TRUE;
418 cursor->target_x = target_x;
419 }
420
421 /* Choose the character which is the nearest to the
422 * target X. */
423 if (prev_y == y && x - target_x >= target_x - prev_x) {
424 cursor->object = prev_cursor.object;
425 cursor->offset = prev_cursor.offset;
426 cursor->position = prev_cursor.position;
427 }
428
429 debug_location (cursor);
430 return TRUE;
431 }
432 } else {
433 if (new_line && x <= target_x) {
434 if (!cursor->have_target_x) {
435 cursor->have_target_x = TRUE;
436 cursor->target_x = target_x;
437 }
438
439 /* Choose the character which is the nearest to the
440 * target X. */
441 if (prev_y == y && target_x - x >= prev_x - target_x) {
442 cursor->object = prev_cursor.object;
443 cursor->offset = prev_cursor.offset;
444 cursor->position = prev_cursor.position;
445 }
446
447 debug_location (cursor);
448 return TRUE;
449 }
450 }
451 }
452 }
453
454
455 gboolean
html_cursor_down(HTMLCursor * cursor,HTMLEngine * engine)456 html_cursor_down (HTMLCursor *cursor,
457 HTMLEngine *engine)
458 {
459 HTMLCursor orig_cursor;
460 HTMLCursor prev_cursor;
461 HTMLDirection dir;
462 gint prev_x, prev_y;
463 gint x, y;
464 gint target_x;
465 gboolean new_line;
466
467 gtk_html_im_reset (engine->widget);
468
469 if (cursor->object == NULL) {
470 g_warning ("The cursor is in a NULL position: going home.");
471 html_cursor_home (cursor, engine);
472 return TRUE;
473 }
474
475 if (engine->need_spell_check)
476 html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
477
478 if (cursor->object->parent)
479 dir = html_object_get_direction (cursor->object->parent);
480 else
481 dir = HTML_DIRECTION_LTR;
482
483 html_cursor_copy (&orig_cursor, cursor);
484
485 html_object_get_cursor_base (cursor->object,
486 engine->painter, cursor->offset,
487 &x, &y);
488
489 if (!cursor->have_target_x) {
490 cursor->target_x = x;
491 cursor->have_target_x = TRUE;
492 }
493
494 target_x = cursor->target_x;
495
496 new_line = FALSE;
497
498 while (1) {
499 prev_cursor = *cursor;
500 prev_x = x;
501 prev_y = y;
502
503 if (dir == HTML_DIRECTION_RTL) {
504 if (!move_left (cursor, engine))
505 return FALSE;
506 } else {
507 if (!move_right (cursor, engine))
508 return FALSE;
509 }
510
511 html_object_get_cursor_base (cursor->object,
512 engine->painter, cursor->offset,
513 &x, &y);
514
515 if (html_cursor_equal (&prev_cursor, cursor)) {
516 html_cursor_copy (cursor, &orig_cursor);
517 return FALSE;
518 }
519
520 if (y - cursor->object->ascent > prev_y + prev_cursor.object->descent - 1) {
521 if (new_line) {
522 html_cursor_copy (cursor, &prev_cursor);
523 return TRUE;
524 }
525
526 new_line = TRUE;
527 }
528 if (cursor->object->parent)
529 dir = html_object_get_direction (cursor->object->parent);
530 else
531 dir = HTML_DIRECTION_LTR;
532
533 if (dir == HTML_DIRECTION_RTL) {
534 if (new_line && x <= target_x) {
535 if (!cursor->have_target_x) {
536 cursor->have_target_x = TRUE;
537 cursor->target_x = target_x;
538 }
539
540 /* Choose the character which is the nearest to the
541 * target X. */
542 if (prev_y == y && target_x - x >= prev_x - target_x) {
543 cursor->object = prev_cursor.object;
544 cursor->offset = prev_cursor.offset;
545 cursor->position = prev_cursor.position;
546 }
547
548 debug_location (cursor);
549 return TRUE;
550 }
551 } else {
552 if (new_line && x >= target_x) {
553 if (!cursor->have_target_x) {
554 cursor->have_target_x = TRUE;
555 cursor->target_x = target_x;
556 }
557
558 /* Choose the character which is the nearest to the
559 * target X. */
560 if (prev_y == y && x - target_x >= target_x - prev_x) {
561 cursor->object = prev_cursor.object;
562 cursor->offset = prev_cursor.offset;
563 cursor->position = prev_cursor.position;
564 }
565
566 debug_location (cursor);
567 return TRUE;
568 }
569 }
570 }
571 }
572
573
574 static gboolean
html_cursor_real_jump_to(HTMLCursor * cursor,HTMLEngine * engine,HTMLObject * object,guint offset,gboolean exact_position)575 html_cursor_real_jump_to (HTMLCursor *cursor,
576 HTMLEngine *engine,
577 HTMLObject *object,
578 guint offset,
579 gboolean exact_position)
580 {
581 HTMLCursor original;
582
583 g_return_val_if_fail (cursor != NULL, FALSE);
584 g_return_val_if_fail (object != NULL, FALSE);
585
586 gtk_html_im_reset (engine->widget);
587
588 if (engine->need_spell_check)
589 html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
590
591 html_cursor_normalize (cursor);
592 normalize (&object, &offset);
593
594 if (cursor->object == object && cursor->offset == offset)
595 return TRUE;
596
597 html_cursor_copy (&original, cursor);
598
599 while (forward (cursor, engine, exact_position)) {
600 if (cursor->object == object && cursor->offset == offset)
601 return TRUE;
602 }
603
604 html_cursor_copy (cursor, &original);
605
606 while (backward (cursor, engine, exact_position)) {
607 if (cursor->object == object && cursor->offset == offset)
608 return TRUE;
609 }
610
611 return FALSE;
612 }
613
614 /**
615 * html_cursor_jump_to:
616 * @cursor:
617 * @object:
618 * @offset:
619 *
620 * Move the cursor to the specified @offset in the specified @object.
621 * Where exactly move to, depends on the is_cursor_position in PangoLogAttr say.
622 * This is useful for such as Indic languages that relies on that feature.
623 *
624 * Return value: %TRUE if successful, %FALSE if failed.
625 **/
626 gboolean
html_cursor_jump_to(HTMLCursor * cursor,HTMLEngine * engine,HTMLObject * object,guint offset)627 html_cursor_jump_to (HTMLCursor *cursor,
628 HTMLEngine *engine,
629 HTMLObject *object,
630 guint offset)
631 {
632 return html_cursor_real_jump_to (cursor, engine, object, offset, FALSE);
633 }
634
635 /**
636 * html_cursor_exactly_jump_to:
637 * @cursor:
638 * @object:
639 * @offset:
640 *
641 * Move the cursor to near the specified @offset in the specified @object.
642 *
643 * Return value: %TRUE if successful, %FALSE if failed.
644 **/
645 gboolean
html_cursor_exactly_jump_to(HTMLCursor * cursor,HTMLEngine * engine,HTMLObject * object,guint offset)646 html_cursor_exactly_jump_to (HTMLCursor *cursor,
647 HTMLEngine *engine,
648 HTMLObject *object,
649 guint offset)
650 {
651 return html_cursor_real_jump_to (cursor, engine, object, offset, TRUE);
652 }
653
654
655 /* Complex cursor movement commands. */
656
657 void
html_cursor_beginning_of_document(HTMLCursor * cursor,HTMLEngine * engine)658 html_cursor_beginning_of_document (HTMLCursor *cursor,
659 HTMLEngine *engine)
660 {
661 g_return_if_fail (cursor != NULL);
662 g_return_if_fail (engine != NULL);
663 g_return_if_fail (HTML_IS_ENGINE (engine));
664
665 gtk_html_im_reset (engine->widget);
666
667 if (engine->need_spell_check)
668 html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
669
670 while (backward (cursor, engine, FALSE))
671 ;
672 }
673
674 void
html_cursor_end_of_document(HTMLCursor * cursor,HTMLEngine * engine)675 html_cursor_end_of_document (HTMLCursor *cursor,
676 HTMLEngine *engine)
677 {
678 g_return_if_fail (cursor != NULL);
679 g_return_if_fail (engine != NULL);
680 g_return_if_fail (HTML_IS_ENGINE (engine));
681
682 gtk_html_im_reset (engine->widget);
683
684 if (engine->need_spell_check)
685 html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
686
687 while (forward (cursor, engine, FALSE))
688 ;
689 }
690
691 gint
html_cursor_get_position(HTMLCursor * cursor)692 html_cursor_get_position (HTMLCursor *cursor)
693 {
694 g_return_val_if_fail (cursor != NULL, 0);
695
696 return cursor->position;
697 }
698
699 static void
html_cursor_real_jump_to_position(HTMLCursor * cursor,HTMLEngine * engine,gint position,gboolean exact_position)700 html_cursor_real_jump_to_position (HTMLCursor *cursor,
701 HTMLEngine *engine,
702 gint position,
703 gboolean exact_position)
704 {
705 g_return_if_fail (cursor != NULL);
706 g_return_if_fail (position >= 0);
707
708 if (engine->need_spell_check)
709 html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
710
711 if (cursor->position < position) {
712 while (cursor->position < position) {
713 if (!forward (cursor, engine, exact_position))
714 break;
715 }
716 } else if (cursor->position > position) {
717 while (cursor->position > position) {
718 if (!backward (cursor, engine, exact_position))
719 break;
720 }
721 }
722 gtk_html_im_reset (engine->widget);
723 }
724
725 void
html_cursor_jump_to_position(HTMLCursor * cursor,HTMLEngine * engine,gint position)726 html_cursor_jump_to_position (HTMLCursor *cursor,
727 HTMLEngine *engine,
728 gint position)
729 {
730 html_cursor_real_jump_to_position (cursor, engine, position, FALSE);
731 }
732
733 void
html_cursor_exactly_jump_to_position(HTMLCursor * cursor,HTMLEngine * engine,gint position)734 html_cursor_exactly_jump_to_position (HTMLCursor *cursor,
735 HTMLEngine *engine,
736 gint position)
737 {
738 html_cursor_real_jump_to_position (cursor, engine, position, TRUE);
739 }
740
741 static void
html_cursor_real_jump_to_position_no_spell(HTMLCursor * cursor,HTMLEngine * engine,gint position,gboolean exact_position)742 html_cursor_real_jump_to_position_no_spell (HTMLCursor *cursor,
743 HTMLEngine *engine,
744 gint position,
745 gboolean exact_position)
746 {
747 gboolean need_spell_check;
748
749 need_spell_check = engine->need_spell_check;
750 engine->need_spell_check = FALSE;
751 html_cursor_real_jump_to_position (cursor, engine, position, exact_position);
752 engine->need_spell_check = need_spell_check;
753 }
754
755 void
html_cursor_jump_to_position_no_spell(HTMLCursor * cursor,HTMLEngine * engine,gint position)756 html_cursor_jump_to_position_no_spell (HTMLCursor *cursor,
757 HTMLEngine *engine,
758 gint position)
759 {
760 html_cursor_real_jump_to_position_no_spell (cursor, engine, position, FALSE);
761 }
762
763 void
html_cursor_exactly_jump_to_position_no_spell(HTMLCursor * cursor,HTMLEngine * engine,gint position)764 html_cursor_exactly_jump_to_position_no_spell (HTMLCursor *cursor,
765 HTMLEngine *engine,
766 gint position)
767 {
768 html_cursor_real_jump_to_position_no_spell (cursor, engine, position, TRUE);
769 }
770
771
772 /* Comparison. */
773
774 gboolean
html_cursor_equal(const HTMLCursor * a,const HTMLCursor * b)775 html_cursor_equal (const HTMLCursor *a,
776 const HTMLCursor *b)
777 {
778 g_return_val_if_fail (a != NULL, FALSE);
779 g_return_val_if_fail (b != NULL, FALSE);
780
781 return a->object == b->object && a->offset == b->offset;
782 }
783
784 gboolean
html_cursor_precedes(const HTMLCursor * a,const HTMLCursor * b)785 html_cursor_precedes (const HTMLCursor *a,
786 const HTMLCursor *b)
787 {
788 g_return_val_if_fail (a != NULL, FALSE);
789 g_return_val_if_fail (b != NULL, FALSE);
790
791 return a->position < b->position;
792 }
793
794 gboolean
html_cursor_follows(const HTMLCursor * a,const HTMLCursor * b)795 html_cursor_follows (const HTMLCursor *a,
796 const HTMLCursor *b)
797 {
798 g_return_val_if_fail (a != NULL, FALSE);
799 g_return_val_if_fail (b != NULL, FALSE);
800
801 return a->position > b->position;
802 }
803
804
805 gunichar
html_cursor_get_current_char(const HTMLCursor * cursor)806 html_cursor_get_current_char (const HTMLCursor *cursor)
807 {
808 HTMLObject *next;
809
810 g_return_val_if_fail (cursor != NULL, 0);
811
812 if (!html_object_is_text (cursor->object)) {
813 if (cursor->offset < html_object_get_length (cursor->object))
814 return 0;
815
816 next = html_object_next_not_slave (cursor->object);
817 if (next != NULL && html_object_is_text (next))
818 return html_text_get_char (HTML_TEXT (next), 0);
819
820 return 0;
821 }
822
823 if (cursor->offset < HTML_TEXT (cursor->object)->text_len)
824 return html_text_get_char (HTML_TEXT (cursor->object), cursor->offset);
825
826 next = html_object_next_not_slave (cursor->object);
827 if (next == NULL || !html_object_is_text (next))
828 return 0;
829
830 return html_text_get_char (HTML_TEXT (next), 0);
831 }
832
833 gunichar
html_cursor_get_prev_char(const HTMLCursor * cursor)834 html_cursor_get_prev_char (const HTMLCursor *cursor)
835 {
836 HTMLObject *prev;
837
838 g_return_val_if_fail (cursor != NULL, 0);
839
840 if (cursor->offset)
841 return (html_object_is_text (cursor->object))
842 ? html_text_get_char (HTML_TEXT (cursor->object), cursor->offset - 1)
843 : 0;
844 prev = html_object_prev_not_slave (cursor->object);
845 return (prev && html_object_is_text (prev))
846 ? html_text_get_char (HTML_TEXT (prev), HTML_TEXT (prev)->text_len - 1)
847 : 0;
848 }
849
850 gboolean
html_cursor_beginning_of_paragraph(HTMLCursor * cursor,HTMLEngine * engine)851 html_cursor_beginning_of_paragraph (HTMLCursor *cursor,
852 HTMLEngine *engine)
853 {
854 HTMLCursor copy;
855 HTMLObject *flow;
856 gboolean rv = FALSE;
857 gint level, new_level;
858
859 gtk_html_im_reset (engine->widget);
860
861 level = html_object_get_parent_level (cursor->object);
862 flow = cursor->object->parent;
863
864 if (engine->need_spell_check)
865 html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
866
867 while (1) {
868 if (!cursor->offset) {
869 html_cursor_copy (©, cursor);
870 if (backward (cursor, engine, FALSE)) {
871 new_level = html_object_get_parent_level (cursor->object);
872 if (new_level < level
873 || (new_level == level && flow != cursor->object->parent)) {
874 html_cursor_copy (cursor, ©);
875 break;
876 }
877 } else
878 break;
879 }
880 else
881 if (!backward (cursor, engine, FALSE))
882 break;
883 rv = TRUE;
884 }
885
886 return rv;
887 }
888
889 gboolean
html_cursor_end_of_paragraph(HTMLCursor * cursor,HTMLEngine * engine)890 html_cursor_end_of_paragraph (HTMLCursor *cursor,
891 HTMLEngine *engine)
892 {
893 HTMLCursor copy;
894 HTMLObject *flow;
895 gboolean rv = FALSE;
896 gint level, new_level;
897
898 gtk_html_im_reset (engine->widget);
899
900 level = html_object_get_parent_level (cursor->object);
901 flow = cursor->object->parent;
902
903 if (engine->need_spell_check)
904 html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
905
906 while (1) {
907 if (cursor->offset == html_object_get_length (cursor->object)) {
908 html_cursor_copy (©, cursor);
909 if (forward (cursor, engine, FALSE)) {
910 new_level = html_object_get_parent_level (cursor->object);
911 if (new_level < level
912 || (new_level == level && flow != cursor->object->parent)) {
913 html_cursor_copy (cursor, ©);
914 break;
915 }
916 } else
917 break;
918 }
919 else
920 if (!forward (cursor, engine, FALSE))
921 break;
922 rv = TRUE;
923 }
924
925 return rv;
926 }
927
928 gboolean
html_cursor_forward_n(HTMLCursor * cursor,HTMLEngine * e,guint n)929 html_cursor_forward_n (HTMLCursor *cursor,
930 HTMLEngine *e,
931 guint n)
932 {
933 gboolean rv = FALSE;
934
935 while (n && html_cursor_forward (cursor, e)) {
936 n--;
937 rv = TRUE;
938 }
939
940 return rv;
941 }
942
943 gboolean
html_cursor_backward_n(HTMLCursor * cursor,HTMLEngine * e,guint n)944 html_cursor_backward_n (HTMLCursor *cursor,
945 HTMLEngine *e,
946 guint n)
947 {
948 gboolean rv = FALSE;
949
950 while (n && html_cursor_backward (cursor, e)) {
951 n--;
952 rv = TRUE;
953 }
954
955 return rv;
956 }
957
958 HTMLObject *
html_cursor_child_of(HTMLCursor * cursor,HTMLObject * parent)959 html_cursor_child_of (HTMLCursor *cursor,
960 HTMLObject *parent)
961 {
962 HTMLObject *child = cursor->object;
963
964 while (child) {
965 if (child->parent == parent)
966 return child;
967 child = child->parent;
968 }
969
970 return NULL;
971 }
972
973 static gboolean
move_to_next_object(HTMLCursor * cursor,HTMLEngine * e)974 move_to_next_object (HTMLCursor *cursor,
975 HTMLEngine *e)
976 {
977 HTMLObject *next;
978
979 next = html_object_next_cursor (cursor->object, (gint *) &cursor->offset);
980 if (next && next->parent) {
981 cursor->object = next;
982 cursor->position++;
983
984 if (!html_object_is_container (next)) {
985 if (html_object_get_direction (next->parent) == HTML_DIRECTION_RTL) {
986 cursor->offset = html_object_get_right_edge_offset (next, e->painter, 0);
987 } else {
988 cursor->offset = html_object_get_left_edge_offset (next, e->painter, 0);
989 }
990 cursor->position += cursor->offset;
991 }
992
993 return TRUE;
994 } else
995 return FALSE;
996 }
997
998 static gboolean
move_to_prev_object(HTMLCursor * cursor,HTMLEngine * e)999 move_to_prev_object (HTMLCursor *cursor,
1000 HTMLEngine *e)
1001 {
1002 HTMLObject *prev;
1003
1004 prev = html_object_prev_cursor (cursor->object, (gint *) &cursor->offset);
1005 if (prev && prev->parent) {
1006 cursor->object = prev;
1007 cursor->position--;
1008
1009 if (!html_object_is_container (prev)) {
1010 if (html_object_get_direction (prev->parent) == HTML_DIRECTION_RTL) {
1011 cursor->offset = html_object_get_left_edge_offset (prev, e->painter, html_object_get_length (prev));
1012 } else {
1013 cursor->offset = html_object_get_right_edge_offset (prev, e->painter, html_object_get_length (prev));
1014 }
1015 cursor->position -= cursor->offset - html_object_get_length (prev);
1016 }
1017
1018 return TRUE;
1019 } else
1020 return FALSE;
1021 }
1022
1023 static gboolean
move_left(HTMLCursor * cursor,HTMLEngine * e)1024 move_left (HTMLCursor *cursor,
1025 HTMLEngine *e)
1026 {
1027 if (!html_object_cursor_left (cursor->object, e->painter, cursor)) {
1028 if (cursor->object->parent) {
1029 if (html_object_get_direction (cursor->object->parent) == HTML_DIRECTION_RTL)
1030 return move_to_next_object (cursor, e);
1031 else
1032 return move_to_prev_object (cursor, e);
1033 }
1034 }
1035
1036 return TRUE;
1037 }
1038
1039 gboolean
html_cursor_left(HTMLCursor * cursor,HTMLEngine * engine)1040 html_cursor_left (HTMLCursor *cursor,
1041 HTMLEngine *engine)
1042 {
1043 gboolean retval;
1044
1045 g_return_val_if_fail (cursor != NULL, FALSE);
1046 g_return_val_if_fail (engine != NULL, FALSE);
1047
1048 gtk_html_im_reset (engine->widget);
1049
1050 if (engine->need_spell_check)
1051 html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
1052
1053 cursor->have_target_x = FALSE;
1054 retval = move_left (cursor, engine);
1055
1056 debug_location (cursor);
1057
1058 return retval;
1059 }
1060
1061 static gboolean
left_in_flow(HTMLCursor * cursor,HTMLEngine * e)1062 left_in_flow (HTMLCursor *cursor,
1063 HTMLEngine *e)
1064 {
1065 gboolean retval;
1066
1067 if (cursor->offset != html_object_get_left_edge_offset (cursor->object, e->painter, cursor->offset) && html_object_is_container (cursor->object)) {
1068 HTMLObject *obj;
1069
1070 obj = cursor->object;
1071 while ((retval = move_left (cursor, e)) && cursor->object != obj)
1072 ;
1073 } else {
1074 if (cursor->offset > 1 || !cursor->object->prev)
1075 retval = html_object_cursor_left (cursor->object, e->painter, cursor);
1076 else if (cursor->object->prev)
1077 retval = move_left (cursor, e);
1078 else
1079 retval = FALSE;
1080 }
1081
1082 debug_location (cursor);
1083
1084 return retval;
1085 }
1086
1087 static gboolean
html_cursor_left_edge_of_line(HTMLCursor * cursor,HTMLEngine * engine)1088 html_cursor_left_edge_of_line (HTMLCursor *cursor,
1089 HTMLEngine *engine)
1090 {
1091 HTMLCursor prev_cursor;
1092 gint x, y, prev_y;
1093
1094 g_return_val_if_fail (cursor != NULL, FALSE);
1095 g_return_val_if_fail (engine != NULL, FALSE);
1096 g_return_val_if_fail (HTML_IS_ENGINE (engine), FALSE);
1097
1098 gtk_html_im_reset (engine->widget);
1099
1100 cursor->have_target_x = FALSE;
1101
1102 if (engine->need_spell_check)
1103 html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
1104
1105 html_cursor_copy (&prev_cursor, cursor);
1106 html_object_get_cursor_base (cursor->object, engine->painter, cursor->offset,
1107 &x, &prev_y);
1108
1109 while (1) {
1110 if (!left_in_flow (cursor, engine))
1111 return TRUE;
1112
1113 html_object_get_cursor_base (cursor->object, engine->painter, cursor->offset,
1114 &x, &y);
1115
1116 if (y + cursor->object->descent - 1 < prev_y - prev_cursor.object->ascent) {
1117 html_cursor_copy (cursor, &prev_cursor);
1118 return TRUE;
1119 }
1120
1121 prev_y = y;
1122 html_cursor_copy (&prev_cursor, cursor);
1123 }
1124 }
1125
1126 static gboolean
move_right(HTMLCursor * cursor,HTMLEngine * e)1127 move_right (HTMLCursor *cursor,
1128 HTMLEngine *e)
1129 {
1130 gboolean retval;
1131
1132 retval = TRUE;
1133 if (!html_object_cursor_right (cursor->object, e->painter, cursor)) {
1134 gboolean rv;
1135 HTMLObject *orig = cursor->object;
1136
1137 if (cursor->object->parent &&
1138 html_object_get_direction (cursor->object->parent) == HTML_DIRECTION_RTL)
1139 rv = move_to_prev_object (cursor, e);
1140 else
1141 rv = move_to_next_object (cursor, e);
1142
1143 if (rv && !html_object_is_container (cursor->object) && cursor->object->parent == orig->parent) {
1144 if (html_object_get_direction (cursor->object) == HTML_DIRECTION_RTL)
1145 cursor->offset--;
1146 else
1147 cursor->offset++;
1148 }
1149
1150 return rv;
1151 }
1152 return retval;
1153 }
1154
1155 gboolean
html_cursor_right(HTMLCursor * cursor,HTMLEngine * engine)1156 html_cursor_right (HTMLCursor *cursor,
1157 HTMLEngine *engine)
1158 {
1159 gboolean retval;
1160
1161 g_return_val_if_fail (cursor != NULL, FALSE);
1162 g_return_val_if_fail (engine != NULL, FALSE);
1163
1164 gtk_html_im_reset (engine->widget);
1165
1166 if (engine->need_spell_check)
1167 html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
1168
1169 cursor->have_target_x = FALSE;
1170 retval = move_right (cursor, engine);
1171
1172 debug_location (cursor);
1173
1174 return retval;
1175 }
1176
1177 static gboolean
right_in_flow(HTMLCursor * cursor,HTMLEngine * e)1178 right_in_flow (HTMLCursor *cursor,
1179 HTMLEngine *e)
1180 {
1181 gboolean retval;
1182
1183 if (cursor->offset != html_object_get_right_edge_offset (cursor->object, e->painter, cursor->offset)) {
1184 if (html_object_is_container (cursor->object)) {
1185 HTMLObject *obj;
1186
1187 obj = cursor->object;
1188 while ((retval = move_right (cursor, e)) && cursor->object != obj)
1189 ;
1190 } else
1191 retval = html_object_cursor_right (cursor->object, e->painter, cursor);
1192 } else {
1193 if (html_object_next_not_slave (cursor->object))
1194 retval = move_right (cursor, e);
1195 else
1196 retval = FALSE;
1197 }
1198
1199 debug_location (cursor);
1200
1201 return retval;
1202 }
1203
1204 static gboolean
html_cursor_right_edge_of_line(HTMLCursor * cursor,HTMLEngine * engine)1205 html_cursor_right_edge_of_line (HTMLCursor *cursor,
1206 HTMLEngine *engine)
1207 {
1208 HTMLCursor prev_cursor;
1209 gint x, y, prev_y;
1210
1211 g_return_val_if_fail (cursor != NULL, FALSE);
1212 g_return_val_if_fail (engine != NULL, FALSE);
1213 g_return_val_if_fail (HTML_IS_ENGINE (engine), FALSE);
1214
1215 gtk_html_im_reset (engine->widget);
1216
1217 cursor->have_target_x = FALSE;
1218
1219 if (engine->need_spell_check)
1220 html_engine_spell_check_range (engine, engine->cursor, engine->cursor);
1221
1222 html_cursor_copy (&prev_cursor, cursor);
1223 html_object_get_cursor_base (cursor->object, engine->painter, cursor->offset,
1224 &x, &prev_y);
1225
1226 while (1) {
1227 if (!right_in_flow (cursor, engine))
1228 return TRUE;
1229
1230 html_object_get_cursor_base (cursor->object, engine->painter, cursor->offset,
1231 &x, &y);
1232
1233 if (y - cursor->object->ascent > prev_y + prev_cursor.object->descent - 1) {
1234 html_cursor_copy (cursor, &prev_cursor);
1235 return TRUE;
1236 }
1237 prev_y = y;
1238 html_cursor_copy (&prev_cursor, cursor);
1239 }
1240 }
1241
1242 gboolean
html_cursor_beginning_of_line(HTMLCursor * cursor,HTMLEngine * engine)1243 html_cursor_beginning_of_line (HTMLCursor *cursor,
1244 HTMLEngine *engine)
1245 {
1246 if (html_object_get_direction (cursor->object) == HTML_DIRECTION_RTL)
1247 return html_cursor_right_edge_of_line (cursor, engine);
1248 else
1249 return html_cursor_left_edge_of_line (cursor, engine);
1250 }
1251
1252 gboolean
html_cursor_end_of_line(HTMLCursor * cursor,HTMLEngine * engine)1253 html_cursor_end_of_line (HTMLCursor *cursor,
1254 HTMLEngine *engine)
1255 {
1256 if (html_object_get_direction (cursor->object) == HTML_DIRECTION_RTL)
1257 return html_cursor_left_edge_of_line (cursor, engine);
1258 else
1259 return html_cursor_right_edge_of_line (cursor, engine);
1260 }
1261