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