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  *  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 #include <config.h>
23 #include <gtk/gtk.h>
24 #include "gtkhtml.h"
25 #include "gtkhtml-private.h"
26 #include "htmlcursor.h"
27 #include "htmlengine.h"
28 #include "htmlengine-edit-cursor.h"
29 #include "htmlengine-edit-table.h"
30 #include "htmlengine-edit-tablecell.h"
31 #include "htmlgdkpainter.h"
32 #include "htmlimage.h"
33 #include "htmlobject.h"
34 #include "htmltable.h"
35 #include "htmlembedded.h"
36 
37 static GdkColor table_stipple_active_on      = { 0, 0,      0,      0xffff };
38 static GdkColor table_stipple_active_off     = { 0, 0xffff, 0xffff, 0xffff };
39 static GdkColor table_stipple_non_active_on  = { 0, 0xaaaa, 0xaaaa, 0xaaaa };
40 static GdkColor table_stipple_non_active_off = { 0, 0xffff, 0xffff, 0xffff };
41 
42 static GdkColor cell_stipple_active_on      = { 0, 0x7fff, 0x7fff, 0 };
43 static GdkColor cell_stipple_active_off     = { 0, 0xffff, 0xffff, 0xffff };
44 static GdkColor cell_stipple_non_active_on  = { 0, 0x7aaa, 0x7aaa, 0x7aaa };
45 static GdkColor cell_stipple_non_active_off = { 0, 0xffff, 0xffff, 0xffff };
46 
47 static GdkColor image_stipple_active_on      = { 0, 0xffff, 0,      0 };
48 static GdkColor image_stipple_active_off     = { 0, 0xffff, 0xffff, 0xffff };
49 
50 static gint blink_timeout = 500;
51 
52 void
html_engine_set_cursor_blink_timeout(gint timeout)53 html_engine_set_cursor_blink_timeout (gint timeout)
54 {
55 	blink_timeout = timeout;
56 }
57 
58 void
html_engine_hide_cursor(HTMLEngine * engine)59 html_engine_hide_cursor (HTMLEngine *engine)
60 {
61 	HTMLEngine *e = engine;
62 
63 	g_return_if_fail (engine != NULL);
64 	g_return_if_fail (HTML_IS_ENGINE (engine));
65 
66 	if ((engine->editable || engine->caret_mode) && engine->cursor_hide_count == 0) {
67 		if (!engine->editable) {
68 			e = html_object_engine (engine->cursor->object, NULL);
69 			if (e) {
70 				e->caret_mode = engine->caret_mode;
71 				html_cursor_copy (e->cursor, engine->cursor);
72 			} else	e = engine;
73 		}
74 		html_engine_draw_cursor_in_area (e, 0, 0, -1, -1);
75 	}
76 
77 	engine->cursor_hide_count++;
78 }
79 
80 void
html_engine_show_cursor(HTMLEngine * engine)81 html_engine_show_cursor (HTMLEngine *engine)
82 {
83 	HTMLEngine * e = engine;
84 
85 	g_return_if_fail (engine != NULL);
86 	g_return_if_fail (HTML_IS_ENGINE (engine));
87 	g_return_if_fail (engine->cursor != NULL);
88 
89 	if (engine->cursor_hide_count > 0) {
90 		engine->cursor_hide_count--;
91 		if ((engine->editable || engine->caret_mode) && engine->cursor_hide_count == 0) {
92 			if (!engine->editable) {
93 				e = html_object_engine (engine->cursor->object, NULL);
94 				if (e) {
95 					e->caret_mode = engine->caret_mode;
96 					html_cursor_copy (e->cursor, engine->cursor);
97 				} else e = engine;
98 			}
99 			html_engine_draw_cursor_in_area (e, 0, 0, -1, -1);
100 		}
101 	}
102 }
103 
104 static gboolean
clip_cursor(HTMLEngine * engine,gint x,gint y,gint width,gint height,gint * x1,gint * y1,gint * x2,gint * y2)105 clip_cursor (HTMLEngine *engine,
106              gint x,
107              gint y,
108              gint width,
109              gint height,
110              gint *x1,
111              gint *y1,
112              gint *x2,
113              gint *y2)
114 {
115 	if (*x1 > x + width || *y1 > y + height || *x2 < x || *y2 < y)
116 		return FALSE;
117 
118 	*x1 = CLAMP (*x1, x, x + width);
119 	*x2 = CLAMP (*x2, x, x + width);
120 	*y1 = CLAMP (*y1, y, y + height);
121 	*y2 = CLAMP (*y2, y, y + height);
122 
123 	return TRUE;
124 }
125 
126 static void
draw_cursor_rectangle(HTMLEngine * e,gint x1,gint y1,gint x2,gint y2,GdkColor * on_color,GdkColor * off_color,gint offset)127 draw_cursor_rectangle (HTMLEngine *e,
128                        gint x1,
129                        gint y1,
130                        gint x2,
131                        gint y2,
132                        GdkColor *on_color,
133                        GdkColor *off_color,
134                        gint offset)
135 {
136 	cairo_t *cr;
137 	const double dashes[2] = { 1, 3 };
138 	gint ndash = G_N_ELEMENTS (dashes);
139 
140 	if (x1 > x2 || y1 > y2 || !e->window)
141 		return;
142 
143 	/* FIXME: what is the off color for? */
144 	cr = gdk_cairo_create (e->window);
145 	gdk_cairo_set_source_color (cr, on_color);
146 	cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
147 	cairo_set_dash (cr, dashes, ndash, offset);
148 	cairo_rectangle (cr, x1 - 0.5, y1 - 0.5, x2 - x1 + 1, y2 - y1 + 1);
149 	cairo_set_line_width (cr, 1.0);
150 	cairo_stroke (cr);
151 	cairo_destroy (cr);
152 }
153 
154 static gint cursor_enabled = TRUE;
155 
156 static inline void
refresh_under_cursor(HTMLEngine * e,HTMLCursorRectangle * cr,gboolean * enabled)157 refresh_under_cursor (HTMLEngine *e,
158                       HTMLCursorRectangle *cr,
159                       gboolean *enabled)
160 {
161 	if (cr->x1 > cr->x2 || cr->y1 > cr->y2)
162 		return;
163 
164 	*enabled = cursor_enabled = FALSE;
165 	html_engine_draw (e, cr->x1, cr->y1,
166 			  cr->x2 - cr->x1 + 1, cr->y2 - cr->y1 + 1);
167 	*enabled = cursor_enabled = TRUE;
168 }
169 
170 #define COLORS(x) animate ? &x ## _stipple_active_on  : &x ## _stipple_non_active_on, \
171                   animate ? &x ## _stipple_active_off : &x ## _stipple_non_active_off
172 
173 static void
html_engine_draw_image_cursor(HTMLEngine * e)174 html_engine_draw_image_cursor (HTMLEngine *e)
175 {
176 	HTMLCursorRectangle *cr;
177 	HTMLObject *io;
178 	static gboolean enabled = TRUE;
179 
180 	if (!enabled)
181 		return;
182 
183 	cr    = &e->cursor_image;
184 	io    = e->cursor->object;
185 
186 	if (io && HTML_IS_IMAGE (e->cursor->object)) {
187 		static gint offset = 3;
188 
189 		if (io != cr->object) {
190 			if (cr->object)
191 				refresh_under_cursor (e, cr, &enabled);
192 			cr->object = io;
193 		}
194 
195 		html_object_calc_abs_position (io, &cr->x1, &cr->y1);
196 		cr->x2 = cr->x1 + io->width - 1;
197 		cr->y2 = cr->y1 + io->descent - 1;
198 		cr->y1 -= io->ascent;
199 
200 		draw_cursor_rectangle (e, cr->x1, cr->y1, cr->x2, cr->y2,
201 				       &image_stipple_active_on, &image_stipple_active_off, offset);
202 		if (!offset)
203 			offset = 3;
204 		else
205 			offset--;
206 	} else
207 		if (cr->object) {
208 			refresh_under_cursor (e, cr, &enabled);
209 			cr->object = NULL;
210 		}
211 }
212 
213 void
html_engine_draw_cell_cursor(HTMLEngine * e)214 html_engine_draw_cell_cursor (HTMLEngine *e)
215 {
216 	HTMLCursorRectangle *cr;
217 	HTMLTableCell *cell;
218 	HTMLObject    *co;
219 	static gboolean enabled = TRUE;
220 
221 	if (!enabled)
222 		return;
223 
224 	cr   = &e->cursor_cell;
225 	cell = html_engine_get_table_cell (e);
226 	co   = HTML_OBJECT (cell);
227 
228 	if (cell) {
229 		static gint offset = 0;
230 		gboolean animate;
231 
232 		if (co != cr->object) {
233 			if (cr->object)
234 				refresh_under_cursor (e, cr, &enabled);
235 			cr->object = co;
236 		}
237 
238 		html_object_calc_abs_position (co, &cr->x1, &cr->y2);
239 		cr->x2  = cr->x1 + co->width - 1;
240 		cr->y2 -= 2;
241 		cr->y1  = cr->y2 - (co->ascent + co->descent - 2);
242 
243 		animate = !HTML_IS_IMAGE (e->cursor->object);
244 		if (animate) {
245 			offset++;
246 			offset %= 4;
247 		}
248 		draw_cursor_rectangle (e, cr->x1, cr->y1, cr->x2, cr->y2, COLORS (cell), offset);
249 	} else
250 		if (cr->object) {
251 			refresh_under_cursor (e, cr, &enabled);
252 			cr->object = NULL;
253 		}
254 }
255 
256 void
html_engine_draw_table_cursor(HTMLEngine * e)257 html_engine_draw_table_cursor (HTMLEngine *e)
258 {
259 	HTMLCursorRectangle *cr;
260 	HTMLTable *table;
261 	HTMLObject *to;
262 	static gboolean enabled = TRUE;
263 
264 	if (!enabled)
265 		return;
266 
267 	cr    = &e->cursor_table;
268 	table = html_engine_get_table (e);
269 	to    = HTML_OBJECT (table);
270 
271 	if (table) {
272 		static gint offset = 0;
273 		gboolean animate;
274 
275 		if (to != cr->object) {
276 			if (cr->object)
277 				refresh_under_cursor (e, cr, &enabled);
278 			cr->object = to;
279 		}
280 
281 		html_object_calc_abs_position (to, &cr->x1, &cr->y2);
282 		cr->x2 = cr->x1 + to->width - 1;
283 		cr->y2--;
284 		cr->y1 = cr->y2 - (to->ascent + to->descent - 1);
285 
286 		animate = HTML_IS_TABLE (e->cursor->object) && !html_engine_get_table_cell (e);
287 		if (animate) {
288 			offset++;
289 			offset %= 4;
290 		}
291 		draw_cursor_rectangle (e, cr->x1, cr->y1, cr->x2, cr->y2, COLORS (table), offset);
292 	} else
293 		if (cr->object) {
294 			refresh_under_cursor (e, cr, &enabled);
295 			cr->object = NULL;
296 		}
297 }
298 
299 void
html_engine_draw_cursor_in_area(HTMLEngine * engine,gint x,gint y,gint width,gint height)300 html_engine_draw_cursor_in_area (HTMLEngine *engine,
301                                  gint x,
302                                  gint y,
303                                  gint width,
304                                  gint height)
305 {
306 	HTMLObject *obj;
307 	guint offset;
308 	gint x1, y1, x2, y2, sc_x, sc_y;
309 	GdkRectangle pos;
310 	GtkAdjustment *hadj, *vadj;
311 
312 	if ((engine->editable || engine->caret_mode) && (engine->cursor_hide_count <= 0 && !engine->thaw_idle_id)) {
313 		html_engine_draw_table_cursor (engine);
314 		html_engine_draw_cell_cursor (engine);
315 		html_engine_draw_image_cursor (engine);
316 	}
317 
318 	if (!cursor_enabled || engine->cursor_hide_count > 0 || !(engine->editable || engine->caret_mode) || engine->thaw_idle_id)
319 		return;
320 
321 	obj = engine->cursor->object;
322 	if (obj == NULL || engine->window == NULL)
323 		return;
324 
325 	offset = engine->cursor->offset;
326 
327 	if (width < 0 || height < 0) {
328 		width = html_engine_get_doc_width (engine);
329 		height = html_engine_get_doc_height (engine);
330 		x = 0;
331 		y = 0;
332 	}
333 
334 	html_object_get_cursor (obj, engine->painter, offset, &x1, &y1, &x2, &y2);
335 	while (obj) {
336 		if (html_object_is_frame (obj)) {
337 			x1 -= HTML_EMBEDDED (obj)->abs_x;
338 			x2 -= HTML_EMBEDDED (obj)->abs_x;
339 			y1 -= HTML_EMBEDDED (obj)->abs_y;
340 			y2 -= HTML_EMBEDDED (obj)->abs_y;
341 			break;
342 		}
343 		obj = obj->parent;
344 	}
345 
346 	/* get scroll offset */
347 	hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (engine->widget));
348 	vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (engine->widget));
349 	sc_x = (gint) gtk_adjustment_get_value (hadj);
350 	sc_y = (gint) gtk_adjustment_get_value (vadj);
351 
352 	pos.x = x1 - sc_x;
353 	pos.y = y1 - sc_y;
354 	pos.width = x2 - x1;
355 	pos.height = y2 - y1;
356 
357 	gtk_im_context_set_cursor_location (GTK_HTML (engine->widget)->priv->im_context, &pos);
358 
359 	if (clip_cursor (engine, x, y, width, height, &x1, &y1, &x2, &y2)) {
360 		cairo_t *cr;
361 		gboolean using_painter_cr;
362 
363 		using_painter_cr = engine->painter &&
364 		    HTML_IS_GDK_PAINTER (engine->painter) &&
365 		    HTML_GDK_PAINTER (engine->painter)->cr != NULL;
366 
367 		if (using_painter_cr) {
368 			HTMLGdkPainter *gdk_painter = HTML_GDK_PAINTER (engine->painter);
369 
370 			cr = gdk_painter->cr;
371 			cairo_save (cr);
372 
373 			x1 -= gdk_painter->x1;
374 			y1 -= gdk_painter->y1;
375 			x2 -= gdk_painter->x1;
376 			y2 -= gdk_painter->y1;
377 		} else {
378 			cr = gdk_cairo_create (engine->window);
379 		}
380 
381 		cairo_set_source_rgb (cr, 1, 1, 1);
382 		cairo_set_operator (cr, CAIRO_OPERATOR_DIFFERENCE);
383 		cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
384 		cairo_move_to (cr, x1 + 0.5, y1 + 0.5);
385 		cairo_line_to (cr, x2 + 0.5, y2 - 0.5);
386 		cairo_set_line_width (cr, 1);
387 		cairo_stroke (cr);
388 
389 		if (using_painter_cr)
390 			cairo_restore (cr);
391 		else
392 			cairo_destroy (cr);
393 	}
394 }
395 
396 
397 /* Blinking cursor implementation.  */
398 
399 static gint
blink_timeout_cb(gpointer data)400 blink_timeout_cb (gpointer data)
401 {
402 	HTMLEngine *engine;
403 
404 	g_return_val_if_fail (HTML_IS_ENGINE (data), FALSE);
405 	engine = HTML_ENGINE (data);
406 
407 	engine->blinking_status = !engine->blinking_status;
408 
409 	if (engine->blinking_status)
410 		html_engine_show_cursor (engine);
411 	else
412 		html_engine_hide_cursor (engine);
413 
414 	return TRUE;
415 }
416 
417 void
html_engine_setup_blinking_cursor(HTMLEngine * engine)418 html_engine_setup_blinking_cursor (HTMLEngine *engine)
419 {
420 	g_return_if_fail (engine != NULL);
421 	g_return_if_fail (HTML_IS_ENGINE (engine));
422 	g_return_if_fail (engine->blinking_timer_id == 0);
423 
424 	html_engine_show_cursor (engine);
425 	engine->blinking_status = FALSE;
426 
427 	blink_timeout_cb (engine);
428 	if (blink_timeout > 0)
429 		engine->blinking_timer_id = g_timeout_add (blink_timeout, blink_timeout_cb, engine);
430 	else
431 		engine->blinking_timer_id = -1;
432 }
433 
434 void
html_engine_stop_blinking_cursor(HTMLEngine * engine)435 html_engine_stop_blinking_cursor (HTMLEngine *engine)
436 {
437 	g_return_if_fail (engine != NULL);
438 	g_return_if_fail (HTML_IS_ENGINE (engine));
439 	g_return_if_fail (engine->blinking_timer_id != 0);
440 
441 	if (engine->blinking_status) {
442 		html_engine_hide_cursor (engine);
443 		engine->blinking_status = FALSE;
444 	}
445 
446 	if (engine->blinking_timer_id != -1)
447 		g_source_remove (engine->blinking_timer_id);
448 	engine->blinking_timer_id = 0;
449 }
450 
451 void
html_engine_reset_blinking_cursor(HTMLEngine * engine)452 html_engine_reset_blinking_cursor (HTMLEngine *engine)
453 {
454 	g_return_if_fail (engine != NULL);
455 	g_return_if_fail (HTML_IS_ENGINE (engine));
456 	g_return_if_fail (engine->blinking_timer_id != 0);
457 
458 	if (engine->blinking_status)
459 		return;
460 
461 	html_engine_show_cursor (engine);
462 	engine->blinking_status = TRUE;
463 
464 	if (engine->blinking_timer_id != -1)
465 		g_source_remove (engine->blinking_timer_id);
466 
467 	if (blink_timeout > 0)
468 		engine->blinking_timer_id = g_timeout_add (blink_timeout, blink_timeout_cb, engine);
469 	else {
470 		engine->blinking_timer_id = -1;
471 		/* show the cursor */
472 		engine->blinking_status = FALSE;
473 		blink_timeout_cb (engine);
474 	}
475 }
476