Updated Esperanto translation
[gtkhtml.git] / gtkhtml / gtkhtml.c
blobef6ee36610c994d79d10627f809df51be445fc44
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* This file is part of the GtkHTML library.
4 * Copyright 1999, 2000 Helix Code, Inc.
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.
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.
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.
22 #include <config.h>
23 #include <ctype.h>
25 #include <gdk/gdkkeysyms.h>
26 #include <glib/gi18n-lib.h>
27 #include <string.h>
29 #include "../a11y/object.h"
31 #include "htmlcolorset.h"
32 #include "htmlcluev.h"
33 #include "htmlcursor.h"
34 #include "htmldrawqueue.h"
35 #include "htmlengine-edit.h"
36 #include "htmlengine-edit-clueflowstyle.h"
37 #include "htmlengine-edit-cut-and-paste.h"
38 #include "htmlengine-edit-fontstyle.h"
39 #include "htmlengine-edit-rule.h"
40 #include "htmlengine-edit-movement.h"
41 #include "htmlengine-edit-cursor.h"
42 #include "htmlengine-edit-table.h"
43 #include "htmlengine-edit-tablecell.h"
44 #include "htmlengine-edit-text.h"
45 #include "htmlengine-edit-selection-updater.h"
46 #include "htmlengine-print.h"
47 #include "htmlengine-save.h"
48 #include "htmlform.h"
49 #include "htmlframe.h"
50 #include "htmlframeset.h"
51 #include "htmliframe.h"
52 #include "htmlimage.h"
53 #include "htmlinterval.h"
54 #include "htmlmarshal.h"
55 #include "htmlplainpainter.h"
56 #include "htmlsettings.h"
57 #include "htmltable.h"
58 #include "htmltext.h"
59 #include "htmltextslave.h"
60 #include "htmlselection.h"
61 #include "htmlundo.h"
63 #include "gtkhtml.h"
64 #include "gtkhtml-embedded.h"
65 #include "gtkhtml-keybinding.h"
66 #include "gtkhtml-search.h"
67 #include "gtkhtml-stream.h"
68 #include "gtkhtml-private.h"
69 #include "gtkhtml-properties.h"
70 #include "math.h"
72 enum DndTargetType {
73 DND_TARGET_TYPE_TEXT_URI_LIST,
74 DND_TARGET_TYPE_MOZILLA_URL,
75 DND_TARGET_TYPE_TEXT_HTML,
76 DND_TARGET_TYPE_UTF8_STRING,
77 DND_TARGET_TYPE_TEXT_PLAIN,
78 DND_TARGET_TYPE_STRING
81 static GtkTargetEntry drag_dest_targets[] = {
82 { (gchar *) "text/uri-list", 0, DND_TARGET_TYPE_TEXT_URI_LIST },
83 { (gchar *) "_NETSCAPE_URL", 0, DND_TARGET_TYPE_MOZILLA_URL },
84 { (gchar *) "text/html", 0, DND_TARGET_TYPE_TEXT_HTML },
85 { (gchar *) "UTF8_STRING", 0, DND_TARGET_TYPE_UTF8_STRING },
86 { (gchar *) "text/plain", 0, DND_TARGET_TYPE_TEXT_PLAIN },
87 { (gchar *) "STRING", 0, DND_TARGET_TYPE_STRING },
90 static GtkTargetEntry drag_source_targets[] = {
91 { (gchar *) "text/uri-list", 0, DND_TARGET_TYPE_TEXT_URI_LIST },
92 { (gchar *) "_NETSCAPE_URL", 0, DND_TARGET_TYPE_MOZILLA_URL },
93 { (gchar *) "text/html", 0, DND_TARGET_TYPE_TEXT_HTML },
94 { (gchar *) "UTF8_STRING", 0, DND_TARGET_TYPE_UTF8_STRING },
95 { (gchar *) "text/plain", 0, DND_TARGET_TYPE_TEXT_PLAIN },
96 { (gchar *) "STRING", 0, DND_TARGET_TYPE_STRING },
99 enum _TargetInfo {
100 TARGET_HTML,
101 TARGET_UTF8_STRING,
102 TARGET_COMPOUND_TEXT,
103 TARGET_STRING,
104 TARGET_TEXT
107 typedef enum _TargetInfo TargetInfo;
109 static const GtkTargetEntry selection_targets[] = {
110 { (gchar *) "text/html", GTK_TARGET_SAME_APP, TARGET_HTML },
111 { (gchar *) "UTF8_STRING", 0, TARGET_UTF8_STRING },
112 { (gchar *) "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT },
113 { (gchar *) "STRING", 0, TARGET_STRING },
114 { (gchar *) "TEXT", 0, TARGET_TEXT }
117 static const guint n_selection_targets = G_N_ELEMENTS (selection_targets);
119 typedef struct _ClipboardContents ClipboardContents;
121 struct _ClipboardContents {
122 gchar *html_text;
123 gchar *plain_text;
126 #define d_s(x)
127 #define D_IM(x)
129 static GtkLayoutClass *parent_class = NULL;
131 enum {
132 TITLE_CHANGED,
133 URL_REQUESTED,
134 LOAD_DONE,
135 LINK_CLICKED,
136 SET_BASE,
137 SET_BASE_TARGET,
138 ON_URL,
139 REDIRECT,
140 SUBMIT,
141 OBJECT_REQUESTED,
142 CURRENT_PARAGRAPH_STYLE_CHANGED,
143 CURRENT_PARAGRAPH_INDENTATION_CHANGED,
144 CURRENT_PARAGRAPH_ALIGNMENT_CHANGED,
145 INSERTION_FONT_STYLE_CHANGED,
146 INSERTION_COLOR_CHANGED,
147 SIZE_CHANGED,
148 IFRAME_CREATED,
149 /* keybindings signals */
150 SCROLL,
151 CURSOR_MOVE,
152 COMMAND,
153 CURSOR_CHANGED,
154 OBJECT_INSERTED,
155 OBJECT_DELETE,
156 /* now only last signal */
157 LAST_SIGNAL
160 /* #define USE_PROPS */
161 #ifdef USE_PROPS
162 enum {
163 PROP_0,
164 PROP_EDITABLE,
165 PROP_TITLE,
166 PROP_DOCUMENT_BASE,
167 PROP_TARGET_BASE,
170 static void gtk_html_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
171 static void gtk_html_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
172 #endif
174 static guint signals[LAST_SIGNAL] = { 0 };
176 static void
177 gtk_html_update_scrollbars_on_resize (GtkHTML *html,
178 gdouble old_doc_width,
179 gdouble old_doc_height,
180 gdouble old_width,
181 gdouble old_height,
182 gboolean *changed_x,
183 gboolean *changed_y);
185 static void update_primary_selection (GtkHTML *html);
186 static void clipboard_paste_received_cb (GtkClipboard *clipboard,
187 GtkSelectionData *selection_data,
188 gpointer user_data);
189 static gboolean motion_notify_event (GtkWidget *widget,
190 GdkEventMotion *event);
192 /* keybindings signal hadlers */
193 static void scroll (GtkHTML *html,
194 GtkOrientation orientation,
195 GtkScrollType scroll_type,
196 gfloat position);
197 static void cursor_move (GtkHTML *html,
198 GtkDirectionType dir_type,
199 GtkHTMLCursorSkipType skip);
200 static gboolean command (GtkHTML *html,
201 GtkHTMLCommandType com_type);
202 static gint mouse_change_pos (GtkWidget *widget,
203 GdkWindow *window,
204 gint x,
205 gint y,
206 gint state);
207 static void add_bindings (GtkHTMLClass *klass);
208 static const gchar *get_value_nick (GtkHTMLCommandType com_type);
209 static void gtk_html_adjust_cursor_position (GtkHTML *html);
210 static gboolean any_has_cursor_moved (GtkHTML *html);
211 static gboolean any_has_skip_update_cursor (GtkHTML *html);
213 /* Interval for scrolling during selection. */
214 #define SCROLL_TIMEOUT_INTERVAL 10
217 GtkHTMLParagraphStyle
218 clueflow_style_to_paragraph_style (HTMLClueFlowStyle style,
219 HTMLListType item_type)
221 switch (style) {
222 case HTML_CLUEFLOW_STYLE_NORMAL:
223 return GTK_HTML_PARAGRAPH_STYLE_NORMAL;
224 case HTML_CLUEFLOW_STYLE_H1:
225 return GTK_HTML_PARAGRAPH_STYLE_H1;
226 case HTML_CLUEFLOW_STYLE_H2:
227 return GTK_HTML_PARAGRAPH_STYLE_H2;
228 case HTML_CLUEFLOW_STYLE_H3:
229 return GTK_HTML_PARAGRAPH_STYLE_H3;
230 case HTML_CLUEFLOW_STYLE_H4:
231 return GTK_HTML_PARAGRAPH_STYLE_H4;
232 case HTML_CLUEFLOW_STYLE_H5:
233 return GTK_HTML_PARAGRAPH_STYLE_H5;
234 case HTML_CLUEFLOW_STYLE_H6:
235 return GTK_HTML_PARAGRAPH_STYLE_H6;
236 case HTML_CLUEFLOW_STYLE_ADDRESS:
237 return GTK_HTML_PARAGRAPH_STYLE_ADDRESS;
238 case HTML_CLUEFLOW_STYLE_PRE:
239 return GTK_HTML_PARAGRAPH_STYLE_PRE;
240 case HTML_CLUEFLOW_STYLE_LIST_ITEM:
241 switch (item_type) {
242 case HTML_LIST_TYPE_UNORDERED:
243 return GTK_HTML_PARAGRAPH_STYLE_ITEMDOTTED;
244 case HTML_LIST_TYPE_ORDERED_ARABIC:
245 return GTK_HTML_PARAGRAPH_STYLE_ITEMDIGIT;
246 case HTML_LIST_TYPE_ORDERED_LOWER_ROMAN:
247 case HTML_LIST_TYPE_ORDERED_UPPER_ROMAN:
248 return GTK_HTML_PARAGRAPH_STYLE_ITEMROMAN;
249 case HTML_LIST_TYPE_ORDERED_LOWER_ALPHA:
250 case HTML_LIST_TYPE_ORDERED_UPPER_ALPHA:
251 return GTK_HTML_PARAGRAPH_STYLE_ITEMALPHA;
252 default:
253 return GTK_HTML_PARAGRAPH_STYLE_ITEMDOTTED;
255 default: /* This should not really happen, though. */
256 return GTK_HTML_PARAGRAPH_STYLE_NORMAL;
260 void
261 paragraph_style_to_clueflow_style (GtkHTMLParagraphStyle style,
262 HTMLClueFlowStyle *flow_style,
263 HTMLListType *item_type)
265 *item_type = HTML_LIST_TYPE_BLOCKQUOTE;
266 *flow_style = HTML_CLUEFLOW_STYLE_LIST_ITEM;
268 switch (style) {
269 case GTK_HTML_PARAGRAPH_STYLE_NORMAL:
270 *flow_style = HTML_CLUEFLOW_STYLE_NORMAL;
271 break;
272 case GTK_HTML_PARAGRAPH_STYLE_H1:
273 *flow_style = HTML_CLUEFLOW_STYLE_H1;
274 break;
275 case GTK_HTML_PARAGRAPH_STYLE_H2:
276 *flow_style = HTML_CLUEFLOW_STYLE_H2;
277 break;
278 case GTK_HTML_PARAGRAPH_STYLE_H3:
279 *flow_style = HTML_CLUEFLOW_STYLE_H3;
280 break;
281 case GTK_HTML_PARAGRAPH_STYLE_H4:
282 *flow_style = HTML_CLUEFLOW_STYLE_H4;
283 break;
284 case GTK_HTML_PARAGRAPH_STYLE_H5:
285 *flow_style = HTML_CLUEFLOW_STYLE_H5;
286 break;
287 case GTK_HTML_PARAGRAPH_STYLE_H6:
288 *flow_style = HTML_CLUEFLOW_STYLE_H6;
289 break;
290 case GTK_HTML_PARAGRAPH_STYLE_ADDRESS:
291 *flow_style = HTML_CLUEFLOW_STYLE_ADDRESS;
292 break;
293 case GTK_HTML_PARAGRAPH_STYLE_PRE:
294 *flow_style = HTML_CLUEFLOW_STYLE_PRE;
295 break;
296 case GTK_HTML_PARAGRAPH_STYLE_ITEMDOTTED:
297 *item_type = HTML_LIST_TYPE_UNORDERED;
298 break;
299 case GTK_HTML_PARAGRAPH_STYLE_ITEMROMAN:
300 *item_type = HTML_LIST_TYPE_ORDERED_UPPER_ROMAN;
301 break;
302 case GTK_HTML_PARAGRAPH_STYLE_ITEMALPHA:
303 *item_type = HTML_LIST_TYPE_ORDERED_UPPER_ALPHA;
304 break;
305 case GTK_HTML_PARAGRAPH_STYLE_ITEMDIGIT:
306 *item_type = HTML_LIST_TYPE_ORDERED_ARABIC;
307 break;
308 default: /* This should not really happen, though. */
309 *flow_style = HTML_CLUEFLOW_STYLE_NORMAL;
313 HTMLHAlignType
314 paragraph_alignment_to_html (GtkHTMLParagraphAlignment alignment)
316 switch (alignment) {
317 case GTK_HTML_PARAGRAPH_ALIGNMENT_LEFT:
318 return HTML_HALIGN_LEFT;
319 case GTK_HTML_PARAGRAPH_ALIGNMENT_RIGHT:
320 return HTML_HALIGN_RIGHT;
321 case GTK_HTML_PARAGRAPH_ALIGNMENT_CENTER:
322 return HTML_HALIGN_CENTER;
323 default:
324 return HTML_HALIGN_LEFT;
328 GtkHTMLParagraphAlignment
329 html_alignment_to_paragraph (HTMLHAlignType alignment)
331 switch (alignment) {
332 case HTML_HALIGN_LEFT:
333 return GTK_HTML_PARAGRAPH_ALIGNMENT_LEFT;
334 case HTML_HALIGN_CENTER:
335 return GTK_HTML_PARAGRAPH_ALIGNMENT_CENTER;
336 case HTML_HALIGN_RIGHT:
337 return GTK_HTML_PARAGRAPH_ALIGNMENT_RIGHT;
338 default:
339 return GTK_HTML_PARAGRAPH_ALIGNMENT_LEFT;
343 void
344 gtk_html_update_styles (GtkHTML *html)
346 GtkHTMLParagraphStyle paragraph_style;
347 GtkHTMLParagraphAlignment alignment;
348 HTMLEngine *engine;
349 HTMLClueFlowStyle flow_style;
350 HTMLListType item_type;
351 guint indentation;
353 /* printf ("gtk_html_update_styles called\n"); */
355 if (!html_engine_get_editable (html->engine))
356 return;
358 engine = html->engine;
359 html_engine_get_current_clueflow_style (engine, &flow_style, &item_type);
360 paragraph_style = clueflow_style_to_paragraph_style (flow_style, item_type);
362 if (paragraph_style != html->priv->paragraph_style) {
363 html->priv->paragraph_style = paragraph_style;
364 g_signal_emit (html, signals[CURRENT_PARAGRAPH_STYLE_CHANGED], 0, paragraph_style);
367 indentation = html_engine_get_current_clueflow_indentation (engine);
368 if (indentation != html->priv->paragraph_indentation) {
369 html->priv->paragraph_indentation = indentation;
370 g_signal_emit (html, signals[CURRENT_PARAGRAPH_INDENTATION_CHANGED], 0, indentation);
373 alignment = html_alignment_to_paragraph (html_engine_get_current_clueflow_alignment (engine));
374 if (alignment != html->priv->paragraph_alignment) {
375 html->priv->paragraph_alignment = alignment;
376 g_signal_emit (html, signals[CURRENT_PARAGRAPH_ALIGNMENT_CHANGED], 0, alignment);
379 if (html_engine_update_insertion_font_style (engine))
380 g_signal_emit (html, signals[INSERTION_FONT_STYLE_CHANGED], 0, engine->insertion_font_style);
381 if (html_engine_update_insertion_color (engine))
382 g_signal_emit (html, signals[INSERTION_COLOR_CHANGED], 0, engine->insertion_color);
384 /* TODO add insertion_url_or_targed_changed signal */
385 html_engine_update_insertion_url_and_target (engine);
389 /* GTK+ idle loop handler. */
391 static gint
392 idle_handler (gpointer data)
394 GtkHTML *html;
395 HTMLEngine *engine;
396 gboolean also_update_cursor;
398 html = GTK_HTML (data);
399 engine = html->engine;
401 also_update_cursor = any_has_cursor_moved (html) || !any_has_skip_update_cursor (html);
403 if (html->engine->thaw_idle_id == 0 && !html_engine_frozen (html->engine))
404 html_engine_flush_draw_queue (engine);
406 if (also_update_cursor)
407 gtk_html_adjust_cursor_position (html);
409 html->priv->idle_handler_id = 0;
410 html->priv->skip_update_cursor = FALSE;
411 html->priv->cursor_moved = FALSE;
413 while (html->iframe_parent) {
414 html = GTK_HTML (html->iframe_parent);
416 if (html) {
417 html->priv->skip_update_cursor = FALSE;
418 html->priv->cursor_moved = FALSE;
421 if (also_update_cursor)
422 gtk_html_adjust_cursor_position (html);
425 return FALSE;
428 static void
429 gtk_html_adjust_cursor_position (GtkHTML *html)
431 HTMLEngine *e;
432 GtkAdjustment *hadjustment;
433 GtkAdjustment *vadjustment;
434 e = html->engine;
436 if (html->priv->scroll_timeout_id == 0 &&
437 html->engine->thaw_idle_id == 0 &&
438 !html_engine_frozen (html->engine))
439 html_engine_make_cursor_visible (e);
441 hadjustment = gtk_layout_get_hadjustment (GTK_LAYOUT (html));
442 vadjustment = gtk_layout_get_vadjustment (GTK_LAYOUT (html));
444 gtk_adjustment_set_value (hadjustment, (gfloat) e->x_offset);
445 gtk_adjustment_set_value (vadjustment, (gfloat) e->y_offset);
446 gtk_html_private_calc_scrollbars (html, NULL, NULL);
450 static void
451 queue_draw (GtkHTML *html)
453 if (html->priv->idle_handler_id == 0)
454 html->priv->idle_handler_id =
455 /* schedule with priority higher than gtk+ uses for animations (check docs for G_PRIORITY_HIGH_IDLE) */
456 g_idle_add_full (G_PRIORITY_HIGH_IDLE, idle_handler, html, NULL);
460 /* HTMLEngine callbacks. */
462 static void
463 html_engine_title_changed_cb (HTMLEngine *engine,
464 gpointer data)
466 GtkHTML *gtk_html;
468 gtk_html = GTK_HTML (data);
469 g_signal_emit (gtk_html, signals[TITLE_CHANGED], 0, engine->title->str);
472 static void
473 html_engine_set_base_cb (HTMLEngine *engine,
474 const gchar *base,
475 gpointer data)
477 GtkHTML *gtk_html;
479 gtk_html = GTK_HTML (data);
480 gtk_html_set_base (gtk_html, base);
481 g_signal_emit (gtk_html, signals[SET_BASE], 0, base);
484 static void
485 html_engine_set_base_target_cb (HTMLEngine *engine,
486 const gchar *base_target,
487 gpointer data)
489 GtkHTML *gtk_html;
491 gtk_html = GTK_HTML (data);
492 g_signal_emit (gtk_html, signals[SET_BASE_TARGET], 0, base_target);
495 static void
496 html_engine_load_done_cb (HTMLEngine *engine,
497 gpointer data)
499 GtkHTML *gtk_html;
501 gtk_html = GTK_HTML (data);
502 g_signal_emit (gtk_html, signals[LOAD_DONE], 0);
505 static void
506 html_engine_url_requested_cb (HTMLEngine *engine,
507 const gchar *url,
508 GtkHTMLStream *handle,
509 gpointer data)
511 GtkHTML *gtk_html;
512 gchar *expanded = NULL;
513 gtk_html = GTK_HTML (data);
515 if (engine->stopped)
516 return;
518 expanded = gtk_html_get_url_base_relative (gtk_html, url);
519 g_signal_emit (gtk_html, signals[URL_REQUESTED], 0, expanded, handle);
520 g_free (expanded);
523 static void
524 html_engine_draw_pending_cb (HTMLEngine *engine,
525 gpointer data)
527 GtkHTML *html;
529 html = GTK_HTML (data);
530 html->priv->skip_update_cursor = TRUE;
531 queue_draw (html);
534 static void
535 html_engine_redirect_cb (HTMLEngine *engine,
536 const gchar *url,
537 gint delay,
538 gpointer data)
540 GtkHTML *gtk_html;
542 gtk_html = GTK_HTML (data);
544 g_signal_emit (gtk_html, signals[REDIRECT], 0, url, delay);
547 static void
548 html_engine_submit_cb (HTMLEngine *engine,
549 const gchar *method,
550 const gchar *url,
551 const gchar *encoding,
552 gpointer data)
554 GtkHTML *gtk_html;
556 gtk_html = GTK_HTML (data);
558 g_signal_emit (gtk_html, signals[SUBMIT], 0, method, url, encoding);
561 static gboolean
562 html_engine_object_requested_cb (HTMLEngine *engine,
563 GtkHTMLEmbedded *eb,
564 gpointer data)
566 GtkHTML *gtk_html;
567 gboolean object_found = FALSE;
569 gtk_html = GTK_HTML (data);
571 object_found = FALSE;
572 g_signal_emit (gtk_html, signals[OBJECT_REQUESTED], 0, eb, &object_found);
573 return object_found;
577 /* GtkAdjustment handling. */
579 static void
580 scroll_update_mouse (GtkWidget *widget)
582 GdkWindow *window;
583 GdkWindow *bin_window;
584 gint x, y;
586 if (!gtk_widget_get_realized (widget))
587 return;
589 window = gtk_widget_get_window (widget);
590 bin_window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
592 gdk_window_get_pointer (bin_window, &x, &y, NULL);
593 mouse_change_pos (widget, window, x, y, 0);
596 static void
597 vertical_scroll_cb (GtkAdjustment *adjustment,
598 gpointer data)
600 GtkHTML *html = GTK_HTML (data);
602 if (html->engine->keep_scroll)
603 return;
605 html->engine->y_offset = (gint) gtk_adjustment_get_value (adjustment);
606 scroll_update_mouse (GTK_WIDGET (data));
609 static void
610 horizontal_scroll_cb (GtkAdjustment *adjustment,
611 gpointer data)
613 GtkHTML *html = GTK_HTML (data);
615 if (html->engine->keep_scroll)
616 return;
618 html->engine->x_offset = (gint) gtk_adjustment_get_value (adjustment);
619 scroll_update_mouse (GTK_WIDGET (data));
622 static void
623 hadjustment_notify_cb (GtkHTML *html)
625 GtkScrollable *scrollable;
626 GtkAdjustment *hadjustment;
628 if (!html->priv)
629 return;
631 scrollable = GTK_SCROLLABLE (html);
632 hadjustment = gtk_scrollable_get_hadjustment (scrollable);
634 if (html->hadj_connection > 0) {
635 g_signal_handler_disconnect (
636 html->priv->hadjustment, html->hadj_connection);
637 g_object_unref (html->priv->hadjustment);
640 if (hadjustment != NULL) {
641 html->hadj_connection = g_signal_connect (
642 hadjustment, "value_changed",
643 G_CALLBACK (horizontal_scroll_cb), html);
644 html->priv->hadjustment = g_object_ref (hadjustment);
645 } else {
646 html->hadj_connection = 0;
647 html->priv->hadjustment = NULL;
651 static void
652 vadjustment_notify_cb (GtkHTML *html)
654 GtkScrollable *scrollable;
655 GtkAdjustment *vadjustment;
657 if (!html->priv)
658 return;
660 scrollable = GTK_SCROLLABLE (html);
661 vadjustment = gtk_scrollable_get_vadjustment (scrollable);
663 if (html->vadj_connection != 0) {
664 g_signal_handler_disconnect (
665 html->priv->vadjustment, html->vadj_connection);
666 g_object_unref (html->priv->vadjustment);
669 if (vadjustment != NULL) {
670 html->vadj_connection = g_signal_connect (
671 vadjustment, "value_changed",
672 G_CALLBACK (vertical_scroll_cb), html);
673 html->priv->vadjustment = g_object_ref (vadjustment);
674 } else {
675 html->vadj_connection = 0;
676 html->priv->vadjustment = NULL;
681 /* Scroll timeout handling. */
683 static void
684 inc_adjustment (GtkAdjustment *adj,
685 gint doc_width,
686 gint alloc_width,
687 gint inc)
689 gfloat value;
690 gint max;
692 value = gtk_adjustment_get_value (adj) + (gfloat) inc;
694 if (doc_width > alloc_width)
695 max = doc_width - alloc_width;
696 else
697 max = 0;
699 if (value > (gfloat) max)
700 value = (gfloat) max;
701 else if (value < 0)
702 value = 0.0;
704 gtk_adjustment_set_value (adj, value);
707 static gint
708 scroll_timeout_cb (gpointer data)
710 GtkWidget *widget;
711 GdkWindow *window;
712 GtkHTML *html;
713 HTMLEngine *engine;
714 GtkAllocation allocation;
715 GtkAdjustment *hadjustment;
716 GtkAdjustment *vadjustment;
717 gint x_scroll, y_scroll;
718 gint x, y;
720 widget = GTK_WIDGET (data);
721 html = GTK_HTML (data);
722 engine = html->engine;
724 window = gtk_widget_get_window (widget);
725 gdk_window_get_pointer (window, &x, &y, NULL);
727 gtk_widget_get_allocation (widget, &allocation);
729 if (x < 0) {
730 x_scroll = x;
731 if (x + engine->x_offset >= 0)
732 x = 0;
733 } else if (x >= allocation.width) {
734 x_scroll = x - allocation.width + 1;
735 x = allocation.width;
736 } else {
737 x_scroll = 0;
739 x_scroll /= 2;
741 if (y < 0) {
742 y_scroll = y;
743 if (y + engine->y_offset >= 0)
744 y = 0;
745 } else if (y >= allocation.height) {
746 y_scroll = y - allocation.height + 1;
747 y = allocation.height;
748 } else {
749 y_scroll = 0;
751 y_scroll /= 2;
753 if (html->in_selection && (x_scroll != 0 || y_scroll != 0))
754 html_engine_select_region (engine, html->selection_x1, html->selection_y1,
755 x + engine->x_offset, y + engine->y_offset);
757 hadjustment = gtk_layout_get_hadjustment (GTK_LAYOUT (widget));
758 vadjustment = gtk_layout_get_vadjustment (GTK_LAYOUT (widget));
760 inc_adjustment (hadjustment, html_engine_get_doc_width (html->engine),
761 allocation.width, x_scroll);
762 inc_adjustment (vadjustment, html_engine_get_doc_height (html->engine),
763 allocation.height, y_scroll);
765 return TRUE;
768 static void
769 setup_scroll_timeout (GtkHTML *html)
771 if (html->priv->scroll_timeout_id != 0)
772 return;
774 html->priv->scroll_timeout_id = g_timeout_add (SCROLL_TIMEOUT_INTERVAL,
775 scroll_timeout_cb, html);
777 scroll_timeout_cb (html);
780 static void
781 remove_scroll_timeout (GtkHTML *html)
783 if (html->priv->scroll_timeout_id == 0)
784 return;
786 g_source_remove (html->priv->scroll_timeout_id);
787 html->priv->scroll_timeout_id = 0;
791 /* GObject methods. */
793 static void
794 dispose (GObject *object)
796 GtkHTML *html;
798 html = GTK_HTML (object);
800 g_free (html->pointer_url);
801 html->pointer_url = NULL;
803 if (html->hand_cursor) {
804 g_object_unref (html->hand_cursor);
805 html->hand_cursor = NULL;
808 if (html->ibeam_cursor) {
809 g_object_unref (html->ibeam_cursor);
810 html->ibeam_cursor = NULL;
813 gtk_scrollable_set_hadjustment (GTK_SCROLLABLE (html), NULL);
814 gtk_scrollable_set_vadjustment (GTK_SCROLLABLE (html), NULL);
816 hadjustment_notify_cb (html);
817 vadjustment_notify_cb (html);
819 g_signal_handlers_disconnect_by_func (html, hadjustment_notify_cb, NULL);
820 g_signal_handlers_disconnect_by_func (html, vadjustment_notify_cb, NULL);
822 if (html->priv->idle_handler_id != 0) {
823 g_source_remove (html->priv->idle_handler_id);
824 html->priv->idle_handler_id = 0;
827 if (html->priv->scroll_timeout_id != 0) {
828 g_source_remove (html->priv->scroll_timeout_id);
829 html->priv->scroll_timeout_id = 0;
832 if (html->priv->desktop_interface != NULL) {
833 g_signal_handlers_disconnect_matched (
834 html->priv->desktop_interface,
835 G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, html);
836 g_object_unref (html->priv->desktop_interface);
837 html->priv->desktop_interface = NULL;
840 if (html->priv->resize_cursor) {
841 g_object_unref (html->priv->resize_cursor);
842 html->priv->resize_cursor = NULL;
845 if (html->priv->im_context) {
846 g_object_unref (html->priv->im_context);
847 html->priv->im_context = NULL;
850 g_free (html->priv->base_url);
851 html->priv->base_url = NULL;
853 g_free (html->priv->caret_first_focus_anchor);
854 html->priv->caret_first_focus_anchor = NULL;
856 if (html->engine) {
857 g_object_unref (html->engine);
858 html->engine = NULL;
861 /* Chain up to parent's dispose() method. */
862 G_OBJECT_CLASS (parent_class)->dispose (object);
865 GtkHTML *
866 gtk_html_get_top_html (GtkHTML *html)
868 while (html->iframe_parent)
869 html = GTK_HTML (html->iframe_parent);
871 return html;
874 void
875 gtk_html_set_fonts (GtkHTML *html,
876 HTMLPainter *painter)
878 GtkWidget *top_level;
879 GtkStyleContext *style_context;
880 GdkScreen *screen;
881 PangoFontDescription *fixed_desc = NULL;
882 const PangoFontDescription *font_desc;
883 gchar *fixed_name = NULL;
884 const gchar *fixed_family = NULL;
885 gint fixed_size = 0;
886 gboolean fixed_points = FALSE;
887 const gchar *font_var = NULL;
888 gint font_var_size = 0;
889 gboolean font_var_points = FALSE;
891 top_level = GTK_WIDGET (gtk_html_get_top_html (html));
892 style_context = gtk_widget_get_style_context (top_level);
893 font_desc = gtk_style_context_get_font (style_context, GTK_STATE_FLAG_NORMAL);
895 font_var = pango_font_description_get_family (font_desc);
896 font_var_size = pango_font_description_get_size (font_desc);
897 font_var_points = !pango_font_description_get_size_is_absolute (font_desc);
899 gtk_widget_style_get (GTK_WIDGET (top_level), "fixed_font_name", &fixed_name, NULL);
900 if (fixed_name) {
901 fixed_desc = pango_font_description_from_string (fixed_name);
902 if (pango_font_description_get_family (fixed_desc)) {
903 fixed_size = pango_font_description_get_size (fixed_desc);
904 fixed_points = !pango_font_description_get_size_is_absolute (fixed_desc);
905 fixed_family = pango_font_description_get_family (fixed_desc);
906 } else {
907 g_free (fixed_name);
908 fixed_name = NULL;
912 if (!fixed_name) {
913 GSettings *settings;
915 settings = g_settings_new ("org.gnome.desktop.interface");
916 fixed_name = g_settings_get_string (settings, "monospace-font-name");
917 g_object_unref (settings);
919 if (fixed_name) {
920 fixed_desc = pango_font_description_from_string (fixed_name);
921 if (fixed_desc) {
922 fixed_size = pango_font_description_get_size (fixed_desc);
923 fixed_points = !pango_font_description_get_size_is_absolute (fixed_desc);
924 fixed_family = pango_font_description_get_family (fixed_desc);
925 } else {
926 g_free (fixed_name);
927 fixed_name = NULL;
932 if (!fixed_name) {
933 fixed_family = "Monospace";
934 fixed_size = font_var_size;
937 html_font_manager_set_default (&painter->font_manager,
938 (gchar *) font_var, (gchar *) fixed_family,
939 font_var_size, font_var_points,
940 fixed_size, fixed_points);
941 if (fixed_desc)
942 pango_font_description_free (fixed_desc);
944 screen = gtk_widget_get_screen (GTK_WIDGET (html));
945 if (screen != NULL)
946 pango_cairo_context_set_font_options (
947 painter->pango_context,
948 gdk_screen_get_font_options (screen));
950 g_free (fixed_name);
953 static void
954 set_caret_mode (HTMLEngine *engine,
955 gboolean caret_mode)
957 if (engine->editable)
958 return;
960 if (!caret_mode && engine->blinking_timer_id)
961 html_engine_stop_blinking_cursor (engine);
963 engine->caret_mode = caret_mode;
965 if (caret_mode && !engine->parsing && !engine->timerId == 0)
966 gtk_html_edit_make_cursor_visible (engine->widget);
968 /* Normally, blink cursor handler is setup in focus in event.
969 * However, in the case focus already in this engine, and user
970 * type F7 to enable cursor, we must setup the handler by
971 * ourselves.
973 if (caret_mode && !engine->blinking_timer_id && engine->have_focus)
974 html_engine_setup_blinking_cursor (engine);
976 return;
979 /* GtkWidget methods. */
981 static void
982 style_updated (GtkWidget *widget)
984 HTMLEngine *engine = GTK_HTML (widget)->engine;
986 /* we don't need to set font's in idle time so call idle callback directly to avoid
987 * recalculating whole document
989 if (engine) {
990 gtk_html_set_fonts (GTK_HTML (widget), engine->painter);
991 html_engine_refresh_fonts (engine);
994 html_colorset_set_style (engine->defaultSettings->color_set, widget);
995 html_colorset_set_unchanged (engine->settings->color_set,
996 engine->defaultSettings->color_set);
998 /* link and quotation colors might changed => need to refresh pango info in text objects */
999 if (engine->clue)
1000 html_object_change_set_down (engine->clue, HTML_CHANGE_RECALC_PI);
1001 html_engine_schedule_update (engine);
1004 static void
1005 update_mouse_cursor (GtkWidget *widget,
1006 guint state)
1008 GdkEventMotion event;
1010 /* a bit hacky here */
1011 memset (&event, 0, sizeof (GdkEventMotion));
1012 event.type = GDK_MOTION_NOTIFY;
1013 event.window = gtk_widget_get_window (widget);
1014 event.send_event = FALSE;
1015 event.state = state;
1017 motion_notify_event (widget, &event);
1020 static gboolean
1021 key_press_event (GtkWidget *widget,
1022 GdkEventKey *event)
1024 GtkHTML *html = GTK_HTML (widget);
1025 GtkHTMLClass *html_class = GTK_HTML_CLASS (GTK_WIDGET_GET_CLASS (html));
1026 gboolean retval = FALSE, update = TRUE;
1027 HTMLObject *focus_object;
1028 gint focus_object_offset;
1029 gboolean url_test_mode;
1031 html->binding_handled = FALSE;
1032 html->priv->update_styles = FALSE;
1033 html->priv->event_time = event->time;
1035 if ((event->keyval == GDK_KEY_Control_L || event->keyval == GDK_KEY_Control_R)
1036 && html_engine_get_editable (html->engine))
1037 url_test_mode = TRUE;
1038 else
1039 url_test_mode = FALSE;
1041 if (html->priv->in_url_test_mode != url_test_mode) {
1042 html->priv->in_url_test_mode = url_test_mode;
1043 update_mouse_cursor (widget, event->state);
1046 if (html_engine_get_editable (html->engine)) {
1047 if (gtk_im_context_filter_keypress (html->priv->im_context, event)) {
1048 html_engine_reset_blinking_cursor (html->engine);
1049 html->priv->need_im_reset = TRUE;
1050 return TRUE;
1054 if (html_class->use_emacs_bindings && html_class->emacs_bindings && !html->binding_handled)
1055 gtk_binding_set_activate (html_class->emacs_bindings, event->keyval, event->state, G_OBJECT (widget));
1057 if (!html->binding_handled) {
1058 html->priv->in_key_binding = TRUE;
1059 retval = GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
1060 html->priv->in_key_binding = FALSE;
1063 retval = retval || html->binding_handled;
1064 update = html->priv->update_styles;
1066 if (retval && update)
1067 gtk_html_update_styles (html);
1069 html->priv->event_time = 0;
1071 /* FIXME: use bindings */
1072 if (!html_engine_get_editable (html->engine)) {
1073 switch (event->keyval) {
1074 case GDK_KEY_Return:
1075 case GDK_KEY_KP_Enter:
1076 /* the toplevel gtkhtml's focus object may be a frame or ifame */
1077 focus_object = html_engine_get_focus_object (html->engine, &focus_object_offset);
1079 if (focus_object) {
1080 gchar *url;
1081 url = html_object_get_complete_url (focus_object, focus_object_offset);
1082 if (url) {
1083 /* printf ("link clicked: %s\n", url); */
1084 if (HTML_IS_TEXT (focus_object)) {
1085 html_text_set_link_visited (HTML_TEXT (focus_object), focus_object_offset, html->engine, TRUE);
1086 g_signal_emit (html, signals[LINK_CLICKED], 0, url);
1088 g_free (url);
1091 break;
1092 default:
1097 if (retval && (html_engine_get_editable (html->engine) || html->engine->caret_mode))
1098 html_engine_reset_blinking_cursor (html->engine);
1100 /* printf ("retval: %d\n", retval); */
1102 return retval;
1105 static gboolean
1106 key_release_event (GtkWidget *widget,
1107 GdkEventKey *event)
1109 GtkHTML *html = GTK_HTML (widget);
1111 if (html->priv->in_url_test_mode) {
1112 html->priv->in_url_test_mode = FALSE;
1113 update_mouse_cursor (widget, event->state);
1116 if (!html->binding_handled && html_engine_get_editable (html->engine)) {
1117 if (gtk_im_context_filter_keypress (html->priv->im_context, event)) {
1118 html->priv->need_im_reset = TRUE;
1119 return TRUE;
1123 return GTK_WIDGET_CLASS (parent_class)->key_release_event (widget, event);
1126 void
1127 gtk_html_drag_dest_set (GtkHTML *html)
1129 if (html_engine_get_editable (html->engine))
1130 gtk_drag_dest_set (
1131 GTK_WIDGET (html),
1132 GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
1133 drag_dest_targets, G_N_ELEMENTS (drag_dest_targets),
1134 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
1135 else
1136 gtk_drag_dest_unset (GTK_WIDGET (html));
1139 static void
1140 realize (GtkWidget *widget)
1142 GtkHTML *html;
1143 GdkWindow *window;
1144 GdkWindow *bin_window;
1145 GtkAdjustment *hadjustment;
1146 GtkAdjustment *vadjustment;
1148 g_return_if_fail (widget != NULL);
1149 g_return_if_fail (GTK_IS_HTML (widget));
1151 html = GTK_HTML (widget);
1152 hadjustment = gtk_layout_get_hadjustment (GTK_LAYOUT (widget));
1153 vadjustment = gtk_layout_get_vadjustment (GTK_LAYOUT (widget));
1155 if (GTK_WIDGET_CLASS (parent_class)->realize)
1156 (* GTK_WIDGET_CLASS (parent_class)->realize) (widget);
1158 window = gtk_widget_get_window (widget);
1159 bin_window = gtk_layout_get_bin_window (&html->layout);
1161 gdk_window_set_events (bin_window,
1162 (gdk_window_get_events (bin_window)
1163 | GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK
1164 | GDK_ENTER_NOTIFY_MASK
1165 | GDK_BUTTON_PRESS_MASK
1166 | GDK_BUTTON_RELEASE_MASK
1167 | GDK_VISIBILITY_NOTIFY_MASK
1168 | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK));
1170 html_engine_realize (html->engine, bin_window);
1172 gdk_window_set_cursor (window, NULL);
1174 /* This sets the backing pixmap to None, so that scrolling does not
1175 * erase the newly exposed area, thus making the thing smoother. */
1176 gdk_window_set_background_pattern (bin_window, NULL);
1178 /* If someone was silly enough to stick us in something that doesn't
1179 * have adjustments, go ahead and create them now since we expect them
1180 * and love them and pat them
1182 if (hadjustment == NULL) {
1183 hadjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1184 gtk_layout_set_hadjustment (GTK_LAYOUT (widget), hadjustment);
1187 if (vadjustment == NULL) {
1188 vadjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
1189 gtk_layout_set_vadjustment (GTK_LAYOUT (widget), vadjustment);
1192 gtk_html_drag_dest_set (html);
1194 gtk_im_context_set_client_window (html->priv->im_context, window);
1196 html_image_factory_start_animations (html->engine->image_factory);
1199 static void
1200 unrealize (GtkWidget *widget)
1202 GtkHTML *html = GTK_HTML (widget);
1204 html_engine_unrealize (html->engine);
1206 gtk_im_context_set_client_window (html->priv->im_context, NULL);
1208 html_image_factory_stop_animations (html->engine->image_factory);
1210 if (GTK_WIDGET_CLASS (parent_class)->unrealize)
1211 (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
1214 static gboolean
1215 draw (GtkWidget *widget,
1216 cairo_t *cr)
1218 html_engine_draw_cb (GTK_HTML (widget)->engine, cr);
1220 if (GTK_WIDGET_CLASS (parent_class)->draw)
1221 (* GTK_WIDGET_CLASS (parent_class)->draw) (widget, cr);
1223 return FALSE;
1226 static void
1227 gtk_html_get_preferred_height (GtkWidget *widget,
1228 gint *minimum_height,
1229 gint *natural_height)
1231 HTMLEngine *e = GTK_HTML (widget)->engine;
1232 if (!e->writing) {
1233 gint old_height;
1235 old_height = e->height;
1236 e->height = *minimum_height;
1237 html_engine_calc_size (e, NULL);
1238 *minimum_height = *natural_height = html_engine_get_doc_height (e);
1239 e->height = old_height;
1240 html_engine_calc_size (e, NULL);
1241 } else {
1242 *minimum_height = *natural_height = html_engine_get_doc_height (e);
1246 static void
1247 gtk_html_get_preferred_width (GtkWidget *widget,
1248 gint *minimum_width,
1249 gint *natural_width)
1251 HTMLEngine *e = GTK_HTML (widget)->engine;
1252 if (!e->writing) {
1253 gint old_width;
1255 old_width = e->width;
1256 e->width = *minimum_width;
1257 html_engine_calc_size (e, NULL);
1258 *minimum_width = *natural_width = html_engine_get_doc_width (e);
1259 e->width = old_width;
1260 html_engine_calc_size (e, NULL);
1261 } else {
1262 *minimum_width = *natural_width = html_engine_get_doc_width (e);
1266 static void
1267 child_size_allocate (HTMLObject *o,
1268 HTMLEngine *e,
1269 gpointer data)
1271 if (html_object_is_embedded (o)) {
1272 HTMLEmbedded *eo = HTML_EMBEDDED (o);
1274 if (eo->widget) {
1275 GtkAllocation allocation;
1277 html_object_calc_abs_position_in_frame (o, &allocation.x, &allocation.y);
1278 allocation.y -= o->ascent;
1279 allocation.width = o->width;
1280 allocation.height = o->ascent + o->descent;
1281 gtk_widget_size_allocate (eo->widget, &allocation);
1286 static void
1287 set_adjustment_upper (GtkAdjustment *adjustment,
1288 gdouble upper)
1290 gdouble page_size;
1291 gdouble value;
1292 gdouble min;
1294 /* XXX Stolen from gtklayout.c and simplified. */
1296 value = gtk_adjustment_get_value (adjustment);
1297 page_size = gtk_adjustment_get_page_size (adjustment);
1298 min = MAX (0., upper - page_size);
1300 gtk_adjustment_set_upper (adjustment, upper);
1302 if (value > min)
1303 gtk_adjustment_set_value (adjustment, min);
1306 static void
1307 gtk_layout_faux_size_allocate (GtkWidget *widget,
1308 GtkAllocation *allocation)
1310 GtkLayout *layout = GTK_LAYOUT (widget);
1311 GtkAdjustment *adjustment;
1312 guint width, height;
1314 /* XXX This is essentially a copy of GtkLayout's size_allocate()
1315 * method, but with the GtkLayoutChild loop removed. We call
1316 * this instead of chaining up to GtkLayout. */
1318 gtk_widget_set_allocation (widget, allocation);
1319 gtk_layout_get_size (layout, &width, &height);
1321 if (gtk_widget_get_realized (widget)) {
1322 gdk_window_move_resize (
1323 gtk_widget_get_window (widget),
1324 allocation->x, allocation->y,
1325 allocation->width, allocation->height);
1327 gdk_window_resize (
1328 gtk_layout_get_bin_window (layout),
1329 MAX (width, allocation->width),
1330 MAX (height, allocation->height));
1333 /* XXX Does the previous logic alter the GtkLayout size?
1334 * Not sure, so best refetch the size just to be safe. */
1335 gtk_layout_get_size (layout, &width, &height);
1337 adjustment = gtk_layout_get_hadjustment (layout);
1338 g_object_freeze_notify (G_OBJECT (adjustment));
1339 gtk_adjustment_set_page_size (adjustment, allocation->width);
1340 gtk_adjustment_set_page_increment (adjustment, allocation->width * 0.9);
1341 gtk_adjustment_set_lower (adjustment, 0);
1342 set_adjustment_upper (adjustment, MAX (allocation->width, width));
1343 g_object_thaw_notify (G_OBJECT (adjustment));
1345 adjustment = gtk_layout_get_vadjustment (layout);
1346 g_object_freeze_notify (G_OBJECT (adjustment));
1347 gtk_adjustment_set_page_size (adjustment, allocation->height);
1348 gtk_adjustment_set_page_increment (adjustment, allocation->height * 0.9);
1349 gtk_adjustment_set_lower (adjustment, 0);
1350 set_adjustment_upper (adjustment, MAX (allocation->height, height));
1351 g_object_thaw_notify (G_OBJECT (adjustment));
1354 static void
1355 size_allocate (GtkWidget *widget,
1356 GtkAllocation *allocation)
1358 GtkHTML *html;
1359 gboolean changed_x = FALSE, changed_y = FALSE;
1361 g_return_if_fail (widget != NULL);
1362 g_return_if_fail (GTK_IS_HTML (widget));
1363 g_return_if_fail (allocation != NULL);
1365 html = GTK_HTML (widget);
1367 /* isolate children from layout - we want to set them after calc size is performed
1368 * and we know the position of the children */
1370 gtk_layout_faux_size_allocate (widget, allocation);
1372 if (html->engine->width != allocation->width
1373 || html->engine->height != allocation->height) {
1374 HTMLEngine *e = html->engine;
1375 gint old_doc_width, old_doc_height, old_width, old_height;
1377 old_doc_width = html_engine_get_doc_width (html->engine);
1378 old_doc_height = html_engine_get_doc_height (html->engine);
1379 old_width = e->width;
1380 old_height = e->height;
1382 e->width = allocation->width;
1383 e->height = allocation->height;
1385 html_engine_calc_size (html->engine, NULL);
1386 gtk_html_update_scrollbars_on_resize (html, old_doc_width, old_doc_height, old_width, old_height,
1387 &changed_x, &changed_y);
1390 if (!html->engine->keep_scroll)
1391 gtk_html_private_calc_scrollbars (html, &changed_x, &changed_y);
1393 if (html->engine->clue)
1394 html_object_forall (html->engine->clue, html->engine, child_size_allocate, NULL);
1397 static void
1398 set_pointer_url (GtkHTML *html,
1399 const gchar *url)
1401 if (url == html->pointer_url)
1402 return;
1404 if (url && html->pointer_url && !strcmp (url, html->pointer_url))
1405 return;
1407 g_free (html->pointer_url);
1408 html->pointer_url = url ? g_strdup (url) : NULL;
1409 g_signal_emit (html, signals[ON_URL], 0, html->pointer_url);
1412 static void
1413 dnd_link_set (GtkWidget *widget,
1414 HTMLObject *o,
1415 gint offset)
1417 if (!html_engine_get_editable (GTK_HTML (widget)->engine)) {
1418 /* printf ("dnd_link_set %p\n", o); */
1420 gtk_drag_source_set (
1421 widget, GDK_BUTTON1_MASK,
1422 drag_source_targets, G_N_ELEMENTS (drag_source_targets),
1423 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
1424 GTK_HTML (widget)->priv->dnd_object = o;
1425 GTK_HTML (widget)->priv->dnd_object_offset = offset;
1429 static void
1430 dnd_link_unset (GtkWidget *widget)
1432 if (!html_engine_get_editable (GTK_HTML (widget)->engine)) {
1433 /* printf ("dnd_link_unset\n"); */
1435 gtk_drag_source_unset (widget);
1436 GTK_HTML (widget)->priv->dnd_object = NULL;
1440 static void
1441 on_object (GtkWidget *widget,
1442 GdkWindow *window,
1443 HTMLObject *obj,
1444 gint offset,
1445 gint x,
1446 gint y)
1448 GtkHTML *html = GTK_HTML (widget);
1450 if (obj) {
1451 gchar *url;
1453 if (gtk_html_get_editable (html)) {
1454 if (HTML_IS_IMAGE (obj)) {
1455 gint ox, oy;
1457 html_object_calc_abs_position (obj, &ox, &oy);
1458 if (ox + obj->width - 5 <= x && oy + obj->descent - 5 <= y) {
1459 gdk_window_set_cursor (window, html->priv->resize_cursor);
1461 return;
1466 url = gtk_html_get_url_object_relative (html, obj,
1467 html_object_get_url (obj, offset));
1468 if (url != NULL) {
1469 set_pointer_url (html, url);
1470 dnd_link_set (widget, obj, offset);
1472 if (html->engine->editable && !html->priv->in_url_test_mode)
1473 gdk_window_set_cursor (window, html->ibeam_cursor);
1474 else {
1475 gdk_window_set_cursor (window, html->hand_cursor);
1477 } else {
1478 set_pointer_url (html, NULL);
1479 dnd_link_unset (widget);
1481 if (html_object_is_text (obj) && html->allow_selection)
1482 gdk_window_set_cursor (window, html->ibeam_cursor);
1483 else
1484 gdk_window_set_cursor (window, NULL);
1487 g_free (url);
1488 } else {
1489 set_pointer_url (html, NULL);
1490 dnd_link_unset (widget);
1492 gdk_window_set_cursor (window, NULL);
1496 #define HTML_DIST(x,y) sqrt(x*x + y*y)
1498 static gint
1499 mouse_change_pos (GtkWidget *widget,
1500 GdkWindow *window,
1501 gint x,
1502 gint y,
1503 gint state)
1505 GtkHTML *html;
1506 HTMLEngine *engine;
1507 HTMLObject *obj;
1508 HTMLType type;
1509 gint offset;
1511 if (!gtk_widget_get_realized (widget))
1512 return FALSE;
1514 html = GTK_HTML (widget);
1515 engine = html->engine;
1516 obj = html_engine_get_object_at (engine, x, y, (guint *) &offset, FALSE);
1518 if ((html->in_selection || html->in_selection_drag) && html->allow_selection) {
1519 GtkAllocation allocation;
1520 gboolean need_scroll;
1522 gtk_widget_get_allocation (widget, &allocation);
1524 if (obj) {
1525 type = HTML_OBJECT_TYPE (obj);
1527 /* FIXME this is broken */
1529 if (type == HTML_TYPE_BUTTON ||
1530 type == HTML_TYPE_CHECKBOX ||
1531 type == HTML_TYPE_EMBEDDED ||
1532 type == HTML_TYPE_HIDDEN ||
1533 type == HTML_TYPE_IMAGEINPUT ||
1534 type == HTML_TYPE_RADIO ||
1535 type == HTML_TYPE_SELECT ||
1536 type == HTML_TYPE_TEXTAREA ||
1537 type == HTML_TYPE_TEXTINPUT) {
1538 return FALSE;
1542 if (HTML_DIST ((x - html->selection_x1), (y - html->selection_y1))
1543 > html_painter_get_space_width (engine->painter, GTK_HTML_FONT_STYLE_SIZE_3, NULL)) {
1544 html->in_selection = TRUE;
1545 html->in_selection_drag = TRUE;
1548 need_scroll = FALSE;
1550 if (x < html->engine->x_offset) {
1551 need_scroll = TRUE;
1552 } else if (x >= allocation.width) {
1553 need_scroll = TRUE;
1556 if (y < html->engine->y_offset) {
1557 need_scroll = TRUE;
1558 } else if (y >= allocation.height) {
1559 need_scroll = TRUE;
1562 if (need_scroll)
1563 setup_scroll_timeout (html);
1564 else
1565 remove_scroll_timeout (html);
1567 /* This will put the mark at the position of the
1568 * previous click. */
1569 if (engine->mark == NULL && engine->editable)
1570 html_engine_set_mark (engine);
1572 html_engine_select_region (engine, html->selection_x1, html->selection_y1, x, y);
1575 if (html->priv->in_object_resize) {
1576 HTMLObject *o = html->priv->resize_object;
1577 gint ox, oy;
1579 html_object_calc_abs_position (o, &ox, &oy);
1580 oy -= o->ascent;
1581 g_assert (HTML_IS_IMAGE (o));
1582 if (x > ox && y > oy) {
1583 gint w, h;
1585 w = x - ox;
1586 h = y - oy;
1587 if (!(state & GDK_SHIFT_MASK)) {
1588 w = MAX (w, h);
1589 h = -1;
1591 html_image_set_size (HTML_IMAGE (o), w, h, FALSE, FALSE);
1593 } else
1594 on_object (widget, window, obj, offset, x, y);
1596 return TRUE;
1599 static const gchar *
1600 skip_host (const gchar *url)
1602 const gchar *host;
1604 host = url;
1605 while (*host && (*host != '/') && (*host != ':'))
1606 host++;
1608 if (*host == ':') {
1609 host++;
1611 if (*host == '/')
1612 host++;
1614 url = host;
1616 if (*host == '/') {
1617 host++;
1619 while (*host && (*host != '/'))
1620 host++;
1622 url = host;
1626 return url;
1629 static gsize
1630 path_len (const gchar *base,
1631 gboolean absolute)
1633 const gchar *last;
1634 const gchar *cur;
1635 const gchar *start;
1637 start = last = skip_host (base);
1638 if (!absolute) {
1639 cur = strrchr (start, '/');
1641 if (cur)
1642 last = cur;
1645 return last - base;
1648 #if 0
1649 gchar *
1650 collapse_path (gchar *url)
1652 gchar *start;
1653 gchar *end;
1654 gchar *cur;
1655 gsize len;
1657 start = skip_host (url);
1659 cur = start;
1660 while ((cur = strstr (cur, "/../"))) {
1661 end = cur + 3;
1663 /* handle the case of a rootlevel /../ specialy */
1664 if (cur == start) {
1665 len = strlen (end);
1666 memmove (cur, end, len + 1);
1669 while (cur > start) {
1670 cur--;
1671 if ((*cur == '/') || (cur == start)) {
1672 len = strlen (end);
1673 memmove (cur, end, len + 1);
1674 break;
1678 return url;
1680 #endif
1682 static gboolean
1683 url_is_absolute (const gchar *url)
1686 * URI Syntactic Components
1688 * The URI syntax is dependent upon the scheme. In general, absolute
1689 * URI are written as follows:
1691 * <scheme>:<scheme-specific-part>
1693 * scheme = alpha *( alpha | digit | "+" | "-" | "." )
1696 if (!url)
1697 return FALSE;
1699 if (!isalpha (*url))
1700 return FALSE;
1701 url++;
1703 while (*url && (isalnum (*url) || *url == '+' || *url == '-' || *url == '.'))
1704 url++;
1706 return *url && *url == ':';
1709 static gchar *
1710 expand_relative (const gchar *base,
1711 const gchar *url)
1713 gchar *new_url = NULL;
1714 gsize base_len, url_len;
1715 gboolean absolute = FALSE;
1717 if (!base || url_is_absolute (url)) {
1719 g_warning ("base = %s url = %s new_url = %s",
1720 base, url, new_url);
1722 return g_strdup (url);
1725 if (*url == '/') {
1726 absolute = TRUE;;
1729 base_len = path_len (base, absolute);
1730 url_len = strlen (url);
1732 new_url = g_malloc (base_len + url_len + 2);
1734 if (base_len) {
1735 memcpy (new_url, base, base_len);
1737 if (base[base_len - 1] != '/')
1738 new_url[base_len++] = '/';
1739 if (absolute)
1740 url++;
1743 memcpy (new_url + base_len, url, url_len);
1744 new_url[base_len + url_len] = '\0';
1747 * g_warning ("base = %s url = %s new_url = %s",
1748 * base, url, new_url);
1750 return new_url;
1753 gchar *
1754 gtk_html_get_url_base_relative (GtkHTML *html,
1755 const gchar *url)
1757 return expand_relative (gtk_html_get_base (html), url);
1760 static gchar *
1761 expand_frame_url (GtkHTML *html,
1762 const gchar *url)
1764 gchar *new_url;
1766 new_url = gtk_html_get_url_base_relative (html, url);
1767 while (html->iframe_parent) {
1768 gchar *expanded;
1770 expanded = gtk_html_get_url_base_relative (GTK_HTML (html->iframe_parent),
1771 new_url);
1772 g_free (new_url);
1773 new_url = expanded;
1775 html = GTK_HTML (html->iframe_parent);
1777 return new_url;
1780 gchar *
1781 gtk_html_get_url_object_relative (GtkHTML *html,
1782 HTMLObject *o,
1783 const gchar *url)
1785 HTMLEngine *e;
1786 HTMLObject *parent;
1788 g_return_val_if_fail (GTK_IS_HTML (html), NULL);
1790 /* start at the top always */
1791 while (html->iframe_parent)
1792 html = GTK_HTML (html->iframe_parent);
1794 parent = o;
1795 while (parent->parent) {
1796 parent = parent->parent;
1797 if ((HTML_OBJECT_TYPE (parent) == HTML_TYPE_FRAME)
1798 || (HTML_OBJECT_TYPE (parent) == HTML_TYPE_IFRAME))
1799 break;
1802 e = html_object_get_engine (parent, html->engine);
1804 if (!e) {
1805 g_warning ("Cannot find object for url");
1806 return NULL;
1810 if (e == html->engine)
1811 g_warning ("engine matches engine");
1813 return url ? expand_frame_url (e->widget, url) : NULL;
1816 static GtkWidget *
1817 shift_to_iframe_parent (GtkWidget *widget,
1818 gint *x,
1819 gint *y)
1821 while (GTK_HTML (widget)->iframe_parent) {
1822 GtkWidget *scrolled_window;
1823 GtkAllocation allocation;
1825 scrolled_window = gtk_widget_get_parent (widget);
1827 g_return_val_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window), widget);
1829 gtk_widget_get_allocation (scrolled_window, &allocation);
1831 if (x)
1832 *x += allocation.x - GTK_HTML (widget)->engine->x_offset;
1833 if (y)
1834 *y += allocation.y - GTK_HTML (widget)->engine->y_offset;
1836 widget = GTK_HTML (widget)->iframe_parent;
1840 return widget;
1843 static gboolean
1844 motion_notify_event (GtkWidget *widget,
1845 GdkEventMotion *event)
1847 GdkWindow *window;
1848 GdkWindow *bin_window;
1849 HTMLEngine *engine;
1850 gint x, y;
1852 g_return_val_if_fail (widget != NULL, 0);
1853 g_return_val_if_fail (GTK_IS_HTML (widget), 0);
1854 g_return_val_if_fail (event != NULL, 0);
1856 /* printf ("motion_notify_event\n"); */
1858 if (GTK_HTML (widget)->priv->dnd_in_progress)
1859 return TRUE;
1861 widget = shift_to_iframe_parent (widget, &x, &y);
1863 window = gtk_widget_get_window (widget);
1864 bin_window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
1866 gdk_window_get_pointer (bin_window, &x, &y, NULL);
1868 if (!mouse_change_pos (widget, window, x, y, event->state))
1869 return FALSE;
1871 engine = GTK_HTML (widget)->engine;
1872 if (GTK_HTML (widget)->in_selection_drag && html_engine_get_editable (engine))
1873 html_engine_jump_at (engine, x, y);
1874 return TRUE;
1877 static gboolean
1878 toplevel_unmap (GtkWidget *widget,
1879 GdkEvent *event,
1880 GtkHTML *html)
1882 html_image_factory_stop_animations (html->engine->image_factory);
1884 return FALSE;
1887 static void
1888 hierarchy_changed (GtkWidget *widget,
1889 GtkWidget *old_toplevel)
1891 GtkWidget *toplevel;
1892 GtkHTMLPrivate *priv = GTK_HTML (widget)->priv;
1894 if (old_toplevel && priv->toplevel_unmap_handler) {
1895 g_signal_handler_disconnect (old_toplevel,
1896 priv->toplevel_unmap_handler);
1897 priv->toplevel_unmap_handler = 0;
1900 toplevel = gtk_widget_get_toplevel (widget);
1902 if (gtk_widget_is_toplevel (toplevel) && priv->toplevel_unmap_handler == 0) {
1903 priv->toplevel_unmap_handler = g_signal_connect (G_OBJECT (toplevel), "unmap-event",
1904 G_CALLBACK (toplevel_unmap), widget);
1908 static gint
1909 visibility_notify_event (GtkWidget *widget,
1910 GdkEventVisibility *event)
1912 if (event->state == GDK_VISIBILITY_FULLY_OBSCURED)
1913 html_image_factory_stop_animations (GTK_HTML (widget)->engine->image_factory);
1914 else
1915 html_image_factory_start_animations (GTK_HTML (widget)->engine->image_factory);
1917 return FALSE;
1920 static gint
1921 button_press_event (GtkWidget *widget,
1922 GdkEventButton *event)
1924 GtkHTML *html;
1925 GtkWidget *orig_widget = widget;
1926 HTMLEngine *engine;
1927 gint value, x, y;
1929 /* printf ("button_press_event\n"); */
1931 x = event->x;
1932 y = event->y;
1934 widget = shift_to_iframe_parent (widget, &x, &y);
1935 html = GTK_HTML (widget);
1936 engine = html->engine;
1938 if (event->button == 1 || ((event->button == 2 || event->button == 3) && html_engine_get_editable (engine))) {
1939 html->priv->is_first_focus = FALSE;
1940 html->priv->skip_update_cursor = TRUE;
1941 html->priv->cursor_moved = FALSE;
1942 gtk_widget_grab_focus (widget);
1945 if (event->type == GDK_BUTTON_PRESS) {
1946 GtkAdjustment *adjustment;
1947 gdouble adj_value;
1948 gdouble lower;
1949 gdouble upper;
1950 gdouble page_size;
1951 gdouble step_increment;
1953 adjustment = gtk_layout_get_vadjustment (GTK_LAYOUT (widget));
1954 adj_value = gtk_adjustment_get_value (adjustment);
1955 lower = gtk_adjustment_get_lower (adjustment);
1956 upper = gtk_adjustment_get_upper (adjustment);
1957 page_size = gtk_adjustment_get_page_size (adjustment);
1958 step_increment = gtk_adjustment_get_step_increment (adjustment);
1960 switch (event->button) {
1961 case 4:
1962 /* Mouse wheel scroll up. */
1963 if (event->state & GDK_CONTROL_MASK)
1964 gtk_html_command (html, "zoom-out");
1965 else {
1966 value = adj_value - step_increment * 3;
1968 if (value < lower)
1969 value = lower;
1971 gtk_adjustment_set_value (adjustment, value);
1973 return TRUE;
1974 case 5:
1975 /* Mouse wheel scroll down. */
1976 if (event->state & GDK_CONTROL_MASK)
1977 gtk_html_command (html, "zoom-in");
1978 else {
1979 value = adj_value + step_increment * 3;
1981 if (value > (upper - page_size))
1982 value = upper - page_size;
1984 gtk_adjustment_set_value (adjustment, value);
1986 return TRUE;
1987 case 2:
1988 if (html_engine_get_editable (engine)) {
1989 gint type;
1990 html_engine_disable_selection (html->engine);
1991 html_engine_jump_at (engine, x, y);
1992 gtk_html_update_styles (html);
1993 html->priv->selection_as_cite = event->state & GDK_SHIFT_MASK;
1994 type = event->state & GDK_CONTROL_MASK ? 1 : 0;
1995 gtk_clipboard_request_contents (
1996 gtk_widget_get_clipboard (GTK_WIDGET (html), GDK_SELECTION_PRIMARY),
1997 gdk_atom_intern (selection_targets[type].target, FALSE),
1998 clipboard_paste_received_cb, html);
1999 return TRUE;
2001 break;
2002 case 1:
2003 html->in_selection_drag = TRUE;
2004 if (html_engine_get_editable (engine)) {
2005 HTMLObject *obj;
2007 obj = html_engine_get_object_at (engine, x, y, NULL, FALSE);
2009 if (obj && HTML_IS_IMAGE (obj)) {
2010 gint ox, oy;
2012 html_object_calc_abs_position (obj, &ox, &oy);
2013 if (ox + obj->width - 5 <= x && oy + obj->descent - 5 <= y) {
2014 html->priv->in_object_resize = TRUE;
2015 html->priv->resize_object = obj;
2016 html->in_selection_drag = FALSE;
2020 if (html->allow_selection && !html->priv->in_object_resize)
2021 if (!(event->state & GDK_SHIFT_MASK)
2022 || (!engine->mark && event->state & GDK_SHIFT_MASK))
2023 html_engine_set_mark (engine);
2024 html_engine_jump_at (engine, x, y);
2025 } else {
2026 HTMLObject *obj;
2027 HTMLEngine *orig_e;
2028 gint offset;
2029 gchar *url = NULL;
2031 orig_e = GTK_HTML (orig_widget)->engine;
2032 obj = html_engine_get_object_at (engine, x, y, (guint *) &offset, FALSE);
2033 if (obj && ((HTML_IS_IMAGE (obj) && HTML_IMAGE (obj)->url && *HTML_IMAGE (obj)->url)
2034 || (HTML_IS_TEXT (obj) && (url = html_object_get_complete_url (obj, offset))))) {
2035 g_free (url);
2036 html_engine_set_focus_object (orig_e, obj, offset);
2037 } else {
2038 html_engine_set_focus_object (orig_e, NULL, 0);
2039 if (orig_e->caret_mode || engine->caret_mode)
2040 html_engine_jump_at (engine, x, y);
2043 if (html->allow_selection && !html->priv->in_object_resize) {
2044 if (event->state & GDK_SHIFT_MASK)
2045 html_engine_select_region (engine,
2046 html->selection_x1, html->selection_y1, x, y);
2047 else {
2048 GdkWindow *bin_window;
2050 bin_window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
2051 html_engine_disable_selection (engine);
2052 if (gdk_pointer_grab (bin_window, FALSE,
2053 (GDK_BUTTON_RELEASE_MASK
2054 | GDK_BUTTON_MOTION_MASK
2055 | GDK_POINTER_MOTION_HINT_MASK),
2056 NULL, NULL, event->time) == 0) {
2057 html->selection_x1 = x;
2058 html->selection_y1 = y;
2063 engine->selection_mode = FALSE;
2064 if (html_engine_get_editable (engine))
2065 gtk_html_update_styles (html);
2066 break;
2067 default:
2068 break;
2070 } else if (event->button == 1 && html->allow_selection) {
2071 if (event->type == GDK_2BUTTON_PRESS) {
2072 html->in_selection_drag = FALSE;
2073 gtk_html_select_word (html);
2074 html->in_selection = TRUE;
2076 else if (event->type == GDK_3BUTTON_PRESS) {
2077 html->in_selection_drag = FALSE;
2078 gtk_html_select_line (html);
2079 html->in_selection = TRUE;
2083 return FALSE;
2086 static gboolean
2087 any_has_cursor_moved (GtkHTML *html)
2089 while (html) {
2090 if (html->priv->cursor_moved)
2091 return TRUE;
2093 html = html->iframe_parent ? GTK_HTML (html->iframe_parent) : NULL;
2096 return FALSE;
2099 static gboolean
2100 any_has_skip_update_cursor (GtkHTML *html)
2102 while (html) {
2103 if (html->priv->skip_update_cursor)
2104 return TRUE;
2106 html = html->iframe_parent ? GTK_HTML (html->iframe_parent) : NULL;
2109 return FALSE;
2112 static gint
2113 button_release_event (GtkWidget *initial_widget,
2114 GdkEventButton *event)
2116 GtkWidget *widget;
2117 GtkHTML *html;
2118 HTMLEngine *engine;
2119 gint x, y;
2120 HTMLObject *focus_object;
2121 gint focus_object_offset;
2123 /* printf ("button_release_event\n"); */
2125 x = event->x;
2126 y = event->y;
2127 widget = shift_to_iframe_parent (initial_widget, &x, &y);
2128 html = GTK_HTML (widget);
2130 remove_scroll_timeout (html);
2131 gtk_grab_remove (widget);
2132 gdk_pointer_ungrab (event->time);
2134 engine = html->engine;
2136 if (html->in_selection && !html->priv->dnd_in_progress) {
2137 html_engine_update_selection_active_state (html->engine, html->priv->event_time);
2138 if (html->in_selection_drag)
2139 html_engine_select_region (engine, html->selection_x1, html->selection_y1,
2140 x, y);
2141 gtk_html_update_styles (html);
2142 update_primary_selection (html);
2143 queue_draw (html);
2146 if (event->button == 1) {
2148 if (html->in_selection_drag && html_engine_get_editable (engine))
2149 html_engine_jump_at (engine, x, y);
2151 html->in_selection_drag = FALSE;
2153 if (!html->priv->dnd_in_progress
2154 && html->pointer_url != NULL && !html->in_selection
2155 && (!gtk_html_get_editable (html) || html->priv->in_url_test_mode)) {
2156 g_signal_emit (widget, signals[LINK_CLICKED], 0, html->pointer_url);
2157 focus_object = html_engine_get_focus_object (html->engine, &focus_object_offset);
2158 if (HTML_IS_TEXT (focus_object)) {
2159 html_text_set_link_visited (HTML_TEXT (focus_object), focus_object_offset, html->engine, TRUE);
2162 if (html->priv->in_url_test_mode) {
2163 GValue arg;
2164 guint offset;
2166 memset (&arg, 0, sizeof (GValue));
2167 g_value_init (&arg, G_TYPE_STRING);
2168 g_value_set_string (&arg, html->pointer_url);
2170 gtk_html_editor_event (html, GTK_HTML_EDITOR_EVENT_LINK_CLICKED, &arg);
2172 g_value_unset (&arg);
2174 focus_object = html_engine_get_object_at (html->engine, x, y, &offset, TRUE);
2175 if (HTML_IS_TEXT (focus_object))
2176 html_text_set_link_visited (HTML_TEXT (focus_object), (gint) offset, html->engine, TRUE);
2179 html->priv->skip_update_cursor = TRUE;
2183 html->in_selection = FALSE;
2184 html->priv->in_object_resize = FALSE;
2186 return TRUE;
2189 static void
2190 gtk_html_keymap_direction_changed (GdkKeymap *keymap,
2191 GtkHTML *html)
2193 if (html_engine_get_editable (html->engine)) {
2194 html_engine_edit_set_direction (html->engine, html_text_direction_pango_to_html (gdk_keymap_get_direction (keymap)));
2198 static gboolean
2199 goto_caret_anchor (GtkHTML *html)
2201 gint x = 0, y = 0;
2203 g_return_val_if_fail (html != NULL, FALSE);
2204 g_return_val_if_fail (GTK_IS_HTML (html), FALSE);
2206 if (!html->priv->is_first_focus)
2207 return FALSE;
2209 html->priv->is_first_focus = FALSE;
2211 if (html->priv->caret_first_focus_anchor && html_object_find_anchor (html->engine->clue, html->priv->caret_first_focus_anchor, &x, &y)) {
2212 GtkAdjustment *adjustment;
2213 GtkLayout *layout;
2214 gdouble page_size;
2215 gdouble value;
2217 html_engine_jump_at (html->engine, x, y);
2219 layout = GTK_LAYOUT (html->engine->widget);
2220 adjustment = gtk_layout_get_vadjustment (layout);
2221 page_size = gtk_adjustment_get_page_size (adjustment);
2222 value = gtk_adjustment_get_value (adjustment);
2224 /* scroll to the position on screen if not visible */
2225 if (y < value || y > value + page_size)
2226 gtk_adjustment_set_value (adjustment, y);
2228 return TRUE;
2231 return FALSE;
2234 static gint
2235 focus_in_event (GtkWidget *widget,
2236 GdkEventFocus *event)
2238 GtkHTML *html = GTK_HTML (widget);
2240 /* printf ("focus in\n"); */
2241 if (!html->iframe_parent) {
2242 if (html->engine->cursor && html->engine->cursor->position == 0 && html->engine->caret_mode)
2243 goto_caret_anchor (html);
2244 html_engine_set_focus (html->engine, TRUE);
2245 } else {
2246 GtkWidget *window = gtk_widget_get_ancestor (widget, gtk_window_get_type ());
2247 if (window)
2248 gtk_window_set_focus (GTK_WINDOW (window), html->iframe_parent);
2251 html->priv->need_im_reset = TRUE;
2252 gtk_im_context_focus_in (html->priv->im_context);
2254 gtk_html_keymap_direction_changed (gdk_keymap_get_for_display (gtk_widget_get_display (widget)),
2255 html);
2256 g_signal_connect (gdk_keymap_get_for_display (gtk_widget_get_display (widget)),
2257 "direction_changed", G_CALLBACK (gtk_html_keymap_direction_changed), html);
2259 return FALSE;
2262 static gint
2263 focus_out_event (GtkWidget *widget,
2264 GdkEventFocus *event)
2266 GtkHTML *html = GTK_HTML (widget);
2268 html_painter_set_focus (html->engine->painter, FALSE);
2269 html_engine_redraw_selection (html->engine);
2270 /* printf ("focus out\n"); */
2271 if (!html->iframe_parent) {
2272 html_engine_set_focus (html->engine, FALSE);
2275 html->priv->need_im_reset = TRUE;
2276 gtk_im_context_focus_out (html->priv->im_context);
2278 g_signal_handlers_disconnect_by_func (gdk_keymap_get_for_display (gtk_widget_get_display (widget)),
2279 gtk_html_keymap_direction_changed, html);
2281 return FALSE;
2284 static gint
2285 enter_notify_event (GtkWidget *widget,
2286 GdkEventCrossing *event)
2288 GdkWindow *window;
2289 gint x, y;
2291 x = event->x;
2292 y = event->y;
2293 window = gtk_widget_get_window (widget);
2294 widget = shift_to_iframe_parent (widget, &x, &y);
2296 mouse_change_pos (widget, window, x, y, event->state);
2298 return TRUE;
2301 /* X11 selection support. */
2303 static const gchar *
2304 utf16_order (gboolean swap)
2306 gboolean be;
2309 * FIXME owen tells me this logic probably isn't needed
2310 * because smart iconvs will notice the BOM and act accordingly
2311 * I don't have as much faith in the various iconv implementations
2312 * so I am leaving it in for now
2315 be = G_BYTE_ORDER == G_BIG_ENDIAN;
2316 be = swap ? be : !be;
2318 if (be)
2319 return "UTF-16BE";
2320 else
2321 return "UTF-16LE";
2325 static gchar *
2326 get_selection_string (GtkHTML *html,
2327 gint *len,
2328 gboolean selection,
2329 gboolean primary,
2330 gboolean html_format)
2332 HTMLObject *selection_object = NULL;
2333 gchar *selection_string = NULL;
2334 gboolean free_object = FALSE;
2336 if (selection && html_engine_is_selection_active (html->engine)) {
2337 guint selection_len;
2338 html_engine_copy_object (html->engine, &selection_object, &selection_len);
2339 free_object = TRUE;
2340 } else {
2341 if (primary) {
2342 if (html->engine->primary) {
2343 selection_object = html->engine->primary;
2345 } else /* CLIPBOARD */ {
2346 if (html->engine->clipboard) {
2347 selection_object = html->engine->clipboard;
2352 if (html_format) {
2353 if (selection_object) {
2354 HTMLEngineSaveState *state;
2355 GString *buffer;
2357 state = html_engine_save_buffer_new (html->engine, TRUE);
2358 buffer = (GString *) state->user_data;
2360 html_object_save (selection_object, state);
2361 g_string_append_unichar (buffer, 0x0000);
2363 if (len)
2364 *len = buffer->len;
2365 selection_string = html_engine_save_buffer_free (state, FALSE);
2367 } else {
2368 if (selection_object)
2369 selection_string = html_object_get_selection_string (selection_object, html->engine);
2370 if (len && selection_string)
2371 *len = strlen (selection_string);
2374 if (selection_object && free_object)
2375 html_object_destroy (selection_object);
2377 return selection_string;
2380 /* returned pointer should be freed with g_free */
2381 gchar *
2382 gtk_html_get_selection_html (GtkHTML *html,
2383 gint *len)
2385 return get_selection_string (html, len, TRUE, FALSE, TRUE);
2388 /* returned pointer should be freed with g_free */
2389 gchar *
2390 gtk_html_get_selection_plain_text (GtkHTML *html,
2391 gint *len)
2393 return get_selection_string (html, len, TRUE, FALSE, FALSE);
2396 static gchar *
2397 utf16_to_utf8_with_bom_check (guchar *data,
2398 guint len)
2400 const gchar *fromcode = NULL;
2401 GError *error = NULL;
2402 guint16 c;
2403 gsize read_len;
2404 gsize written_len;
2405 gchar *utf8_ret;
2408 * Unicode Techinical Report 20
2409 * (http://www.unicode.org/unicode/reports/tr20/) says to treat an
2410 * initial 0xfeff (ZWNBSP) as a byte order indicator so that is
2411 * what we do. If there is no indicator assume it is in the default
2412 * order
2415 memcpy (&c, data, 2);
2417 switch (c) {
2418 case 0xfeff:
2419 case 0xfffe:
2420 fromcode = utf16_order (c == 0xfeff);
2421 data += 2;
2422 len -= 2;
2423 break;
2424 default:
2425 fromcode = "UTF-16";
2426 break;
2429 utf8_ret = g_convert ((gchar *) data,
2430 len,
2431 "UTF-8",
2432 fromcode,
2433 &read_len,
2434 &written_len,
2435 &error);
2437 if (error) {
2438 g_warning ("g_convert error: %s\n", error->message);
2439 g_error_free (error);
2441 return (utf8_ret);
2444 /* removes useless leading BOM from UTF-8 string if present */
2445 static gchar *
2446 utf8_filter_out_bom (gchar *str)
2448 if (!str)
2449 return NULL;
2451 /* input is always valid, NUL-terminated UTF-8 sequence, we don't need
2452 * to validated it again */
2453 if (g_utf8_get_char (str) == 0xfeff) {
2454 gchar *out = g_strdup (g_utf8_next_char (str));
2455 g_free (str);
2456 return out;
2459 return str;
2462 /* Initialization. */
2463 static void
2464 set_focus_child (GtkContainer *containter,
2465 GtkWidget *w)
2467 HTMLObject *o = NULL;
2469 while (w && !(o = g_object_get_data (G_OBJECT (w), "embeddedelement")))
2470 w = gtk_widget_get_parent (w);
2472 if (o && !html_object_is_frame (o))
2473 html_engine_set_focus_object (GTK_HTML (containter)->engine, o, 0);
2475 (*GTK_CONTAINER_CLASS (parent_class)->set_focus_child) (containter, w);
2478 static gboolean
2479 focus (GtkWidget *w,
2480 GtkDirectionType direction)
2482 HTMLEngine *e = GTK_HTML (w)->engine;
2484 if (html_engine_get_editable (e)) {
2485 gboolean rv;
2487 rv = (*GTK_WIDGET_CLASS (parent_class)->focus) (w, direction);
2488 html_engine_set_focus (GTK_HTML (w)->engine, rv);
2489 return rv;
2492 /* Reset selection. */
2493 if (e->shift_selection || e->mark) {
2494 html_engine_disable_selection (e);
2495 html_engine_edit_selection_updater_schedule (e->selection_updater);
2496 e->shift_selection = FALSE;
2499 if (!gtk_widget_has_focus (w) && e->caret_mode) {
2500 if (goto_caret_anchor (GTK_HTML (w))) {
2501 gtk_widget_grab_focus (w);
2503 update_primary_selection (GTK_HTML (w));
2504 g_signal_emit (GTK_HTML (w), signals[CURSOR_CHANGED], 0);
2506 return TRUE;
2510 if (((e->focus_object && !(gtk_widget_has_focus (w))) || html_engine_focus (e, direction)) && e->focus_object) {
2511 gint offset;
2512 HTMLObject *obj = html_engine_get_focus_object (e, &offset);
2513 gint x1, y1, x2, y2, xo, yo;
2515 xo = e->x_offset;
2516 yo = e->y_offset;
2518 if (HTML_IS_TEXT (obj)) {
2519 if (!html_text_get_link_rectangle (HTML_TEXT (obj), e->painter, offset, &x1, &y1, &x2, &y2))
2520 return FALSE;
2521 } else {
2522 html_object_calc_abs_position (obj, &x1, &y1);
2523 y2 = y1 + obj->descent;
2524 x2 = x1 + obj->width;
2525 y1 -= obj->ascent;
2528 /* printf ("child pos: %d,%d x %d,%d\n", x1, y1, x2, y2); */
2530 if (x2 > e->x_offset + e->width)
2531 e->x_offset = x2 - e->width;
2532 if (x1 < e->x_offset)
2533 e->x_offset = x1;
2534 if (e->width > 2 * RIGHT_BORDER && e->x_offset == x2 - e->width)
2535 e->x_offset = MIN (x2 - e->width + RIGHT_BORDER + 1,
2536 html_engine_get_doc_width (e) - e->width);
2537 if (e->width > 2 * LEFT_BORDER && e->x_offset >= x1)
2538 e->x_offset = MAX (x1 - LEFT_BORDER, 0);
2540 if (y2 >= e->y_offset + e->height)
2541 e->y_offset = y2 - e->height + 1;
2542 if (y1 < e->y_offset)
2543 e->y_offset = y1;
2544 if (e->height > 2 * BOTTOM_BORDER && e->y_offset == y2 - e->height + 1)
2545 e->y_offset = MIN (y2 - e->height + BOTTOM_BORDER + 1,
2546 html_engine_get_doc_height (e) - e->height);
2547 if (e->height > 2 * TOP_BORDER && e->y_offset >= y1)
2548 e->y_offset = MAX (y1 - TOP_BORDER, 0);
2550 if (e->x_offset != xo) {
2551 GtkAdjustment *adjustment;
2553 adjustment = gtk_layout_get_hadjustment (GTK_LAYOUT (w));
2554 gtk_adjustment_set_value (adjustment, (gfloat) e->x_offset);
2556 if (e->y_offset != yo) {
2557 GtkAdjustment *adjustment;
2559 adjustment = gtk_layout_get_vadjustment (GTK_LAYOUT (w));
2560 gtk_adjustment_set_value (adjustment, (gfloat) e->y_offset);
2562 /* printf ("engine pos: %d,%d x %d,%d\n",
2563 * e->x_offset, e->y_offset, e->x_offset + e->width, e->y_offset + e->height); */
2565 if (!gtk_widget_has_focus (w) && !html_object_is_embedded (obj))
2566 gtk_widget_grab_focus (w);
2567 if (e->caret_mode) {
2568 html_engine_jump_to_object (e, obj, offset);
2571 update_primary_selection (GTK_HTML (w));
2572 g_signal_emit (GTK_HTML (w), signals[CURSOR_CHANGED], 0);
2574 return TRUE;
2577 return FALSE;
2580 /* dnd begin */
2582 static void
2583 drag_begin (GtkWidget *widget,
2584 GdkDragContext *context)
2586 HTMLInterval *i;
2587 HTMLObject *o;
2589 /* printf ("drag_begin\n"); */
2590 GTK_HTML (widget)->priv->dnd_real_object = o = GTK_HTML (widget)->priv->dnd_object;
2591 GTK_HTML (widget)->priv->dnd_real_object_offset = GTK_HTML (widget)->priv->dnd_object_offset;
2592 GTK_HTML (widget)->priv->dnd_in_progress = TRUE;
2594 i = html_interval_new (o, o, 0, html_object_get_length (o));
2595 html_engine_select_interval (GTK_HTML (widget)->engine, i);
2598 static void
2599 drag_end (GtkWidget *widget,
2600 GdkDragContext *context)
2602 GtkHTML *html;
2604 /* printf ("drag_end\n"); */
2605 g_return_if_fail (GTK_IS_HTML (widget));
2607 html = GTK_HTML (widget);
2608 if (html->priv)
2609 html->priv->dnd_in_progress = FALSE;
2612 static void
2613 drag_data_get (GtkWidget *widget,
2614 GdkDragContext *context,
2615 GtkSelectionData *selection_data,
2616 guint info,
2617 guint time)
2619 /* printf ("drag_data_get\n"); */
2620 switch (info) {
2621 case DND_TARGET_TYPE_MOZILLA_URL:
2622 case DND_TARGET_TYPE_TEXT_URI_LIST:
2623 /* printf ("\ttext/uri-list\n"); */
2624 case DND_TARGET_TYPE_TEXT_HTML:
2625 case DND_TARGET_TYPE_TEXT_PLAIN:
2626 case DND_TARGET_TYPE_UTF8_STRING:
2627 case DND_TARGET_TYPE_STRING: {
2628 HTMLObject *obj = GTK_HTML (widget)->priv->dnd_real_object;
2629 gint offset = GTK_HTML (widget)->priv->dnd_real_object_offset;
2630 const gchar *url, *target;
2631 gchar *complete_url;
2633 /* printf ("\ttext/plain\n"); */
2634 if (obj) {
2635 /* printf ("obj %p\n", obj); */
2636 url = html_object_get_url (obj, offset);
2637 target = html_object_get_target (obj, offset);
2638 if (url && *url) {
2640 complete_url = g_strconcat (url, target && *target ? "#" : NULL, target, NULL);
2642 if (info == DND_TARGET_TYPE_MOZILLA_URL) {
2643 /* MOZ_URL is in UTF-16 but in format 8. BROKEN!
2645 * The data contains the URL, a \n, then the
2646 * title of the web page.
2648 gchar *utf16;
2649 gchar *utf8;
2650 gsize written_len;
2651 GdkAtom atom;
2653 if (HTML_IS_TEXT (obj)) {
2654 Link *link = html_text_get_link_at_offset (HTML_TEXT (obj), offset);
2655 gchar *text;
2657 g_return_if_fail (link);
2658 text = g_strndup (HTML_TEXT (obj)->text + link->start_index, link->end_index - link->start_index);
2659 utf8 = g_strconcat (complete_url, "\n", text, NULL);
2660 } else
2661 utf8 = g_strconcat (complete_url, "\n", complete_url, NULL);
2663 utf16 = g_convert (utf8, strlen (utf8), "UTF-16", "UTF-8", NULL, &written_len, NULL);
2664 atom = gtk_selection_data_get_target (selection_data);
2665 gtk_selection_data_set (selection_data, atom, 8,
2666 (guchar *) utf16, written_len);
2667 g_free (utf8);
2668 g_free (complete_url);
2669 GTK_HTML (widget)->priv->dnd_url = utf16;
2670 } else {
2671 GdkAtom atom;
2673 atom = gtk_selection_data_get_target (selection_data);
2674 gtk_selection_data_set (selection_data, atom, 8,
2675 (guchar *) complete_url, strlen (complete_url));
2676 /* printf ("complete URL %s\n", complete_url); */
2677 GTK_HTML (widget)->priv->dnd_url = complete_url;
2682 break;
2686 static void
2687 drag_data_delete (GtkWidget *widget,
2688 GdkDragContext *context)
2690 g_free (GTK_HTML (widget)->priv->dnd_url);
2691 GTK_HTML (widget)->priv->dnd_url = NULL;
2694 static gchar *
2695 next_uri (guchar **uri_list,
2696 gint *len,
2697 gint *list_len)
2699 guchar *uri, *begin;
2701 begin = *uri_list;
2702 *len = 0;
2703 while (**uri_list && **uri_list != '\n' && **uri_list != '\r' && *list_len) {
2704 (*uri_list) ++;
2705 (*len) ++;
2706 (*list_len) --;
2709 uri = (guchar *) g_strndup ((gchar *) begin, *len);
2711 while ((!**uri_list || **uri_list == '\n' || **uri_list == '\r') && *list_len) {
2712 (*uri_list) ++;
2713 (*list_len) --;
2716 return (gchar *) uri;
2719 static HTMLObject *
2720 new_img_obj_from_uri (HTMLEngine *e,
2721 gchar *uri,
2722 gchar *title,
2723 gint len)
2725 if (!strncmp (uri, "file:", 5)) {
2726 if (!HTML_IS_PLAIN_PAINTER (e->painter)) {
2727 GdkPixbuf *pixbuf = NULL;
2728 gchar *img_path = g_filename_from_uri (uri, NULL, NULL);
2729 if (img_path) {
2730 pixbuf = gdk_pixbuf_new_from_file (img_path, NULL);
2731 g_free (img_path);
2733 if (pixbuf) {
2734 g_object_unref (pixbuf);
2735 return html_image_new (html_engine_get_image_factory (e), uri,
2736 NULL, NULL, -1, -1, FALSE, FALSE, 0,
2737 html_colorset_get_color (e->settings->color_set, HTMLTextColor),
2738 HTML_VALIGN_BOTTOM, TRUE);
2742 return NULL;
2745 static void
2746 move_before_paste (GtkWidget *widget,
2747 gint x,
2748 gint y)
2750 HTMLEngine *engine = GTK_HTML (widget)->engine;
2752 if (html_engine_is_selection_active (engine)) {
2753 HTMLObject *obj;
2754 guint offset;
2756 obj = html_engine_get_object_at (engine, x, y, &offset, FALSE);
2757 if (!html_engine_point_in_selection (engine, obj, offset)) {
2758 html_engine_disable_selection (engine);
2759 html_engine_edit_selection_updater_update_now (engine->selection_updater);
2762 if (!html_engine_is_selection_active (engine)) {
2764 html_engine_jump_at (engine, x, y);
2765 gtk_html_update_styles (GTK_HTML (widget));
2769 static void
2770 drag_data_received (GtkWidget *widget,
2771 GdkDragContext *context,
2772 gint x,
2773 gint y,
2774 GtkSelectionData *selection_data,
2775 guint info,
2776 guint time)
2778 HTMLEngine *engine = GTK_HTML (widget)->engine;
2779 GdkWindow *bin_window;
2780 gboolean pasted = FALSE;
2781 const guchar *data;
2782 gint length;
2784 /* printf ("drag data received at %d,%d\n", x, y); */
2786 data = gtk_selection_data_get_data (selection_data);
2787 length = gtk_selection_data_get_length (selection_data);
2789 if (!data || length < 0 || !html_engine_get_editable (engine))
2790 return;
2792 bin_window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
2793 gdk_window_get_pointer (bin_window, &x, &y, NULL);
2794 move_before_paste (widget, x, y);
2796 switch (info) {
2797 case DND_TARGET_TYPE_TEXT_PLAIN:
2798 case DND_TARGET_TYPE_UTF8_STRING:
2799 case DND_TARGET_TYPE_STRING:
2800 case DND_TARGET_TYPE_TEXT_HTML:
2801 clipboard_paste_received_cb (
2802 gtk_widget_get_clipboard (GTK_WIDGET (widget), GDK_SELECTION_PRIMARY),
2803 selection_data, widget);
2804 pasted = TRUE;
2805 break;
2806 case DND_TARGET_TYPE_MOZILLA_URL :
2807 break;
2808 case DND_TARGET_TYPE_TEXT_URI_LIST:
2809 if (!HTML_IS_PLAIN_PAINTER (engine->painter)) {
2810 HTMLObject *obj;
2811 gint list_len, len;
2812 gchar *uri;
2813 html_undo_level_begin (engine->undo, "Dropped URI(s)", "Remove Dropped URI(s)");
2814 list_len = length;
2815 do {
2816 uri = next_uri ((guchar **) &data, &len, &list_len);
2817 obj = new_img_obj_from_uri (engine, uri, NULL, -1);
2818 if (obj) {
2819 html_engine_paste_object (engine, obj, html_object_get_length (obj));
2820 pasted = TRUE;
2822 } while (list_len);
2823 html_undo_level_end (engine->undo, engine);
2825 break;
2827 gtk_drag_finish (context, pasted, FALSE, time);
2830 static gboolean
2831 drag_motion (GtkWidget *widget,
2832 GdkDragContext *context,
2833 gint x,
2834 gint y,
2835 guint time)
2837 GdkWindow *window;
2838 GdkWindow *bin_window;
2840 if (!gtk_html_get_editable (GTK_HTML (widget)))
2841 return FALSE;
2843 window = gtk_widget_get_window (widget);
2844 bin_window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
2845 gdk_window_get_pointer (bin_window, &x, &y, NULL);
2847 html_engine_disable_selection (GTK_HTML (widget)->engine);
2848 html_engine_jump_at (GTK_HTML (widget)->engine, x, y);
2849 html_engine_show_cursor (GTK_HTML (widget)->engine);
2851 mouse_change_pos (widget, window, x, y, 0);
2853 return TRUE;
2856 /* dnd end */
2858 static void
2859 settings_key_theme_changed (GSettings *settings,
2860 const gchar *key,
2861 GtkHTMLClass *html_class)
2863 gchar *key_theme;
2865 key_theme = g_settings_get_string (settings, key);
2866 html_class->use_emacs_bindings = (g_strcmp0 (key_theme, "Emacs") == 0);
2867 g_free (key_theme);
2870 static void
2871 settings_monospace_font_name_changed (GSettings *settings,
2872 const gchar *key,
2873 GtkHTML *html)
2875 if (html->engine != NULL && html->engine->painter != NULL) {
2876 gtk_html_set_fonts (html, html->engine->painter);
2877 html_engine_refresh_fonts (html->engine);
2881 static void
2882 settings_cursor_blink_changed (GSettings *settings,
2883 const gchar *key,
2884 GtkHTMLClass *html_class)
2886 gint blink_time = 0;
2888 if (g_settings_get_boolean (settings, "cursor-blink"))
2889 blink_time = g_settings_get_int (settings, "cursor-blink-time");
2891 html_engine_set_cursor_blink_timeout (blink_time / 2);
2894 static void
2895 gtk_html_direction_changed (GtkWidget *widget,
2896 GtkTextDirection previous_dir)
2898 GtkHTML *html = GTK_HTML (widget);
2900 if (html->engine->clue) {
2901 HTMLDirection old_direction = html_object_get_direction (html->engine->clue);
2903 switch (gtk_widget_get_direction (widget)) {
2904 case GTK_TEXT_DIR_NONE:
2905 HTML_CLUEV (html->engine->clue)->dir = HTML_DIRECTION_DERIVED;
2906 break;
2907 case GTK_TEXT_DIR_LTR:
2908 HTML_CLUEV (html->engine->clue)->dir = HTML_DIRECTION_LTR;
2909 break;
2910 case GTK_TEXT_DIR_RTL:
2911 HTML_CLUEV (html->engine->clue)->dir = HTML_DIRECTION_RTL;
2912 break;
2915 if (old_direction != html_object_get_direction (html->engine->clue))
2916 html_engine_schedule_update (html->engine);
2919 GTK_WIDGET_CLASS (parent_class)->direction_changed (widget, previous_dir);
2922 static void
2923 gtk_html_class_init (GtkHTMLClass *klass)
2925 GObjectClass *object_class;
2926 GtkHTMLClass *html_class;
2927 GtkWidgetClass *widget_class;
2928 GtkContainerClass *container_class;
2929 gchar *filename;
2930 GSettings *settings;
2932 g_type_class_add_private (klass, sizeof (GtkHTMLPrivate));
2934 html_class = (GtkHTMLClass *) klass;
2935 object_class = (GObjectClass *) klass;
2936 widget_class = (GtkWidgetClass *) klass;
2937 container_class = (GtkContainerClass *) klass;
2939 parent_class = g_type_class_peek_parent (klass);
2941 signals[TITLE_CHANGED] =
2942 g_signal_new ("title_changed",
2943 G_TYPE_FROM_CLASS (object_class),
2944 G_SIGNAL_RUN_FIRST,
2945 G_STRUCT_OFFSET (GtkHTMLClass, title_changed),
2946 NULL, NULL,
2947 g_cclosure_marshal_VOID__STRING,
2948 G_TYPE_NONE, 1,
2949 G_TYPE_STRING);
2950 signals[URL_REQUESTED] =
2951 g_signal_new ("url_requested",
2952 G_TYPE_FROM_CLASS (object_class),
2953 G_SIGNAL_RUN_LAST,
2954 G_STRUCT_OFFSET (GtkHTMLClass, url_requested),
2955 NULL, NULL,
2956 html_g_cclosure_marshal_VOID__STRING_POINTER,
2957 G_TYPE_NONE, 2,
2958 G_TYPE_STRING,
2959 G_TYPE_POINTER);
2960 signals[LOAD_DONE] =
2961 g_signal_new ("load_done",
2962 G_TYPE_FROM_CLASS (object_class),
2963 G_SIGNAL_RUN_FIRST,
2964 G_STRUCT_OFFSET (GtkHTMLClass, load_done),
2965 NULL, NULL,
2966 g_cclosure_marshal_VOID__VOID,
2967 G_TYPE_NONE, 0);
2968 signals[LINK_CLICKED] =
2969 g_signal_new ("link_clicked",
2970 G_TYPE_FROM_CLASS (object_class),
2971 G_SIGNAL_RUN_FIRST,
2972 G_STRUCT_OFFSET (GtkHTMLClass, link_clicked),
2973 NULL, NULL,
2974 g_cclosure_marshal_VOID__STRING,
2975 G_TYPE_NONE, 1,
2976 G_TYPE_STRING);
2977 signals[SET_BASE] =
2978 g_signal_new ("set_base",
2979 G_TYPE_FROM_CLASS (object_class),
2980 G_SIGNAL_RUN_FIRST,
2981 G_STRUCT_OFFSET (GtkHTMLClass, set_base),
2982 NULL, NULL,
2983 g_cclosure_marshal_VOID__STRING,
2984 G_TYPE_NONE, 1,
2985 G_TYPE_STRING);
2986 signals[SET_BASE_TARGET] =
2987 g_signal_new ("set_base_target",
2988 G_TYPE_FROM_CLASS (object_class),
2989 G_SIGNAL_RUN_FIRST,
2990 G_STRUCT_OFFSET (GtkHTMLClass, set_base_target),
2991 NULL, NULL,
2992 g_cclosure_marshal_VOID__STRING,
2993 G_TYPE_NONE, 1,
2994 G_TYPE_STRING);
2996 signals[ON_URL] =
2997 g_signal_new ("on_url",
2998 G_TYPE_FROM_CLASS (object_class),
2999 G_SIGNAL_RUN_FIRST,
3000 G_STRUCT_OFFSET (GtkHTMLClass, on_url),
3001 NULL, NULL,
3002 g_cclosure_marshal_VOID__STRING,
3003 G_TYPE_NONE, 1,
3004 G_TYPE_STRING);
3006 signals[REDIRECT] =
3007 g_signal_new ("redirect",
3008 G_TYPE_FROM_CLASS (object_class),
3009 G_SIGNAL_RUN_FIRST,
3010 G_STRUCT_OFFSET (GtkHTMLClass, redirect),
3011 NULL, NULL,
3012 html_g_cclosure_marshal_VOID__POINTER_INT,
3013 G_TYPE_NONE, 2,
3014 G_TYPE_STRING,
3015 G_TYPE_INT);
3017 signals[SUBMIT] =
3018 g_signal_new ("submit",
3019 G_TYPE_FROM_CLASS (object_class),
3020 G_SIGNAL_RUN_FIRST,
3021 G_STRUCT_OFFSET (GtkHTMLClass, submit),
3022 NULL, NULL,
3023 html_g_cclosure_marshal_VOID__STRING_STRING_STRING,
3024 G_TYPE_NONE, 3,
3025 G_TYPE_STRING,
3026 G_TYPE_STRING,
3027 G_TYPE_STRING);
3029 signals[OBJECT_REQUESTED] =
3030 g_signal_new ("object_requested",
3031 G_TYPE_FROM_CLASS (object_class),
3032 G_SIGNAL_RUN_LAST,
3033 G_STRUCT_OFFSET (GtkHTMLClass, object_requested),
3034 NULL, NULL,
3035 html_g_cclosure_marshal_BOOL__OBJECT,
3036 G_TYPE_BOOLEAN, 1,
3037 G_TYPE_OBJECT);
3039 signals[CURRENT_PARAGRAPH_STYLE_CHANGED] =
3040 g_signal_new ("current_paragraph_style_changed",
3041 G_TYPE_FROM_CLASS (object_class),
3042 G_SIGNAL_RUN_FIRST,
3043 G_STRUCT_OFFSET (GtkHTMLClass, current_paragraph_style_changed),
3044 NULL, NULL,
3045 g_cclosure_marshal_VOID__INT,
3046 G_TYPE_NONE, 1,
3047 G_TYPE_INT);
3049 signals[CURRENT_PARAGRAPH_INDENTATION_CHANGED] =
3050 g_signal_new ("current_paragraph_indentation_changed",
3051 G_TYPE_FROM_CLASS (object_class),
3052 G_SIGNAL_RUN_FIRST,
3053 G_STRUCT_OFFSET (GtkHTMLClass, current_paragraph_indentation_changed),
3054 NULL, NULL,
3055 g_cclosure_marshal_VOID__INT,
3056 G_TYPE_NONE, 1,
3057 G_TYPE_INT);
3059 signals[CURRENT_PARAGRAPH_ALIGNMENT_CHANGED] =
3060 g_signal_new ("current_paragraph_alignment_changed",
3061 G_TYPE_FROM_CLASS (object_class),
3062 G_SIGNAL_RUN_FIRST,
3063 G_STRUCT_OFFSET (GtkHTMLClass, current_paragraph_alignment_changed),
3064 NULL, NULL,
3065 g_cclosure_marshal_VOID__INT,
3066 G_TYPE_NONE, 1,
3067 G_TYPE_INT);
3069 signals[INSERTION_FONT_STYLE_CHANGED] =
3070 g_signal_new ("insertion_font_style_changed",
3071 G_TYPE_FROM_CLASS (object_class),
3072 G_SIGNAL_RUN_FIRST,
3073 G_STRUCT_OFFSET (GtkHTMLClass, insertion_font_style_changed),
3074 NULL, NULL,
3075 g_cclosure_marshal_VOID__INT,
3076 G_TYPE_NONE, 1,
3077 G_TYPE_INT);
3079 signals[INSERTION_COLOR_CHANGED] =
3080 g_signal_new ("insertion_color_changed",
3081 G_TYPE_FROM_CLASS (object_class),
3082 G_SIGNAL_RUN_FIRST,
3083 G_STRUCT_OFFSET (GtkHTMLClass, insertion_color_changed),
3084 NULL, NULL,
3085 g_cclosure_marshal_VOID__POINTER,
3086 G_TYPE_NONE, 1,
3087 G_TYPE_POINTER);
3089 signals[SIZE_CHANGED] =
3090 g_signal_new ("size_changed",
3091 G_TYPE_FROM_CLASS (object_class),
3092 G_SIGNAL_RUN_FIRST,
3093 G_STRUCT_OFFSET (GtkHTMLClass, size_changed),
3094 NULL, NULL,
3095 g_cclosure_marshal_VOID__VOID,
3096 G_TYPE_NONE, 0);
3097 signals[IFRAME_CREATED] =
3098 g_signal_new ("iframe_created",
3099 G_TYPE_FROM_CLASS (object_class),
3100 G_SIGNAL_RUN_FIRST,
3101 G_STRUCT_OFFSET (GtkHTMLClass, iframe_created),
3102 NULL, NULL,
3103 g_cclosure_marshal_VOID__OBJECT,
3104 G_TYPE_NONE, 1,
3105 GTK_TYPE_HTML);
3107 signals[SCROLL] =
3108 g_signal_new ("scroll",
3109 G_TYPE_FROM_CLASS (object_class),
3110 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3111 G_STRUCT_OFFSET (GtkHTMLClass, scroll),
3112 NULL, NULL,
3113 html_g_cclosure_marshal_VOID__ENUM_ENUM_FLOAT,
3114 G_TYPE_NONE, 3,
3115 GTK_TYPE_ORIENTATION,
3116 GTK_TYPE_SCROLL_TYPE, G_TYPE_FLOAT);
3118 signals[CURSOR_MOVE] =
3119 g_signal_new ("cursor_move",
3120 G_TYPE_FROM_CLASS (object_class),
3121 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3122 G_STRUCT_OFFSET (GtkHTMLClass, cursor_move),
3123 NULL, NULL,
3124 html_g_cclosure_marshal_VOID__ENUM_ENUM,
3125 G_TYPE_NONE, 2, GTK_TYPE_DIRECTION_TYPE, GTK_TYPE_HTML_CURSOR_SKIP);
3127 signals[COMMAND] =
3128 g_signal_new ("command",
3129 G_TYPE_FROM_CLASS (object_class),
3130 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3131 G_STRUCT_OFFSET (GtkHTMLClass, command),
3132 NULL, NULL,
3133 html_g_cclosure_marshal_BOOL__ENUM,
3134 G_TYPE_BOOLEAN, 1, GTK_TYPE_HTML_COMMAND);
3136 signals[CURSOR_CHANGED] =
3137 g_signal_new ("cursor_changed",
3138 G_TYPE_FROM_CLASS (object_class),
3139 G_SIGNAL_RUN_FIRST,
3140 G_STRUCT_OFFSET (GtkHTMLClass, cursor_changed),
3141 NULL, NULL,
3142 g_cclosure_marshal_VOID__VOID,
3143 G_TYPE_NONE, 0);
3145 signals[OBJECT_INSERTED] =
3146 g_signal_new ("object_inserted",
3147 G_TYPE_FROM_CLASS (object_class),
3148 G_SIGNAL_RUN_FIRST,
3149 G_STRUCT_OFFSET (GtkHTMLClass, object_inserted),
3150 NULL, NULL,
3151 html_g_cclosure_marshal_VOID__INT_INT,
3152 G_TYPE_NONE, 2,
3153 G_TYPE_INT, G_TYPE_INT);
3155 signals[OBJECT_DELETE] =
3156 g_signal_new ("object_delete",
3157 G_TYPE_FROM_CLASS (object_class),
3158 G_SIGNAL_RUN_FIRST,
3159 G_STRUCT_OFFSET (GtkHTMLClass, object_delete),
3160 NULL, NULL,
3161 html_g_cclosure_marshal_VOID__INT_INT,
3162 G_TYPE_NONE, 2,
3163 G_TYPE_INT, G_TYPE_INT);
3164 object_class->dispose = dispose;
3166 #ifdef USE_PROPS
3167 object_class->get_property = gtk_html_get_property;
3168 object_class->set_property = gtk_html_set_property;
3170 g_object_class_install_property (object_class,
3171 PROP_EDITABLE,
3172 g_param_spec_boolean ("editable",
3173 "Editable",
3174 "Whether the html can be edited",
3175 FALSE,
3176 G_PARAM_READABLE | G_PARAM_WRITABLE));
3177 g_object_class_install_property (object_class,
3178 PROP_TITLE,
3179 g_param_spec_string ("title",
3180 "Document Title",
3181 "The title of the current document",
3182 NULL,
3183 G_PARAM_WRITABLE | G_PARAM_READABLE));
3184 g_object_class_install_property (object_class,
3185 PROP_DOCUMENT_BASE,
3186 g_param_spec_string ("document_base",
3187 "Document Base",
3188 "The base URL for relative references",
3189 NULL,
3190 G_PARAM_WRITABLE | G_PARAM_READABLE));
3191 g_object_class_install_property (object_class,
3192 PROP_TARGET_BASE,
3193 g_param_spec_string ("target_base",
3194 "Target Base",
3195 "The base URL of the target frame",
3196 NULL,
3197 G_PARAM_WRITABLE | G_PARAM_READABLE));
3199 #endif
3201 gtk_widget_class_install_style_property (widget_class,
3202 g_param_spec_string ("fixed_font_name",
3203 "Fixed Width Font",
3204 "The Monospace font to use for typewriter text",
3205 NULL,
3206 G_PARAM_READABLE));
3208 gtk_widget_class_install_style_property (widget_class,
3209 g_param_spec_boxed ("link_color",
3210 "New Link Color",
3211 "The color of new link elements",
3212 GDK_TYPE_COLOR,
3213 G_PARAM_READABLE));
3214 gtk_widget_class_install_style_property (widget_class,
3215 g_param_spec_boxed ("vlink_color",
3216 "Visited Link Color",
3217 "The color of visited link elements",
3218 GDK_TYPE_COLOR,
3219 G_PARAM_READABLE));
3220 gtk_widget_class_install_style_property (widget_class,
3221 g_param_spec_boxed ("alink_color",
3222 "Active Link Color",
3223 "The color of active link elements",
3224 GDK_TYPE_COLOR,
3225 G_PARAM_READABLE));
3226 gtk_widget_class_install_style_property (widget_class,
3227 g_param_spec_boxed ("spell_error_color",
3228 "Spelling Error Color",
3229 "The color of the spelling error markers",
3230 GDK_TYPE_COLOR,
3231 G_PARAM_READABLE));
3232 gtk_widget_class_install_style_property (widget_class,
3233 g_param_spec_boxed ("cite_color",
3234 "Cite Quotation Color",
3235 "The color of the cited text",
3236 GDK_TYPE_COLOR,
3237 G_PARAM_READABLE));
3239 widget_class->realize = realize;
3240 widget_class->unrealize = unrealize;
3241 widget_class->style_updated = style_updated;
3242 widget_class->key_press_event = key_press_event;
3243 widget_class->key_release_event = key_release_event;
3244 widget_class->draw = draw;
3245 widget_class->get_preferred_width = gtk_html_get_preferred_width;
3246 widget_class->get_preferred_height = gtk_html_get_preferred_height;
3247 widget_class->size_allocate = size_allocate;
3248 widget_class->motion_notify_event = motion_notify_event;
3249 widget_class->visibility_notify_event = visibility_notify_event;
3250 widget_class->hierarchy_changed = hierarchy_changed;
3251 widget_class->button_press_event = button_press_event;
3252 widget_class->button_release_event = button_release_event;
3253 widget_class->focus_in_event = focus_in_event;
3254 widget_class->focus_out_event = focus_out_event;
3255 widget_class->enter_notify_event = enter_notify_event;
3256 widget_class->drag_data_get = drag_data_get;
3257 widget_class->drag_data_delete = drag_data_delete;
3258 widget_class->drag_begin = drag_begin;
3259 widget_class->drag_end = drag_end;
3260 widget_class->drag_data_received = drag_data_received;
3261 widget_class->drag_motion = drag_motion;
3262 widget_class->focus = focus;
3263 widget_class->direction_changed = gtk_html_direction_changed;
3265 container_class->set_focus_child = set_focus_child;
3267 html_class->scroll = scroll;
3268 html_class->cursor_move = cursor_move;
3269 html_class->command = command;
3270 html_class->properties = gtk_html_class_properties_new ();
3272 add_bindings (klass);
3273 gtk_widget_class_set_accessible_type (widget_class, G_TYPE_GTK_HTML_A11Y);
3275 filename = g_build_filename (PREFIX, "share", GTKHTML_RELEASE_STRING, "keybindingsrc.emacs", NULL);
3276 gtk_rc_parse (filename);
3277 g_free (filename);
3278 html_class->emacs_bindings = gtk_binding_set_find ("gtkhtml-bindings-emacs");
3280 /* XXX We leak this GSettings reference but...
3281 * meh, GtkHTML will be orphaned soon. */
3282 settings = g_settings_new ("org.gnome.desktop.interface");
3284 settings_key_theme_changed (settings, "gtk-key-theme", html_class);
3286 g_signal_connect (
3287 settings, "changed::gtk-key-theme",
3288 G_CALLBACK (settings_key_theme_changed), html_class);
3290 g_signal_connect (
3291 settings, "changed::cursor-blink",
3292 G_CALLBACK (settings_cursor_blink_changed), html_class);
3293 g_signal_connect (
3294 settings, "changed::cursor-blink-time",
3295 G_CALLBACK (settings_cursor_blink_changed), html_class);
3297 settings_cursor_blink_changed (settings, "cursor-blink", html_class);
3300 void
3301 gtk_html_im_reset (GtkHTML *html)
3303 if (!html->priv->im_block_reset) {
3304 D_IM (printf ("IM reset requested\n");)
3305 if (html->priv->need_im_reset) {
3306 if (html->engine->freeze_count == 1)
3307 html_engine_thaw_idle_flush (html->engine);
3308 html->priv->need_im_reset = FALSE;
3309 gtk_im_context_reset (html->priv->im_context);
3310 D_IM (printf ("IM reset called\n");)
3315 static void
3316 gtk_html_im_commit_cb (GtkIMContext *context,
3317 const gchar *str,
3318 GtkHTML *html)
3320 gboolean state = html->priv->im_block_reset;
3321 gint pos;
3323 html->priv->im_block_reset = TRUE;
3325 if (html->priv->im_pre_len > 0) {
3326 D_IM (printf ("IM delete last preedit %d + %d\n", html->priv->im_pre_pos, html->priv->im_pre_len);)
3328 html_undo_freeze (html->engine->undo);
3329 html_cursor_exactly_jump_to_position_no_spell (html->engine->cursor, html->engine, html->priv->im_pre_pos);
3330 html_engine_set_mark (html->engine);
3331 html_cursor_exactly_jump_to_position_no_spell (html->engine->cursor, html->engine, html->priv->im_pre_pos + html->priv->im_pre_len);
3332 html_engine_delete (html->engine);
3333 html->priv->im_pre_len = 0;
3334 html_undo_thaw (html->engine->undo);
3337 pos = html->engine->cursor->position;
3338 if (html->engine->mark && html->engine->mark->position > pos)
3339 pos = html->engine->mark->position;
3341 D_IM (printf ("IM commit %s\n", str);)
3342 html_engine_paste_text (html->engine, str, g_utf8_strlen (str, -1));
3343 html->priv->im_block_reset = state;
3345 D_IM (printf ("IM commit pos: %d pre_pos: %d\n", pos, html->priv->im_pre_pos);)
3346 if (html->priv->im_pre_pos >= pos)
3347 html->priv->im_pre_pos += html->engine->cursor->position - pos;
3350 static void
3351 gtk_html_im_preedit_start_cb (GtkIMContext *context,
3352 GtkHTML *html)
3354 html->priv->im_pre_len = 0;
3357 static void
3358 gtk_html_im_preedit_changed_cb (GtkIMContext *context,
3359 GtkHTML *html)
3361 PangoAttrList *attrs;
3362 gchar *preedit_string;
3363 gint cursor_pos, initial_position;
3364 gboolean state = html->priv->im_block_reset;
3365 gboolean pop_selection = FALSE;
3366 gint deleted = 0;
3368 D_IM (printf ("IM preedit changed cb [begin] cursor %d(%p) mark %d(%p) active: %d\n",
3369 html->engine->cursor ? html->engine->cursor->position : 0, html->engine->cursor,
3370 html->engine->mark ? html->engine->mark->position : 0, html->engine->mark,
3371 html_engine_is_selection_active (html->engine));)
3373 if (!html->engine->cursor) {
3374 return;
3377 html->priv->im_block_reset = TRUE;
3379 if (html->engine->mark && html_engine_is_selection_active (html->engine)) {
3380 D_IM (printf ("IM push selection\n");)
3381 html_engine_selection_push (html->engine);
3382 html_engine_disable_selection (html->engine);
3383 html_engine_edit_selection_updater_update_now (html->engine->selection_updater);
3384 pop_selection = TRUE;
3386 initial_position = html->engine->cursor->position;
3387 D_IM (printf ("IM initial position %d\n", initial_position);)
3389 html_undo_freeze (html->engine->undo);
3391 if (html->priv->im_pre_len > 0) {
3392 D_IM (printf ("IM delete last preedit %d + %d\n", html->priv->im_pre_pos, html->priv->im_pre_len);)
3394 html_cursor_exactly_jump_to_position_no_spell (html->engine->cursor, html->engine, html->priv->im_pre_pos);
3395 html_engine_set_mark (html->engine);
3396 html_cursor_exactly_jump_to_position_no_spell (html->engine->cursor, html->engine, html->priv->im_pre_pos + html->priv->im_pre_len);
3397 html_engine_delete (html->engine);
3398 deleted = html->priv->im_pre_len;
3399 } else
3400 html->priv->im_orig_style = html_engine_get_font_style (html->engine);
3402 gtk_im_context_get_preedit_string (html->priv->im_context, &preedit_string, &attrs, &cursor_pos);
3404 D_IM (printf ("IM preedit changed to %s\n", preedit_string);)
3405 html->priv->im_pre_len = g_utf8_strlen (preedit_string, -1);
3407 if (html->priv->im_pre_len > 0) {
3408 cursor_pos = CLAMP (cursor_pos, 0, html->priv->im_pre_len);
3409 html->priv->im_pre_pos = html->engine->cursor->position;
3410 html_engine_paste_text_with_extra_attributes (html->engine, preedit_string, html->priv->im_pre_len, attrs);
3411 html_cursor_exactly_jump_to_position_no_spell (html->engine->cursor, html->engine, html->priv->im_pre_pos + cursor_pos);
3412 } else
3413 html_engine_set_font_style (html->engine, 0, html->priv->im_orig_style);
3414 g_free (preedit_string);
3416 if (pop_selection) {
3417 gint position= html->engine->cursor->position, cpos, mpos;
3418 D_IM (printf ("IM pop selection\n");)
3419 g_assert (html_engine_selection_stack_top (html->engine, &cpos, &mpos));
3420 if (position < MAX (cpos, mpos) + html->priv->im_pre_len - deleted)
3421 g_assert (html_engine_selection_stack_top_modify (html->engine, html->priv->im_pre_len - deleted));
3422 html_engine_selection_pop (html->engine);
3424 /* that works for now, but idealy we should be able to have cursor positioned outside selection, so that preedit
3425 * cursor is in the right place */
3426 if (html->priv->im_pre_len == 0)
3427 html_cursor_jump_to_position_no_spell (html->engine->cursor, html->engine,
3428 initial_position >= html->priv->im_pre_pos + deleted ? initial_position - deleted : initial_position);
3430 if (html->engine->freeze_count == 1)
3431 html_engine_thaw_idle_flush (html->engine);
3432 /* FIXME gtk_im_context_set_cursor_location (im_context, &area); */
3433 html->priv->im_block_reset = state;
3435 html_undo_thaw (html->engine->undo);
3437 D_IM (printf ("IM preedit changed cb [end] cursor %d(%p) mark %d(%p) active: %d\n",
3438 html->engine->cursor ? html->engine->cursor->position : 0, html->engine->cursor,
3439 html->engine->mark ? html->engine->mark->position : 0, html->engine->mark,
3440 html_engine_is_selection_active (html->engine));)
3443 static gchar *
3444 get_surrounding_text (HTMLEngine *e,
3445 gint *offset)
3447 HTMLObject *o = e->cursor->object;
3448 HTMLObject *prev;
3449 gchar *text = NULL;
3451 if (!html_object_is_text (o)) {
3452 *offset = 0;
3453 if (e->cursor->offset == 0) {
3454 prev = html_object_prev_not_slave (o);
3455 if (html_object_is_text (prev)) {
3456 o = prev;
3457 } else
3458 return NULL;
3459 } else if (e->cursor->offset == html_object_get_length (e->cursor->object)) {
3460 HTMLObject *next;
3462 next = html_object_next_not_slave (o);
3463 if (html_object_is_text (next)) {
3464 o = next;
3465 } else
3466 return NULL;
3468 } else
3469 *offset = e->cursor->offset;
3471 while ((prev = html_object_prev_not_slave (o)) && html_object_is_text (prev)) {
3472 o = prev;
3473 *offset += HTML_TEXT (o)->text_len;
3476 while (o) {
3477 if (html_object_is_text (o)) {
3478 if (!text)
3479 text = g_strdup (HTML_TEXT (o)->text);
3480 else {
3481 gchar *concat = g_strconcat (text, HTML_TEXT (o)->text, NULL);
3482 g_free (text);
3483 text = concat;
3486 o = html_object_next_not_slave (o);
3489 return text;
3492 static gboolean
3493 gtk_html_im_retrieve_surrounding_cb (GtkIMContext *context,
3494 GtkHTML *html)
3496 gint offset = 0;
3497 gchar *text;
3499 D_IM (printf ("IM gtk_html_im_retrieve_surrounding_cb\n");)
3501 text = get_surrounding_text (html->engine, &offset);
3502 if (text) {
3503 /* convert gchar offset to byte offset */
3504 offset = g_utf8_offset_to_pointer (text, offset) - text;
3505 gtk_im_context_set_surrounding (context, text, -1, offset);
3506 g_free (text);
3507 } else
3508 gtk_im_context_set_surrounding (context, NULL, 0, 0);
3510 return TRUE;
3513 static gboolean
3514 gtk_html_im_delete_surrounding_cb (GtkIMContext *slave,
3515 gint offset,
3516 gint n_chars,
3517 GtkHTML *html)
3519 D_IM (printf ("IM gtk_html_im_delete_surrounding_cb\n");)
3520 if (html_engine_get_editable (html->engine) && !html_engine_is_selection_active (html->engine)) {
3521 gint orig_position = html->engine->cursor->position;
3523 html_cursor_exactly_jump_to_position_no_spell (html->engine->cursor, html->engine, orig_position + offset);
3524 html_engine_set_mark (html->engine);
3525 html_cursor_exactly_jump_to_position_no_spell (html->engine->cursor, html->engine, orig_position + offset + n_chars);
3526 html_engine_delete (html->engine);
3527 if (offset < 0)
3528 orig_position -= MIN (n_chars, - offset);
3529 html_cursor_jump_to_position_no_spell (html->engine->cursor, html->engine, orig_position);
3531 return TRUE;
3534 static void
3535 gtk_html_init (GtkHTML *html)
3537 GSettings *settings;
3539 gtk_widget_set_can_focus (GTK_WIDGET (html), TRUE);
3540 gtk_widget_set_app_paintable (GTK_WIDGET (html), TRUE);
3541 gtk_widget_set_double_buffered (GTK_WIDGET (html), TRUE);
3543 html->editor_api = NULL;
3544 html->debug = FALSE;
3545 html->allow_selection = TRUE;
3547 html->pointer_url = NULL;
3548 html->hand_cursor = gdk_cursor_new (GDK_HAND2);
3549 html->ibeam_cursor = gdk_cursor_new (GDK_XTERM);
3550 html->hadj_connection = 0;
3551 html->vadj_connection = 0;
3553 html->selection_x1 = 0;
3554 html->selection_y1 = 0;
3556 html->in_selection = FALSE;
3557 html->in_selection_drag = FALSE;
3559 html->priv = G_TYPE_INSTANCE_GET_PRIVATE (
3560 html, GTK_TYPE_HTML, GtkHTMLPrivate);
3562 html->priv->idle_handler_id = 0;
3563 html->priv->scroll_timeout_id = 0;
3564 html->priv->skip_update_cursor = FALSE;
3565 html->priv->cursor_moved = FALSE;
3566 html->priv->paragraph_style = GTK_HTML_PARAGRAPH_STYLE_NORMAL;
3567 html->priv->paragraph_alignment = GTK_HTML_PARAGRAPH_ALIGNMENT_LEFT;
3568 html->priv->paragraph_indentation = 0;
3569 html->priv->insertion_font_style = GTK_HTML_FONT_STYLE_DEFAULT;
3570 html->priv->selection_type = -1;
3571 html->priv->selection_as_cite = FALSE;
3572 html->priv->search_input_line = NULL;
3573 html->priv->in_object_resize = FALSE;
3574 html->priv->resize_cursor = gdk_cursor_new (GDK_BOTTOM_RIGHT_CORNER);
3575 html->priv->in_url_test_mode = FALSE;
3576 html->priv->in_key_binding = FALSE;
3578 html->priv->caret_first_focus_anchor = NULL;
3579 html->priv->is_first_focus = TRUE;
3581 html->priv->hadjustment = NULL;
3582 html->priv->vadjustment = NULL;
3584 /* IM Context */
3585 html->priv->im_context = gtk_im_multicontext_new ();
3586 html->priv->need_im_reset = FALSE;
3587 html->priv->im_block_reset = FALSE;
3588 html->priv->im_pre_len = 0;
3590 g_signal_connect (
3591 html, "notify::hadjustment",
3592 G_CALLBACK (hadjustment_notify_cb), NULL);
3594 g_signal_connect (
3595 html, "notify::vadjustment",
3596 G_CALLBACK (vadjustment_notify_cb), NULL);
3598 g_signal_connect (
3599 html->priv->im_context, "commit",
3600 G_CALLBACK (gtk_html_im_commit_cb), html);
3602 g_signal_connect (
3603 html->priv->im_context, "preedit_start",
3604 G_CALLBACK (gtk_html_im_preedit_start_cb), html);
3606 g_signal_connect (
3607 html->priv->im_context, "preedit_changed",
3608 G_CALLBACK (gtk_html_im_preedit_changed_cb), html);
3610 g_signal_connect (
3611 html->priv->im_context, "retrieve_surrounding",
3612 G_CALLBACK (gtk_html_im_retrieve_surrounding_cb), html);
3614 g_signal_connect (
3615 html->priv->im_context, "delete_surrounding",
3616 G_CALLBACK (gtk_html_im_delete_surrounding_cb), html);
3618 settings = g_settings_new ("org.gnome.desktop.interface");
3620 g_signal_connect (
3621 settings, "changed::monospace-font-name",
3622 G_CALLBACK (settings_monospace_font_name_changed), html);
3624 /* The signal is disconnected and the GSettings
3625 * object unreferenced in our dispose() method. */
3626 html->priv->desktop_interface = settings;
3628 gtk_html_construct (html);
3631 GType
3632 gtk_html_get_type (void)
3634 static GType html_type = 0;
3636 if (!html_type) {
3637 static const GTypeInfo html_info = {
3638 sizeof (GtkHTMLClass),
3639 NULL, /* base_init */
3640 NULL, /* base_finalize */
3641 (GClassInitFunc) gtk_html_class_init,
3642 NULL, /* class_finalize */
3643 NULL, /* class_data */
3644 sizeof (GtkHTML),
3645 1, /* n_preallocs */
3646 (GInstanceInitFunc) gtk_html_init,
3649 html_type = g_type_register_static (GTK_TYPE_LAYOUT, "GtkHTML", &html_info, 0);
3652 return html_type;
3656 * gtk_html_new:
3657 * @void:
3659 * GtkHTML widget contructor. It creates an empty GtkHTML widget.
3661 * Return value: A GtkHTML widget, newly created and empty.
3664 GtkWidget *
3665 gtk_html_new (void)
3667 GtkWidget *html;
3669 html = g_object_new (GTK_TYPE_HTML, NULL);
3671 return html;
3675 * gtk_html_new_from_string:
3676 * @str: A string containing HTML source.
3677 * @len: A length of @str, if @len == -1 then it will be computed using strlen.
3679 * GtkHTML widget constructor. It creates an new GtkHTML widget and loads HTML source from @str.
3680 * It is intended for simple creation. For more complicated loading you probably want to use
3681 * #GtkHTMLStream. See #gtk_html_begin.
3683 * Return value: A GtkHTML widget, newly created, containing document loaded from input @str.
3686 GtkWidget *
3687 gtk_html_new_from_string (const gchar *str,
3688 gint len)
3690 GtkWidget *html;
3692 html = g_object_new (GTK_TYPE_HTML, NULL);
3693 gtk_html_load_from_string (GTK_HTML (html), str, len);
3695 return html;
3698 void
3699 gtk_html_construct (GtkHTML *html)
3701 g_return_if_fail (html != NULL);
3702 g_return_if_fail (GTK_IS_HTML (html));
3704 html->engine = html_engine_new (GTK_WIDGET (html));
3705 html->iframe_parent = NULL;
3707 g_signal_connect (G_OBJECT (html->engine), "title_changed",
3708 G_CALLBACK (html_engine_title_changed_cb), html);
3709 g_signal_connect (G_OBJECT (html->engine), "set_base",
3710 G_CALLBACK (html_engine_set_base_cb), html);
3711 g_signal_connect (G_OBJECT (html->engine), "set_base_target",
3712 G_CALLBACK (html_engine_set_base_target_cb), html);
3713 g_signal_connect (G_OBJECT (html->engine), "load_done",
3714 G_CALLBACK (html_engine_load_done_cb), html);
3715 g_signal_connect (G_OBJECT (html->engine), "url_requested",
3716 G_CALLBACK (html_engine_url_requested_cb), html);
3717 g_signal_connect (G_OBJECT (html->engine), "draw_pending",
3718 G_CALLBACK (html_engine_draw_pending_cb), html);
3719 g_signal_connect (G_OBJECT (html->engine), "redirect",
3720 G_CALLBACK (html_engine_redirect_cb), html);
3721 g_signal_connect (G_OBJECT (html->engine), "submit",
3722 G_CALLBACK (html_engine_submit_cb), html);
3723 g_signal_connect (G_OBJECT (html->engine), "object_requested",
3724 G_CALLBACK (html_engine_object_requested_cb), html);
3728 void
3729 gtk_html_enable_debug (GtkHTML *html,
3730 gboolean debug)
3732 g_return_if_fail (html != NULL);
3733 g_return_if_fail (GTK_IS_HTML (html));
3735 html->debug = debug;
3739 void
3740 gtk_html_allow_selection (GtkHTML *html,
3741 gboolean allow)
3743 g_return_if_fail (html != NULL);
3744 g_return_if_fail (GTK_IS_HTML (html));
3746 html->allow_selection = allow;
3750 * gtk_html_begin_full:
3751 * @html: the GtkHTML widget to operate on.
3752 * @target_frame: the string identifying the frame to load the data into
3753 * @content_type: the content_type of the data that we will be loading
3754 * @flags: the GtkHTMLBeginFlags that control the reload behavior handling
3756 * Opens a new stream of type @content_type to the frame named @target_frame.
3757 * the flags in @flags allow control over what data is reloaded.
3759 * Returns: a new GtkHTMLStream to specified frame
3761 GtkHTMLStream *
3762 gtk_html_begin_full (GtkHTML *html,
3763 gchar *target_frame,
3764 const gchar *content_type,
3765 GtkHTMLBeginFlags flags)
3767 GtkHTMLStream *handle;
3769 g_return_val_if_fail (!gtk_html_get_editable (html), NULL);
3771 if (flags & GTK_HTML_BEGIN_BLOCK_UPDATES)
3772 gtk_html_set_blocking (html, TRUE);
3773 else
3774 gtk_html_set_blocking (html, FALSE);
3776 if (flags & GTK_HTML_BEGIN_BLOCK_IMAGES)
3777 gtk_html_set_images_blocking (html, TRUE);
3778 else
3779 gtk_html_set_images_blocking (html, FALSE);
3781 if (flags & GTK_HTML_BEGIN_KEEP_IMAGES)
3782 gtk_html_images_ref (html);
3784 if (flags & GTK_HTML_BEGIN_KEEP_SCROLL)
3785 html->engine->keep_scroll = TRUE;
3786 else
3787 html->engine->keep_scroll = FALSE;
3789 html->priv->is_first_focus = TRUE;
3791 handle = html_engine_begin (html->engine, content_type);
3792 if (handle == NULL)
3793 return NULL;
3795 html_engine_parse (html->engine);
3797 if (flags & GTK_HTML_BEGIN_KEEP_IMAGES)
3798 gtk_html_images_unref (html);
3800 if (flags & GTK_HTML_BEGIN_KEEP_SCROLL)
3801 html->engine->newPage = FALSE;
3803 /* Enable change content type in engine */
3804 if (flags & GTK_HTML_BEGIN_CHANGECONTENTTYPE)
3805 gtk_html_set_default_engine (html, TRUE);
3807 return handle;
3811 * gtk_html_begin:
3812 * @html: the html widget to operate on.
3814 * Opens a new stream to load new content into the GtkHTML widget @html.
3816 * Returns: a new GtkHTMLStream to store new content.
3818 GtkHTMLStream *
3819 gtk_html_begin (GtkHTML *html)
3821 g_return_val_if_fail (GTK_IS_HTML (html), NULL);
3823 return gtk_html_begin_full (html, NULL, NULL, 0);
3827 * gtk_html_begin_content:
3828 * @html: the html widget to operate on.
3829 * @content_type: a string listing the type of content to expect on the stream.
3831 * Opens a new stream to load new content of type @content_type into
3832 * the GtkHTML widget given in @html.
3834 * Returns: a new GtkHTMLStream to store new content.
3836 GtkHTMLStream *
3837 gtk_html_begin_content (GtkHTML *html,
3838 const gchar *content_type)
3840 g_return_val_if_fail (!gtk_html_get_editable (html), NULL);
3842 return gtk_html_begin_full (html, NULL, content_type , 0);
3846 * gtk_html_write:
3847 * @html: the GtkHTML widget the stream belongs to (unused)
3848 * @handle: the GkHTMLStream to write to.
3849 * @buffer: the data to write to the stream.
3850 * @size: the length of data to read from @buffer
3852 * Writes @size bytes of @buffer to the stream pointed to by @stream.
3854 void
3855 gtk_html_write (GtkHTML *html,
3856 GtkHTMLStream *handle,
3857 const gchar *buffer,
3858 gsize size)
3860 gtk_html_stream_write (handle, buffer, size);
3864 * gtk_html_end:
3865 * @html: the GtkHTML widget the stream belongs to.
3866 * @handle: the GtkHTMLStream to close.
3867 * @status: the GtkHTMLStreamStatus representing the state of the stream when closed.
3869 * Close the GtkHTMLStream represented by @stream and notify @html that is
3870 * should not expect any more content from that stream.
3872 void
3873 gtk_html_end (GtkHTML *html,
3874 GtkHTMLStream *handle,
3875 GtkHTMLStreamStatus status)
3877 gtk_html_stream_close (handle, status);
3881 * gtk_html_stop:
3882 * @html: the GtkHTML widget.
3884 * Stop requesting any more data by url_requested signal.
3886 void
3887 gtk_html_stop (GtkHTML *html)
3889 g_return_if_fail (GTK_IS_HTML (html));
3891 html_engine_stop (html->engine);
3896 * gtk_html_get_title:
3897 * @html: The GtkHTML widget.
3899 * Retrieve the title of the document currently loaded in the GtkHTML widget.
3901 * Returns: the title of the current document
3903 const gchar *
3904 gtk_html_get_title (GtkHTML *html)
3906 g_return_val_if_fail (html != NULL, NULL);
3907 g_return_val_if_fail (GTK_IS_HTML (html), NULL);
3909 if (html->engine->title == NULL)
3910 return NULL;
3912 return html->engine->title->str;
3916 * gtk_html_set_title:
3917 * @html: The GtkHTML widget.
3919 * Set the title of the document currently loaded in the GtkHTML widget.
3922 void
3923 gtk_html_set_title (GtkHTML *html,
3924 const gchar *title)
3926 g_return_if_fail (html != NULL);
3927 g_return_if_fail (GTK_IS_HTML (html));
3929 html_engine_set_title (html->engine, title);
3934 * gtk_html_jump_to_anchor:
3935 * @html: the GtkHTML widget.
3936 * @anchor: a string containing the name of the anchor.
3938 * Scroll the document display to show the HTML anchor listed in @anchor
3940 * Returns: TRUE if the anchor is found, FALSE otherwise.
3942 gboolean
3943 gtk_html_jump_to_anchor (GtkHTML *html,
3944 const gchar *anchor)
3946 g_return_val_if_fail (html != NULL, FALSE);
3947 g_return_val_if_fail (GTK_IS_HTML (html), FALSE);
3949 return html_engine_goto_anchor (html->engine, anchor);
3953 gboolean
3954 gtk_html_save (GtkHTML *html,
3955 GtkHTMLSaveReceiverFn receiver,
3956 gpointer data)
3958 g_return_val_if_fail (html != NULL, FALSE);
3959 g_return_val_if_fail (GTK_IS_HTML (html), FALSE);
3960 g_return_val_if_fail (receiver != NULL, FALSE);
3962 return html_engine_save (html->engine, receiver, data);
3966 * gtk_html_export:
3967 * @html: the GtkHTML widget
3968 * @content_type: the expected content_type
3969 * @receiver:
3970 * @user_data: pointer to maintain user state.
3972 * Export the current document into the content type given by @content_type,
3973 * by calling the function listed in @receiver data becomes avaiable. When @receiver is
3974 * called @user_data is passed in as the user_data parameter.
3976 * Returns: TRUE if the export was successful, FALSE otherwise.
3978 gboolean
3979 gtk_html_export (GtkHTML *html,
3980 const gchar *content_type,
3981 GtkHTMLSaveReceiverFn receiver,
3982 gpointer user_data)
3984 g_return_val_if_fail (html != NULL, FALSE);
3985 g_return_val_if_fail (GTK_IS_HTML (html), FALSE);
3986 g_return_val_if_fail (receiver != NULL, FALSE);
3988 if (strcmp (content_type, "text/html") == 0) {
3989 return html_engine_save (html->engine, receiver, user_data);
3990 } else if (strcmp (content_type, "text/plain") == 0) {
3991 return html_engine_save_plain (html->engine, receiver,
3992 user_data);
3993 } else {
3994 return FALSE;
4000 static void
4001 gtk_html_update_scrollbars_on_resize (GtkHTML *html,
4002 gdouble old_doc_width,
4003 gdouble old_doc_height,
4004 gdouble old_width,
4005 gdouble old_height,
4006 gboolean *changed_x,
4007 gboolean *changed_y)
4009 GtkAdjustment *vadj, *hadj;
4010 gdouble doc_width, doc_height;
4012 /* printf ("update on resize\n"); */
4014 hadj = gtk_layout_get_hadjustment (GTK_LAYOUT (html));
4015 vadj = gtk_layout_get_vadjustment (GTK_LAYOUT (html));
4017 doc_height = html_engine_get_doc_height (html->engine);
4018 doc_width = html_engine_get_doc_width (html->engine);
4020 if (!html->engine->keep_scroll) {
4021 if (old_doc_width - old_width > 0) {
4022 gdouble value;
4024 value = gtk_adjustment_get_value (hadj);
4025 html->engine->x_offset = (gint) (value * (doc_width - html->engine->width)
4026 / (old_doc_width - old_width));
4028 gtk_adjustment_set_value (hadj, html->engine->x_offset);
4031 if (old_doc_height - old_height > 0) {
4032 gdouble value;
4034 value = gtk_adjustment_get_value (vadj);
4035 html->engine->y_offset = (gint) (value * (doc_height - html->engine->height)
4036 / (old_doc_height - old_height));
4037 gtk_adjustment_set_value (vadj, html->engine->y_offset);
4042 void
4043 gtk_html_private_calc_scrollbars (GtkHTML *html,
4044 gboolean *changed_x,
4045 gboolean *changed_y)
4047 GtkLayout *layout;
4048 GtkAdjustment *vadj, *hadj;
4049 guint layout_width, layout_height;
4050 gint width, height;
4051 gdouble value;
4053 if (!gtk_widget_get_realized (GTK_WIDGET (html)))
4054 return;
4056 /* printf ("calc scrollbars\n"); */
4058 height = html_engine_get_doc_height (html->engine);
4059 width = html_engine_get_doc_width (html->engine);
4061 layout = GTK_LAYOUT (html);
4062 hadj = gtk_layout_get_hadjustment (layout);
4063 vadj = gtk_layout_get_vadjustment (layout);
4065 gtk_adjustment_set_page_size (vadj, html->engine->height);
4066 gtk_adjustment_set_step_increment (vadj, 14); /* FIXME */
4067 gtk_adjustment_set_page_increment (vadj, html->engine->height);
4069 value = gtk_adjustment_get_value (vadj);
4070 if (value > height - html->engine->height) {
4071 gtk_adjustment_set_value (vadj, height - html->engine->height);
4072 if (changed_y)
4073 *changed_y = TRUE;
4076 gtk_adjustment_set_page_size (hadj, html->engine->width);
4077 gtk_adjustment_set_step_increment (hadj, 14); /* FIXME */
4078 gtk_adjustment_set_page_increment (hadj, html->engine->width);
4080 gtk_layout_get_size (layout, &layout_width, &layout_height);
4081 if ((width != layout_width) || (height != layout_height)) {
4082 g_signal_emit (html, signals[SIZE_CHANGED], 0);
4083 gtk_layout_set_size (layout, width, height);
4086 value = gtk_adjustment_get_value (hadj);
4087 if (value > width - html->engine->width || value > MAX_WIDGET_WIDTH - html->engine->width) {
4088 gtk_adjustment_set_value (hadj, MIN (width - html->engine->width, MAX_WIDGET_WIDTH - html->engine->width));
4089 if (changed_x)
4090 *changed_x = TRUE;
4097 #ifdef USE_PROPS
4098 static void
4099 gtk_html_set_property (GObject *object,
4100 guint prop_id,
4101 const GValue *value,
4102 GParamSpec *pspec)
4104 GtkHTML *html = GTK_HTML (object);
4106 switch (prop_id) {
4107 case PROP_EDITABLE:
4108 gtk_html_set_editable (html, g_value_get_boolean (value));
4109 break;
4110 case PROP_TITLE:
4111 gtk_html_set_title (html, g_value_get_string (value));
4112 break;
4113 case PROP_DOCUMENT_BASE:
4114 gtk_html_set_base (html, g_value_get_string (value));
4115 break;
4116 case PROP_TARGET_BASE:
4117 /* This doesn't do anything yet */
4118 break;
4119 default:
4120 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
4121 break;
4125 static void
4126 gtk_html_get_property (GObject *object,
4127 guint prop_id,
4128 GValue *value,
4129 GParamSpec *pspec)
4131 GtkHTML *html = GTK_HTML (object);
4133 switch (prop_id) {
4134 case PROP_EDITABLE:
4135 g_value_set_boolean (value, gtk_html_get_editable (html));
4136 break;
4137 case PROP_TITLE:
4138 g_value_set_static_string (value, gtk_html_get_title (html));
4139 break;
4140 case PROP_DOCUMENT_BASE:
4141 g_value_set_static_string (value, gtk_html_get_base (html));
4142 break;
4143 case PROP_TARGET_BASE:
4144 g_value_set_static_string (value, gtk_html_get_base (html));
4145 break;
4146 default:
4147 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
4148 break;
4151 #endif
4153 void
4154 gtk_html_set_editable (GtkHTML *html,
4155 gboolean editable)
4157 g_return_if_fail (html != NULL);
4158 g_return_if_fail (GTK_IS_HTML (html));
4160 html_engine_set_editable (html->engine, editable);
4162 if (editable)
4163 gtk_html_update_styles (html);
4166 gboolean
4167 gtk_html_get_editable (const GtkHTML *html)
4169 g_return_val_if_fail (html != NULL, FALSE);
4170 g_return_val_if_fail (GTK_IS_HTML (html), FALSE);
4172 return html_engine_get_editable (html->engine);
4175 void
4176 gtk_html_set_inline_spelling (GtkHTML *html,
4177 gboolean inline_spell)
4179 g_return_if_fail (html != NULL);
4180 g_return_if_fail (GTK_IS_HTML (html));
4182 html->priv->inline_spelling = inline_spell;
4184 /* do not update content, when there is none set (yet) */
4185 if (!html->engine || !html->engine->clue)
4186 return;
4188 if (gtk_html_get_editable (html) && html->priv->inline_spelling)
4189 html_engine_spell_check (html->engine);
4190 else
4191 html_engine_clear_spell_check (html->engine);
4194 gboolean
4195 gtk_html_get_inline_spelling (const GtkHTML *html)
4197 g_return_val_if_fail (html != NULL, FALSE);
4198 g_return_val_if_fail (GTK_IS_HTML (html), FALSE);
4200 return html->priv->inline_spelling;
4203 void
4204 gtk_html_set_magic_links (GtkHTML *html,
4205 gboolean links)
4207 g_return_if_fail (html != NULL);
4208 g_return_if_fail (GTK_IS_HTML (html));
4210 html->priv->magic_links = links;
4213 gboolean
4214 gtk_html_get_magic_links (const GtkHTML *html)
4216 g_return_val_if_fail (html != NULL, FALSE);
4217 g_return_val_if_fail (GTK_IS_HTML (html), FALSE);
4219 return html->priv->magic_links;
4222 void
4223 gtk_html_set_magic_smileys (GtkHTML *html,
4224 gboolean smile)
4226 g_return_if_fail (html != NULL);
4227 g_return_if_fail (GTK_IS_HTML (html));
4229 html->priv->magic_smileys = smile;
4232 gboolean
4233 gtk_html_get_magic_smileys (const GtkHTML *html)
4235 g_return_val_if_fail (html != NULL, FALSE);
4236 g_return_val_if_fail (GTK_IS_HTML (html), FALSE);
4238 return html->priv->magic_smileys;
4241 static void
4242 frame_set_animate (HTMLObject *o,
4243 HTMLEngine *e,
4244 gpointer data)
4246 if (HTML_IS_FRAME (o)) {
4247 html_image_factory_set_animate (GTK_HTML (HTML_FRAME (o)->html)->engine->image_factory,
4248 *(gboolean *)data);
4249 } else if (HTML_IS_IFRAME (o)) {
4250 html_image_factory_set_animate (GTK_HTML (HTML_IFRAME (o)->html)->engine->image_factory,
4251 *(gboolean *)data);
4255 void
4256 gtk_html_set_caret_mode (GtkHTML *html,
4257 gboolean caret_mode)
4259 g_return_if_fail (GTK_IS_HTML (html));
4260 g_return_if_fail (HTML_IS_ENGINE (html->engine));
4262 set_caret_mode (html->engine, caret_mode);
4265 gboolean
4266 gtk_html_get_caret_mode (const GtkHTML *html)
4268 g_return_val_if_fail (GTK_IS_HTML (html), FALSE);
4269 g_return_val_if_fail (HTML_IS_ENGINE (html->engine), FALSE);
4271 return html->engine->caret_mode;
4275 * gtk_html_set_caret_first_focus_anchor:
4276 * When setting focus to the GtkHTML first time and is in caret mode,
4277 * then looks for an anchor of name @param name and tries to set focus
4278 * just after it. If NULL, then behaves as before.
4280 * @param html GtkHTML instance.
4281 * @param name Name of the anchor to be set the first focus just after it,
4282 * or NULL to not look for the anchor.
4284 void
4285 gtk_html_set_caret_first_focus_anchor (GtkHTML *html,
4286 const gchar *name)
4288 g_return_if_fail (GTK_IS_HTML (html));
4289 g_return_if_fail (html->priv != NULL);
4291 g_free (html->priv->caret_first_focus_anchor);
4292 html->priv->caret_first_focus_anchor = g_strdup (name);
4295 void
4296 gtk_html_set_animate (GtkHTML *html,
4297 gboolean animate)
4299 g_return_if_fail (GTK_IS_HTML (html));
4300 g_return_if_fail (HTML_IS_ENGINE (html->engine));
4302 html_image_factory_set_animate (html->engine->image_factory, animate);
4303 if (html->engine->clue)
4304 html_object_forall (html->engine->clue, html->engine, frame_set_animate, &animate);
4307 gboolean
4308 gtk_html_get_animate (const GtkHTML *html)
4310 g_return_val_if_fail (GTK_IS_HTML (html), FALSE);
4311 g_return_val_if_fail (HTML_IS_ENGINE (html->engine), FALSE);
4313 return html_image_factory_get_animate (html->engine->image_factory);
4316 void
4317 gtk_html_load_empty (GtkHTML *html)
4319 g_return_if_fail (html != NULL);
4320 g_return_if_fail (GTK_IS_HTML (html));
4322 html_engine_load_empty (html->engine);
4325 void
4326 gtk_html_load_from_string (GtkHTML *html,
4327 const gchar *str,
4328 gint len)
4330 GtkHTMLStream *stream;
4332 stream = gtk_html_begin_content (html, "text/html; charset=utf-8");
4333 gtk_html_stream_write (stream, str, (len == -1) ? strlen (str) : len);
4334 gtk_html_stream_close (stream, GTK_HTML_STREAM_OK);
4337 void
4338 gtk_html_set_base (GtkHTML *html,
4339 const gchar *url)
4341 g_return_if_fail (GTK_IS_HTML (html));
4343 g_free (html->priv->base_url);
4344 html->priv->base_url = g_strdup (url);
4347 const gchar *
4348 gtk_html_get_base (GtkHTML *html)
4350 g_return_val_if_fail (GTK_IS_HTML (html), NULL);
4352 return html->priv->base_url;
4356 /* Printing. */
4357 void
4358 gtk_html_print_page (GtkHTML *html,
4359 GtkPrintContext *context)
4361 g_return_if_fail (html != NULL);
4362 g_return_if_fail (GTK_IS_HTML (html));
4364 html_engine_print (html->engine, context, .0, .0, NULL, NULL, NULL);
4367 void
4368 gtk_html_print_page_with_header_footer (GtkHTML *html,
4369 GtkPrintContext *context,
4370 gdouble header_height,
4371 gdouble footer_height,
4372 GtkHTMLPrintCallback header_print,
4373 GtkHTMLPrintCallback footer_print,
4374 gpointer user_data)
4376 g_return_if_fail (html != NULL);
4377 g_return_if_fail (GTK_IS_HTML (html));
4379 html_engine_print (
4380 html->engine, context, header_height, footer_height,
4381 header_print, footer_print, user_data);
4385 /* Editing. */
4387 void
4388 gtk_html_set_paragraph_style (GtkHTML *html,
4389 GtkHTMLParagraphStyle style)
4391 HTMLClueFlowStyle current_style;
4392 HTMLClueFlowStyle clueflow_style;
4393 HTMLListType item_type;
4394 HTMLListType cur_item_type;
4396 g_return_if_fail (html != NULL);
4397 g_return_if_fail (GTK_IS_HTML (html));
4399 /* FIXME precondition: check if it's a valid style. */
4401 paragraph_style_to_clueflow_style (style, &clueflow_style, &item_type);
4403 html_engine_get_current_clueflow_style (html->engine, &current_style, &cur_item_type);
4404 if (!html_engine_is_selection_active (html->engine) && current_style == clueflow_style
4405 && (current_style != HTML_CLUEFLOW_STYLE_LIST_ITEM || item_type == cur_item_type))
4406 return;
4408 if (!html_engine_set_clueflow_style (html->engine, clueflow_style, item_type, 0, 0, NULL,
4409 HTML_ENGINE_SET_CLUEFLOW_STYLE, HTML_UNDO_UNDO, TRUE))
4410 return;
4412 html->priv->paragraph_style = style;
4414 g_signal_emit (html, signals[CURRENT_PARAGRAPH_STYLE_CHANGED], 0, style);
4415 queue_draw (html);
4418 GtkHTMLParagraphStyle
4419 gtk_html_get_paragraph_style (GtkHTML *html)
4421 HTMLClueFlowStyle style;
4422 HTMLListType item_type;
4424 html_engine_get_current_clueflow_style (html->engine, &style, &item_type);
4426 return clueflow_style_to_paragraph_style (style, item_type);
4429 guint
4430 gtk_html_get_paragraph_indentation (GtkHTML *html)
4432 return html_engine_get_current_clueflow_indentation (html->engine);
4435 void
4436 gtk_html_set_indent (GtkHTML *html,
4437 GByteArray *levels)
4439 g_return_if_fail (html != NULL);
4440 g_return_if_fail (GTK_IS_HTML (html));
4442 html_engine_set_clueflow_style (html->engine, 0, 0, 0,
4443 levels ? levels->len : 0,
4444 levels ? levels->data : NULL,
4445 HTML_ENGINE_SET_CLUEFLOW_INDENTATION, HTML_UNDO_UNDO, TRUE);
4447 gtk_html_update_styles (html);
4450 static void
4451 gtk_html_modify_indent_by_delta (GtkHTML *html,
4452 gint delta,
4453 guint8 *levels)
4455 g_return_if_fail (html != NULL);
4456 g_return_if_fail (GTK_IS_HTML (html));
4458 html_engine_set_clueflow_style (html->engine, 0, 0, 0, delta, levels,
4459 HTML_ENGINE_SET_CLUEFLOW_INDENTATION_DELTA, HTML_UNDO_UNDO, TRUE);
4461 gtk_html_update_styles (html);
4464 void
4465 gtk_html_indent_push_level (GtkHTML *html,
4466 HTMLListType level_type)
4468 guint8 type = (guint8) level_type;
4469 gtk_html_modify_indent_by_delta (html, +1, &type);
4472 void
4473 gtk_html_indent_pop_level (GtkHTML *html)
4475 gtk_html_modify_indent_by_delta (html, -1, NULL);
4478 void
4479 gtk_html_set_font_style (GtkHTML *html,
4480 GtkHTMLFontStyle and_mask,
4481 GtkHTMLFontStyle or_mask)
4483 g_return_if_fail (html != NULL);
4484 g_return_if_fail (GTK_IS_HTML (html));
4486 if (html_engine_set_font_style (html->engine, and_mask, or_mask))
4487 g_signal_emit (html, signals[INSERTION_FONT_STYLE_CHANGED], 0, html->engine->insertion_font_style);
4490 void
4491 gtk_html_set_color (GtkHTML *html,
4492 HTMLColor *color)
4494 g_return_if_fail (html != NULL);
4495 g_return_if_fail (GTK_IS_HTML (html));
4497 if (html_engine_set_color (html->engine, color))
4498 g_signal_emit (html, signals[INSERTION_COLOR_CHANGED], 0, html->engine->insertion_color);
4501 void
4502 gtk_html_toggle_font_style (GtkHTML *html,
4503 GtkHTMLFontStyle style)
4505 g_return_if_fail (html != NULL);
4506 g_return_if_fail (GTK_IS_HTML (html));
4508 if (HTML_IS_PLAIN_PAINTER (html->engine->painter))
4509 return;
4511 if (html_engine_toggle_font_style (html->engine, style))
4512 g_signal_emit (html, signals[INSERTION_FONT_STYLE_CHANGED], 0, html->engine->insertion_font_style);
4515 GtkHTMLParagraphAlignment
4516 gtk_html_get_paragraph_alignment (GtkHTML *html)
4518 /* This makes the function return a HTMLHalignType. Should the below
4519 * call really be html_alignment_to_paragraph()? */
4521 return paragraph_alignment_to_html (html_engine_get_current_clueflow_alignment (html->engine));
4524 void
4525 gtk_html_set_paragraph_alignment (GtkHTML *html,
4526 GtkHTMLParagraphAlignment alignment)
4528 HTMLHAlignType align;
4530 g_return_if_fail (html != NULL);
4531 g_return_if_fail (GTK_IS_HTML (html));
4533 align = paragraph_alignment_to_html (alignment);
4535 if (html_engine_set_clueflow_style (html->engine, 0, 0, align, 0, NULL,
4536 HTML_ENGINE_SET_CLUEFLOW_ALIGNMENT, HTML_UNDO_UNDO, TRUE)) {
4537 html->priv->paragraph_alignment = alignment;
4538 g_signal_emit (html, signals[CURRENT_PARAGRAPH_ALIGNMENT_CHANGED], 0, alignment);
4543 /* Clipboard operations. */
4545 static void
4546 free_contents (ClipboardContents *contents)
4548 if (contents->html_text)
4549 g_free (contents->html_text);
4550 if (contents->plain_text)
4551 g_free (contents->plain_text);
4553 contents->html_text = NULL;
4554 contents->plain_text = NULL;
4556 g_free (contents);
4559 static void
4560 clipboard_get_contents_cb (GtkClipboard *clipboard,
4561 GtkSelectionData *selection_data,
4562 guint info,
4563 gpointer data)
4565 ClipboardContents *contents = (ClipboardContents *) data;
4567 if (info == TARGET_HTML && contents->html_text) {
4568 gtk_selection_data_set (selection_data,
4569 gdk_atom_intern ("text/html", FALSE), 8,
4570 (const guchar *) contents->html_text,
4571 (gint) strlen (contents->html_text));
4572 } else if (contents->plain_text) {
4573 gtk_selection_data_set_text (selection_data,
4574 contents->plain_text,
4575 (gint) strlen (contents->plain_text));
4579 static void
4580 clipboard_clear_contents_cb (GtkClipboard *clipboard,
4581 gpointer data)
4583 ClipboardContents *contents = (ClipboardContents *) data;
4585 free_contents (contents);
4588 static void
4589 clipboard_paste_received_cb (GtkClipboard *clipboard,
4590 GtkSelectionData *selection_data,
4591 gpointer user_data)
4593 gint i = 0;
4594 GtkWidget *widget = GTK_WIDGET (user_data);
4595 GdkAtom data_type;
4596 GdkAtom target;
4597 gboolean as_cite;
4598 HTMLEngine *e;
4599 const guchar *data;
4600 gint length;
4602 e = GTK_HTML (widget)->engine;
4603 as_cite = GTK_HTML (widget)->priv->selection_as_cite;
4605 data = gtk_selection_data_get_data (selection_data);
4606 length = gtk_selection_data_get_length (selection_data);
4607 target = gtk_selection_data_get_target (selection_data);
4608 data_type = gtk_selection_data_get_data_type (selection_data);
4610 if (length > 0) {
4611 gchar *utf8 = NULL;
4613 if (data_type == gdk_atom_intern (selection_targets[TARGET_HTML].target, FALSE)) {
4614 if (length > 1 &&
4615 !g_utf8_validate ((const gchar *) data, length - 1, NULL)) {
4616 utf8 = utf16_to_utf8_with_bom_check ((guchar *) data, length);
4618 } else {
4619 utf8 = utf8_filter_out_bom (g_strndup ((const gchar *) data, length));
4622 if (as_cite && utf8) {
4623 gchar *cite;
4625 cite = g_strdup_printf ("<br><blockquote type=\"cite\">%s</blockquote>", utf8);
4627 g_free (utf8);
4628 utf8 = cite;
4630 if (utf8) {
4631 gint leading_spaces = 0;
4633 /* check for leading spaces */
4634 while (g_ascii_isspace (utf8[leading_spaces]) &&
4635 utf8[leading_spaces] != '\n' &&
4636 utf8[leading_spaces] != '\r')
4637 leading_spaces++;
4639 if (leading_spaces)
4640 html_engine_paste_text (e, utf8, leading_spaces);
4642 if (utf8[leading_spaces])
4643 gtk_html_insert_html (GTK_HTML (widget), utf8 + leading_spaces);
4645 /* check for trailing spaces */
4646 length = g_utf8_strlen (utf8, -1);
4647 if (length > leading_spaces) {
4648 const gchar *ptr, *from = NULL;
4650 ptr = utf8;
4651 while (ptr = g_utf8_next_char (ptr), ptr && *ptr) {
4652 if (g_ascii_isspace (*ptr) && *ptr != '\n' && *ptr != '\r') {
4653 if (!from)
4654 from = ptr;
4655 } else {
4656 from = NULL;
4660 if (from)
4661 html_engine_paste_text (e, from, g_utf8_strlen (from, -1));
4663 } else
4664 g_warning ("selection was empty");
4665 } else if ((utf8 = (gchar *) gtk_selection_data_get_text (selection_data))) {
4666 utf8 = utf8_filter_out_bom (utf8);
4667 if (as_cite) {
4668 gchar *encoded;
4670 encoded = html_encode_entities (utf8, g_utf8_strlen (utf8, -1), NULL);
4671 g_free (utf8);
4672 utf8 = g_strdup_printf ("<br><pre><blockquote type=\"cite\">%s</blockquote></pre>",
4673 encoded);
4674 g_free (encoded);
4675 gtk_html_insert_html (GTK_HTML (widget), utf8);
4676 } else {
4677 html_engine_paste_text (e, utf8, g_utf8_strlen (utf8, -1));
4680 if (HTML_IS_TEXT (e->cursor->object))
4681 html_text_magic_link (HTML_TEXT (e->cursor->object), e, 1);
4684 if (utf8)
4685 g_free (utf8);
4687 return;
4690 while (i < n_selection_targets - 1) {
4691 if (target == gdk_atom_intern (selection_targets[i].target, FALSE))
4692 break;
4693 i++;
4696 if (i < n_selection_targets - 1) {
4697 GTK_HTML (widget)->priv->selection_type = i + 1;
4698 gtk_clipboard_request_contents (clipboard,
4699 gdk_atom_intern (selection_targets[i + 1].target, FALSE),
4700 clipboard_paste_received_cb,
4701 widget);
4705 static ClipboardContents *
4706 create_clipboard_contents (GtkHTML *html)
4708 ClipboardContents *contents;
4709 gint html_len, text_len;
4711 contents = g_new0 (ClipboardContents, 1);
4713 /* set html text */
4714 contents->html_text = get_selection_string (html, &html_len, FALSE, FALSE, TRUE);
4716 /* set plain text */
4717 contents->plain_text = get_selection_string (html, &text_len, FALSE, FALSE, FALSE);
4719 return contents;
4722 void
4723 gtk_html_cut (GtkHTML *html)
4725 GtkClipboard *clipboard;
4726 ClipboardContents *contents;
4728 g_return_if_fail (html != NULL);
4729 g_return_if_fail (GTK_IS_HTML (html));
4731 html_engine_cut (html->engine);
4733 contents = create_clipboard_contents (html);
4735 clipboard = gtk_widget_get_clipboard (GTK_WIDGET (html), GDK_SELECTION_CLIPBOARD);
4737 if (!gtk_clipboard_set_with_data (clipboard, selection_targets, n_selection_targets,
4738 clipboard_get_contents_cb,
4739 clipboard_clear_contents_cb,
4740 contents)) {
4741 free_contents (contents);
4742 } else {
4743 gtk_clipboard_set_can_store (clipboard,
4744 selection_targets + 1,
4745 n_selection_targets - 1);
4749 void
4750 gtk_html_copy (GtkHTML *html)
4752 GtkClipboard *clipboard;
4753 ClipboardContents *contents;
4755 g_return_if_fail (html != NULL);
4756 g_return_if_fail (GTK_IS_HTML (html));
4758 html_engine_copy (html->engine);
4760 contents = create_clipboard_contents (html);
4762 clipboard = gtk_widget_get_clipboard (GTK_WIDGET (html), GDK_SELECTION_CLIPBOARD);
4764 if (!gtk_clipboard_set_with_data (clipboard, selection_targets, n_selection_targets,
4765 clipboard_get_contents_cb,
4766 clipboard_clear_contents_cb,
4767 contents)) {
4768 free_contents (contents);
4770 gtk_clipboard_set_can_store (clipboard,
4771 NULL,
4775 void
4776 gtk_html_paste (GtkHTML *html,
4777 gboolean as_cite)
4779 g_return_if_fail (html != NULL);
4780 g_return_if_fail (GTK_IS_HTML (html));
4782 g_object_ref (html);
4783 html->priv->selection_as_cite = as_cite;
4784 html->priv->selection_type = 0;
4786 gtk_clipboard_request_contents (gtk_widget_get_clipboard (GTK_WIDGET (html), GDK_SELECTION_CLIPBOARD),
4787 gdk_atom_intern (selection_targets[0].target, FALSE),
4788 clipboard_paste_received_cb, html);
4791 static void
4792 update_primary_selection (GtkHTML *html)
4794 GtkClipboard *clipboard;
4795 gint text_len;
4796 gchar *text;
4798 g_return_if_fail (html != NULL);
4799 g_return_if_fail (GTK_IS_HTML (html));
4801 if (!html->allow_selection)
4802 return;
4804 text = get_selection_string (html, &text_len, FALSE, TRUE, FALSE);
4805 if (!text)
4806 return;
4808 clipboard = gtk_widget_get_clipboard (GTK_WIDGET (html), GDK_SELECTION_PRIMARY);
4810 gtk_clipboard_set_text (clipboard, text, text_len);
4812 g_free (text);
4816 /* Undo/redo. */
4818 void
4819 gtk_html_undo (GtkHTML *html)
4821 g_return_if_fail (html != NULL);
4822 g_return_if_fail (GTK_IS_HTML (html));
4824 html_engine_undo (html->engine);
4825 gtk_html_update_styles (html);
4828 void
4829 gtk_html_redo (GtkHTML *html)
4831 g_return_if_fail (html != NULL);
4832 g_return_if_fail (GTK_IS_HTML (html));
4834 html_engine_redo (html->engine);
4835 gtk_html_update_styles (html);
4838 /* misc utils */
4839 /* if engine_type == false - default behaviour*/
4840 void
4841 gtk_html_set_default_engine (GtkHTML *html,
4842 gboolean engine_type)
4844 html_engine_set_engine_type (html->engine, engine_type);
4847 gboolean
4848 gtk_html_get_default_engine (GtkHTML *html)
4850 return html_engine_get_engine_type (html->engine);
4853 void
4854 gtk_html_set_default_content_type (GtkHTML *html,
4855 const gchar *content_type)
4857 html_engine_set_content_type (html->engine, content_type);
4860 const gchar *
4861 gtk_html_get_default_content_type (GtkHTML *html)
4863 return html_engine_get_content_type (html->engine);
4866 gpointer
4867 gtk_html_get_object_by_id (GtkHTML *html,
4868 const gchar *id)
4870 g_return_val_if_fail (html, NULL);
4871 g_return_val_if_fail (id, NULL);
4872 g_return_val_if_fail (GTK_IS_HTML (html), NULL);
4873 g_return_val_if_fail (html->engine, NULL);
4875 return html_engine_get_object_by_id (html->engine, id);
4878 /*******************************************
4880 * keybindings
4884 static gint
4885 get_line_height (GtkHTML *html)
4887 gint w, a, d;
4889 if (!html->engine || !html->engine->painter)
4890 return 0;
4892 html_painter_set_font_style (html->engine->painter, GTK_HTML_FONT_STYLE_SIZE_3);
4893 html_painter_set_font_face (html->engine->painter, NULL);
4894 html_painter_calc_text_size (html->engine->painter, "a", 1, &w, &a, &d);
4896 return a + d;
4899 static void
4900 scroll (GtkHTML *html,
4901 GtkOrientation orientation,
4902 GtkScrollType scroll_type,
4903 gfloat position)
4905 GtkAdjustment *adjustment;
4906 gint line_height;
4907 gfloat delta;
4908 gdouble value;
4909 gdouble lower;
4910 gdouble upper;
4911 gdouble page_size;
4912 gdouble page_increment;
4913 gdouble step_increment;
4915 /* we dont want scroll in editable (move cursor instead) */
4916 if (html_engine_get_editable (html->engine) || html->engine->caret_mode)
4917 return;
4919 adjustment = (orientation == GTK_ORIENTATION_VERTICAL) ?
4920 gtk_layout_get_vadjustment (GTK_LAYOUT (html)) :
4921 gtk_layout_get_hadjustment (GTK_LAYOUT (html));
4923 value = gtk_adjustment_get_value (adjustment);
4924 lower = gtk_adjustment_get_lower (adjustment);
4925 upper = gtk_adjustment_get_upper (adjustment);
4926 page_size = gtk_adjustment_get_page_size (adjustment);
4927 page_increment = gtk_adjustment_get_page_increment (adjustment);
4928 step_increment = gtk_adjustment_get_step_increment (adjustment);
4930 line_height = (html->engine && page_increment > (3 * get_line_height (html)))
4931 ? get_line_height (html)
4932 : 0;
4934 switch (scroll_type) {
4935 case GTK_SCROLL_STEP_FORWARD:
4936 delta = step_increment;
4937 break;
4938 case GTK_SCROLL_STEP_BACKWARD:
4939 delta = -step_increment;
4940 break;
4941 case GTK_SCROLL_PAGE_FORWARD:
4942 delta = page_increment - line_height;
4943 break;
4944 case GTK_SCROLL_PAGE_BACKWARD:
4945 delta = -page_increment + line_height;
4946 break;
4947 default:
4948 g_warning ("invalid scroll parameters: %d %d %f\n", orientation, scroll_type, position);
4949 return;
4952 if (position == 1.0) {
4953 if (lower > (value + delta)) {
4954 if (lower >= value) {
4955 html->binding_handled = FALSE;
4956 return;
4958 } else if (MAX (0.0, upper - page_size) < (value + delta)) {
4960 if (MAX (0.0, upper - page_size) <= value) {
4961 html->binding_handled = FALSE;
4962 return;
4967 gtk_adjustment_set_value (adjustment, CLAMP (value + delta, lower, MAX (0.0, upper - page_size)));
4969 html->binding_handled = TRUE;
4972 static gboolean
4973 scroll_command (GtkHTML *html,
4974 GtkScrollType scroll_type)
4976 GtkAdjustment *adjustment;
4977 gint line_height;
4978 gfloat delta = 0;
4979 gdouble value;
4980 gdouble lower;
4981 gdouble upper;
4982 gdouble page_increment;
4983 gdouble page_size;
4985 /* we dont want scroll in editable (move cursor instead) */
4986 if (html_engine_get_editable (html->engine))
4987 return FALSE;
4989 adjustment = gtk_layout_get_vadjustment (GTK_LAYOUT (html));
4990 value = gtk_adjustment_get_value (adjustment);
4991 lower = gtk_adjustment_get_lower (adjustment);
4992 upper = gtk_adjustment_get_upper (adjustment);
4993 page_increment = gtk_adjustment_get_page_increment (adjustment);
4994 page_size = gtk_adjustment_get_page_size (adjustment);
4996 line_height = (html->engine && page_increment > (3 * get_line_height (html)))
4997 ? get_line_height (html)
4998 : 0;
5000 switch (scroll_type) {
5001 case GTK_SCROLL_PAGE_FORWARD:
5002 delta = page_increment - line_height;
5003 break;
5004 case GTK_SCROLL_PAGE_BACKWARD:
5005 delta = -page_increment + line_height;
5006 break;
5007 default:
5008 break;
5009 return FALSE;
5012 d_s (printf ("%f %f %f\n", value + delta, lower, MAX (0.0, upper - page_size));)
5014 if (lower > (value + delta)) {
5015 if (lower >= value)
5016 return FALSE;
5017 } else if (MAX (0.0, upper - page_size) < (value + delta)) {
5019 if (MAX (0.0, upper - page_size) <= value) {
5020 return FALSE;
5024 gtk_adjustment_set_value (adjustment, CLAMP (value + delta, lower, MAX (0.0, upper - page_size)));
5026 return TRUE;
5029 static void
5030 scroll_by_amount (GtkHTML *html,
5031 gint amount)
5033 GtkAdjustment *adjustment;
5034 gdouble value;
5035 gdouble lower;
5036 gdouble upper;
5037 gdouble page_size;
5039 adjustment = gtk_layout_get_vadjustment (GTK_LAYOUT (html));
5040 value = gtk_adjustment_get_value (adjustment);
5041 lower = gtk_adjustment_get_lower (adjustment);
5042 upper = gtk_adjustment_get_upper (adjustment);
5043 page_size = gtk_adjustment_get_page_size (adjustment);
5045 gtk_adjustment_set_value (
5046 adjustment, CLAMP (value + (gfloat) amount,
5047 lower, MAX (0.0, upper - page_size)));
5050 static void
5051 cursor_move (GtkHTML *html,
5052 GtkDirectionType dir_type,
5053 GtkHTMLCursorSkipType skip)
5055 gint amount;
5057 if (!html->engine->caret_mode && !html_engine_get_editable (html->engine))
5058 return;
5060 html->priv->cursor_moved = TRUE;
5062 if (skip == GTK_HTML_CURSOR_SKIP_NONE) {
5063 update_primary_selection (html);
5064 g_signal_emit (GTK_HTML (html), signals[CURSOR_CHANGED], 0);
5065 return;
5068 if (html->engine->selection_mode) {
5069 if (!html->engine->mark)
5070 html_engine_set_mark (html->engine);
5071 } else if (html->engine->shift_selection || html->engine->mark) {
5072 html_engine_disable_selection (html->engine);
5073 html_engine_edit_selection_updater_schedule (html->engine->selection_updater);
5074 html->engine->shift_selection = FALSE;
5076 switch (skip) {
5077 case GTK_HTML_CURSOR_SKIP_ONE:
5078 switch (dir_type) {
5079 case GTK_DIR_LEFT:
5080 html_engine_move_cursor (html->engine, HTML_ENGINE_CURSOR_LEFT, 1);
5081 break;
5082 case GTK_DIR_RIGHT:
5083 html_engine_move_cursor (html->engine, HTML_ENGINE_CURSOR_RIGHT, 1);
5084 break;
5085 case GTK_DIR_UP:
5086 html_engine_move_cursor (html->engine, HTML_ENGINE_CURSOR_UP, 1);
5087 break;
5088 case GTK_DIR_DOWN:
5089 html_engine_move_cursor (html->engine, HTML_ENGINE_CURSOR_DOWN, 1);
5090 break;
5091 default:
5092 g_warning ("invalid cursor_move parameters\n");
5094 break;
5095 case GTK_HTML_CURSOR_SKIP_WORD:
5096 switch (dir_type) {
5097 case GTK_DIR_UP:
5098 case GTK_DIR_LEFT:
5099 html_engine_backward_word (html->engine);
5100 break;
5101 case GTK_DIR_DOWN:
5102 case GTK_DIR_RIGHT:
5103 html_engine_forward_word (html->engine);
5104 break;
5105 default:
5106 g_warning ("invalid cursor_move parameters\n");
5108 break;
5109 case GTK_HTML_CURSOR_SKIP_PAGE: {
5110 GtkAllocation allocation;
5111 gint line_height;
5113 gtk_widget_get_allocation (GTK_WIDGET (html), &allocation);
5114 line_height = allocation.height > (3 * get_line_height (html))
5115 ? get_line_height (html) : 0;
5117 switch (dir_type) {
5118 case GTK_DIR_UP:
5119 case GTK_DIR_LEFT:
5120 if ((amount = html_engine_scroll_up (html->engine,
5121 allocation.height - line_height)) > 0)
5122 scroll_by_amount (html, - amount);
5123 break;
5124 case GTK_DIR_DOWN:
5125 case GTK_DIR_RIGHT:
5126 if ((amount = html_engine_scroll_down (html->engine,
5127 allocation.height - line_height)) > 0)
5128 scroll_by_amount (html, amount);
5129 break;
5130 default:
5131 g_warning ("invalid cursor_move parameters\n");
5133 break;
5135 case GTK_HTML_CURSOR_SKIP_ALL:
5136 switch (dir_type) {
5137 case GTK_DIR_LEFT:
5138 html_engine_beginning_of_line (html->engine);
5139 break;
5140 case GTK_DIR_RIGHT:
5141 html_engine_end_of_line (html->engine);
5142 break;
5143 case GTK_DIR_UP:
5144 html_engine_beginning_of_document (html->engine);
5145 break;
5146 case GTK_DIR_DOWN:
5147 html_engine_end_of_document (html->engine);
5148 break;
5149 default:
5150 g_warning ("invalid cursor_move parameters\n");
5152 break;
5153 default:
5154 g_warning ("invalid cursor_move parameters\n");
5157 html->binding_handled = TRUE;
5158 html->priv->update_styles = TRUE;
5159 gtk_html_edit_make_cursor_visible (html);
5160 html_engine_update_selection_active_state (html->engine, html->priv->event_time);
5161 update_primary_selection (html);
5162 g_signal_emit (GTK_HTML (html), signals[CURSOR_CHANGED], 0);
5165 static gboolean
5166 move_selection (GtkHTML *html,
5167 GtkHTMLCommandType com_type)
5169 GtkAllocation allocation;
5170 gboolean rv;
5171 gint amount;
5173 if (!html_engine_get_editable (html->engine) && !html->engine->caret_mode)
5174 return FALSE;
5176 gtk_widget_get_allocation (GTK_WIDGET (html), &allocation);
5178 html->priv->cursor_moved = TRUE;
5179 html->engine->shift_selection = TRUE;
5180 if (!html->engine->mark)
5181 html_engine_set_mark (html->engine);
5182 switch (com_type) {
5183 case GTK_HTML_COMMAND_MODIFY_SELECTION_UP:
5184 rv = html_engine_move_cursor (html->engine, HTML_ENGINE_CURSOR_UP, 1) > 0 ? TRUE : FALSE;
5185 break;
5186 case GTK_HTML_COMMAND_MODIFY_SELECTION_DOWN:
5187 rv = html_engine_move_cursor (html->engine, HTML_ENGINE_CURSOR_DOWN, 1) > 0 ? TRUE : FALSE;
5188 break;
5189 case GTK_HTML_COMMAND_MODIFY_SELECTION_LEFT:
5190 rv = html_engine_move_cursor (html->engine, HTML_ENGINE_CURSOR_LEFT, 1) > 0 ? TRUE : FALSE;
5191 break;
5192 case GTK_HTML_COMMAND_MODIFY_SELECTION_RIGHT:
5193 rv = html_engine_move_cursor (html->engine, HTML_ENGINE_CURSOR_RIGHT, 1) > 0 ? TRUE : FALSE;
5194 break;
5195 case GTK_HTML_COMMAND_MODIFY_SELECTION_BOL:
5196 rv = html_engine_beginning_of_line (html->engine);
5197 break;
5198 case GTK_HTML_COMMAND_MODIFY_SELECTION_EOL:
5199 rv = html_engine_end_of_line (html->engine);
5200 break;
5201 case GTK_HTML_COMMAND_MODIFY_SELECTION_BOD:
5202 html_engine_beginning_of_document (html->engine);
5203 rv = TRUE;
5204 break;
5205 case GTK_HTML_COMMAND_MODIFY_SELECTION_EOD:
5206 html_engine_end_of_document (html->engine);
5207 rv = TRUE;
5208 break;
5209 case GTK_HTML_COMMAND_MODIFY_SELECTION_PREV_WORD:
5210 rv = html_engine_backward_word (html->engine);
5211 break;
5212 case GTK_HTML_COMMAND_MODIFY_SELECTION_NEXT_WORD:
5213 rv = html_engine_forward_word (html->engine);
5214 break;
5215 case GTK_HTML_COMMAND_MODIFY_SELECTION_PAGEUP:
5216 if ((amount = html_engine_scroll_up (html->engine, allocation.height)) > 0) {
5217 scroll_by_amount (html, - amount);
5218 rv = TRUE;
5219 } else
5220 rv = FALSE;
5221 break;
5222 case GTK_HTML_COMMAND_MODIFY_SELECTION_PAGEDOWN:
5223 if ((amount = html_engine_scroll_down (html->engine, allocation.height)) > 0) {
5224 scroll_by_amount (html, amount);
5225 rv = TRUE;
5226 } else
5227 rv = FALSE;
5228 break;
5229 default:
5230 g_warning ("invalid move_selection parameters\n");
5231 rv = FALSE;
5234 html->binding_handled = TRUE;
5235 html->priv->update_styles = TRUE;
5237 html_engine_update_selection_active_state (html->engine, html->priv->event_time);
5239 update_primary_selection (html);
5241 return rv;
5244 inline static void
5245 delete_one (HTMLEngine *e,
5246 gboolean forward)
5248 if (e->cursor->object && html_object_is_container (e->cursor->object)
5249 && ((forward && !e->cursor->offset) || (!forward && e->cursor->offset)))
5250 html_engine_delete_container (e);
5251 else
5252 html_engine_delete_n (e, 1, forward);
5255 inline static gboolean
5256 insert_tab_or_next_cell (GtkHTML *html)
5258 HTMLEngine *e = html->engine;
5259 if (!html_engine_next_cell (e, TRUE)) {
5260 if (html_clueflow_tabs (HTML_CLUEFLOW (e->cursor->object->parent), e->painter))
5261 html_engine_paste_text (e, "\t", 1);
5262 else
5263 html_engine_paste_text (e, "\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0", 4);
5264 return TRUE;
5267 return TRUE;
5270 inline static void
5271 indent_more_or_next_cell (GtkHTML *html)
5273 if (!html_engine_next_cell (html->engine, TRUE))
5274 gtk_html_indent_push_level (html, HTML_LIST_TYPE_BLOCKQUOTE);
5277 static gboolean
5278 command (GtkHTML *html,
5279 GtkHTMLCommandType com_type)
5281 HTMLEngine *e = html->engine;
5282 gboolean rv = TRUE;
5284 /* printf ("command %d %s\n", com_type, get_value_nick (com_type)); */
5285 html->binding_handled = TRUE;
5287 #define ensure_key_binding_not_editable() \
5288 if (html->priv->in_key_binding && html_engine_get_editable (e)) { \
5289 html->binding_handled = FALSE; \
5290 rv = FALSE; \
5291 break; \
5294 /* non-editable + editable commands */
5295 switch (com_type) {
5296 case GTK_HTML_COMMAND_ZOOM_IN:
5297 ensure_key_binding_not_editable ();
5298 gtk_html_zoom_in (html);
5299 break;
5300 case GTK_HTML_COMMAND_ZOOM_OUT:
5301 ensure_key_binding_not_editable ();
5302 gtk_html_zoom_out (html);
5303 break;
5304 case GTK_HTML_COMMAND_ZOOM_RESET:
5305 ensure_key_binding_not_editable ();
5306 gtk_html_zoom_reset (html);
5307 break;
5308 case GTK_HTML_COMMAND_SEARCH_INCREMENTAL_FORWARD:
5309 gtk_html_isearch (html, TRUE);
5310 break;
5311 case GTK_HTML_COMMAND_SEARCH_INCREMENTAL_BACKWARD:
5312 gtk_html_isearch (html, FALSE);
5313 break;
5314 case GTK_HTML_COMMAND_FOCUS_FORWARD:
5315 if (!html_engine_get_editable (e))
5316 html->binding_handled = gtk_widget_child_focus (GTK_WIDGET (html), GTK_DIR_TAB_FORWARD);
5317 break;
5318 case GTK_HTML_COMMAND_FOCUS_BACKWARD:
5319 if (!html_engine_get_editable (e))
5320 html->binding_handled = gtk_widget_child_focus (GTK_WIDGET (html), GTK_DIR_TAB_BACKWARD);
5321 break;
5322 case GTK_HTML_COMMAND_SCROLL_FORWARD:
5323 rv = scroll_command (html, GTK_SCROLL_PAGE_FORWARD);
5324 break;
5325 case GTK_HTML_COMMAND_SCROLL_BACKWARD:
5326 rv = scroll_command (html, GTK_SCROLL_PAGE_BACKWARD);
5327 break;
5328 case GTK_HTML_COMMAND_SCROLL_BOD:
5329 if (!html_engine_get_editable (e) && !e->caret_mode)
5330 gtk_adjustment_set_value (gtk_layout_get_vadjustment (GTK_LAYOUT (html)), 0);
5331 break;
5332 case GTK_HTML_COMMAND_SCROLL_EOD:
5333 if (!html_engine_get_editable (e) && !e->caret_mode) {
5334 GtkAdjustment *adjustment;
5335 gdouble upper, page_size;
5337 adjustment = gtk_layout_get_vadjustment (GTK_LAYOUT (html));
5338 upper = gtk_adjustment_get_upper (adjustment);
5339 page_size = gtk_adjustment_get_page_size (adjustment);
5340 gtk_adjustment_set_value (adjustment, upper - page_size);
5342 break;
5343 case GTK_HTML_COMMAND_COPY:
5344 gtk_html_copy (html);
5345 break;
5347 case GTK_HTML_COMMAND_MODIFY_SELECTION_UP:
5348 case GTK_HTML_COMMAND_MODIFY_SELECTION_DOWN:
5349 case GTK_HTML_COMMAND_MODIFY_SELECTION_LEFT:
5350 case GTK_HTML_COMMAND_MODIFY_SELECTION_RIGHT:
5351 case GTK_HTML_COMMAND_MODIFY_SELECTION_BOL:
5352 case GTK_HTML_COMMAND_MODIFY_SELECTION_EOL:
5353 case GTK_HTML_COMMAND_MODIFY_SELECTION_BOD:
5354 case GTK_HTML_COMMAND_MODIFY_SELECTION_EOD:
5355 case GTK_HTML_COMMAND_MODIFY_SELECTION_PAGEUP:
5356 case GTK_HTML_COMMAND_MODIFY_SELECTION_PAGEDOWN:
5357 case GTK_HTML_COMMAND_MODIFY_SELECTION_PREV_WORD:
5358 case GTK_HTML_COMMAND_MODIFY_SELECTION_NEXT_WORD:
5359 if (html->engine->caret_mode || html_engine_get_editable (e)) {
5360 gtk_im_context_reset (html->priv->im_context);
5361 rv = move_selection (html, com_type);
5363 break;
5364 case GTK_HTML_COMMAND_SELECT_ALL:
5365 gtk_html_select_all (html);
5366 break;
5367 case GTK_HTML_COMMAND_EDITABLE_ON:
5368 gtk_html_set_editable (html, TRUE);
5369 break;
5370 case GTK_HTML_COMMAND_EDITABLE_OFF:
5371 gtk_html_set_editable (html, FALSE);
5372 break;
5373 case GTK_HTML_COMMAND_BLOCK_SELECTION:
5374 html_engine_block_selection (html->engine);
5375 break;
5376 case GTK_HTML_COMMAND_UNBLOCK_SELECTION:
5377 html_engine_unblock_selection (html->engine);
5378 break;
5379 case GTK_HTML_COMMAND_IS_SELECTION_ACTIVE:
5380 rv = html_engine_is_selection_active (html->engine);
5381 break;
5382 case GTK_HTML_COMMAND_UNSELECT_ALL:
5383 gtk_html_unselect_all (html);
5384 break;
5386 default:
5387 rv = FALSE;
5388 html->binding_handled = FALSE;
5391 #undef ensure_key_binding_not_editable
5393 if (!html_engine_get_editable (e) || html->binding_handled)
5394 return rv;
5396 html->binding_handled = TRUE;
5397 html->priv->update_styles = FALSE;
5399 /* editable commands only */
5400 switch (com_type) {
5401 case GTK_HTML_COMMAND_UNDO:
5402 gtk_html_undo (html);
5403 break;
5404 case GTK_HTML_COMMAND_REDO:
5405 gtk_html_redo (html);
5406 break;
5407 case GTK_HTML_COMMAND_COPY_AND_DISABLE_SELECTION:
5408 gtk_html_copy (html);
5409 html_engine_disable_selection (e);
5410 html_engine_edit_selection_updater_schedule (e->selection_updater);
5411 e->selection_mode = FALSE;
5412 break;
5413 case GTK_HTML_COMMAND_CUT:
5414 gtk_html_cut (html);
5415 html->priv->update_styles = TRUE;
5416 break;
5417 case GTK_HTML_COMMAND_CUT_LINE:
5418 html_engine_cut_line (e);
5419 html->priv->update_styles = TRUE;
5420 break;
5421 case GTK_HTML_COMMAND_PASTE:
5422 gtk_html_paste (html, FALSE);
5423 html->priv->update_styles = TRUE;
5424 break;
5425 case GTK_HTML_COMMAND_INSERT_RULE:
5426 html_engine_insert_rule (e, 0, 100, 2, TRUE, HTML_HALIGN_LEFT);
5427 break;
5428 case GTK_HTML_COMMAND_INSERT_PARAGRAPH:
5429 html_engine_delete (e);
5431 /* stop inserting links after newlines */
5432 html_engine_set_insertion_link (e, NULL, NULL);
5434 html_engine_insert_empty_paragraph (e);
5435 html->priv->update_styles = TRUE;
5436 break;
5437 case GTK_HTML_COMMAND_DELETE:
5438 if (e->mark != NULL
5439 && e->mark->position != e->cursor->position)
5440 html_engine_delete (e);
5441 else
5442 delete_one (e, TRUE);
5443 html->priv->update_styles = TRUE;
5444 break;
5445 case GTK_HTML_COMMAND_DELETE_BACK:
5446 if (html_engine_is_selection_active (e))
5447 html_engine_delete (e);
5448 else
5449 delete_one (e, FALSE);
5450 html->priv->update_styles = TRUE;
5451 break;
5452 case GTK_HTML_COMMAND_DELETE_BACK_OR_INDENT_DEC:
5453 if (html_engine_is_selection_active (e))
5454 html_engine_delete (e);
5455 else if (html_engine_cursor_on_bop (e) && html_engine_get_indent (e) > 0
5456 && e->cursor->object->parent && HTML_IS_CLUEFLOW (e->cursor->object->parent)
5457 && HTML_CLUEFLOW (e->cursor->object->parent)->style != HTML_CLUEFLOW_STYLE_LIST_ITEM)
5458 gtk_html_indent_pop_level (html);
5459 else
5460 delete_one (e, FALSE);
5461 html->priv->update_styles = TRUE;
5462 break;
5463 case GTK_HTML_COMMAND_DELETE_TABLE:
5464 html_engine_delete_table (e);
5465 html->priv->update_styles = TRUE;
5466 break;
5467 case GTK_HTML_COMMAND_DELETE_TABLE_ROW:
5468 html_engine_delete_table_row (e);
5469 html->priv->update_styles = TRUE;
5470 break;
5471 case GTK_HTML_COMMAND_DELETE_TABLE_COLUMN:
5472 html_engine_delete_table_column (e);
5473 html->priv->update_styles = TRUE;
5474 break;
5475 case GTK_HTML_COMMAND_DELETE_TABLE_CELL_CONTENTS:
5476 html_engine_delete_table_cell_contents (e);
5477 html->priv->update_styles = TRUE;
5478 break;
5479 case GTK_HTML_COMMAND_SELECTION_MODE:
5480 e->selection_mode = TRUE;
5481 break;
5482 case GTK_HTML_COMMAND_DISABLE_SELECTION:
5483 html_engine_disable_selection (e);
5484 html_engine_edit_selection_updater_schedule (e->selection_updater);
5485 e->selection_mode = FALSE;
5486 break;
5487 case GTK_HTML_COMMAND_BOLD_ON:
5488 gtk_html_set_font_style (html, GTK_HTML_FONT_STYLE_MAX, GTK_HTML_FONT_STYLE_BOLD);
5489 break;
5490 case GTK_HTML_COMMAND_BOLD_OFF:
5491 gtk_html_set_font_style (html, ~GTK_HTML_FONT_STYLE_BOLD, 0);
5492 break;
5493 case GTK_HTML_COMMAND_BOLD_TOGGLE:
5494 gtk_html_toggle_font_style (html, GTK_HTML_FONT_STYLE_BOLD);
5495 break;
5496 case GTK_HTML_COMMAND_ITALIC_ON:
5497 gtk_html_set_font_style (html, GTK_HTML_FONT_STYLE_MAX, GTK_HTML_FONT_STYLE_ITALIC);
5498 break;
5499 case GTK_HTML_COMMAND_ITALIC_OFF:
5500 gtk_html_set_font_style (html, ~GTK_HTML_FONT_STYLE_ITALIC, 0);
5501 break;
5502 case GTK_HTML_COMMAND_ITALIC_TOGGLE:
5503 gtk_html_toggle_font_style (html, GTK_HTML_FONT_STYLE_ITALIC);
5504 break;
5505 case GTK_HTML_COMMAND_STRIKEOUT_ON:
5506 gtk_html_set_font_style (html, GTK_HTML_FONT_STYLE_MAX, GTK_HTML_FONT_STYLE_STRIKEOUT);
5507 break;
5508 case GTK_HTML_COMMAND_STRIKEOUT_OFF:
5509 gtk_html_set_font_style (html, ~GTK_HTML_FONT_STYLE_STRIKEOUT, 0);
5510 break;
5511 case GTK_HTML_COMMAND_STRIKEOUT_TOGGLE:
5512 gtk_html_toggle_font_style (html, GTK_HTML_FONT_STYLE_STRIKEOUT);
5513 break;
5514 case GTK_HTML_COMMAND_UNDERLINE_ON:
5515 gtk_html_set_font_style (html, GTK_HTML_FONT_STYLE_MAX, GTK_HTML_FONT_STYLE_UNDERLINE);
5516 break;
5517 case GTK_HTML_COMMAND_UNDERLINE_OFF:
5518 gtk_html_set_font_style (html, ~GTK_HTML_FONT_STYLE_UNDERLINE, 0);
5519 break;
5520 case GTK_HTML_COMMAND_UNDERLINE_TOGGLE:
5521 gtk_html_toggle_font_style (html, GTK_HTML_FONT_STYLE_UNDERLINE);
5522 break;
5523 case GTK_HTML_COMMAND_SIZE_MINUS_2:
5524 gtk_html_set_font_style (html, ~GTK_HTML_FONT_STYLE_SIZE_MASK, GTK_HTML_FONT_STYLE_SIZE_1);
5525 break;
5526 case GTK_HTML_COMMAND_SIZE_MINUS_1:
5527 gtk_html_set_font_style (html, ~GTK_HTML_FONT_STYLE_SIZE_MASK, GTK_HTML_FONT_STYLE_SIZE_2);
5528 break;
5529 case GTK_HTML_COMMAND_SIZE_PLUS_0:
5530 gtk_html_set_font_style (html, ~GTK_HTML_FONT_STYLE_SIZE_MASK, GTK_HTML_FONT_STYLE_SIZE_3);
5531 break;
5532 case GTK_HTML_COMMAND_SIZE_PLUS_1:
5533 gtk_html_set_font_style (html, ~GTK_HTML_FONT_STYLE_SIZE_MASK, GTK_HTML_FONT_STYLE_SIZE_4);
5534 break;
5535 case GTK_HTML_COMMAND_SIZE_PLUS_2:
5536 gtk_html_set_font_style (html, ~GTK_HTML_FONT_STYLE_SIZE_MASK, GTK_HTML_FONT_STYLE_SIZE_5);
5537 break;
5538 case GTK_HTML_COMMAND_SIZE_PLUS_3:
5539 gtk_html_set_font_style (html, ~GTK_HTML_FONT_STYLE_SIZE_MASK, GTK_HTML_FONT_STYLE_SIZE_6);
5540 break;
5541 case GTK_HTML_COMMAND_SIZE_PLUS_4:
5542 gtk_html_set_font_style (html, ~GTK_HTML_FONT_STYLE_SIZE_MASK, GTK_HTML_FONT_STYLE_SIZE_7);
5543 break;
5544 case GTK_HTML_COMMAND_SIZE_INCREASE:
5545 html_engine_font_size_inc_dec (e, TRUE);
5546 break;
5547 case GTK_HTML_COMMAND_SIZE_DECREASE:
5548 html_engine_font_size_inc_dec (e, FALSE);
5549 break;
5550 case GTK_HTML_COMMAND_ALIGN_LEFT:
5551 gtk_html_set_paragraph_alignment (html, GTK_HTML_PARAGRAPH_ALIGNMENT_LEFT);
5552 break;
5553 case GTK_HTML_COMMAND_ALIGN_CENTER:
5554 gtk_html_set_paragraph_alignment (html, GTK_HTML_PARAGRAPH_ALIGNMENT_CENTER);
5555 break;
5556 case GTK_HTML_COMMAND_ALIGN_RIGHT:
5557 gtk_html_set_paragraph_alignment (html, GTK_HTML_PARAGRAPH_ALIGNMENT_RIGHT);
5558 break;
5559 case GTK_HTML_COMMAND_INDENT_ZERO:
5560 gtk_html_set_indent (html, NULL);
5561 break;
5562 case GTK_HTML_COMMAND_INDENT_INC:
5563 gtk_html_indent_push_level (html, HTML_LIST_TYPE_BLOCKQUOTE);
5564 break;
5565 case GTK_HTML_COMMAND_INDENT_INC_OR_NEXT_CELL:
5566 indent_more_or_next_cell (html);
5567 break;
5568 case GTK_HTML_COMMAND_INSERT_TAB:
5569 if (!html_engine_is_selection_active (e)
5570 && html_clueflow_tabs (HTML_CLUEFLOW (e->cursor->object->parent), e->painter))
5571 html_engine_insert_text (e, "\t", 1);
5572 break;
5573 case GTK_HTML_COMMAND_INSERT_TAB_OR_INDENT_MORE:
5574 if (!html_engine_is_selection_active (e)
5575 && html_clueflow_tabs (HTML_CLUEFLOW (e->cursor->object->parent), e->painter))
5576 html_engine_insert_text (e, "\t", 1);
5577 else
5578 gtk_html_indent_push_level (html, HTML_LIST_TYPE_BLOCKQUOTE);
5579 break;
5580 case GTK_HTML_COMMAND_INSERT_TAB_OR_NEXT_CELL:
5581 html->binding_handled = insert_tab_or_next_cell (html);
5582 break;
5583 case GTK_HTML_COMMAND_INDENT_DEC:
5584 gtk_html_indent_pop_level (html);
5585 break;
5586 case GTK_HTML_COMMAND_PREV_CELL:
5587 html->binding_handled = html_engine_prev_cell (html->engine);
5588 break;
5589 case GTK_HTML_COMMAND_INDENT_PARAGRAPH:
5590 html_engine_indent_paragraph (e);
5591 break;
5592 case GTK_HTML_COMMAND_BREAK_AND_FILL_LINE:
5593 html_engine_break_and_fill_line (e);
5594 break;
5595 case GTK_HTML_COMMAND_SPACE_AND_FILL_LINE:
5596 html_engine_space_and_fill_line (e);
5597 break;
5598 case GTK_HTML_COMMAND_PARAGRAPH_STYLE_NORMAL:
5599 gtk_html_set_paragraph_style (html, GTK_HTML_PARAGRAPH_STYLE_NORMAL);
5600 break;
5601 case GTK_HTML_COMMAND_PARAGRAPH_STYLE_H1:
5602 gtk_html_set_paragraph_style (html, GTK_HTML_PARAGRAPH_STYLE_H1);
5603 break;
5604 case GTK_HTML_COMMAND_PARAGRAPH_STYLE_H2:
5605 gtk_html_set_paragraph_style (html, GTK_HTML_PARAGRAPH_STYLE_H2);
5606 break;
5607 case GTK_HTML_COMMAND_PARAGRAPH_STYLE_H3:
5608 gtk_html_set_paragraph_style (html, GTK_HTML_PARAGRAPH_STYLE_H3);
5609 break;
5610 case GTK_HTML_COMMAND_PARAGRAPH_STYLE_H4:
5611 gtk_html_set_paragraph_style (html, GTK_HTML_PARAGRAPH_STYLE_H4);
5612 break;
5613 case GTK_HTML_COMMAND_PARAGRAPH_STYLE_H5:
5614 gtk_html_set_paragraph_style (html, GTK_HTML_PARAGRAPH_STYLE_H5);
5615 break;
5616 case GTK_HTML_COMMAND_PARAGRAPH_STYLE_H6:
5617 gtk_html_set_paragraph_style (html, GTK_HTML_PARAGRAPH_STYLE_H6);
5618 break;
5619 case GTK_HTML_COMMAND_PARAGRAPH_STYLE_PRE:
5620 gtk_html_set_paragraph_style (html, GTK_HTML_PARAGRAPH_STYLE_PRE);
5621 break;
5622 case GTK_HTML_COMMAND_PARAGRAPH_STYLE_ADDRESS:
5623 gtk_html_set_paragraph_style (html, GTK_HTML_PARAGRAPH_STYLE_ADDRESS);
5624 break;
5625 case GTK_HTML_COMMAND_PARAGRAPH_STYLE_ITEMDOTTED:
5626 gtk_html_set_paragraph_style (html, GTK_HTML_PARAGRAPH_STYLE_ITEMDOTTED);
5627 break;
5628 case GTK_HTML_COMMAND_PARAGRAPH_STYLE_ITEMROMAN:
5629 gtk_html_set_paragraph_style (html, GTK_HTML_PARAGRAPH_STYLE_ITEMROMAN);
5630 break;
5631 case GTK_HTML_COMMAND_PARAGRAPH_STYLE_ITEMDIGIT:
5632 gtk_html_set_paragraph_style (html, GTK_HTML_PARAGRAPH_STYLE_ITEMDIGIT);
5633 break;
5634 case GTK_HTML_COMMAND_PARAGRAPH_STYLE_ITEMALPHA:
5635 gtk_html_set_paragraph_style (html, GTK_HTML_PARAGRAPH_STYLE_ITEMALPHA);
5636 break;
5637 case GTK_HTML_COMMAND_SELECT_WORD:
5638 gtk_html_select_word (html);
5639 break;
5640 case GTK_HTML_COMMAND_SELECT_LINE:
5641 gtk_html_select_line (html);
5642 break;
5643 case GTK_HTML_COMMAND_SELECT_PARAGRAPH:
5644 gtk_html_select_paragraph (html);
5645 break;
5646 case GTK_HTML_COMMAND_SELECT_PARAGRAPH_EXTENDED:
5647 gtk_html_select_paragraph_extended (html);
5648 break;
5649 case GTK_HTML_COMMAND_CURSOR_POSITION_SAVE:
5650 html_engine_edit_cursor_position_save (html->engine);
5651 break;
5652 case GTK_HTML_COMMAND_CURSOR_POSITION_RESTORE:
5653 html_engine_edit_cursor_position_restore (html->engine);
5654 break;
5655 case GTK_HTML_COMMAND_CAPITALIZE_WORD:
5656 html_engine_capitalize_word (e);
5657 break;
5658 case GTK_HTML_COMMAND_UPCASE_WORD:
5659 html_engine_upcase_downcase_word (e, TRUE);
5660 break;
5661 case GTK_HTML_COMMAND_DOWNCASE_WORD:
5662 html_engine_upcase_downcase_word (e, FALSE);
5663 break;
5664 case GTK_HTML_COMMAND_SPELL_SUGGEST:
5665 if (html->editor_api && !html_engine_spell_word_is_valid (e))
5666 (*html->editor_api->suggestion_request) (html, html->editor_data);
5667 break;
5668 case GTK_HTML_COMMAND_SPELL_PERSONAL_DICTIONARY_ADD:
5669 case GTK_HTML_COMMAND_SPELL_SESSION_DICTIONARY_ADD: {
5670 gchar *word;
5671 word = html_engine_get_spell_word (e);
5673 if (word && html->editor_api) {
5674 if (com_type == GTK_HTML_COMMAND_SPELL_PERSONAL_DICTIONARY_ADD)
5675 /* FIXME fire popup menu with more than 1 language enabled */
5676 (*html->editor_api->add_to_personal) (html, word, html_engine_get_language (html->engine), html->editor_data);
5677 else
5678 (*html->editor_api->add_to_session) (html, word, html->editor_data);
5679 g_free (word);
5680 html_engine_spell_check (e);
5681 gtk_widget_queue_draw (GTK_WIDGET (html));
5683 break;
5685 case GTK_HTML_COMMAND_CURSOR_FORWARD:
5686 rv = html_cursor_forward (html->engine->cursor, html->engine);
5687 break;
5688 case GTK_HTML_COMMAND_CURSOR_BACKWARD:
5689 rv = html_cursor_backward (html->engine->cursor, html->engine);
5690 break;
5691 case GTK_HTML_COMMAND_INSERT_TABLE_1_1:
5692 html_engine_insert_table_1_1 (e);
5693 break;
5694 case GTK_HTML_COMMAND_TABLE_INSERT_COL_BEFORE:
5695 html_engine_insert_table_column (e, FALSE);
5696 break;
5697 case GTK_HTML_COMMAND_TABLE_INSERT_COL_AFTER:
5698 html_engine_insert_table_column (e, TRUE);
5699 break;
5700 case GTK_HTML_COMMAND_TABLE_DELETE_COL:
5701 html_engine_delete_table_column (e);
5702 break;
5703 case GTK_HTML_COMMAND_TABLE_INSERT_ROW_BEFORE:
5704 html_engine_insert_table_row (e, FALSE);
5705 break;
5706 case GTK_HTML_COMMAND_TABLE_INSERT_ROW_AFTER:
5707 html_engine_insert_table_row (e, TRUE);
5708 break;
5709 case GTK_HTML_COMMAND_TABLE_DELETE_ROW:
5710 html_engine_delete_table_row (e);
5711 break;
5712 case GTK_HTML_COMMAND_TABLE_BORDER_WIDTH_INC:
5713 html_engine_table_set_border_width (e, html_engine_get_table (e), 1, TRUE);
5714 break;
5715 case GTK_HTML_COMMAND_TABLE_BORDER_WIDTH_DEC:
5716 html_engine_table_set_border_width (e, html_engine_get_table (e), -1, TRUE);
5717 break;
5718 case GTK_HTML_COMMAND_TABLE_BORDER_WIDTH_ZERO:
5719 html_engine_table_set_border_width (e, html_engine_get_table (e), 0, FALSE);
5720 break;
5721 case GTK_HTML_COMMAND_TABLE_SPACING_INC:
5722 html_engine_table_set_spacing (e, html_engine_get_table (e), 1, TRUE);
5723 break;
5724 case GTK_HTML_COMMAND_TABLE_SPACING_DEC:
5725 html_engine_table_set_spacing (e, html_engine_get_table (e), -1, TRUE);
5726 break;
5727 case GTK_HTML_COMMAND_TABLE_SPACING_ZERO:
5728 html_engine_table_set_spacing (e, html_engine_get_table (e), 0, FALSE);
5729 break;
5730 case GTK_HTML_COMMAND_TABLE_PADDING_INC:
5731 html_engine_table_set_padding (e, html_engine_get_table (e), 1, TRUE);
5732 break;
5733 case GTK_HTML_COMMAND_TABLE_PADDING_DEC:
5734 html_engine_table_set_padding (e, html_engine_get_table (e), -1, TRUE);
5735 break;
5736 case GTK_HTML_COMMAND_TABLE_PADDING_ZERO:
5737 html_engine_table_set_padding (e, html_engine_get_table (e), 0, FALSE);
5738 break;
5739 case GTK_HTML_COMMAND_TEXT_SET_DEFAULT_COLOR:
5740 html_engine_set_color (e, NULL);
5741 break;
5742 case GTK_HTML_COMMAND_CURSOR_BOD:
5743 html_engine_beginning_of_document (e);
5744 break;
5745 case GTK_HTML_COMMAND_CURSOR_EOD:
5746 html_engine_end_of_document (e);
5747 break;
5748 case GTK_HTML_COMMAND_BLOCK_REDRAW:
5749 html_engine_block_redraw (e);
5750 break;
5751 case GTK_HTML_COMMAND_UNBLOCK_REDRAW:
5752 html_engine_unblock_redraw (e);
5753 break;
5754 case GTK_HTML_COMMAND_GRAB_FOCUS:
5755 gtk_widget_grab_focus (GTK_WIDGET (html));
5756 break;
5757 case GTK_HTML_COMMAND_KILL_WORD:
5758 case GTK_HTML_COMMAND_KILL_WORD_BACKWARD:
5759 html_engine_block_selection (e);
5760 html_engine_set_mark (e);
5761 html_engine_update_selection_if_necessary (e);
5762 html_engine_freeze (e);
5763 rv = com_type == GTK_HTML_COMMAND_KILL_WORD
5764 ? html_engine_forward_word (e)
5765 : html_engine_backward_word (e);
5766 if (rv)
5767 html_engine_delete (e);
5768 html_engine_unblock_selection (e);
5769 html_engine_thaw (e);
5770 break;
5771 case GTK_HTML_COMMAND_SAVE_DATA_ON:
5772 html->engine->save_data = TRUE;
5773 break;
5774 case GTK_HTML_COMMAND_SAVE_DATA_OFF:
5775 html->engine->save_data = FALSE;
5776 break;
5777 case GTK_HTML_COMMAND_SAVED:
5778 html_engine_saved (html->engine);
5779 break;
5780 case GTK_HTML_COMMAND_IS_SAVED:
5781 rv = html_engine_is_saved (html->engine);
5782 break;
5783 case GTK_HTML_COMMAND_CELL_CSPAN_INC:
5784 rv = html_engine_cspan_delta (html->engine, 1);
5785 break;
5786 case GTK_HTML_COMMAND_CELL_RSPAN_INC:
5787 rv = html_engine_rspan_delta (html->engine, 1);
5788 break;
5789 case GTK_HTML_COMMAND_CELL_CSPAN_DEC:
5790 rv = html_engine_cspan_delta (html->engine, -1);
5791 break;
5792 case GTK_HTML_COMMAND_CELL_RSPAN_DEC:
5793 rv = html_engine_rspan_delta (html->engine, -1);
5794 break;
5795 default:
5796 html->binding_handled = FALSE;
5799 if (!html->binding_handled && html->editor_api)
5800 html->binding_handled = (* html->editor_api->command) (html, com_type, html->editor_data);
5802 return rv;
5805 static void
5806 add_bindings (GtkHTMLClass *klass)
5808 GtkBindingSet *binding_set;
5810 /* ensure enums are defined */
5811 gtk_html_cursor_skip_get_type ();
5812 gtk_html_command_get_type ();
5814 binding_set = gtk_binding_set_by_class (klass);
5816 /* layout scrolling */
5817 #define BSCROLL(m,key,orient,sc) \
5818 gtk_binding_entry_add_signal (binding_set, GDK_KEY_ ## key, m, \
5819 "scroll", 3, \
5820 GTK_TYPE_ORIENTATION, GTK_ORIENTATION_ ## orient, \
5821 GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_ ## sc, \
5822 G_TYPE_FLOAT, 0.0);
5824 #define BSPACESCROLL(m,key,orient,sc) \
5825 gtk_binding_entry_add_signal (binding_set, GDK_KEY_ ## key, m, \
5826 "scroll", 3, \
5827 GTK_TYPE_ORIENTATION, GTK_ORIENTATION_ ## orient, \
5828 GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_ ## sc, \
5829 G_TYPE_FLOAT, 1.0);
5831 BSCROLL (0, Up, VERTICAL, STEP_BACKWARD);
5832 BSCROLL (0, KP_Up, VERTICAL, STEP_BACKWARD);
5833 BSCROLL (0, Down, VERTICAL, STEP_FORWARD);
5834 BSCROLL (0, KP_Down, VERTICAL, STEP_FORWARD);
5836 BSCROLL (0, Left, HORIZONTAL, STEP_BACKWARD);
5837 BSCROLL (0, KP_Left, HORIZONTAL, STEP_BACKWARD);
5838 BSCROLL (0, Right, HORIZONTAL, STEP_FORWARD);
5839 BSCROLL (0, KP_Right, HORIZONTAL, STEP_FORWARD);
5841 BSCROLL (0, Page_Up, VERTICAL, PAGE_BACKWARD);
5842 BSCROLL (0, KP_Page_Up, VERTICAL, PAGE_BACKWARD);
5843 BSCROLL (0, Page_Down, VERTICAL, PAGE_FORWARD);
5844 BSCROLL (0, KP_Page_Down, VERTICAL, PAGE_FORWARD);
5845 BSPACESCROLL (0, BackSpace, VERTICAL, PAGE_BACKWARD);
5846 BSPACESCROLL (0, space, VERTICAL, PAGE_FORWARD);
5847 BSPACESCROLL (GDK_SHIFT_MASK, space, VERTICAL, PAGE_BACKWARD);
5849 BSCROLL (GDK_SHIFT_MASK, Left, HORIZONTAL, PAGE_BACKWARD);
5850 BSCROLL (GDK_SHIFT_MASK, KP_Left, HORIZONTAL, PAGE_BACKWARD);
5851 BSCROLL (GDK_SHIFT_MASK, Right, HORIZONTAL, PAGE_FORWARD);
5852 BSCROLL (GDK_SHIFT_MASK, KP_Right, HORIZONTAL, PAGE_FORWARD);
5854 /* editing */
5856 #define BMOVE(m,key,dir,sk) \
5857 gtk_binding_entry_add_signal (binding_set, GDK_KEY_ ## key, m, \
5858 "cursor_move", 2, \
5859 GTK_TYPE_DIRECTION_TYPE, GTK_DIR_ ## dir, \
5860 GTK_TYPE_HTML_CURSOR_SKIP, GTK_HTML_CURSOR_SKIP_ ## sk);
5862 BMOVE (0, Left, LEFT, ONE);
5863 BMOVE (0, KP_Left, LEFT, ONE);
5864 BMOVE (0, Right, RIGHT, ONE);
5865 BMOVE (0, KP_Right, RIGHT, ONE);
5866 BMOVE (0, Up, UP , ONE);
5867 BMOVE (0, KP_Up, UP , ONE);
5868 BMOVE (0, Down, DOWN, ONE);
5869 BMOVE (0, KP_Down, DOWN, ONE);
5871 BMOVE (GDK_CONTROL_MASK, KP_Left, LEFT, WORD);
5872 BMOVE (GDK_CONTROL_MASK, Left, LEFT, WORD);
5873 BMOVE (GDK_MOD1_MASK, Left, LEFT, WORD);
5874 BMOVE (GDK_SHIFT_MASK, Left, LEFT, NONE);
5875 BMOVE (GDK_SHIFT_MASK | GDK_CONTROL_MASK, Left, LEFT, NONE);
5877 BMOVE (GDK_CONTROL_MASK, KP_Right, RIGHT, WORD);
5878 BMOVE (GDK_CONTROL_MASK, Right, RIGHT, WORD);
5879 BMOVE (GDK_MOD1_MASK, Right, RIGHT, WORD);
5880 BMOVE (GDK_SHIFT_MASK, Right, RIGHT, NONE);
5881 BMOVE (GDK_SHIFT_MASK | GDK_CONTROL_MASK, Right, RIGHT, NONE);
5883 BMOVE (0, Page_Up, UP, PAGE);
5884 BMOVE (0, KP_Page_Up, UP, PAGE);
5885 BMOVE (0, Page_Down, DOWN, PAGE);
5886 BMOVE (0, KP_Page_Down, DOWN, PAGE);
5888 BMOVE (0, Home, LEFT, ALL);
5889 BMOVE (0, KP_Home, LEFT, ALL);
5890 BMOVE (0, End, RIGHT, ALL);
5891 BMOVE (0, KP_End, RIGHT, ALL);
5892 BMOVE (GDK_CONTROL_MASK, Home, UP, ALL);
5893 BMOVE (GDK_CONTROL_MASK, KP_Home, UP, ALL);
5894 BMOVE (GDK_CONTROL_MASK, End, DOWN, ALL);
5895 BMOVE (GDK_CONTROL_MASK, KP_End, DOWN, ALL);
5897 #define BCOM(m,key,com) \
5898 gtk_binding_entry_add_signal (binding_set, GDK_KEY_ ## key, m, \
5899 "command", 1, \
5900 GTK_TYPE_HTML_COMMAND, GTK_HTML_COMMAND_ ## com);
5902 BCOM (0, Home, SCROLL_BOD);
5903 BCOM (0, KP_Home, SCROLL_BOD);
5904 BCOM (0, End, SCROLL_EOD);
5905 BCOM (0, KP_End, SCROLL_EOD);
5907 BCOM (GDK_CONTROL_MASK, c, COPY);
5909 BCOM (0, Return, INSERT_PARAGRAPH);
5910 BCOM (GDK_SHIFT_MASK, Return, INSERT_PARAGRAPH);
5911 BCOM (0, KP_Enter, INSERT_PARAGRAPH);
5912 BCOM (GDK_SHIFT_MASK, KP_Enter, INSERT_PARAGRAPH);
5913 BCOM (0, BackSpace, DELETE_BACK_OR_INDENT_DEC);
5914 BCOM (GDK_SHIFT_MASK, BackSpace, DELETE_BACK_OR_INDENT_DEC);
5915 BCOM (0, Delete, DELETE);
5916 BCOM (0, KP_Delete, DELETE);
5918 BCOM (GDK_CONTROL_MASK | GDK_SHIFT_MASK, plus, ZOOM_IN);
5919 BCOM (GDK_CONTROL_MASK, plus, ZOOM_IN);
5920 BCOM (GDK_CONTROL_MASK, equal, ZOOM_IN);
5921 BCOM (GDK_CONTROL_MASK, KP_Add, ZOOM_IN);
5922 BCOM (GDK_CONTROL_MASK, minus, ZOOM_OUT);
5923 BCOM (GDK_CONTROL_MASK, KP_Subtract, ZOOM_OUT);
5924 BCOM (GDK_CONTROL_MASK, 8, ZOOM_IN);
5925 BCOM (GDK_CONTROL_MASK, 9, ZOOM_RESET);
5926 BCOM (GDK_CONTROL_MASK, 0, ZOOM_OUT);
5927 BCOM (GDK_CONTROL_MASK, KP_Multiply, ZOOM_RESET);
5929 /* selection */
5930 BCOM (GDK_SHIFT_MASK, Up, MODIFY_SELECTION_UP);
5931 BCOM (GDK_SHIFT_MASK, Down, MODIFY_SELECTION_DOWN);
5932 BCOM (GDK_SHIFT_MASK, Left, MODIFY_SELECTION_LEFT);
5933 BCOM (GDK_SHIFT_MASK, Right, MODIFY_SELECTION_RIGHT);
5934 BCOM (GDK_SHIFT_MASK | GDK_CONTROL_MASK, Left, MODIFY_SELECTION_PREV_WORD);
5935 BCOM (GDK_SHIFT_MASK | GDK_CONTROL_MASK, Right, MODIFY_SELECTION_NEXT_WORD);
5936 BCOM (GDK_SHIFT_MASK, Home, MODIFY_SELECTION_BOL);
5937 BCOM (GDK_SHIFT_MASK, KP_Home, MODIFY_SELECTION_BOL);
5938 BCOM (GDK_SHIFT_MASK, End, MODIFY_SELECTION_EOL);
5939 BCOM (GDK_SHIFT_MASK, KP_End, MODIFY_SELECTION_EOL);
5940 BCOM (GDK_SHIFT_MASK | GDK_CONTROL_MASK, Home, MODIFY_SELECTION_BOD);
5941 BCOM (GDK_SHIFT_MASK | GDK_CONTROL_MASK, KP_Home, MODIFY_SELECTION_BOD);
5942 BCOM (GDK_SHIFT_MASK | GDK_CONTROL_MASK, End, MODIFY_SELECTION_EOD);
5943 BCOM (GDK_SHIFT_MASK | GDK_CONTROL_MASK, KP_End, MODIFY_SELECTION_EOD);
5944 BCOM (GDK_SHIFT_MASK, Page_Up, MODIFY_SELECTION_PAGEUP);
5945 BCOM (GDK_SHIFT_MASK, Page_Down, MODIFY_SELECTION_PAGEDOWN);
5946 BCOM (GDK_SHIFT_MASK, KP_Page_Up, MODIFY_SELECTION_PAGEUP);
5947 BCOM (GDK_SHIFT_MASK, KP_Page_Down, MODIFY_SELECTION_PAGEDOWN);
5948 BCOM (GDK_CONTROL_MASK, a, SELECT_ALL);
5949 BCOM (GDK_CONTROL_MASK, p, SELECT_PARAGRAPH);
5951 /* copy, cut, paste, delete */
5952 BCOM (GDK_CONTROL_MASK, c, COPY);
5953 BCOM (GDK_CONTROL_MASK, Insert, COPY);
5954 BCOM (GDK_CONTROL_MASK, KP_Insert, COPY);
5955 BCOM (0, F16, COPY);
5956 BCOM (GDK_CONTROL_MASK, x, CUT);
5957 BCOM (GDK_SHIFT_MASK, Delete, CUT);
5958 BCOM (GDK_SHIFT_MASK, KP_Delete, CUT);
5959 BCOM (0, F20, CUT);
5960 BCOM (GDK_CONTROL_MASK, v, PASTE);
5961 BCOM (GDK_SHIFT_MASK, Insert, PASTE);
5962 BCOM (GDK_SHIFT_MASK, KP_Insert, PASTE);
5963 BCOM (0, F18, PASTE);
5964 BCOM (GDK_CONTROL_MASK, Delete, KILL_WORD);
5965 BCOM (GDK_CONTROL_MASK, BackSpace, KILL_WORD_BACKWARD);
5967 /* font style */
5968 BCOM (GDK_CONTROL_MASK, b, BOLD_TOGGLE);
5969 BCOM (GDK_CONTROL_MASK, i, ITALIC_TOGGLE);
5970 BCOM (GDK_CONTROL_MASK, u, UNDERLINE_TOGGLE);
5971 BCOM (GDK_CONTROL_MASK, o, TEXT_COLOR_APPLY);
5972 BCOM (GDK_MOD1_MASK, 1, SIZE_MINUS_2);
5973 BCOM (GDK_MOD1_MASK, 2, SIZE_MINUS_1);
5974 BCOM (GDK_MOD1_MASK, 3, SIZE_PLUS_0);
5975 BCOM (GDK_MOD1_MASK, 4, SIZE_PLUS_1);
5976 BCOM (GDK_MOD1_MASK, 5, SIZE_PLUS_2);
5977 BCOM (GDK_MOD1_MASK, 6, SIZE_PLUS_3);
5978 BCOM (GDK_MOD1_MASK, 7, SIZE_PLUS_4);
5979 BCOM (GDK_MOD1_MASK, 8, SIZE_INCREASE);
5980 BCOM (GDK_MOD1_MASK, 9, SIZE_DECREASE);
5982 /* undo/redo */
5983 BCOM (GDK_CONTROL_MASK, z, UNDO);
5984 BCOM (0, F14, UNDO);
5985 BCOM (GDK_CONTROL_MASK, r, REDO);
5987 /* paragraph style */
5988 BCOM (GDK_CONTROL_MASK | GDK_MOD1_MASK, l, ALIGN_LEFT);
5989 BCOM (GDK_CONTROL_MASK | GDK_MOD1_MASK, r, ALIGN_RIGHT);
5990 BCOM (GDK_CONTROL_MASK | GDK_MOD1_MASK, c, ALIGN_CENTER);
5992 /* tabs */
5993 BCOM (0, Tab, INSERT_TAB_OR_NEXT_CELL);
5994 BCOM (GDK_SHIFT_MASK, Tab, PREV_CELL);
5996 /* spell checking */
5997 BCOM (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK, s, SPELL_SUGGEST);
5998 BCOM (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK, p, SPELL_PERSONAL_DICTIONARY_ADD);
5999 BCOM (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK, n, SPELL_SESSION_DICTIONARY_ADD);
6001 /* popup menu, properties dialog */
6002 BCOM (GDK_MOD1_MASK, space, POPUP_MENU);
6003 BCOM (GDK_MOD1_MASK, Return, PROPERTIES_DIALOG);
6004 BCOM (GDK_MOD1_MASK, KP_Enter, PROPERTIES_DIALOG);
6006 /* tables */
6007 BCOM (GDK_CONTROL_MASK | GDK_SHIFT_MASK, t, INSERT_TABLE_1_1);
6008 BCOM (GDK_SHIFT_MASK | GDK_CONTROL_MASK, l, TABLE_INSERT_COL_AFTER);
6009 BCOM (GDK_SHIFT_MASK | GDK_CONTROL_MASK, r, TABLE_INSERT_ROW_AFTER);
6010 BCOM (GDK_SHIFT_MASK | GDK_CONTROL_MASK, s, TABLE_SPACING_INC);
6011 BCOM (GDK_SHIFT_MASK | GDK_CONTROL_MASK, p, TABLE_PADDING_INC);
6012 BCOM (GDK_SHIFT_MASK | GDK_CONTROL_MASK, o, TABLE_BORDER_WIDTH_INC);
6013 BCOM (GDK_MOD1_MASK | GDK_CONTROL_MASK, l, TABLE_INSERT_COL_BEFORE);
6014 BCOM (GDK_MOD1_MASK | GDK_CONTROL_MASK, r, TABLE_INSERT_ROW_BEFORE);
6015 BCOM (GDK_MOD1_MASK | GDK_CONTROL_MASK, s, TABLE_SPACING_DEC);
6016 BCOM (GDK_MOD1_MASK | GDK_CONTROL_MASK, p, TABLE_PADDING_DEC);
6017 BCOM (GDK_MOD1_MASK | GDK_CONTROL_MASK, o, TABLE_BORDER_WIDTH_DEC);
6018 BCOM (GDK_SHIFT_MASK | GDK_MOD1_MASK, l, TABLE_DELETE_COL);
6019 BCOM (GDK_SHIFT_MASK | GDK_MOD1_MASK, r, TABLE_DELETE_ROW);
6020 BCOM (GDK_SHIFT_MASK | GDK_MOD1_MASK, s, TABLE_SPACING_ZERO);
6021 BCOM (GDK_SHIFT_MASK | GDK_MOD1_MASK, p, TABLE_PADDING_ZERO);
6022 BCOM (GDK_SHIFT_MASK | GDK_MOD1_MASK, o, TABLE_BORDER_WIDTH_ZERO);
6025 gint
6026 gtk_html_set_iframe_parent (GtkHTML *html,
6027 GtkWidget *parent,
6028 HTMLObject *frame)
6030 GtkWidget *top_level;
6031 gint depth = 0;
6032 g_assert (GTK_IS_HTML (parent));
6034 gtk_html_set_animate (html, gtk_html_get_animate (GTK_HTML (parent)));
6036 html->iframe_parent = parent;
6037 html->frame = frame;
6039 top_level = GTK_WIDGET (gtk_html_get_top_html (html));
6040 if (html->engine && html->engine->painter) {
6041 html_painter_set_widget (html->engine->painter, top_level);
6042 gtk_html_set_fonts (html, html->engine->painter);
6044 g_signal_emit (top_level, signals[IFRAME_CREATED], 0, html);
6046 while (html->iframe_parent) {
6047 depth++;
6048 html = GTK_HTML (html->iframe_parent);
6051 return depth;
6054 void
6055 gtk_html_select_word (GtkHTML *html)
6057 HTMLEngine *e;
6059 if (!html->allow_selection)
6060 return;
6062 e = html->engine;
6063 if (html_engine_get_editable (e))
6064 html_engine_select_word_editable (e);
6065 else
6066 html_engine_select_word (e);
6068 html_engine_update_selection_active_state (html->engine, html->priv->event_time);
6069 update_primary_selection (html);
6072 void
6073 gtk_html_select_line (GtkHTML *html)
6075 HTMLEngine *e;
6077 if (!html->allow_selection)
6078 return;
6080 e = html->engine;
6081 if (html_engine_get_editable (e))
6082 html_engine_select_line_editable (e);
6083 else
6084 html_engine_select_line (e);
6086 html_engine_update_selection_active_state (html->engine, html->priv->event_time);
6087 update_primary_selection (html);
6090 void
6091 gtk_html_select_paragraph (GtkHTML *html)
6093 HTMLEngine *e;
6095 if (!html->allow_selection)
6096 return;
6098 e = html->engine;
6099 if (html_engine_get_editable (e))
6100 html_engine_select_paragraph_editable (e);
6101 /* FIXME: does anybody need this? if so bother me. rodo
6102 * else
6103 * html_engine_select_paragraph (e); */
6105 html_engine_update_selection_active_state (html->engine, html->priv->event_time);
6106 update_primary_selection (html);
6109 void
6110 gtk_html_select_paragraph_extended (GtkHTML *html)
6112 HTMLEngine *e;
6114 if (!html->allow_selection)
6115 return;
6117 e = html->engine;
6118 if (html_engine_get_editable (e))
6119 html_engine_select_paragraph_extended (e);
6120 /* FIXME: does anybody need this? if so bother me. rodo
6121 * else
6122 * html_engine_select_paragraph (e); */
6124 html_engine_update_selection_active_state (html->engine, html->priv->event_time);
6125 update_primary_selection (html);
6128 void
6129 gtk_html_select_all (GtkHTML *html)
6131 HTMLEngine *e;
6133 if (!html->allow_selection)
6134 return;
6136 e = html->engine;
6138 if (html_engine_get_editable (e))
6139 html_engine_select_all_editable (e);
6140 else {
6141 html_engine_select_all (e);
6144 html_engine_update_selection_active_state (html->engine, html->priv->event_time);
6145 update_primary_selection (html);
6148 void
6149 gtk_html_unselect_all (GtkHTML *html)
6151 HTMLEngine *e;
6153 e = html->engine;
6155 html_engine_unselect_all (e);
6157 html_engine_update_selection_active_state (html->engine, html->priv->event_time);
6158 update_primary_selection (html);
6161 void
6162 gtk_html_api_set_language (GtkHTML *html)
6164 g_return_if_fail (GTK_IS_HTML (html));
6166 if (html->editor_api) {
6167 html->editor_api->set_language (html, html_engine_get_language (html->engine), html->editor_data);
6168 html_engine_spell_check (html->engine);
6172 void
6173 gtk_html_set_editor_api (GtkHTML *html,
6174 GtkHTMLEditorAPI *api,
6175 gpointer data)
6177 html->editor_api = api;
6178 html->editor_data = data;
6180 gtk_html_api_set_language (html);
6183 static const gchar *
6184 get_value_nick (GtkHTMLCommandType com_type)
6186 GEnumValue *val;
6187 GEnumClass *enum_class;
6189 enum_class = g_type_class_ref (GTK_TYPE_HTML_COMMAND);
6190 val = g_enum_get_value (enum_class, com_type);
6191 g_type_class_unref (enum_class);
6192 if (val)
6193 return val->value_nick;
6195 g_warning ("Invalid GTK_TYPE_HTML_COMMAND enum value %d\n", com_type);
6197 return NULL;
6200 void
6201 gtk_html_editor_event_command (GtkHTML *html,
6202 GtkHTMLCommandType com_type,
6203 gboolean before)
6205 GValue arg;
6207 memset (&arg, 0, sizeof (GValue));
6208 g_value_init (&arg, G_TYPE_STRING);
6209 g_value_set_string (&arg, get_value_nick (com_type));
6211 gtk_html_editor_event (html, before ? GTK_HTML_EDITOR_EVENT_COMMAND_BEFORE : GTK_HTML_EDITOR_EVENT_COMMAND_AFTER,
6212 &arg);
6214 g_value_unset (&arg);
6217 void
6218 gtk_html_editor_event (GtkHTML *html,
6219 GtkHTMLEditorEventType event,
6220 GValue *args)
6222 GValue *retval = NULL;
6224 if (html->editor_api && !html->engine->block_events)
6225 retval = (*html->editor_api->event) (html, event, args, html->editor_data);
6227 if (retval) {
6228 g_value_unset (retval);
6229 g_free (retval);
6233 gboolean
6234 gtk_html_command (GtkHTML *html,
6235 const gchar *command_name)
6237 GEnumClass *class;
6238 GEnumValue *val;
6240 g_return_val_if_fail (GTK_IS_HTML (html), FALSE);
6241 g_return_val_if_fail (command_name != NULL, FALSE);
6243 class = G_ENUM_CLASS (g_type_class_ref (GTK_TYPE_HTML_COMMAND));
6244 val = g_enum_get_value_by_nick (class, command_name);
6245 g_type_class_unref (class);
6246 if (val) {
6247 if (command (html, val->value)) {
6248 if (html->priv->update_styles)
6249 gtk_html_update_styles (html);
6250 return TRUE;
6254 return FALSE;
6257 gboolean
6258 gtk_html_edit_make_cursor_visible (GtkHTML *html)
6260 GtkAdjustment *hadjustment;
6261 GtkAdjustment *vadjustment;
6262 gboolean rv = FALSE;
6264 g_return_val_if_fail (GTK_IS_HTML (html), rv);
6266 hadjustment = gtk_layout_get_hadjustment (GTK_LAYOUT (html));
6267 vadjustment = gtk_layout_get_vadjustment (GTK_LAYOUT (html));
6269 html_engine_hide_cursor (html->engine);
6270 if (html_engine_make_cursor_visible (html->engine)) {
6271 gtk_adjustment_set_value (hadjustment, (gfloat) html->engine->x_offset);
6272 gtk_adjustment_set_value (vadjustment, (gfloat) html->engine->y_offset);
6273 rv = TRUE;
6275 html_engine_show_cursor (html->engine);
6277 return rv;
6280 gboolean
6281 gtk_html_build_with_gconf (void)
6283 return TRUE;
6286 static void
6287 reparent_embedded (HTMLObject *o,
6288 HTMLEngine *e,
6289 gpointer data)
6291 if (html_object_is_embedded (o)) {
6292 HTMLEmbedded *eo = HTML_EMBEDDED (o);
6293 GtkWidget *parent = NULL;
6295 if (eo->widget != NULL)
6296 parent = gtk_widget_get_parent (eo->widget);
6298 if (parent && GTK_IS_HTML (parent) &&
6299 GTK_HTML (parent)->iframe_parent == NULL) {
6300 g_object_ref (eo->widget);
6301 gtk_container_remove (GTK_CONTAINER (parent), eo->widget);
6302 g_object_force_floating (G_OBJECT (eo->widget));
6304 eo->parent = data;
6307 if (HTML_IS_IFRAME (o) && GTK_HTML (HTML_IFRAME (o)->html)->iframe_parent &&
6308 GTK_HTML (GTK_HTML (HTML_IFRAME (o)->html)->iframe_parent)->iframe_parent == NULL)
6309 gtk_html_set_iframe_parent (GTK_HTML (HTML_IFRAME (o)->html), data, o);
6311 if (HTML_IS_FRAME (o) && GTK_HTML (HTML_FRAME (o)->html)->iframe_parent &&
6312 GTK_HTML (GTK_HTML (HTML_FRAME (o)->html)->iframe_parent)->iframe_parent == NULL)
6313 gtk_html_set_iframe_parent (GTK_HTML (HTML_FRAME (o)->html), data, o);
6315 if (HTML_IS_FRAMESET (o) && HTML_FRAMESET (o)->parent &&
6316 HTML_FRAMESET (o)->parent->iframe_parent == NULL) {
6317 HTML_FRAMESET (o)->parent = data;
6321 static void
6322 gtk_html_insert_html_generic (GtkHTML *html,
6323 GtkHTML *tmp,
6324 const gchar *html_src,
6325 gboolean obj_only)
6327 GtkWidget *window, *sw;
6328 HTMLObject *o;
6330 html_engine_freeze (html->engine);
6331 html_engine_delete (html->engine);
6332 if (!tmp)
6333 tmp = GTK_HTML (gtk_html_new_from_string (html_src, -1));
6334 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
6335 sw = gtk_scrolled_window_new (NULL, NULL);
6336 gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (sw));
6337 gtk_container_add (GTK_CONTAINER (sw), GTK_WIDGET (tmp));
6338 gtk_widget_realize (GTK_WIDGET (tmp));
6339 html_image_factory_move_images (html->engine->image_factory, tmp->engine->image_factory);
6341 /* copy the forms */
6342 g_list_foreach (tmp->engine->formList, (GFunc) html_form_set_engine, html->engine);
6344 /* move top level iframes and embedded widgets from tmp to html */
6345 html_object_forall (tmp->engine->clue, html->engine, reparent_embedded, html);
6347 if (tmp->engine->formList && html->engine->formList) {
6348 GList *form_last;
6350 form_last = g_list_last (html->engine->formList);
6351 tmp->engine->formList->prev = form_last;
6352 form_last->next = tmp->engine->formList;
6353 } else if (tmp->engine->formList) {
6354 html->engine->formList = tmp->engine->formList;
6356 tmp->engine->formList = NULL;
6358 if (obj_only) {
6359 HTMLObject *next;
6360 g_return_if_fail (tmp->engine->clue && HTML_CLUE (tmp->engine->clue)->head
6361 && HTML_CLUE (HTML_CLUE (tmp->engine->clue)->head)->head);
6363 html_undo_level_begin (html->engine->undo, "Append HTML", "Remove appended HTML");
6364 o = HTML_CLUE (tmp->engine->clue)->head;
6365 for (; o; o = next) {
6366 next = o->next;
6367 html_object_remove_child (o->parent, o);
6368 html_engine_append_flow (html->engine, o, html_object_get_recursive_length (o));
6370 html_undo_level_end (html->engine->undo, html->engine);
6371 } else {
6372 g_return_if_fail (tmp->engine->clue);
6374 o = tmp->engine->clue;
6375 /* skip empty HTML */
6376 if (html_object_get_recursive_length (o) > 0) {
6377 tmp->engine->clue = NULL;
6378 html_engine_insert_object (html->engine, o,
6379 html_object_get_recursive_length (o),
6380 html_engine_get_insert_level_for_object (html->engine, o));
6383 gtk_widget_destroy (window);
6384 html_engine_thaw (html->engine);
6387 void
6388 gtk_html_insert_html (GtkHTML *html,
6389 const gchar *html_src)
6391 g_return_if_fail (GTK_IS_HTML (html));
6393 gtk_html_insert_html_generic (html, NULL, html_src, FALSE);
6396 void
6397 gtk_html_insert_gtk_html (GtkHTML *html,
6398 GtkHTML *to_be_destroyed)
6400 g_return_if_fail (GTK_IS_HTML (html));
6402 gtk_html_insert_html_generic (html, to_be_destroyed, NULL, FALSE);
6405 void
6406 gtk_html_append_html (GtkHTML *html,
6407 const gchar *html_src)
6409 g_return_if_fail (GTK_IS_HTML (html));
6411 gtk_html_insert_html_generic (html, NULL, html_src, TRUE);
6414 static void
6415 set_magnification (HTMLObject *o,
6416 HTMLEngine *e,
6417 gpointer data)
6419 if (HTML_IS_FRAME (o)) {
6420 html_font_manager_set_magnification (&GTK_HTML (HTML_FRAME (o)->html)->engine->painter->font_manager,
6421 *(gdouble *) data);
6422 } else if (HTML_IS_IFRAME (o)) {
6423 html_font_manager_set_magnification (&GTK_HTML (HTML_IFRAME (o)->html)->engine->painter->font_manager,
6424 *(gdouble *) data);
6425 } else if (HTML_IS_TEXT (o))
6426 html_text_calc_font_size (HTML_TEXT (o), e);
6429 void
6430 gtk_html_set_magnification (GtkHTML *html,
6431 gdouble magnification)
6433 g_return_if_fail (GTK_IS_HTML (html));
6435 if (magnification > 0.05 && magnification < 20.0
6436 && magnification * html->engine->painter->font_manager.var_size >= 4 * PANGO_SCALE
6437 && magnification * html->engine->painter->font_manager.fix_size >= 4 * PANGO_SCALE) {
6438 html_font_manager_set_magnification (&html->engine->painter->font_manager, magnification);
6439 if (html->engine->clue) {
6440 html_object_forall (html->engine->clue, html->engine,
6441 set_magnification, &magnification);
6442 html_object_change_set_down (html->engine->clue, HTML_CHANGE_ALL);
6445 html_engine_schedule_update (html->engine);
6449 #define MAG_STEP 1.1
6451 void
6452 gtk_html_zoom_in (GtkHTML *html)
6454 g_return_if_fail (GTK_IS_HTML (html));
6456 gtk_html_set_magnification (html, html->engine->painter->font_manager.magnification * MAG_STEP);
6459 void
6460 gtk_html_zoom_out (GtkHTML *html)
6462 g_return_if_fail (GTK_IS_HTML (html));
6463 g_return_if_fail (HTML_IS_ENGINE (html->engine));
6465 gtk_html_set_magnification (html, html->engine->painter->font_manager.magnification * (1.0 / MAG_STEP));
6468 void
6469 gtk_html_zoom_reset (GtkHTML *html)
6471 g_return_if_fail (GTK_IS_HTML (html));
6473 gtk_html_set_magnification (html, 1.0);
6476 void
6477 gtk_html_set_allow_frameset (GtkHTML *html,
6478 gboolean allow)
6480 g_return_if_fail (GTK_IS_HTML (html));
6481 g_return_if_fail (HTML_IS_ENGINE (html->engine));
6483 html->engine->allow_frameset = allow;
6486 gboolean
6487 gtk_html_get_allow_frameset (GtkHTML *html)
6489 g_return_val_if_fail (GTK_IS_HTML (html), FALSE);
6490 g_return_val_if_fail (HTML_IS_ENGINE (html->engine), FALSE);
6492 return html->engine->allow_frameset;
6495 void
6496 gtk_html_images_ref (GtkHTML *html)
6498 html_image_factory_ref_all_images (HTML_IMAGE_FACTORY (html->engine->image_factory));
6501 void
6502 gtk_html_images_unref (GtkHTML *html)
6504 html_image_factory_unref_all_images (HTML_IMAGE_FACTORY (html->engine->image_factory));
6507 void
6508 gtk_html_image_ref (GtkHTML *html,
6509 const gchar *url)
6511 html_image_factory_ref_image_ptr (HTML_IMAGE_FACTORY (html->engine->image_factory), url);
6514 void
6515 gtk_html_image_unref (GtkHTML *html,
6516 const gchar *url)
6518 html_image_factory_unref_image_ptr (HTML_IMAGE_FACTORY (html->engine->image_factory), url);
6521 void
6522 gtk_html_image_preload (GtkHTML *html,
6523 const gchar *url)
6525 html_image_factory_register (HTML_IMAGE_FACTORY (html->engine->image_factory), NULL, url, FALSE);
6528 void
6529 gtk_html_set_blocking (GtkHTML *html,
6530 gboolean block)
6532 html->engine->block = block;
6535 void
6536 gtk_html_set_images_blocking (GtkHTML *html,
6537 gboolean block)
6539 html->engine->block_images = block;
6542 gint
6543 gtk_html_print_page_get_pages_num (GtkHTML *html,
6544 GtkPrintContext *context,
6545 gdouble header_height,
6546 gdouble footer_height)
6548 return html_engine_print_get_pages_num (
6549 html->engine, context, header_height, footer_height);
6552 GtkPrintOperationResult
6553 gtk_html_print_operation_run (GtkHTML *html,
6554 GtkPrintOperation *operation,
6555 GtkPrintOperationAction action,
6556 GtkWindow *parent,
6557 GtkHTMLPrintCalcHeight calc_header_height,
6558 GtkHTMLPrintCalcHeight calc_footer_height,
6559 GtkHTMLPrintDrawFunc draw_header,
6560 GtkHTMLPrintDrawFunc draw_footer,
6561 gpointer user_data,
6562 GError **error)
6564 return html_engine_print_operation_run (
6565 html->engine, operation, action, parent,
6566 calc_header_height, calc_footer_height,
6567 draw_header, draw_footer, user_data, error);
6570 gboolean
6571 gtk_html_has_undo (GtkHTML *html)
6573 return html_undo_has_undo_steps (html->engine->undo);
6576 void
6577 gtk_html_drop_undo (GtkHTML *html)
6579 html_undo_reset (html->engine->undo);
6582 void
6583 gtk_html_flush (GtkHTML *html)
6585 html_engine_flush (html->engine);
6588 const gchar *
6589 gtk_html_get_object_id_at (GtkHTML *html,
6590 gint x,
6591 gint y)
6593 HTMLObject *o = html_engine_get_object_at (html->engine, x, y, NULL, FALSE);
6594 const gchar *id = NULL;
6596 while (o) {
6597 id = html_object_get_id (o);
6598 if (id)
6599 break;
6600 o = o->parent;
6603 return id;
6606 gchar *
6607 gtk_html_get_url_at (GtkHTML *html,
6608 gint x,
6609 gint y)
6611 HTMLObject *obj;
6612 gint offset;
6614 g_return_val_if_fail (GTK_IS_HTML (html), NULL);
6616 obj = html_engine_get_object_at (html->engine, x, y, (guint *) &offset, FALSE);
6618 if (obj)
6619 return gtk_html_get_url_object_relative (html, obj, html_object_get_url (obj, offset));
6621 return NULL;
6624 gchar *
6625 gtk_html_get_cursor_url (GtkHTML *html)
6627 HTMLObject *obj;
6628 gint offset;
6630 g_return_val_if_fail (GTK_IS_HTML (html), NULL);
6632 if (html->engine->caret_mode) {
6633 obj = html->engine->cursor->object;
6634 offset = html->engine->cursor->offset;
6635 } else
6636 obj = html_engine_get_focus_object (html->engine, &offset);
6638 if (obj)
6639 return gtk_html_get_url_object_relative (html, obj, html_object_get_url (obj, offset));
6641 return NULL;
6644 gchar *
6645 gtk_html_get_image_src_at (GtkHTML *html,
6646 gint x,
6647 gint y)
6649 HTMLObject *obj;
6650 gint offset;
6652 g_return_val_if_fail (GTK_IS_HTML (html), NULL);
6654 obj = html_engine_get_object_at (html->engine, x, y, (guint *) &offset, FALSE);
6656 if (obj && HTML_IS_IMAGE (obj)) {
6657 HTMLImage *image = (HTMLImage *) obj;
6659 if (!image->image_ptr)
6660 return NULL;
6662 return g_strdup (image->image_ptr->url);
6665 return NULL;
6668 /* Unref when done with it */
6669 GdkPixbufAnimation *
6670 gtk_html_get_image_at (GtkHTML *html,
6671 gint x,
6672 gint y)
6674 HTMLObject *obj;
6675 gint offset;
6677 g_return_val_if_fail (GTK_IS_HTML (html), NULL);
6679 obj = html_engine_get_object_at (html->engine, x, y, (guint *) &offset, FALSE);
6681 if (obj && HTML_IS_IMAGE (obj)) {
6682 HTMLImage *image = (HTMLImage *) obj;
6684 if (!image->image_ptr || !image->image_ptr->animation)
6685 return NULL;
6687 return g_object_ref (image->image_ptr->animation);
6690 return NULL;
6693 gchar *
6694 gtk_html_get_cursor_image_src (GtkHTML *html)
6696 HTMLObject *obj;
6697 gint offset;
6699 g_return_val_if_fail (GTK_IS_HTML (html), NULL);
6701 if (html->engine->caret_mode) {
6702 obj = html->engine->cursor->object;
6703 offset = html->engine->cursor->offset;
6704 } else
6705 obj = html_engine_get_focus_object (html->engine, &offset);
6707 if (obj && HTML_IS_IMAGE (obj)) {
6708 HTMLImage *image = (HTMLImage *) obj;
6710 if (!image->image_ptr)
6711 return NULL;
6713 return g_strdup (image->image_ptr->url);
6716 return NULL;
6719 void
6720 gtk_html_set_tokenizer (GtkHTML *html,
6721 HTMLTokenizer *tokenizer)
6723 g_return_if_fail (GTK_IS_HTML (html));
6725 html_engine_set_tokenizer (html->engine, tokenizer);
6728 gboolean
6729 gtk_html_get_cursor_pos (GtkHTML *html,
6730 gint *position,
6731 gint *offset)
6733 gboolean read = FALSE;
6735 g_return_val_if_fail (html != NULL, FALSE);
6736 g_return_val_if_fail (GTK_IS_HTML (html), FALSE);
6738 if (html->engine && html->engine->cursor) {
6739 read = TRUE;
6741 if (position)
6742 *position = html->engine->cursor->position;
6743 if (offset)
6744 *offset = html->engine->cursor->offset;
6747 return read;
6750 gchar *
6751 gtk_html_filename_from_uri (const gchar *uri)
6753 const gchar *relative_fpath;
6754 gchar *temp_uri, *temp_filename;
6755 gchar *retval;
6757 if (!uri || !*uri)
6758 return NULL;
6760 if (g_ascii_strncasecmp (uri, "file://", 7) == 0)
6761 return g_filename_from_uri (uri, NULL, NULL);
6763 if (g_ascii_strncasecmp (uri, "file:", 5) == 0) {
6764 /* Relative (file or other) URIs shouldn't contain the
6765 * scheme prefix at all. But accept such broken URIs
6766 * anyway. Whether they are URI-encoded or not is
6767 * anybody's guess, assume they are.
6769 relative_fpath = uri + 5;
6770 } else {
6771 /* A proper relative file URI. Just do the URI-decoding. */
6772 relative_fpath = uri;
6775 if (g_path_is_absolute (relative_fpath)) {
6776 /* The totally broken case of "file:" followed
6777 * directly by an absolute pathname.
6779 /* file:/foo/bar.zap or file:c:/foo/bar.zap */
6780 #ifdef G_OS_WIN32
6781 if (g_ascii_isalpha (relative_fpath[0]) && relative_fpath[1] == ':')
6782 temp_uri = g_strconcat ("file:///", relative_fpath, NULL);
6783 else
6784 temp_uri = g_strconcat ("file://", relative_fpath, NULL);
6785 #else
6786 temp_uri = g_strconcat ("file://", relative_fpath, NULL);
6787 #endif
6788 retval = g_filename_from_uri (temp_uri, NULL, NULL);
6789 g_free (temp_uri);
6791 return retval;
6794 /* Create a dummy absolute file: URI and call
6795 * g_filename_from_uri(), then strip off the dummy
6796 * prefix.
6798 #ifdef G_OS_WIN32
6799 if (g_ascii_isalpha (relative_fpath[0]) && relative_fpath[1] == ':') {
6800 /* file:c:relative/path/foo.bar */
6801 gchar drive_letter = relative_fpath[0];
6803 temp_uri = g_strconcat ("file:///dummy/", relative_fpath + 2, NULL);
6804 temp_filename = g_filename_from_uri (temp_uri, NULL, NULL);
6805 g_free (temp_uri);
6807 if (temp_filename == NULL)
6808 return NULL;
6810 g_assert (strncmp (temp_filename, G_DIR_SEPARATOR_S "dummy" G_DIR_SEPARATOR_S, 7) == 0);
6812 retval = g_strdup_printf ("%c:%s", drive_letter, temp_filename + 7);
6813 g_free (temp_filename);
6815 return retval;
6817 #endif
6818 temp_uri = g_strconcat ("file:///dummy/", relative_fpath, NULL);
6819 temp_filename = g_filename_from_uri (temp_uri, NULL, NULL);
6820 g_free (temp_uri);
6822 if (temp_filename == NULL)
6823 return NULL;
6825 g_assert (strncmp (temp_filename, G_DIR_SEPARATOR_S "dummy" G_DIR_SEPARATOR_S, 7) == 0);
6827 retval = g_strdup (temp_filename + 7);
6828 g_free (temp_filename);
6830 return retval;
6833 gchar *
6834 gtk_html_filename_to_uri (const gchar *filename)
6836 gchar *fake_filename, *fake_uri, *retval;
6837 const gchar dummy_prefix[] = "file:///dummy/";
6838 const gint dummy_prefix_len = sizeof (dummy_prefix) - 1;
6839 #ifdef G_OS_WIN32
6840 gchar drive_letter = 0;
6841 #else
6842 gchar *first_end, *colon;
6843 #endif
6845 if (!filename || !*filename)
6846 return NULL;
6848 if (g_path_is_absolute (filename))
6849 return g_filename_to_uri (filename, NULL, NULL);
6851 /* filename is a relative path, and the corresponding URI is
6852 * filename as such but URI-escaped. Instead of yet again
6853 * copy-pasteing the URI-escape code from gconvert.c (or
6854 * somewhere else), prefix a fake top-level directory to make
6855 * it into an absolute path, call g_filename_to_uri() to turn it
6856 * into a full file: URI, and then strip away the file:/// and
6857 * the fake top-level directory.
6860 #ifdef G_OS_WIN32
6861 if (g_ascii_isalpha (*filename) && filename[1] == ':') {
6862 /* A non-absolute path, but with a drive letter. Ugh. */
6863 drive_letter = *filename;
6864 filename += 2;
6866 #endif
6867 fake_filename = g_build_filename ("/dummy", filename, NULL);
6868 fake_uri = g_filename_to_uri (fake_filename, NULL, NULL);
6869 g_free (fake_filename);
6871 if (fake_uri == NULL)
6872 return NULL;
6874 g_assert (strncmp (fake_uri, dummy_prefix, dummy_prefix_len) == 0);
6876 #ifdef G_OS_WIN32
6877 /* Re-insert the drive letter if we had one. Double ugh.
6878 * URI-encode the colon so the drive letter isn't taken for a
6879 * URI scheme!
6881 if (drive_letter)
6882 retval = g_strdup_printf ("%c%%3a%s",
6883 drive_letter,
6884 fake_uri + dummy_prefix_len);
6885 else
6886 retval = g_strdup (fake_uri + dummy_prefix_len);
6887 #else
6888 retval = g_strdup (fake_uri + dummy_prefix_len);
6889 #endif
6890 g_free (fake_uri);
6892 #ifdef G_OS_UNIX
6893 /* Check if there are colons in the first component of the
6894 * pathname, and URI-encode them so that the part up to the
6895 * colon isn't taken for a URI scheme name! This isn't
6896 * necessary on Win32 as there can't be colons in a file name
6897 * in the first place.
6899 first_end = strchr (retval, '/');
6900 if (first_end == NULL)
6901 first_end = retval + strlen (retval);
6903 while ((colon = strchr (retval, ':')) != NULL && colon < first_end) {
6904 gchar *new_retval = g_malloc (strlen (retval) + 3);
6906 strncpy (new_retval, retval, colon - retval);
6907 strcpy (new_retval + (colon - retval), "%3a");
6908 strcpy (new_retval + (colon - retval) + 3, colon + 1);
6910 g_free (retval);
6911 retval = new_retval;
6913 #endif
6915 return retval;
6918 #ifdef UNIT_TEST_URI_CONVERSIONS
6920 /* To test the uri<->filename code, cut&paste the above two functions
6921 * and this part into a separate file, insert #include <glib.h>, and
6922 * build with -DUNIT_TEST_URI_CONVERSIONS.
6925 static const gchar *const tests[][3] = {
6926 /* Each test case has three strings:
6928 * 0) a URI, the source for the uri->filename conversion test,
6929 * or NULL if this test is only for the filename->uri
6930 * direction.
6932 * 1) a filename or NULL, the expected result from
6933 * uri->filename conversion. If non-NULL also the source for
6934 * the filename->uri conversion test,
6936 * 2) a URI if the expected result from filename->uri is
6937 * different than string 0, or NULL if the result should be
6938 * equal to string 0.
6940 { "file:///top/s%20pace%20d/sub/file", "/top/s pace d/sub/file", NULL },
6941 { "file:///top/sub/sub/", "/top/sub/sub/", NULL },
6942 { "file:///top/sub/file#segment", NULL, NULL },
6943 { "file://tem", NULL, NULL },
6944 { "file:/tem", "/tem", "file:///tem" },
6945 { "file:sub/tem", "sub/tem", "sub/tem" },
6946 { "sub/tem", "sub/tem", NULL },
6947 { "s%20pace%20d/tem", "s pace d/tem", NULL },
6948 { "tem", "tem", NULL },
6949 { "tem#segment", NULL, NULL },
6950 #ifdef G_OS_WIN32
6951 /* More or less same tests, but including a drive letter */
6952 { "file:///x:/top/s%20pace%20d/sub/file", "x:/top/s pace d/sub/file", NULL },
6953 { "file:///x:top/sub/sub/", "x:top/sub/sub/", "x%3atop/sub/sub/" },
6954 { "file:///x:top/sub/file#segment", NULL, NULL },
6955 { "file://x:tem", NULL, NULL },
6956 { "file:x:/tem", "x:/tem", "file:///x:/tem" },
6957 { "file:x:tem", "x:tem", "x%3atem" },
6958 { "file:x:sub/tem", "x:sub/tem", "x%3asub/tem" },
6959 { "x%3as%20pace%20d/tem", "x:s pace d/tem", NULL },
6960 { "x%3atem", "x:tem", NULL },
6961 { "x%3atem#segment", NULL, NULL },
6962 #endif
6963 #ifdef G_OS_UNIX
6964 /* Test filenames with a colon in them. That's not possible on Win32 */
6965 { "file:///top/silly:name/bar", "/top/silly:name/bar", NULL },
6966 { "silly%3aname/bar", "silly:name/bar", NULL },
6967 { "silly%3aname", "silly:name", NULL },
6968 #endif
6969 { NULL, NULL }
6972 gint
6973 main (gint argc,
6974 gchar **argv)
6976 gint failures = 0;
6977 gint i;
6979 for (i = 0; i < G_N_ELEMENTS (tests); i++) {
6980 gchar *filename;
6981 #ifdef G_OS_WIN32
6982 gchar *expected_result;
6983 gchar *slash;
6984 #else
6985 const gchar *expected_result;
6986 #endif
6987 if (tests[i][0] == NULL)
6988 continue;
6990 filename = gtk_html_filename_from_uri (tests[i][0]);
6991 #ifdef G_OS_WIN32
6992 expected_result = g_strdup (tests[i][1]);
6993 if (expected_result)
6994 while ((slash = strchr (expected_result, '/')) != NULL)
6995 *slash = '\\';
6996 #else
6997 expected_result = tests[i][1];
6998 #endif
7000 if (((filename == NULL) != (expected_result == NULL)) ||
7001 (filename != NULL && strcmp (filename, expected_result) != 0)) {
7002 g_print ("FAIL: %s -> %s, GOT: %s\n",
7003 tests[i][0],
7004 expected_result ? expected_result : "NULL",
7005 filename ? filename : "NULL");
7006 failures++;
7007 } else {
7008 g_print ("OK: %s -> %s\n",
7009 tests[i][0],
7010 filename ? filename : "NULL");
7014 for (i = 0; i < G_N_ELEMENTS (tests); i++) {
7015 gchar *uri;
7016 const gchar *expected_result;
7018 if (tests[i][1] == NULL)
7019 continue;
7021 uri = gtk_html_filename_to_uri (tests[i][1]);
7022 expected_result = tests[i][2] ? tests[i][2] : tests[i][0];
7023 if (((uri == NULL) != (expected_result == NULL)) ||
7024 (uri != NULL && strcmp (uri, expected_result) != 0)) {
7025 g_printf ("FAIL: %s -> %s, GOT: %s\n",
7026 tests[i][1],
7027 expected_result ? expected_result : "NULL",
7028 uri ? uri : "NULL");
7029 failures++;
7030 } else {
7031 g_print ("OK: %s -> %s\n",
7032 tests[i][1],
7033 uri ? uri : "NULL");
7037 #ifdef G_OS_WIN32
7038 /* Test filename->uri also with backslashes */
7039 for (i = 0; i < G_N_ELEMENTS (tests); i++) {
7040 gchar *uri;
7041 gchar *filename, *slash;
7042 const gchar *expected_result;
7044 if (tests[i][1] == NULL || strchr (tests[i][1], '/') == NULL)
7045 continue;
7047 filename = g_strdup (tests[i][1]);
7048 while ((slash = strchr (filename, '/')) != NULL)
7049 *slash = '\\';
7050 uri = gtk_html_filename_to_uri (tests[i][1]);
7051 expected_result = tests[i][2] ? tests[i][2] : tests[i][0];
7052 if (((uri == NULL) != (expected_result == NULL)) ||
7053 (uri != NULL && strcmp (uri, expected_result) != 0)) {
7054 g_printf ("FAIL: %s -> %s, GOT: %s\n",
7055 filename,
7056 expected_result ? expected_result : "NULL",
7057 uri ? uri : "NULL");
7058 failures++;
7059 } else {
7060 g_print ("OK: %s -> %s\n",
7061 filename,
7062 uri ? uri : "NULL");
7065 #endif
7067 return failures != 0;
7070 #endif