[libyelp/yelp-view] Use GAppLaunchContext instead of gdk_spawn
[yelp.git] / libyelp / yelp-view.c
blobe30d263966d5f43b0230c520dd445e9c82cf0e84
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * Copyright (C) 2009 Shaun McCance <shaunm@gnome.org>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
20 * Author: Shaun McCance <shaunm@gnome.org>
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
27 #include <glib/gi18n.h>
28 #include <glib-object.h>
29 #include <gio/gio.h>
30 #include <gtk/gtk.h>
31 #include <webkit/webkit.h>
33 #include "yelp-debug.h"
34 #include "yelp-docbook-document.h"
35 #include "yelp-error.h"
36 #include "yelp-settings.h"
37 #include "yelp-types.h"
38 #include "yelp-view.h"
40 #define BOGUS_URI "file:///bogus/"
41 #define BOGUS_URI_LEN 14
43 static void yelp_view_init (YelpView *view);
44 static void yelp_view_class_init (YelpViewClass *klass);
45 static void yelp_view_dispose (GObject *object);
46 static void yelp_view_finalize (GObject *object);
47 static void yelp_view_get_property (GObject *object,
48 guint prop_id,
49 GValue *value,
50 GParamSpec *pspec);
51 static void yelp_view_set_property (GObject *object,
52 guint prop_id,
53 const GValue *value,
54 GParamSpec *pspec);
56 static void view_scrolled (GtkAdjustment *adjustment,
57 YelpView *view);
58 static void view_set_hadjustment (YelpView *view,
59 GParamSpec *pspec,
60 gpointer data);
61 static void view_set_vadjustment (YelpView *view,
62 GParamSpec *pspec,
63 gpointer data);
64 static void popup_open_link (GtkMenuItem *item,
65 YelpView *view);
66 static void popup_open_link_new (GtkMenuItem *item,
67 YelpView *view);
68 static void popup_save_image (GtkMenuItem *item,
69 YelpView *view);
70 static void popup_send_image (GtkMenuItem *item,
71 YelpView *view);
72 static void popup_copy_code (GtkMenuItem *item,
73 YelpView *view);
74 static void popup_save_code (GtkMenuItem *item,
75 YelpView *view);
76 static void view_populate_popup (YelpView *view,
77 GtkMenu *menu,
78 gpointer data);
79 static void view_script_alert (YelpView *view,
80 WebKitWebFrame *frame,
81 gchar *message,
82 gpointer data);
83 static gboolean view_navigation_requested (WebKitWebView *view,
84 WebKitWebFrame *frame,
85 WebKitNetworkRequest *request,
86 WebKitWebNavigationAction *action,
87 WebKitWebPolicyDecision *decision,
88 gpointer user_data);
89 static void view_resource_request (WebKitWebView *view,
90 WebKitWebFrame *frame,
91 WebKitWebResource *resource,
92 WebKitNetworkRequest *request,
93 WebKitNetworkResponse *response,
94 gpointer user_data);
96 static void view_print (GtkAction *action,
97 YelpView *view);
98 static void view_history_action (GtkAction *action,
99 YelpView *view);
100 static void view_navigation_action (GtkAction *action,
101 YelpView *view);
103 static void view_clear_load (YelpView *view);
104 static void view_load_page (YelpView *view);
105 static void view_show_error_page (YelpView *view,
106 GError *error);
108 static void settings_set_fonts (YelpSettings *settings);
109 static void settings_show_text_cursor (YelpSettings *settings);
111 static void uri_resolved (YelpUri *uri,
112 YelpView *view);
113 static void document_callback (YelpDocument *document,
114 YelpDocumentSignal signal,
115 YelpView *view,
116 GError *error);
118 static const GtkActionEntry entries[] = {
119 {"YelpViewPrint", GTK_STOCK_PRINT,
120 N_("_Print..."),
121 "<Control>P",
122 NULL,
123 G_CALLBACK (view_print) },
124 {"YelpViewGoBack", GTK_STOCK_GO_BACK,
125 N_("_Back"),
126 "<Alt>Left",
127 NULL,
128 G_CALLBACK (view_history_action) },
129 {"YelpViewGoForward", GTK_STOCK_GO_FORWARD,
130 N_("_Forward"),
131 "<Alt>Right",
132 NULL,
133 G_CALLBACK (view_history_action) },
134 {"YelpViewGoPrevious", NULL,
135 N_("_Previous Page"),
136 "<Control>Page_Up",
137 NULL,
138 G_CALLBACK (view_navigation_action) },
139 {"YelpViewGoNext", NULL,
140 N_("_Next Page"),
141 "<Control>Page_Down",
142 NULL,
143 G_CALLBACK (view_navigation_action) }
146 static gchar *nautilus_sendto = NULL;
148 enum {
149 PROP_0,
150 PROP_URI,
151 PROP_STATE,
152 PROP_PAGE_ID,
153 PROP_ROOT_TITLE,
154 PROP_PAGE_TITLE,
155 PROP_PAGE_DESC,
156 PROP_PAGE_ICON
159 enum {
160 NEW_VIEW_REQUESTED,
161 EXTERNAL_URI,
162 LOADED,
163 LAST_SIGNAL
165 static gint signals[LAST_SIGNAL] = { 0 };
167 G_DEFINE_TYPE (YelpView, yelp_view, WEBKIT_TYPE_WEB_VIEW);
168 #define GET_PRIV(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), YELP_TYPE_VIEW, YelpViewPrivate))
170 static WebKitWebSettings *websettings;
172 typedef struct _YelpActionEntry YelpActionEntry;
173 struct _YelpActionEntry {
174 GtkAction *action;
175 YelpViewActionValidFunc func;
176 gpointer data;
178 action_entry_free (YelpActionEntry *entry)
180 if (entry == NULL)
181 return;
182 g_object_unref (entry->action);
183 g_free (entry);
186 typedef struct _YelpBackEntry YelpBackEntry;
187 struct _YelpBackEntry {
188 YelpUri *uri;
189 gchar *title;
190 gchar *desc;
191 gdouble hadj;
192 gdouble vadj;
194 static void
195 back_entry_free (YelpBackEntry *back)
197 if (back == NULL)
198 return;
199 g_object_unref (back->uri);
200 g_free (back->title);
201 g_free (back->desc);
202 g_free (back);
205 typedef struct _YelpViewPrivate YelpViewPrivate;
206 struct _YelpViewPrivate {
207 YelpUri *uri;
208 gulong uri_resolved;
209 gchar *bogus_uri;
210 YelpDocument *document;
211 GCancellable *cancellable;
212 GtkAdjustment *vadjustment;
213 GtkAdjustment *hadjustment;
214 gdouble vadjust;
215 gdouble hadjust;
216 gulong vadjuster;
217 gulong hadjuster;
219 gchar *popup_link_uri;
220 gchar *popup_link_text;
221 gchar *popup_image_uri;
222 WebKitDOMNode *popup_code_node;
223 WebKitDOMNode *popup_code_title;
224 gchar *popup_code_text;
226 YelpViewState state;
228 gchar *page_id;
229 gchar *root_title;
230 gchar *page_title;
231 gchar *page_desc;
232 gchar *page_icon;
234 GList *back_list;
235 GList *back_cur;
236 gboolean back_load;
238 GtkActionGroup *action_group;
240 GSList *link_actions;
242 gint navigation_requested;
245 #define TARGET_TYPE_URI_LIST "text/uri-list"
246 enum {
247 TARGET_URI_LIST
250 static void
251 yelp_view_init (YelpView *view)
253 YelpViewPrivate *priv = GET_PRIV (view);
255 g_object_set (view, "settings", websettings, NULL);
257 priv->cancellable = NULL;
259 priv->state = YELP_VIEW_STATE_BLANK;
261 priv->navigation_requested =
262 g_signal_connect (view, "navigation-policy-decision-requested",
263 G_CALLBACK (view_navigation_requested), NULL);
264 g_signal_connect (view, "resource-request-starting",
265 G_CALLBACK (view_resource_request), NULL);
266 g_signal_connect (view, "notify::hadjustment",
267 G_CALLBACK (view_set_hadjustment), NULL);
268 g_signal_connect (view, "notify::vadjustment",
269 G_CALLBACK (view_set_vadjustment), NULL);
270 g_signal_connect (view, "populate-popup",
271 G_CALLBACK (view_populate_popup), NULL);
272 g_signal_connect (view, "script-alert",
273 G_CALLBACK (view_script_alert), NULL);
275 priv->action_group = gtk_action_group_new ("YelpView");
276 gtk_action_group_set_translation_domain (priv->action_group, GETTEXT_PACKAGE);
277 gtk_action_group_add_actions (priv->action_group,
278 entries, G_N_ELEMENTS (entries),
279 view);
282 static void
283 yelp_view_dispose (GObject *object)
285 YelpViewPrivate *priv = GET_PRIV (object);
287 if (priv->uri) {
288 g_object_unref (priv->uri);
289 priv->uri = NULL;
292 if (priv->vadjuster > 0) {
293 g_source_remove (priv->vadjuster);
294 priv->vadjuster = 0;
297 if (priv->hadjuster > 0) {
298 g_source_remove (priv->hadjuster);
299 priv->hadjuster = 0;
302 if (priv->cancellable) {
303 g_cancellable_cancel (priv->cancellable);
304 g_object_unref (priv->cancellable);
305 priv->cancellable = NULL;
308 if (priv->action_group) {
309 g_object_unref (priv->action_group);
310 priv->action_group = NULL;
313 if (priv->document) {
314 g_object_unref (priv->document);
315 priv->document = NULL;
318 while (priv->link_actions) {
319 action_entry_free (priv->link_actions->data);
320 priv->link_actions = g_slist_delete_link (priv->link_actions, priv->link_actions);
323 priv->back_cur = NULL;
324 while (priv->back_list) {
325 back_entry_free ((YelpBackEntry *) priv->back_list->data);
326 priv->back_list = g_list_delete_link (priv->back_list, priv->back_list);
329 G_OBJECT_CLASS (yelp_view_parent_class)->dispose (object);
332 static void
333 yelp_view_finalize (GObject *object)
335 YelpViewPrivate *priv = GET_PRIV (object);
337 g_free (priv->popup_link_uri);
338 g_free (priv->popup_link_text);
339 g_free (priv->popup_image_uri);
340 g_free (priv->popup_code_text);
342 g_free (priv->page_id);
343 g_free (priv->root_title);
344 g_free (priv->page_title);
345 g_free (priv->page_desc);
346 g_free (priv->page_icon);
348 g_free (priv->bogus_uri);
350 G_OBJECT_CLASS (yelp_view_parent_class)->finalize (object);
353 static void
354 yelp_view_class_init (YelpViewClass *klass)
356 GObjectClass *object_class = G_OBJECT_CLASS (klass);
357 YelpSettings *settings = yelp_settings_get_default ();
359 nautilus_sendto = g_find_program_in_path ("nautilus-sendto");
361 websettings = webkit_web_settings_new ();
362 g_object_set (websettings, "enable-universal-access-from-file-uris", TRUE, NULL);
363 g_signal_connect (settings,
364 "fonts-changed",
365 G_CALLBACK (settings_set_fonts),
366 NULL);
367 settings_set_fonts (settings);
368 g_signal_connect (settings,
369 "notify::show-text-cursor",
370 G_CALLBACK (settings_show_text_cursor),
371 NULL);
372 settings_show_text_cursor (settings);
374 object_class->dispose = yelp_view_dispose;
375 object_class->finalize = yelp_view_finalize;
376 object_class->get_property = yelp_view_get_property;
377 object_class->set_property = yelp_view_set_property;
379 signals[NEW_VIEW_REQUESTED] =
380 g_signal_new ("new-view-requested",
381 G_TYPE_FROM_CLASS (klass),
382 G_SIGNAL_RUN_LAST,
383 0, NULL, NULL,
384 g_cclosure_marshal_VOID__OBJECT,
385 G_TYPE_NONE, 1, YELP_TYPE_URI);
387 signals[EXTERNAL_URI] =
388 g_signal_new ("external-uri",
389 G_TYPE_FROM_CLASS (klass),
390 G_SIGNAL_RUN_LAST,
391 0, NULL, NULL,
392 g_cclosure_marshal_VOID__OBJECT,
393 G_TYPE_NONE, 1, YELP_TYPE_URI);
395 signals[LOADED] =
396 g_signal_new ("loaded",
397 G_TYPE_FROM_CLASS (klass),
398 G_SIGNAL_RUN_LAST,
399 0, NULL, NULL,
400 g_cclosure_marshal_VOID__VOID,
401 G_TYPE_NONE, 0);
403 g_type_class_add_private (klass, sizeof (YelpViewPrivate));
405 g_object_class_install_property (object_class,
406 PROP_URI,
407 g_param_spec_object ("yelp-uri",
408 _("Yelp URI"),
409 _("A YelpUri with the current location"),
410 YELP_TYPE_URI,
411 G_PARAM_READWRITE | G_PARAM_STATIC_NAME |
412 G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
414 g_object_class_install_property (object_class,
415 PROP_STATE,
416 g_param_spec_enum ("state",
417 N_("Loading State"),
418 N_("The loading state of the view"),
419 YELP_TYPE_VIEW_STATE,
420 YELP_VIEW_STATE_BLANK,
421 G_PARAM_READWRITE | G_PARAM_STATIC_NAME |
422 G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
424 g_object_class_install_property (object_class,
425 PROP_PAGE_ID,
426 g_param_spec_string ("page-id",
427 N_("Page ID"),
428 N_("The ID of the root page of the page being viewed"),
429 NULL,
430 G_PARAM_READABLE |
431 G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
433 g_object_class_install_property (object_class,
434 PROP_ROOT_TITLE,
435 g_param_spec_string ("root-title",
436 N_("Root Title"),
437 N_("The title of the root page of the page being viewed"),
438 NULL,
439 G_PARAM_READABLE |
440 G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
442 g_object_class_install_property (object_class,
443 PROP_PAGE_TITLE,
444 g_param_spec_string ("page-title",
445 N_("Page Title"),
446 N_("The title of the page being viewed"),
447 NULL,
448 G_PARAM_READABLE |
449 G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
451 g_object_class_install_property (object_class,
452 PROP_PAGE_DESC,
453 g_param_spec_string ("page-desc",
454 N_("Page Description"),
455 N_("The description of the page being viewed"),
456 NULL,
457 G_PARAM_READABLE |
458 G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
460 g_object_class_install_property (object_class,
461 PROP_PAGE_ICON,
462 g_param_spec_string ("page-icon",
463 N_("Page Icon"),
464 N_("The icon of the page being viewed"),
465 NULL,
466 G_PARAM_READABLE |
467 G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB));
470 static void
471 yelp_view_get_property (GObject *object,
472 guint prop_id,
473 GValue *value,
474 GParamSpec *pspec)
476 YelpViewPrivate *priv = GET_PRIV (object);
478 switch (prop_id)
480 case PROP_URI:
481 g_value_set_object (value, priv->uri);
482 break;
483 case PROP_PAGE_ID:
484 g_value_set_string (value, priv->page_id);
485 break;
486 case PROP_ROOT_TITLE:
487 g_value_set_string (value, priv->root_title);
488 break;
489 case PROP_PAGE_TITLE:
490 g_value_set_string (value, priv->page_title);
491 break;
492 case PROP_PAGE_DESC:
493 g_value_set_string (value, priv->page_desc);
494 break;
495 case PROP_PAGE_ICON:
496 if (priv->page_icon)
497 g_value_set_string (value, priv->page_icon);
498 else
499 g_value_set_string (value, "help-contents");
500 break;
501 case PROP_STATE:
502 g_value_set_enum (value, priv->state);
503 break;
504 default:
505 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
506 break;
510 static void
511 yelp_view_set_property (GObject *object,
512 guint prop_id,
513 const GValue *value,
514 GParamSpec *pspec)
516 YelpUri *uri;
517 YelpViewPrivate *priv = GET_PRIV (object);
519 switch (prop_id)
521 case PROP_URI:
522 uri = g_value_get_object (value);
523 yelp_view_load_uri (YELP_VIEW (object), uri);
524 g_object_unref (uri);
525 break;
526 case PROP_STATE:
527 priv->state = g_value_get_enum (value);
528 break;
529 default:
530 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
531 break;
535 /******************************************************************************/
537 GtkWidget *
538 yelp_view_new (void)
540 return (GtkWidget *) g_object_new (YELP_TYPE_VIEW, NULL);
543 void
544 yelp_view_load (YelpView *view,
545 const gchar *uri)
547 YelpUri *yuri = yelp_uri_new (uri);
548 yelp_view_load_uri (view, yuri);
549 g_object_unref (yuri);
552 void
553 yelp_view_load_uri (YelpView *view,
554 YelpUri *uri)
556 YelpViewPrivate *priv = GET_PRIV (view);
557 GParamSpec *spec;
559 view_clear_load (view);
560 g_object_set (view, "state", YELP_VIEW_STATE_LOADING, NULL);
562 g_free (priv->page_id);
563 g_free (priv->root_title);
564 g_free (priv->page_title);
565 g_free (priv->page_desc);
566 g_free (priv->page_icon);
567 priv->page_id = NULL;
568 priv->root_title = NULL;
569 priv->page_title = NULL;
570 priv->page_desc = NULL;
571 priv->page_icon = NULL;
573 spec = g_object_class_find_property ((GObjectClass *) YELP_VIEW_GET_CLASS (view),
574 "page-id");
575 g_signal_emit_by_name (view, "notify::page-id", spec);
577 spec = g_object_class_find_property ((GObjectClass *) YELP_VIEW_GET_CLASS (view),
578 "root-title");
579 g_signal_emit_by_name (view, "notify::root-title", spec);
581 spec = g_object_class_find_property ((GObjectClass *) YELP_VIEW_GET_CLASS (view),
582 "page-title");
583 g_signal_emit_by_name (view, "notify::page-title", spec);
585 spec = g_object_class_find_property ((GObjectClass *) YELP_VIEW_GET_CLASS (view),
586 "page-desc");
587 g_signal_emit_by_name (view, "notify::page-desc", spec);
589 spec = g_object_class_find_property ((GObjectClass *) YELP_VIEW_GET_CLASS (view),
590 "page-icon");
591 g_signal_emit_by_name (view, "notify::page-icon", spec);
593 gtk_action_set_sensitive (gtk_action_group_get_action (priv->action_group,
594 "YelpViewGoPrevious"),
595 FALSE);
596 gtk_action_set_sensitive (gtk_action_group_get_action (priv->action_group,
597 "YelpViewGoNext"),
598 FALSE);
600 priv->uri = g_object_ref (uri);
601 if (!yelp_uri_is_resolved (uri)) {
602 priv->uri_resolved = g_signal_connect (uri, "resolved",
603 G_CALLBACK (uri_resolved),
604 view);
605 yelp_uri_resolve (uri);
607 else {
608 uri_resolved (uri, view);
612 void
613 yelp_view_load_document (YelpView *view,
614 YelpUri *uri,
615 YelpDocument *document)
617 YelpViewPrivate *priv = GET_PRIV (view);
619 g_return_if_fail (yelp_uri_is_resolved (uri));
621 view_clear_load (view);
622 g_object_set (view, "state", YELP_VIEW_STATE_LOADING, NULL);
624 priv->uri = g_object_ref (uri);
625 g_object_ref (document);
626 if (priv->document)
627 g_object_unref (document);
628 priv->document = document;
630 view_load_page (view);
633 YelpDocument *
634 yelp_view_get_document (YelpView *view)
636 YelpViewPrivate *priv = GET_PRIV (view);
637 return priv->document;
640 GtkActionGroup *
641 yelp_view_get_action_group (YelpView *view)
643 YelpViewPrivate *priv = GET_PRIV (view);
644 return priv->action_group;
647 /******************************************************************************/
649 void
650 yelp_view_add_link_action (YelpView *view,
651 GtkAction *action,
652 YelpViewActionValidFunc func,
653 gpointer data)
655 YelpActionEntry *entry;
656 YelpViewPrivate *priv = GET_PRIV (view);
658 entry = g_new0 (YelpActionEntry, 1);
659 entry->action = g_object_ref (action);
660 entry->func = func;
661 entry->data = data;
663 priv->link_actions = g_slist_append (priv->link_actions, entry);
666 YelpUri *
667 yelp_view_get_active_link_uri (YelpView *view)
669 YelpViewPrivate *priv = GET_PRIV (view);
670 YelpUri *uri;
672 if (g_str_has_prefix (priv->popup_link_uri, BOGUS_URI))
673 uri = yelp_uri_new_relative (priv->uri, priv->popup_link_uri + BOGUS_URI_LEN);
674 else
675 uri = yelp_uri_new_relative (priv->uri, priv->popup_link_uri);
677 return uri;
680 gchar *
681 yelp_view_get_active_link_text (YelpView *view)
683 YelpViewPrivate *priv = GET_PRIV (view);
684 return g_strdup (priv->popup_link_text);
687 /******************************************************************************/
689 static void
690 view_scrolled (GtkAdjustment *adjustment,
691 YelpView *view)
693 YelpViewPrivate *priv = GET_PRIV (view);
694 if (priv->back_cur == NULL || priv->back_cur->data == NULL)
695 return;
696 if (adjustment == priv->vadjustment)
697 ((YelpBackEntry *) priv->back_cur->data)->vadj = gtk_adjustment_get_value (adjustment);
698 else if (adjustment = priv->hadjustment)
699 ((YelpBackEntry *) priv->back_cur->data)->hadj = gtk_adjustment_get_value (adjustment);
702 static void
703 view_set_hadjustment (YelpView *view,
704 GParamSpec *pspec,
705 gpointer data)
707 YelpViewPrivate *priv = GET_PRIV (view);
708 priv->hadjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (view));
709 if (priv->hadjuster > 0)
710 g_source_remove (priv->hadjuster);
711 priv->hadjuster = 0;
712 if (priv->hadjustment)
713 priv->hadjuster = g_signal_connect (priv->hadjustment, "value-changed",
714 G_CALLBACK (view_scrolled), view);
717 static void
718 view_set_vadjustment (YelpView *view,
719 GParamSpec *pspec,
720 gpointer data)
722 YelpViewPrivate *priv = GET_PRIV (view);
723 priv->vadjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (view));
724 if (priv->vadjuster > 0)
725 g_source_remove (priv->vadjuster);
726 priv->vadjuster = 0;
727 if (priv->vadjustment)
728 priv->vadjuster = g_signal_connect (priv->vadjustment, "value-changed",
729 G_CALLBACK (view_scrolled), view);
732 static void
733 popup_open_link (GtkMenuItem *item,
734 YelpView *view)
736 YelpViewPrivate *priv = GET_PRIV (view);
737 YelpUri *uri;
739 if (g_str_has_prefix (priv->popup_link_uri, BOGUS_URI))
740 uri = yelp_uri_new_relative (priv->uri, priv->popup_link_uri + BOGUS_URI_LEN);
741 else
742 uri = yelp_uri_new_relative (priv->uri, priv->popup_link_uri);
744 yelp_view_load_uri (view, uri);
746 g_free (priv->popup_link_uri);
747 priv->popup_link_uri = NULL;
749 g_free (priv->popup_link_text);
750 priv->popup_link_text = NULL;
753 static void
754 popup_open_link_new (GtkMenuItem *item,
755 YelpView *view)
757 YelpViewPrivate *priv = GET_PRIV (view);
758 YelpUri *uri;
760 if (g_str_has_prefix (priv->popup_link_uri, BOGUS_URI))
761 uri = yelp_uri_new_relative (priv->uri, priv->popup_link_uri + BOGUS_URI_LEN);
762 else
763 uri = yelp_uri_new_relative (priv->uri, priv->popup_link_uri);
765 g_free (priv->popup_link_uri);
766 priv->popup_link_uri = NULL;
768 g_free (priv->popup_link_text);
769 priv->popup_link_text = NULL;
771 g_signal_emit (view, signals[NEW_VIEW_REQUESTED], 0, uri);
772 g_object_unref (uri);
775 typedef struct _YelpSaveData YelpSaveData;
776 struct _YelpSaveData {
777 GFile *orig;
778 GFile *dest;
779 YelpView *view;
780 GtkWindow *window;
783 static void
784 file_copied (GFile *file,
785 GAsyncResult *res,
786 YelpSaveData *data)
788 GError *error = NULL;
789 if (!g_file_copy_finish (file, res, &error)) {
790 GtkWidget *dialog = gtk_message_dialog_new (gtk_widget_get_visible (GTK_WIDGET (data->window)) ? data->window : NULL,
791 GTK_DIALOG_DESTROY_WITH_PARENT,
792 GTK_MESSAGE_ERROR,
793 GTK_BUTTONS_OK,
794 "%s", error->message);
795 gtk_dialog_run (GTK_DIALOG (dialog));
796 gtk_widget_destroy (dialog);
798 g_object_unref (data->orig);
799 g_object_unref (data->dest);
800 g_object_unref (data->view);
801 g_object_unref (data->window);
804 static void
805 popup_save_image (GtkMenuItem *item,
806 YelpView *view)
808 YelpSaveData *data;
809 GtkWidget *dialog, *window;
810 gchar *basename;
811 gint res;
812 YelpViewPrivate *priv = GET_PRIV (view);
814 for (window = gtk_widget_get_parent (GTK_WIDGET (view));
815 window && !GTK_IS_WINDOW (window);
816 window = gtk_widget_get_parent (window));
818 data = g_new0 (YelpSaveData, 1);
819 data->orig = g_file_new_for_uri (priv->popup_image_uri);
820 data->view = g_object_ref (view);
821 data->window = g_object_ref (window);
822 g_free (priv->popup_image_uri);
823 priv->popup_image_uri = NULL;
825 dialog = gtk_file_chooser_dialog_new (_("Save Image"),
826 GTK_WINDOW (window),
827 GTK_FILE_CHOOSER_ACTION_SAVE,
828 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
829 GTK_STOCK_SAVE, GTK_RESPONSE_OK,
830 NULL);
831 gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
832 basename = g_file_get_basename (data->orig);
833 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), basename);
834 g_free (basename);
835 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
836 g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP));
838 res = gtk_dialog_run (GTK_DIALOG (dialog));
840 if (res == GTK_RESPONSE_OK) {
841 data->dest = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
842 g_file_copy_async (data->orig, data->dest,
843 G_FILE_COPY_OVERWRITE,
844 G_PRIORITY_DEFAULT,
845 NULL, NULL, NULL,
846 (GAsyncReadyCallback) file_copied,
847 data);
849 else {
850 g_object_unref (data->orig);
851 g_object_unref (data->view);
852 g_object_unref (data->window);
853 g_free (data);
856 gtk_widget_destroy (dialog);
859 static void
860 popup_send_image (GtkMenuItem *item,
861 YelpView *view)
863 gchar *command;
864 GAppInfo *app;
865 GAppLaunchContext *context;
866 GError *error = NULL;
867 YelpViewPrivate *priv = GET_PRIV (view);
869 command = g_strdup_printf ("%s %s", nautilus_sendto, priv->popup_image_uri);
870 context = (GAppLaunchContext *) gdk_app_launch_context_new ();
872 app = g_app_info_create_from_commandline (command, NULL, 0, &error);
873 if (app) {
874 g_app_info_launch (app, NULL, context, &error);
875 g_object_unref (app);
878 if (error) {
879 g_debug ("Could not launch nautilus-sendto: %s", error->message);
880 g_error_free (error);
883 g_object_unref (context);
884 g_free (command);
885 g_free (priv->popup_image_uri);
886 priv->popup_image_uri = NULL;
889 static void
890 popup_copy_code (GtkMenuItem *item,
891 YelpView *view)
893 YelpViewPrivate *priv = GET_PRIV (view);
894 GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
895 gchar *content = webkit_dom_node_get_text_content (priv->popup_code_node);
896 gtk_clipboard_set_text (clipboard, content, -1);
897 g_free (content);
900 static void
901 popup_save_code (GtkMenuItem *item,
902 YelpView *view)
904 YelpViewPrivate *priv = GET_PRIV (view);
905 GtkWidget *dialog, *window;
906 gint res;
908 g_free (priv->popup_code_text);
909 priv->popup_code_text = webkit_dom_node_get_text_content (priv->popup_code_node);
910 if (!g_str_has_suffix (priv->popup_code_text, "\n")) {
911 gchar *tmp = g_strconcat (priv->popup_code_text, "\n", NULL);
912 g_free (priv->popup_code_text);
913 priv->popup_code_text = tmp;
916 for (window = gtk_widget_get_parent (GTK_WIDGET (view));
917 window && !GTK_IS_WINDOW (window);
918 window = gtk_widget_get_parent (window));
920 dialog = gtk_file_chooser_dialog_new (_("Save Code"),
921 GTK_WINDOW (window),
922 GTK_FILE_CHOOSER_ACTION_SAVE,
923 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
924 GTK_STOCK_SAVE, GTK_RESPONSE_OK,
925 NULL);
926 gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
927 if (priv->popup_code_title) {
928 gchar *filename = webkit_dom_node_get_text_content (priv->popup_code_title);
929 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), filename);
930 g_free (filename);
932 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
933 g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS));
935 res = gtk_dialog_run (GTK_DIALOG (dialog));
937 if (res == GTK_RESPONSE_OK) {
938 GError *error = NULL;
939 GFile *file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
940 GFileOutputStream *stream = g_file_replace (file, NULL, FALSE,
941 G_FILE_CREATE_NONE,
942 NULL,
943 &error);
944 if (stream == NULL) {
945 GtkWidget *dialog = gtk_message_dialog_new (gtk_widget_get_visible (window) ? GTK_WINDOW (window) : NULL,
946 GTK_DIALOG_DESTROY_WITH_PARENT,
947 GTK_MESSAGE_ERROR,
948 GTK_BUTTONS_OK,
949 "%s", error->message);
950 gtk_dialog_run (GTK_DIALOG (dialog));
951 gtk_widget_destroy (dialog);
952 g_error_free (error);
954 else {
955 /* FIXME: we should do this async */
956 GDataOutputStream *datastream = g_data_output_stream_new (G_OUTPUT_STREAM (stream));
957 if (!g_data_output_stream_put_string (datastream, priv->popup_code_text, NULL, &error)) {
958 GtkWidget *dialog = gtk_message_dialog_new (gtk_widget_get_visible (window) ? GTK_WINDOW (window) : NULL,
959 GTK_DIALOG_DESTROY_WITH_PARENT,
960 GTK_MESSAGE_ERROR,
961 GTK_BUTTONS_OK,
962 "%s", error->message);
963 gtk_dialog_run (GTK_DIALOG (dialog));
964 gtk_widget_destroy (dialog);
965 g_error_free (error);
967 g_object_unref (datastream);
969 g_object_unref (file);
972 priv->popup_code_node = NULL;
973 priv->popup_code_title = NULL;
974 g_free (priv->popup_code_text);
975 priv->popup_code_text = NULL;
977 gtk_widget_destroy (dialog);
980 static void
981 view_populate_popup (YelpView *view,
982 GtkMenu *menu,
983 gpointer data)
985 WebKitHitTestResult *result;
986 WebKitHitTestResultContext context;
987 GdkEvent *event;
988 YelpViewPrivate *priv = GET_PRIV (view);
989 GList *children;
990 GtkWidget *item;
991 WebKitDOMNode *node, *cur, *link_node = NULL, *code_node = NULL, *code_title_node = NULL;
993 children = gtk_container_get_children (GTK_CONTAINER (menu));
994 while (children) {
995 gtk_container_remove (GTK_CONTAINER (menu),
996 GTK_WIDGET (children->data));
997 children = children->next;
999 g_list_free (children);
1001 event = gtk_get_current_event ();
1003 result = webkit_web_view_get_hit_test_result (WEBKIT_WEB_VIEW (view), (GdkEventButton *) event);
1004 g_object_get (result,
1005 "context", &context,
1006 "inner-node", &node,
1007 NULL);
1008 for (cur = node; cur != NULL; cur = webkit_dom_node_get_parent_node (cur)) {
1009 if (WEBKIT_DOM_IS_ELEMENT (cur) &&
1010 webkit_dom_element_webkit_matches_selector ((WebKitDOMElement *) cur,
1011 "a", NULL))
1012 link_node = cur;
1014 if (WEBKIT_DOM_IS_ELEMENT (cur) &&
1015 webkit_dom_element_webkit_matches_selector ((WebKitDOMElement *) cur,
1016 "div.code", NULL)) {
1017 WebKitDOMNode *title;
1018 code_node = (WebKitDOMNode *)
1019 webkit_dom_element_query_selector ((WebKitDOMElement *) cur,
1020 "pre.contents", NULL);
1021 title = webkit_dom_node_get_parent_node (cur);
1022 if (title != NULL && WEBKIT_DOM_IS_ELEMENT (title) &&
1023 webkit_dom_element_webkit_matches_selector ((WebKitDOMElement *) title,
1024 "div.contents", NULL)) {
1025 title = webkit_dom_node_get_previous_sibling (title);
1026 if (title != NULL && WEBKIT_DOM_IS_ELEMENT (title) &&
1027 webkit_dom_element_webkit_matches_selector ((WebKitDOMElement *) title,
1028 "div.title", NULL)) {
1029 code_title_node = title;
1035 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
1036 gchar *uri;
1037 g_object_get (result, "link-uri", &uri, NULL);
1038 g_free (priv->popup_link_uri);
1039 priv->popup_link_uri = uri;
1041 g_free (priv->popup_link_text);
1042 priv->popup_link_text = NULL;
1043 if (link_node != NULL) {
1044 WebKitDOMNode *child;
1045 gchar *tmp;
1046 gint i, tmpi;
1047 gboolean ws;
1049 child = (WebKitDOMNode *)
1050 webkit_dom_element_query_selector (WEBKIT_DOM_ELEMENT (link_node),
1051 "div.linkdiv div.title", NULL);
1052 if (child != NULL)
1053 priv->popup_link_text = webkit_dom_node_get_text_content (child);
1055 if (priv->popup_link_text == NULL)
1056 priv->popup_link_text = webkit_dom_node_get_text_content (link_node);
1058 tmp = g_new0 (gchar, strlen(priv->popup_link_text) + 1);
1059 ws = FALSE;
1060 for (i = 0, tmpi = 0; priv->popup_link_text[i] != '\0'; i++) {
1061 if (priv->popup_link_text[i] == ' ' || priv->popup_link_text[i] == '\n') {
1062 if (!ws) {
1063 tmp[tmpi] = ' ';
1064 tmpi++;
1065 ws = TRUE;
1068 else {
1069 tmp[tmpi] = priv->popup_link_text[i];
1070 tmpi++;
1071 ws = FALSE;
1074 tmp[tmpi] = '\0';
1075 g_free (priv->popup_link_text);
1076 priv->popup_link_text = tmp;
1078 else {
1079 priv->popup_link_text = g_strdup (uri);
1082 if (g_str_has_prefix (priv->popup_link_uri, "mailto:")) {
1083 /* Not using a mnemonic because underscores are common in email
1084 * addresses, and we'd have to escape them. There doesn't seem
1085 * to be a quick GTK+ function for this. In practice, there will
1086 * probably only be one menu item for mailto link popups anyway,
1087 * so the mnemonic's not that big of a deal.
1089 gchar *label = g_strdup_printf (_("Send email to %s"),
1090 priv->popup_link_uri + 7);
1091 item = gtk_menu_item_new_with_label (label);
1092 g_signal_connect (item, "activate",
1093 G_CALLBACK (popup_open_link), view);
1094 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1096 else {
1097 GSList *cur;
1099 item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
1100 g_signal_connect (item, "activate",
1101 G_CALLBACK (popup_open_link), view);
1102 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1104 item = gtk_menu_item_new_with_mnemonic (_("Open Link in New _Window"));
1105 g_signal_connect (item, "activate",
1106 G_CALLBACK (popup_open_link_new), view);
1107 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1109 for (cur = priv->link_actions; cur != NULL; cur = cur->next) {
1110 gboolean add;
1111 YelpActionEntry *entry = (YelpActionEntry *) cur->data;
1112 if (entry->func == NULL)
1113 add = TRUE;
1114 else
1115 add = (* entry->func) (view, entry->action,
1116 priv->popup_link_uri,
1117 entry->data);
1118 if (add) {
1119 item = gtk_action_create_menu_item (entry->action);
1120 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1125 else {
1126 item = gtk_action_create_menu_item (gtk_action_group_get_action (priv->action_group,
1127 "YelpViewGoBack"));
1128 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1129 item = gtk_action_create_menu_item (gtk_action_group_get_action (priv->action_group,
1130 "YelpViewGoForward"));
1131 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1134 if ((context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) ||
1135 (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA)) {
1136 /* This doesn't currently work for video with automatic controls,
1137 * because WebKit puts the hit test on the div with the controls.
1139 gboolean image = context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE;
1140 gchar *uri;
1141 g_object_get (result, image ? "image-uri" : "media-uri", &uri, NULL);
1142 g_free (priv->popup_image_uri);
1143 if (g_str_has_prefix (uri, BOGUS_URI)) {
1144 priv->popup_image_uri = yelp_uri_locate_file_uri (priv->uri, uri + BOGUS_URI_LEN);
1145 g_free (uri);
1147 else {
1148 priv->popup_image_uri = uri;
1151 item = gtk_separator_menu_item_new ();
1152 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1154 if (image)
1155 item = gtk_menu_item_new_with_mnemonic (_("_Save Image As..."));
1156 else
1157 item = gtk_menu_item_new_with_mnemonic (_("_Save Video As..."));
1158 g_signal_connect (item, "activate",
1159 G_CALLBACK (popup_save_image), view);
1160 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1162 if (nautilus_sendto) {
1163 if (image)
1164 item = gtk_menu_item_new_with_mnemonic (_("S_end Image To..."));
1165 else
1166 item = gtk_menu_item_new_with_mnemonic (_("S_end Video To..."));
1167 g_signal_connect (item, "activate",
1168 G_CALLBACK (popup_send_image), view);
1169 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1173 if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION) {
1174 item = gtk_separator_menu_item_new ();
1175 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1177 item = gtk_menu_item_new_with_mnemonic (_("_Copy Text"));
1178 g_signal_connect_swapped (item, "activate",
1179 G_CALLBACK (webkit_web_view_copy_clipboard), view);
1180 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1183 if (code_node != NULL) {
1184 item = gtk_separator_menu_item_new ();
1185 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1187 priv->popup_code_node = code_node;
1188 priv->popup_code_title = code_title_node;
1190 item = gtk_menu_item_new_with_mnemonic (_("C_opy Code Block"));
1191 g_signal_connect (item, "activate",
1192 G_CALLBACK (popup_copy_code), view);
1193 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1195 item = gtk_menu_item_new_with_mnemonic (_("Save Code _Block As..."));
1196 g_signal_connect (item, "activate",
1197 G_CALLBACK (popup_save_code), view);
1198 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1201 g_object_unref (result);
1202 gdk_event_free (event);
1203 gtk_widget_show_all (GTK_WIDGET (menu));
1206 static void
1207 view_script_alert (YelpView *view,
1208 WebKitWebFrame *frame,
1209 gchar *message,
1210 gpointer data)
1212 printf ("\n\n===ALERT===\n%s\n\n", message);
1215 static gboolean
1216 view_navigation_requested (WebKitWebView *view,
1217 WebKitWebFrame *frame,
1218 WebKitNetworkRequest *request,
1219 WebKitWebNavigationAction *action,
1220 WebKitWebPolicyDecision *decision,
1221 gpointer user_data)
1223 const gchar *requri = webkit_network_request_get_uri (request);
1224 YelpViewPrivate *priv = GET_PRIV (view);
1225 YelpUri *uri;
1227 if (g_str_has_prefix (requri, BOGUS_URI))
1228 uri = yelp_uri_new_relative (priv->uri, requri + BOGUS_URI_LEN);
1229 else
1230 uri = yelp_uri_new_relative (priv->uri, requri);
1232 webkit_web_policy_decision_ignore (decision);
1234 yelp_view_load_uri ((YelpView *) view, uri);
1236 return TRUE;
1239 static void
1240 view_resource_request (WebKitWebView *view,
1241 WebKitWebFrame *frame,
1242 WebKitWebResource *resource,
1243 WebKitNetworkRequest *request,
1244 WebKitNetworkResponse *response,
1245 gpointer user_data)
1247 YelpViewPrivate *priv = GET_PRIV (view);
1248 const gchar *requri = webkit_network_request_get_uri (request);
1249 gchar last;
1250 gchar *newpath;
1252 if (!g_str_has_prefix (requri, BOGUS_URI))
1253 return;
1255 /* We get this signal for the page itself. Ignore. */
1256 if (g_str_equal (requri, priv->bogus_uri))
1257 return;
1259 newpath = yelp_uri_locate_file_uri (priv->uri, requri + BOGUS_URI_LEN);
1260 if (newpath != NULL) {
1261 webkit_network_request_set_uri (request, newpath);
1262 g_free (newpath);
1264 else {
1265 webkit_network_request_set_uri (request, "about:blank");
1269 static void
1270 view_print (GtkAction *action, YelpView *view)
1272 webkit_web_frame_print (webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (view)));
1275 static void
1276 view_history_action (GtkAction *action,
1277 YelpView *view)
1279 GList *newcur;
1280 YelpViewPrivate *priv = GET_PRIV (view);
1282 if (priv->back_cur == NULL)
1283 return;
1285 if (g_str_equal (gtk_action_get_name (action), "YelpViewGoBack"))
1286 newcur = priv->back_cur->next;
1287 else
1288 newcur = priv->back_cur->prev;
1290 if (newcur == NULL)
1291 return;
1293 priv->back_cur = newcur;
1295 if (priv->back_cur->data == NULL)
1296 return;
1298 priv->back_load = TRUE;
1299 yelp_view_load_uri (view, ((YelpBackEntry *) priv->back_cur->data)->uri);
1300 priv->vadjust = ((YelpBackEntry *) priv->back_cur->data)->vadj;
1301 priv->hadjust = ((YelpBackEntry *) priv->back_cur->data)->hadj;
1304 static void
1305 view_navigation_action (GtkAction *action,
1306 YelpView *view)
1308 YelpViewPrivate *priv = GET_PRIV (view);
1309 gchar *page_id, *new_id, *xref;
1310 YelpUri *new_uri;
1312 page_id = yelp_uri_get_page_id (priv->uri);
1314 if (g_str_equal (gtk_action_get_name (action), "YelpViewGoPrevious"))
1315 new_id = yelp_document_get_prev_id (priv->document, page_id);
1316 else
1317 new_id = yelp_document_get_next_id (priv->document, page_id);
1319 /* Just in case we screwed up somewhere */
1320 if (new_id == NULL) {
1321 gtk_action_set_sensitive (action, FALSE);
1322 return;
1325 xref = g_strconcat ("xref:", new_id, NULL);
1326 new_uri = yelp_uri_new_relative (priv->uri, xref);
1327 yelp_view_load_uri (view, new_uri);
1329 g_free (xref);
1330 g_free (new_id);
1331 g_object_unref (new_uri);
1334 static void
1335 view_clear_load (YelpView *view)
1337 YelpViewPrivate *priv = GET_PRIV (view);
1339 if (priv->uri) {
1340 if (priv->uri_resolved != 0) {
1341 g_signal_handler_disconnect (priv->uri, priv->uri_resolved);
1342 priv->uri_resolved = 0;
1344 g_object_unref (priv->uri);
1345 priv->uri = NULL;
1348 if (priv->cancellable) {
1349 g_cancellable_cancel (priv->cancellable);
1350 priv->cancellable = NULL;
1354 static void
1355 view_load_page (YelpView *view)
1357 YelpViewPrivate *priv = GET_PRIV (view);
1358 gchar *page_id;
1360 debug_print (DB_FUNCTION, "entering\n");
1362 g_return_if_fail (priv->cancellable == NULL);
1364 if (priv->document == NULL) {
1365 GError *error;
1366 gchar *docuri;
1367 /* FIXME: and if priv->uri is NULL? */
1368 docuri = yelp_uri_get_document_uri (priv->uri);
1369 /* FIXME: CANT_READ isn't right */
1370 if (docuri) {
1371 error = g_error_new (YELP_ERROR, YELP_ERROR_CANT_READ,
1372 _("Could not load a document for ‘%s’"),
1373 docuri);
1374 g_free (docuri);
1376 else {
1377 error = g_error_new (YELP_ERROR, YELP_ERROR_CANT_READ,
1378 _("Could not load a document"));
1380 view_show_error_page (view, error);
1381 return;
1384 page_id = yelp_uri_get_page_id (priv->uri);
1385 priv->cancellable = g_cancellable_new ();
1386 yelp_document_request_page (priv->document,
1387 page_id,
1388 priv->cancellable,
1389 (YelpDocumentCallback) document_callback,
1390 view);
1391 g_free (page_id);
1394 static void
1395 view_show_error_page (YelpView *view,
1396 GError *error)
1398 YelpViewPrivate *priv = GET_PRIV (view);
1399 static const gchar *errorpage =
1400 "<html><head>"
1401 "<style type='text/css'>"
1402 "body {"
1403 " margin: 1em;"
1404 " color: %s;"
1405 " background-color: %s;"
1406 " }\n"
1407 "div.note {"
1408 " padding: 6px;"
1409 " border-color: %s;"
1410 " border-top: solid 1px;"
1411 " border-bottom: solid 1px;"
1412 " background-color: %s;"
1413 " }\n"
1414 "div.note div.inner {"
1415 " margin: 0; padding: 0;"
1416 " background-image: url(%s);"
1417 " background-position: %s top;"
1418 " background-repeat: no-repeat;"
1419 " min-height: %ipx;"
1420 " }\n"
1421 "div.note div.contents {"
1422 " margin-%s: %ipx;"
1423 " }\n"
1424 "div.note div.title {"
1425 " margin-%s: %ipx;"
1426 " margin-bottom: 0.2em;"
1427 " font-weight: bold;"
1428 " color: %s;"
1429 " }\n"
1430 "</style>"
1431 "</head><body>"
1432 "<div class='note'><div class='inner'>"
1433 "<div class='title'>%s</div>"
1434 "<div class='contents'>%s</div>"
1435 "</div></div>"
1436 "</body></html>";
1437 YelpSettings *settings = yelp_settings_get_default ();
1438 gchar *page, *title = NULL;
1439 gchar *textcolor, *bgcolor, *noteborder, *notebg, *titlecolor, *noteicon;
1440 gint iconsize;
1441 const gchar *left = (gtk_widget_get_direction((GtkWidget *) view) == GTK_TEXT_DIR_RTL) ? "right" : "left";
1442 if (error->domain == YELP_ERROR)
1443 switch (error->code) {
1444 case YELP_ERROR_NOT_FOUND:
1445 title = _("Not Found");
1446 break;
1447 case YELP_ERROR_CANT_READ:
1448 title = _("Cannot Read");
1449 break;
1450 default:
1451 break;
1453 if (title == NULL)
1454 title = _("Unknown Error");
1455 textcolor = yelp_settings_get_color (settings, YELP_SETTINGS_COLOR_TEXT);
1456 bgcolor = yelp_settings_get_color (settings, YELP_SETTINGS_COLOR_BASE);
1457 noteborder = yelp_settings_get_color (settings, YELP_SETTINGS_COLOR_RED_BORDER);
1458 notebg = yelp_settings_get_color (settings, YELP_SETTINGS_COLOR_YELLOW_BASE);
1459 titlecolor = yelp_settings_get_color (settings, YELP_SETTINGS_COLOR_TEXT_LIGHT);
1460 noteicon = yelp_settings_get_icon (settings, YELP_SETTINGS_ICON_WARNING);
1461 iconsize = yelp_settings_get_icon_size (settings) + 6;
1462 page = g_strdup_printf (errorpage,
1463 textcolor, bgcolor, noteborder, notebg, noteicon,
1464 left, iconsize, left, iconsize, left, iconsize,
1465 titlecolor, title, error->message);
1466 g_object_set (view, "state", YELP_VIEW_STATE_ERROR, NULL);
1467 g_signal_handler_block (view, priv->navigation_requested);
1468 webkit_web_view_load_string (WEBKIT_WEB_VIEW (view),
1469 page,
1470 "text/html",
1471 "UTF-8",
1472 "file:///error/");
1473 g_signal_handler_unblock (view, priv->navigation_requested);
1474 g_error_free (error);
1475 g_free (page);
1479 static void
1480 settings_set_fonts (YelpSettings *settings)
1482 gchar *family;
1483 gint size;
1485 g_object_set (websettings,
1486 "default-encoding", "utf-8",
1487 "enable-private-browsing", TRUE,
1488 NULL);
1490 family = yelp_settings_get_font_family (settings,
1491 YELP_SETTINGS_FONT_VARIABLE);
1492 size = yelp_settings_get_font_size (settings,
1493 YELP_SETTINGS_FONT_VARIABLE);
1494 g_object_set (websettings,
1495 "default-font-family", family,
1496 "sans-serif-font-family", family,
1497 "default-font-size", size,
1498 NULL);
1499 g_free (family);
1501 family = yelp_settings_get_font_family (settings,
1502 YELP_SETTINGS_FONT_FIXED);
1503 size = yelp_settings_get_font_size (settings,
1504 YELP_SETTINGS_FONT_FIXED);
1505 g_object_set (websettings,
1506 "monospace-font-family", family,
1507 "default-monospace-font-size", size,
1508 NULL);
1509 g_free (family);
1512 static void
1513 settings_show_text_cursor (YelpSettings *settings)
1515 g_object_set (websettings,
1516 "enable-caret-browsing",
1517 yelp_settings_get_show_text_cursor (settings),
1518 NULL);
1521 /******************************************************************************/
1523 static void
1524 uri_resolved (YelpUri *uri,
1525 YelpView *view)
1527 YelpViewPrivate *priv = GET_PRIV (view);
1528 YelpDocument *document;
1529 YelpBackEntry *back;
1530 GtkAction *action;
1531 GSList *proxies, *cur;
1532 GError *error;
1533 gchar *struri;
1534 GParamSpec *spec;
1536 debug_print (DB_FUNCTION, "entering\n");
1538 switch (yelp_uri_get_document_type (uri)) {
1539 case YELP_URI_DOCUMENT_TYPE_EXTERNAL:
1540 g_signal_emit (view, signals[EXTERNAL_URI], 0, uri);
1541 return;
1542 case YELP_URI_DOCUMENT_TYPE_NOT_FOUND:
1543 struri = yelp_uri_get_canonical_uri (uri);
1544 if (struri != NULL) {
1545 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
1546 _("The URI ‘%s’ does not point to a valid page."),
1547 struri);
1548 g_free (struri);
1550 else {
1551 error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
1552 _("The URI does not point to a valid page."),
1553 struri);
1555 view_show_error_page (view, error);
1556 return;
1557 case YELP_URI_DOCUMENT_TYPE_ERROR:
1558 struri = yelp_uri_get_canonical_uri (uri);
1559 error = g_error_new (YELP_ERROR, YELP_ERROR_PROCESSING,
1560 _("The URI ‘%s’ could not be parsed."),
1561 struri);
1562 g_free (struri);
1563 view_show_error_page (view, error);
1564 return;
1565 default:
1566 break;
1569 document = yelp_document_get_for_uri (uri);
1570 if (priv->document)
1571 g_object_unref (priv->document);
1572 priv->document = document;
1574 if (!priv->back_load) {
1575 back = g_new0 (YelpBackEntry, 1);
1576 back->uri = g_object_ref (uri);
1577 while (priv->back_list != priv->back_cur) {
1578 back_entry_free ((YelpBackEntry *) priv->back_list->data);
1579 priv->back_list = g_list_delete_link (priv->back_list, priv->back_list);
1581 priv->back_list = g_list_prepend (priv->back_list, back);
1582 priv->back_cur = priv->back_list;
1584 priv->back_load = FALSE;
1586 action = gtk_action_group_get_action (priv->action_group, "YelpViewGoBack");
1587 gtk_action_set_sensitive (action, FALSE);
1588 proxies = gtk_action_get_proxies (action);
1589 if (priv->back_cur->next && priv->back_cur->next->data) {
1590 gchar *tooltip = "";
1591 back = priv->back_cur->next->data;
1593 gtk_action_set_sensitive (action, TRUE);
1594 if (back->title && back->desc) {
1595 gchar *color;
1596 color = yelp_settings_get_color (yelp_settings_get_default (),
1597 YELP_SETTINGS_COLOR_TEXT_LIGHT);
1598 tooltip = g_markup_printf_escaped ("<span size='larger'>%s</span>\n<span color='%s'>%s</span>",
1599 back->title, color, back->desc);
1600 g_free (color);
1602 else if (back->title)
1603 tooltip = g_markup_printf_escaped ("<span size='larger'>%s</span>",
1604 back->title);
1605 /* Can't seem to use markup on GtkAction tooltip */
1606 for (cur = proxies; cur != NULL; cur = cur->next)
1607 gtk_widget_set_tooltip_markup (GTK_WIDGET (cur->data), tooltip);
1609 else {
1610 for (cur = proxies; cur != NULL; cur = cur->next)
1611 gtk_widget_set_tooltip_text (GTK_WIDGET (cur->data), "");
1614 action = gtk_action_group_get_action (priv->action_group, "YelpViewGoForward");
1615 gtk_action_set_sensitive (action, FALSE);
1616 proxies = gtk_action_get_proxies (action);
1617 if (priv->back_cur->prev && priv->back_cur->prev->data) {
1618 gchar *tooltip = "";
1619 back = priv->back_cur->prev->data;
1621 gtk_action_set_sensitive (action, TRUE);
1622 if (back->title && back->desc) {
1623 gchar *color;
1624 color = yelp_settings_get_color (yelp_settings_get_default (),
1625 YELP_SETTINGS_COLOR_TEXT_LIGHT);
1626 tooltip = g_markup_printf_escaped ("<span size='larger'>%s</span>\n<span color='%s'>%s</span>",
1627 back->title, color, back->desc);
1628 g_free (color);
1630 else if (back->title)
1631 tooltip = g_markup_printf_escaped ("<span size='larger'>%s</span>",
1632 back->title);
1633 /* Can't seem to use markup on GtkAction tooltip */
1634 for (cur = proxies; cur != NULL; cur = cur->next)
1635 gtk_widget_set_tooltip_markup (GTK_WIDGET (cur->data), tooltip);
1637 else {
1638 for (cur = proxies; cur != NULL; cur = cur->next)
1639 gtk_widget_set_tooltip_text (GTK_WIDGET (cur->data), "");
1642 spec = g_object_class_find_property ((GObjectClass *) YELP_VIEW_GET_CLASS (view),
1643 "yelp-uri");
1644 g_signal_emit_by_name (view, "notify::yelp-uri", spec);
1646 g_free (priv->page_id);
1647 priv->page_id = yelp_uri_get_page_id (priv->uri);
1648 spec = g_object_class_find_property ((GObjectClass *) YELP_VIEW_GET_CLASS (view),
1649 "page-id");
1650 g_signal_emit_by_name (view, "notify::page-id", spec);
1652 view_load_page (view);
1655 static void
1656 document_callback (YelpDocument *document,
1657 YelpDocumentSignal signal,
1658 YelpView *view,
1659 GError *error)
1661 YelpViewPrivate *priv = GET_PRIV (view);
1663 debug_print (DB_FUNCTION, "entering\n");
1665 if (signal == YELP_DOCUMENT_SIGNAL_INFO) {
1666 gchar *prev_id, *next_id, *real_id;
1667 GtkAction *action;
1668 YelpBackEntry *back = NULL;
1669 GParamSpec *spec;
1671 real_id = yelp_document_get_page_id (document, priv->page_id);
1672 if (priv->page_id && real_id && g_str_equal (real_id, priv->page_id)) {
1673 g_free (real_id);
1675 else {
1676 GParamSpec *spec;
1677 g_free (priv->page_id);
1678 priv->page_id = real_id;
1679 spec = g_object_class_find_property ((GObjectClass *) YELP_VIEW_GET_CLASS (view),
1680 "page-id");
1681 g_signal_emit_by_name (view, "notify::page-id", spec);
1684 g_free (priv->root_title);
1685 g_free (priv->page_title);
1686 g_free (priv->page_desc);
1687 g_free (priv->page_icon);
1689 priv->root_title = yelp_document_get_root_title (document, priv->page_id);
1690 priv->page_title = yelp_document_get_page_title (document, priv->page_id);
1691 priv->page_desc = yelp_document_get_page_desc (document, priv->page_id);
1692 priv->page_icon = yelp_document_get_page_icon (document, priv->page_id);
1694 if (priv->back_cur)
1695 back = priv->back_cur->data;
1696 if (back) {
1697 g_free (back->title);
1698 back->title = g_strdup (priv->page_title);
1699 g_free (back->desc);
1700 back->desc = g_strdup (priv->page_desc);
1703 prev_id = yelp_document_get_prev_id (document, priv->page_id);
1704 action = gtk_action_group_get_action (priv->action_group, "YelpViewGoPrevious");
1705 gtk_action_set_sensitive (action, prev_id != NULL);
1706 g_free (prev_id);
1708 next_id = yelp_document_get_next_id (document, priv->page_id);
1709 action = gtk_action_group_get_action (priv->action_group, "YelpViewGoNext");
1710 gtk_action_set_sensitive (action, next_id != NULL);
1711 g_free (next_id);
1713 spec = g_object_class_find_property ((GObjectClass *) YELP_VIEW_GET_CLASS (view),
1714 "root-title");
1715 g_signal_emit_by_name (view, "notify::root-title", spec);
1717 spec = g_object_class_find_property ((GObjectClass *) YELP_VIEW_GET_CLASS (view),
1718 "page-title");
1719 g_signal_emit_by_name (view, "notify::page-title", spec);
1721 spec = g_object_class_find_property ((GObjectClass *) YELP_VIEW_GET_CLASS (view),
1722 "page-desc");
1723 g_signal_emit_by_name (view, "notify::page-desc", spec);
1725 spec = g_object_class_find_property ((GObjectClass *) YELP_VIEW_GET_CLASS (view),
1726 "page-icon");
1727 g_signal_emit_by_name (view, "notify::page-icon", spec);
1729 else if (signal == YELP_DOCUMENT_SIGNAL_CONTENTS) {
1730 YelpUriDocumentType doctype;
1731 const gchar *contents;
1732 gchar *mime_type, *page_id, *frag_id, *full_uri;
1733 page_id = yelp_uri_get_page_id (priv->uri);
1734 debug_print (DB_ARG, " document.uri.page_id=\"%s\"\n", page_id);
1735 mime_type = yelp_document_get_mime_type (document, page_id);
1736 contents = yelp_document_read_contents (document, page_id);
1737 frag_id = yelp_uri_get_frag_id (priv->uri);
1738 g_free (priv->bogus_uri);
1739 /* We don't have actual page and frag IDs for DocBook. We just map IDs
1740 of block elements. The result is that we get xref:someid#someid.
1741 If someid is really the page ID, we just drop the frag reference.
1742 Otherwise, normal page views scroll past the link trail.
1744 if (frag_id != NULL) {
1745 if (YELP_IS_DOCBOOK_DOCUMENT (document)) {
1746 gchar *real_id = yelp_document_get_page_id (document, page_id);
1747 if (g_str_equal (real_id, frag_id)) {
1748 g_free (frag_id);
1749 frag_id = NULL;
1751 g_free (real_id);
1754 /* We have to give WebKit a URI in a scheme it understands, otherwise we
1755 won't get the resource-request-starting signal. So we can't use the
1756 canonical URI, because it might be something like ghelp. We also have
1757 to give it something unique, because WebKit ignores our load_string
1758 call if the URI isn't different. We could try to construct something
1759 based on actual file locations, but in fact it doesn't matter. So
1760 we just make a bogus URI that's easy to process later.
1762 doctype = yelp_uri_get_document_type (priv->uri);
1763 full_uri = yelp_uri_get_canonical_uri (priv->uri);
1764 if (g_str_has_prefix (full_uri, "file:/") &&
1765 (doctype == YELP_URI_DOCUMENT_TYPE_TEXT ||
1766 doctype == YELP_URI_DOCUMENT_TYPE_HTML ||
1767 doctype == YELP_URI_DOCUMENT_TYPE_XHTML )) {
1768 priv->bogus_uri = full_uri;
1770 else {
1771 g_free (full_uri);
1772 if (frag_id != NULL)
1773 priv->bogus_uri = g_strdup_printf ("%s%p#%s", BOGUS_URI, priv->uri, frag_id);
1774 else
1775 priv->bogus_uri = g_strdup_printf ("%s%p", BOGUS_URI, priv->uri);
1777 g_signal_handler_block (view, priv->navigation_requested);
1778 webkit_web_view_load_string (WEBKIT_WEB_VIEW (view),
1779 contents,
1780 mime_type,
1781 "UTF-8",
1782 priv->bogus_uri);
1783 g_signal_handler_unblock (view, priv->navigation_requested);
1784 g_object_set (view, "state", YELP_VIEW_STATE_LOADED, NULL);
1786 /* If we need to set the GtkAdjustment or trigger the page title
1787 * from what WebKit thinks it is (see comment below), we need to
1788 * let the main loop run through.
1790 if (priv->vadjust > 0 || priv->hadjust > 0 || priv->page_title == NULL)
1791 while (g_main_context_pending (NULL))
1792 g_main_context_iteration (NULL, FALSE);
1794 /* Setting adjustments only work after the page is loaded. These
1795 * are set by view_history_action, and they're reset to 0 after
1796 * each load here.
1798 if (priv->vadjust > 0) {
1799 if (priv->vadjustment)
1800 gtk_adjustment_set_value (priv->vadjustment, priv->vadjust);
1801 priv->vadjust = 0;
1803 if (priv->hadjust > 0) {
1804 if (priv->hadjustment)
1805 gtk_adjustment_set_value (priv->hadjustment, priv->hadjust);
1806 priv->hadjust = 0;
1809 /* If the document didn't give us a page title, get it from WebKit.
1810 * We let the main loop run through so that WebKit gets the title
1811 * set so that we can send notify::page-title before loaded. It
1812 * simplifies things if YelpView consumers can assume the title
1813 * is set before loaded is triggered.
1815 if (priv->page_title == NULL) {
1816 GParamSpec *spec;
1817 priv->page_title = g_strdup (webkit_web_view_get_title (WEBKIT_WEB_VIEW (view)));
1818 spec = g_object_class_find_property ((GObjectClass *) YELP_VIEW_GET_CLASS (view),
1819 "page-title");
1820 g_signal_emit_by_name (view, "notify::page-title", spec);
1823 g_free (frag_id);
1824 g_free (page_id);
1825 g_free (mime_type);
1826 yelp_document_finish_read (document, contents);
1827 g_signal_emit (view, signals[LOADED], 0);
1829 else if (signal == YELP_DOCUMENT_SIGNAL_ERROR) {
1830 view_show_error_page (view, error);