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 (&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, &copy);
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 (&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, &copy);
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