1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* This file is part of the GtkHTML library
3  *
4  * Copyright (C) 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  * MERCHcANTABILITY 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 #include <gtk/gtk.h>
23 
24 #include "htmlcursor.h"
25 #include "htmlengine-edit-cursor.h"
26 #include "htmlengine-edit-cut-and-paste.h"
27 #include "htmlentity.h"
28 #include "htmlinterval.h"
29 #include "htmlselection.h"
30 #include "htmlengine-edit.h"
31 #include "htmlengine-edit-selection-updater.h"
32 
33 static gboolean
optimize_selection(HTMLEngine * e,HTMLInterval * i)34 optimize_selection (HTMLEngine *e,
35                     HTMLInterval *i)
36 {
37 	HTMLInterval *s = e->selection;
38 	gboolean optimized = FALSE;
39 
40 	g_return_val_if_fail (s, FALSE);
41 
42 	/* printf ("change selection (%3d,%3d) --> (%3d,%3d)\n",
43 	 * s->from.offset, s->to.offset,
44 	 * i->from.offset, i->to.offset); */
45 	if (html_point_eq (&i->from, &s->from)) {
46 		HTMLPoint *max;
47 
48 		max = html_point_max (&i->to, &s->to);
49 		if (max) {
50 			if (max == &i->to) {
51 				HTMLInterval *sel;
52 
53 				/* printf ("optimize 1\n"); */
54 				sel = html_interval_new (s->to.object, i->to.object,
55 							 i->from.object == s->to.object
56 							 ? i->from.offset : (html_object_is_container (s->to.object) ? s->to.offset : 0), i->to.offset);
57 				html_interval_select (sel, e);
58 				html_interval_destroy (sel);
59 				html_interval_destroy (s);
60 				e->selection = i;
61 				optimized = TRUE;
62 			} else {
63 				HTMLInterval *usel;
64 
65 				/* printf ("optimize 2\n"); */
66 				usel = html_interval_new (i->to.object, s->to.object,
67 							  html_object_is_container (i->to.object) ? i->to.offset : 0, s->to.offset);
68 				html_interval_unselect (usel, e);
69 				if (!html_object_is_container (i->to.object) && i->to.offset) {
70 					gint from = i->from.object == i->to.object ? i->from.offset : 0;
71 					html_object_select_range (i->to.object, e,
72 								  from, i->to.offset - from,
73 								  !html_engine_frozen (e));
74 				}
75 				html_interval_destroy (usel);
76 				html_interval_destroy (s);
77 				e->selection = i;
78 				optimized = TRUE;
79 			}
80 		}
81 	} else if (html_point_eq (&i->to, &s->to)) {
82 		HTMLPoint *min;
83 
84 		min = html_point_min (&i->from, &s->from);
85 		if (min) {
86 			if (min == &i->from) {
87 				HTMLInterval *sel;
88 
89 				/* printf ("optimize 3\n"); */
90 				sel = html_interval_new (i->from.object, s->from.object,
91 							 i->from.offset,
92 							 i->to.object == s->from.object
93 							 ? i->to.offset
94 							 : (html_object_is_container (s->from.object) ? s->from.offset : html_object_get_length (s->from.object)));
95 				html_interval_select (sel, e);
96 				html_interval_destroy (sel);
97 				html_interval_destroy (s);
98 				e->selection = i;
99 				optimized = TRUE;
100 			} else {
101 				HTMLInterval *usel;
102 
103 				/* printf ("optimize 4\n"); */
104 				usel = html_interval_new (s->from.object, i->from.object,
105 							  s->from.offset,
106 							  html_object_is_container (i->from.object) ? i->from.offset : html_object_get_length (i->from.object));
107 				html_interval_unselect (usel, e);
108 				if (!html_object_is_container (i->from.object) && i->from.offset != html_object_get_length (i->from.object)) {
109 					gint to = i->to.object == i->from.object
110 						? s->to.offset
111 						: html_object_get_length (i->from.object);
112 					html_object_select_range (i->from.object, e,
113 								  i->from.offset, to - i->from.offset,
114 								  !html_engine_frozen (e));
115 				}
116 				html_interval_destroy (usel);
117 				html_interval_destroy (s);
118 				e->selection = i;
119 				optimized = TRUE;
120 			}
121 		}
122 	}
123 
124 	/* if (optimized)
125 	 * printf ("Optimized\n"); */
126 
127 	return optimized;
128 }
129 
130 static void
clear_primary(HTMLEngine * e)131 clear_primary (HTMLEngine *e) {
132         if (e->primary)
133                 html_object_destroy (e->primary);
134 
135         e->primary = NULL;
136         e->primary_len = 0;
137 }
138 
139 void
html_engine_select_interval(HTMLEngine * e,HTMLInterval * i)140 html_engine_select_interval (HTMLEngine *e,
141                              HTMLInterval *i)
142 {
143 	e = html_engine_get_top_html_engine (e);
144 	html_engine_hide_cursor (e);
145 	if (e->selection && html_interval_eq (e->selection, i)) {
146 		html_interval_destroy (i);
147 	} else if (i && i->from.object == i->to.object && i->from.offset == i->to.offset) {
148 		/* shouldn't select zero letters */
149 		html_interval_destroy (i);
150 		html_engine_unselect_all (e);
151 	} else {
152 		if (!e->selection || !optimize_selection (e, i)) {
153 			html_engine_unselect_all (e);
154 			e->selection = i;
155 			html_interval_select (e->selection, e);
156 		}
157 	}
158 
159 	html_engine_show_cursor (e);
160 }
161 
162 void
html_engine_select_region(HTMLEngine * e,gint x1,gint y1,gint x2,gint y2)163 html_engine_select_region (HTMLEngine *e,
164                            gint x1,
165                            gint y1,
166                            gint x2,
167                            gint y2)
168 {
169 	HTMLPoint *a, *b;
170 
171 	g_return_if_fail (e != NULL);
172 	g_return_if_fail (HTML_IS_ENGINE (e));
173 
174 	e = html_engine_get_top_html_engine (e);
175 	if (e->clue == NULL)
176 		return;
177 
178 	/* printf ("selection %d,%d x %d,%d\n", x1, y1, x2, y2); */
179 
180 	a = html_engine_get_point_at (e, x1, y1, TRUE);
181 	b = html_engine_get_point_at (e, x2, y2, TRUE);
182 
183 	if (a && b) {
184 		HTMLInterval *new_selection;
185 
186 		/* printf ("points offsets %d, %d\n", a->offset, b->offset); */
187 
188 		new_selection = html_interval_new_from_points (a, b);
189 		html_interval_validate (new_selection);
190 		html_engine_select_interval (e, new_selection);
191 	}
192 
193 	if (a)
194 		html_point_destroy (a);
195 	if (b)
196 		html_point_destroy (b);
197 }
198 
199 void
html_engine_select_all(HTMLEngine * e)200 html_engine_select_all (HTMLEngine *e)
201 {
202 	HTMLObject *a, *b;
203 
204 	g_return_if_fail (e != NULL);
205 	g_return_if_fail (HTML_IS_ENGINE (e));
206 
207 	e = html_engine_get_top_html_engine (e);
208 	if (e->clue == NULL || HTML_CLUE (e->clue)->head == NULL)
209 		return;
210 
211 	a = html_object_get_head_leaf (e->clue);
212 	b = html_object_get_tail_leaf (e->clue);
213 
214 	if (a && b) {
215 		HTMLInterval *new_selection;
216 
217 		new_selection = html_interval_new (a, b, 0, html_object_get_length (b));
218 		html_interval_validate (new_selection);
219 		html_engine_select_interval (e, new_selection);
220 	}
221 }
222 
223 void
html_engine_clear_selection(HTMLEngine * e)224 html_engine_clear_selection (HTMLEngine *e)
225 {
226 	/* printf ("clear selection\n"); */
227 
228 	if (e->selection) {
229 		html_interval_destroy (e->selection);
230 		html_engine_edit_selection_updater_reset (e->selection_updater);
231 		e->selection = NULL;
232 
233 		/*
234 		if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == GTK_WIDGET (e->widget)->window)
235 			gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY,
236 						 html_selection_current_time ());
237 		*/
238 	}
239 
240 	clear_primary (e);
241 }
242 
243 void
html_engine_unselect_all(HTMLEngine * e)244 html_engine_unselect_all (HTMLEngine *e)
245 {
246 	e = html_engine_get_top_html_engine (e);
247 	if (e->selection) {
248 		html_engine_hide_cursor (e);
249 		html_interval_unselect (e->selection, e);
250 		html_engine_clear_selection (e);
251 		html_engine_show_cursor (e);
252 	}
253 }
254 
255 static void
remove_mark(HTMLEngine * e)256 remove_mark (HTMLEngine *e)
257 {
258 	if (e->editable || e->caret_mode) {
259 		if (e->mark == NULL)
260 			return;
261 
262 		html_cursor_destroy (e->mark);
263 		e->mark = NULL;
264 	}
265 }
266 
267 void
html_engine_deactivate_selection(HTMLEngine * e)268 html_engine_deactivate_selection (HTMLEngine *e)
269 {
270 	remove_mark (e);
271 	html_engine_clear_selection (e);
272 }
273 
274 void
html_engine_disable_selection(HTMLEngine * e)275 html_engine_disable_selection (HTMLEngine *e)
276 {
277 	g_return_if_fail (e != NULL);
278 	g_return_if_fail (HTML_IS_ENGINE (e));
279 
280 	html_engine_hide_cursor (e);
281 	remove_mark (e);
282 	html_engine_unselect_all (e);
283 	e->selection_mode = FALSE;
284 	html_engine_show_cursor (e);
285 }
286 
287 static gboolean
line_interval(HTMLEngine * e,HTMLCursor * begin,HTMLCursor * end)288 line_interval (HTMLEngine *e,
289                HTMLCursor *begin,
290                HTMLCursor *end)
291 {
292 	return html_cursor_beginning_of_line (begin, e) && html_cursor_end_of_line (end, e);
293 }
294 
295 gboolean
html_selection_word(gunichar uc)296 html_selection_word (gunichar uc)
297 {
298 	return uc && uc != ' ' && uc != '\t' && uc != ENTITY_NBSP         /* white space */
299 		&& uc != '(' && uc != ')' && uc != '[' && uc != ']';
300 }
301 
302 gboolean
html_selection_spell_word(gunichar uc,gboolean * cited)303 html_selection_spell_word (gunichar uc,
304                            gboolean *cited)
305 {
306 	if (uc == '\'' || uc == '`') {
307 		*cited = TRUE;
308 		return FALSE;
309 	} else {
310 		return g_unichar_isalpha (uc);
311 	}
312 }
313 
314 static gboolean
word_interval(HTMLEngine * e,HTMLCursor * begin,HTMLCursor * end)315 word_interval (HTMLEngine *e,
316                HTMLCursor *begin,
317                HTMLCursor *end)
318 {
319 	/* move to the begin of word */
320 	while (html_selection_word (html_cursor_get_prev_char (begin)))
321 		html_cursor_backward (begin, e);
322 	/* move to the end of word */
323 	while (html_selection_word (html_cursor_get_current_char (end)))
324 		html_cursor_forward (end, e);
325 
326 	return (begin->object && end->object);
327 }
328 
329 static void
selection_helper(HTMLEngine * e,gboolean (* get_interval)(HTMLEngine * e,HTMLCursor * begin,HTMLCursor * end))330 selection_helper (HTMLEngine *e,
331                   gboolean (*get_interval)(HTMLEngine *e,
332                   HTMLCursor *begin,
333                   HTMLCursor *end))
334 {
335 	HTMLCursor *cursor, *begin, *end;
336 	HTMLInterval *i;
337 
338 	html_engine_unselect_all (e);
339 	cursor = html_engine_get_cursor (e);
340 
341 	if (cursor->object) {
342 		begin  = html_cursor_dup (cursor);
343 		end    = html_cursor_dup (cursor);
344 
345 		if ((*get_interval) (e, begin, end)) {
346 			i = html_interval_new_from_cursor (begin, end);
347 			html_engine_select_interval (e, i);
348 		}
349 
350 		html_cursor_destroy (begin);
351 		html_cursor_destroy (end);
352 	}
353 	html_cursor_destroy (cursor);
354 }
355 
356 void
html_engine_select_word(HTMLEngine * e)357 html_engine_select_word (HTMLEngine *e)
358 {
359 	selection_helper (e, word_interval);
360 }
361 
362 void
html_engine_select_line(HTMLEngine * e)363 html_engine_select_line (HTMLEngine *e)
364 {
365 	selection_helper (e, line_interval);
366 }
367 
368 gboolean
html_engine_is_selection_active(HTMLEngine * e)369 html_engine_is_selection_active (HTMLEngine *e)
370 {
371 	html_engine_edit_selection_updater_do_idle (e->selection_updater);
372 	if (e->selection) {
373 		return (!html_engine_get_editable (e) || e->mark) ? TRUE : FALSE;
374 	}
375 
376 	return FALSE;
377 }
378 
379 static void
test_point(HTMLObject * o,HTMLEngine * e,gpointer data)380 test_point (HTMLObject *o,
381             HTMLEngine *e,
382             gpointer data)
383 {
384 	HTMLPoint *point = (HTMLPoint *) data;
385 
386 	if (point->object == o) {
387 		if (point->object == e->selection->from.object && point->offset < e->selection->from.offset)
388 			return;
389 		if (point->object == e->selection->to.object && point->offset > e->selection->to.offset)
390 			return;
391 
392 		/* this indicates that object is IN the selection */
393 		point->object = NULL;
394 	}
395 }
396 
397 gboolean
html_engine_point_in_selection(HTMLEngine * e,HTMLObject * obj,guint offset)398 html_engine_point_in_selection (HTMLEngine *e,
399                                 HTMLObject *obj,
400                                 guint offset)
401 {
402 	HTMLPoint *point;
403 	gboolean rv;
404 
405 	if (!html_engine_is_selection_active (e) || !obj)
406 		return FALSE;
407 
408 	point = html_point_new (obj, offset);
409 	html_interval_forall (e->selection, e, test_point, point);
410 	rv = point->object == NULL;
411 
412 	html_point_destroy (point);
413 
414 	return rv;
415 }
416 
417 void
html_engine_activate_selection(HTMLEngine * e,guint32 time)418 html_engine_activate_selection (HTMLEngine *e,
419                                 guint32 time)
420 {
421 	/* printf ("activate selection\n"); */
422 
423 	if (e->selection && e->block_selection == 0 && gtk_widget_get_realized (GTK_WIDGET (e->widget))) {
424 		/* gtk_selection_owner_set (GTK_WIDGET (e->widget), GDK_SELECTION_PRIMARY, time); */
425 		/* printf ("activated (%u).\n", time); */
426 		clear_primary (e);
427 		html_engine_copy_object (e, &e->primary, &e->primary_len);
428 	}
429 }
430 
431 void
html_engine_block_selection(HTMLEngine * e)432 html_engine_block_selection (HTMLEngine *e)
433 {
434 	e->block_selection++;
435 }
436 
437 void
html_engine_unblock_selection(HTMLEngine * e)438 html_engine_unblock_selection (HTMLEngine *e)
439 {
440 	e->block_selection--;
441 }
442 
443 void
html_engine_update_selection_active_state(HTMLEngine * e,guint32 time)444 html_engine_update_selection_active_state (HTMLEngine *e,
445                                            guint32 time)
446 {
447 	if (html_engine_is_selection_active (e))
448 		html_engine_activate_selection (e, time ? time : gtk_get_current_event_time ());
449 	else
450 		html_engine_deactivate_selection (e);
451 }
452