From 491fcdadd9225ed6c02c0dff184e11f38bb654a7 Mon Sep 17 00:00:00 2001 From: Shaun McCance Date: Wed, 2 Jun 2010 17:54:19 -0500 Subject: Adding "Read Later" feature, still needs proper link text I'm waiting to be able to get the DOM node from a hit test result before I can get the link text in. I'm told it will be there soon. --- data/org.gnome.yelp.gschema.xml | 3 + libyelp/yelp-view.c | 67 +++++++++++++- libyelp/yelp-view.h | 29 ++++--- src/yelp-application.c | 81 +++++++++++++++++ src/yelp-application.h | 9 ++ src/yelp-window.c | 188 +++++++++++++++++++++++++++++++++++++--- 6 files changed, 350 insertions(+), 27 deletions(-) diff --git a/data/org.gnome.yelp.gschema.xml b/data/org.gnome.yelp.gschema.xml index 00470f61..0f325aec 100644 --- a/data/org.gnome.yelp.gschema.xml +++ b/data/org.gnome.yelp.gschema.xml @@ -23,5 +23,8 @@ [] + + [] + diff --git a/libyelp/yelp-view.c b/libyelp/yelp-view.c index 783f7057..d3fb67ef 100644 --- a/libyelp/yelp-view.c +++ b/libyelp/yelp-view.c @@ -194,6 +194,7 @@ struct _YelpViewPrivate { gulong hadjuster; gchar *popup_link_uri; + gchar *popup_link_text; gchar *popup_image_uri; YelpViewState state; @@ -204,13 +205,15 @@ struct _YelpViewPrivate { gchar *page_desc; gchar *page_icon; - GList *back_list; - GList *back_cur; - gboolean back_load; + GList *back_list; + GList *back_cur; + gboolean back_load; GtkActionGroup *action_group; - gint navigation_requested; + GSList *link_actions; + + gint navigation_requested; }; #define TARGET_TYPE_URI_LIST "text/uri-list" @@ -282,6 +285,11 @@ yelp_view_dispose (GObject *object) priv->document = NULL; } + while (priv->link_actions) { + g_object_unref (priv->link_actions->data); + priv->link_actions = g_slist_delete_link (priv->link_actions, priv->link_actions); + } + priv->back_cur = NULL; while (priv->back_list) { back_entry_free ((YelpBackEntry *) priv->back_list->data); @@ -297,6 +305,7 @@ yelp_view_finalize (GObject *object) YelpViewPrivate *priv = GET_PRIV (object); g_free (priv->popup_link_uri); + g_free (priv->popup_link_text); g_free (priv->popup_image_uri); g_free (priv->page_id); @@ -605,6 +614,38 @@ yelp_view_get_action_group (YelpView *view) /******************************************************************************/ +void +yelp_view_add_link_action (YelpView *view, GtkAction *action) +{ + YelpViewPrivate *priv = GET_PRIV (view); + + priv->link_actions = g_slist_append (priv->link_actions, + g_object_ref (action)); +} + +YelpUri * +yelp_view_get_active_link_uri (YelpView *view) +{ + YelpViewPrivate *priv = GET_PRIV (view); + YelpUri *uri; + + if (g_str_has_prefix (priv->popup_link_uri, BOGUS_URI)) + uri = yelp_uri_new_relative (priv->uri, priv->popup_link_uri + BOGUS_URI_LEN); + else + uri = yelp_uri_new_relative (priv->uri, priv->popup_link_uri); + + return uri; +} + +gchar * +yelp_view_get_active_link_text (YelpView *view) +{ + YelpViewPrivate *priv = GET_PRIV (view); + return g_strdup (priv->popup_link_text); +} + +/******************************************************************************/ + static void view_scrolled (GtkAdjustment *adjustment, YelpView *view) @@ -659,6 +700,9 @@ popup_open_link (GtkMenuItem *item, g_free (priv->popup_link_uri); priv->popup_link_uri = NULL; + + g_free (priv->popup_link_text); + priv->popup_link_text = NULL; } static void @@ -676,6 +720,9 @@ popup_open_link_new (GtkMenuItem *item, g_free (priv->popup_link_uri); priv->popup_link_uri = NULL; + g_free (priv->popup_link_text); + priv->popup_link_text = NULL; + g_signal_emit (view, signals[NEW_VIEW_REQUESTED], 0, uri); g_object_unref (uri); } @@ -812,6 +859,10 @@ view_populate_popup (YelpView *view, g_free (priv->popup_link_uri); priv->popup_link_uri = uri; + g_free (priv->popup_link_text); + /* FIXME */ + priv->popup_link_text = g_strdup (uri); + if (g_str_has_prefix (priv->popup_link_uri, "mailto:")) { /* Not using a mnemonic because underscores are common in email * addresses, and we'd have to escape them. There doesn't seem @@ -827,6 +878,8 @@ view_populate_popup (YelpView *view, gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); } else { + GSList *cur; + item = gtk_menu_item_new_with_mnemonic (_("_Open Link")); g_signal_connect (item, "activate", G_CALLBACK (popup_open_link), view); @@ -836,6 +889,12 @@ view_populate_popup (YelpView *view, g_signal_connect (item, "activate", G_CALLBACK (popup_open_link_new), view); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + + for (cur = priv->link_actions; cur != NULL; cur = cur->next) { + GtkAction *action = (GtkAction *) cur->data; + item = gtk_action_create_menu_item (action); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + } } } else { diff --git a/libyelp/yelp-view.h b/libyelp/yelp-view.h index d6930524..572b085d 100644 --- a/libyelp/yelp-view.h +++ b/libyelp/yelp-view.h @@ -58,19 +58,24 @@ typedef enum { YELP_VIEW_STATE_ERROR } YelpViewState; -GType yelp_view_get_type (void); -GType yelp_view_state_get_type (void); +GType yelp_view_get_type (void); +GType yelp_view_state_get_type (void); -GtkWidget * yelp_view_new (void); -void yelp_view_load (YelpView *view, - const gchar *uri); -void yelp_view_load_uri (YelpView *view, - YelpUri *uri); -void yelp_view_load_document (YelpView *view, - YelpUri *uri, - YelpDocument *document); -YelpDocument * yelp_view_get_document (YelpView *view); -GtkActionGroup * yelp_view_get_action_group (YelpView *view); +GtkWidget * yelp_view_new (void); +void yelp_view_load (YelpView *view, + const gchar *uri); +void yelp_view_load_uri (YelpView *view, + YelpUri *uri); +void yelp_view_load_document (YelpView *view, + YelpUri *uri, + YelpDocument *document); +YelpDocument * yelp_view_get_document (YelpView *view); +GtkActionGroup * yelp_view_get_action_group (YelpView *view); + +void yelp_view_add_link_action (YelpView *view, + GtkAction *action); +YelpUri * yelp_view_get_active_link_uri (YelpView *view); +gchar * yelp_view_get_active_link_text (YelpView *view); G_END_DECLS diff --git a/src/yelp-application.c b/src/yelp-application.c index 9dcfba04..071e0c1c 100644 --- a/src/yelp-application.c +++ b/src/yelp-application.c @@ -47,6 +47,7 @@ static gboolean editor_mode = FALSE; enum { BOOKMARKS_CHANGED, + READ_LATER_CHANGED, LAST_SIGNAL }; static gint signals[LAST_SIGNAL] = { 0 }; @@ -139,6 +140,14 @@ yelp_application_class_init (YelpApplicationClass *klass) g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); + signals[READ_LATER_CHANGED] = + g_signal_new ("read-later-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + dbus_g_object_type_install_info (YELP_TYPE_APPLICATION, &dbus_glib_yelp_object_info); @@ -695,6 +704,78 @@ yelp_application_get_bookmarks (YelpApplication *app, return g_settings_get_value (settings, "bookmarks"); } +void +yelp_application_add_read_later (YelpApplication *app, + const gchar *doc_uri, + const gchar *full_uri, + const gchar *title) +{ + GSettings *settings; + + settings = application_get_doc_settings (app, doc_uri); + + if (settings) { + GVariantBuilder builder; + GVariantIter *iter; + gchar *this_uri, *this_title; + gboolean broken = FALSE; + g_settings_get (settings, "readlater", "a(ss)", &iter); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ss)")); + while (g_variant_iter_loop (iter, "(&s&s)", &this_uri, &this_title)) { + if (g_str_equal (full_uri, this_uri)) { + /* Already have this link */ + broken = TRUE; + break; + } + g_variant_builder_add (&builder, "(ss)", this_uri, this_title); + } + g_variant_iter_free (iter); + + if (!broken) { + GVariant *value; + g_variant_builder_add (&builder, "(ss)", full_uri, title); + value = g_variant_builder_end (&builder); + g_settings_set_value (settings, "readlater", value); + g_signal_emit (app, signals[READ_LATER_CHANGED], 0, doc_uri); + } + } +} + +void +yelp_application_remove_read_later (YelpApplication *app, + const gchar *doc_uri, + const gchar *full_uri) +{ + GSettings *settings; + + settings = application_get_doc_settings (app, doc_uri); + + if (settings) { + GVariantBuilder builder; + GVariantIter *iter; + gchar *this_uri, *this_title; + g_settings_get (settings, "readlater", "a(ss)", &iter); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ss)")); + while (g_variant_iter_loop (iter, "(&s&s)", &this_uri, &this_title)) { + if (!g_str_equal (this_uri, full_uri)) + g_variant_builder_add (&builder, "(ss)", this_uri, this_title); + } + g_variant_iter_free (iter); + + g_settings_set_value (settings, "readlater", g_variant_builder_end (&builder)); + g_signal_emit (app, signals[READ_LATER_CHANGED], 0, doc_uri); + } +} + +GVariant * +yelp_application_get_read_later (YelpApplication *app, + const gchar *doc_uri) +{ + GSettings *settings = application_get_doc_settings (app, doc_uri); + + return g_settings_get_value (settings, "readlater"); +} + void yelp_application_install_package (YelpApplication *app, const gchar *pkg, diff --git a/src/yelp-application.h b/src/yelp-application.h index 7c3aa5c8..1060683f 100644 --- a/src/yelp-application.h +++ b/src/yelp-application.h @@ -75,6 +75,15 @@ void yelp_application_update_bookmarks (YelpApplication *app, const gchar *title); GVariant * yelp_application_get_bookmarks (YelpApplication *app, const gchar *doc_uri); +void yelp_application_add_read_later (YelpApplication *app, + const gchar *doc_uri, + const gchar *full_uri, + const gchar *title); +void yelp_application_remove_read_later (YelpApplication *app, + const gchar *doc_uri, + const gchar *full_uri); +GVariant * yelp_application_get_read_later (YelpApplication *app, + const gchar *doc_uri); void yelp_application_install_package (YelpApplication *app, const gchar *pkg, const gchar *alt); diff --git a/src/yelp-window.c b/src/yelp-window.c index fdffdbf3..92b51b9d 100644 --- a/src/yelp-window.c +++ b/src/yelp-window.c @@ -80,7 +80,13 @@ static void window_start_search (GtkAction *action, YelpWindow *window); static void window_open_location (GtkAction *action, YelpWindow *window); - +static void window_read_later (GtkAction *action, + YelpWindow *window); +static void read_later_clicked (GtkLinkButton *button, + YelpWindow *window); +static void app_read_later_changed (YelpApplication *app, + const gchar *doc_uri, + YelpWindow *window); static void app_bookmarks_changed (YelpApplication *app, const gchar *doc_uri, YelpWindow *window); @@ -253,17 +259,20 @@ struct _YelpWindowPrivate { /* no refs on these, owned by containers */ YelpView *view; - GtkWidget *vbox; + GtkWidget *vbox_view; + GtkWidget *vbox_full; GtkWidget *hbox; YelpLocationEntry *entry; GtkWidget *hidden_entry; GtkWidget *find_entry; GtkWidget *find_label; + GtkWidget *read_later_vbox; /* refs because we dynamically add & remove */ GtkWidget *find_bar; GtkWidget *align_location; GtkWidget *align_hidden; + GtkWidget *read_later; gulong entry_location_selected; @@ -334,6 +343,8 @@ yelp_window_init (YelpWindow *window) g_signal_connect (window, "configure-event", G_CALLBACK (window_configure_event), NULL); } +static void suppress_link_buttons (GtkLinkButton *button, const gchar *link, gpointer userdata) {} + static void yelp_window_class_init (YelpWindowClass *klass) { @@ -365,6 +376,13 @@ yelp_window_class_init (YelpWindowClass *klass) G_TYPE_NONE, 0); g_type_class_add_private (klass, sizeof (YelpWindowPrivate)); + + /* We don't want GTK+'s default behavior for link buttons in the read + * later list. I don't see any other way around this, because GTK+'s + * handlers run before Yelp's. If we ever use link buttons elsewhere + * in Yelp, we need to do something in this callback. + */ + gtk_link_button_set_uri_hook (suppress_link_buttons, NULL, NULL); } static void @@ -469,18 +487,27 @@ window_construct (YelpWindow *window) GtkWidget *scroll; GtkActionGroup *view_actions; GtkAction *action; - GtkWidget *button; + GtkWidget *vbox, *button; GtkTreeIter iter; + gchar *color, *text; YelpWindowPrivate *priv = GET_PRIV (window); gtk_window_set_icon_name (GTK_WINDOW (window), "help-browser"); priv->view = (YelpView *) yelp_view_new (); - priv->vbox = gtk_vbox_new (FALSE, 0); - gtk_container_add (GTK_CONTAINER (window), priv->vbox); + action = gtk_action_new ("ReadLinkLater", "Read Link _Later", NULL, NULL); + g_signal_connect (action, "activate", G_CALLBACK (window_read_later), window); + yelp_view_add_link_action (priv->view, action); + g_signal_connect (priv->application, "read-later-changed", G_CALLBACK (app_read_later_changed), window); + + priv->vbox_full = gtk_vbox_new (FALSE, 3); + gtk_container_add (GTK_CONTAINER (window), priv->vbox_full); - priv->action_group = gtk_action_group_new ("WindowActions"); + priv->vbox_view = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (priv->vbox_full), priv->vbox_view, TRUE, TRUE, 0); + + priv->action_group = gtk_action_group_new ("YelpWindowActions"); gtk_action_group_set_translation_domain (priv->action_group, GETTEXT_PACKAGE); gtk_action_group_add_actions (priv->action_group, entries, G_N_ELEMENTS (entries), @@ -500,7 +527,7 @@ window_construct (YelpWindow *window) gtk_window_add_accel_group (GTK_WINDOW (window), gtk_ui_manager_get_accel_group (priv->ui_manager)); gtk_ui_manager_add_ui_from_string (priv->ui_manager, YELP_UI, -1, NULL); - gtk_box_pack_start (GTK_BOX (priv->vbox), + gtk_box_pack_start (GTK_BOX (priv->vbox_view), gtk_ui_manager_get_widget (priv->ui_manager, "/ui/menubar"), FALSE, FALSE, 0); @@ -509,7 +536,7 @@ window_construct (YelpWindow *window) priv->hbox = gtk_hbox_new (FALSE, 0); g_object_set (priv->hbox, "border-width", 2, NULL); - gtk_box_pack_start (GTK_BOX (priv->vbox), priv->hbox, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (priv->vbox_view), priv->hbox, FALSE, FALSE, 0); action = gtk_action_group_get_action (view_actions, "YelpViewGoBack"); button = gtk_action_create_tool_item (action); @@ -584,7 +611,7 @@ window_construct (YelpWindow *window) GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_IN); - gtk_box_pack_start (GTK_BOX (priv->vbox), scroll, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (priv->vbox_view), scroll, TRUE, TRUE, 0); priv->find_bar = g_object_ref_sink (gtk_hbox_new (FALSE, 6)); g_object_set (priv->find_bar, "border-width", 2, NULL); @@ -606,6 +633,23 @@ window_construct (YelpWindow *window) g_object_set (priv->find_label, "xalign", 0.0, NULL); gtk_box_pack_start (GTK_BOX (priv->find_bar), priv->find_label, FALSE, FALSE, 0); + priv->read_later = g_object_ref_sink (gtk_info_bar_new ()); + vbox = gtk_vbox_new (FALSE, 0); + color = yelp_settings_get_color (yelp_settings_get_default (), + YELP_SETTINGS_COLOR_TEXT_LIGHT); + text = g_markup_printf_escaped ("%s", + color, _("Read Later")); + button = gtk_label_new (text); + g_object_set (button, "use-markup", TRUE, "xalign", 0.0, NULL); + g_free (color); + g_free (text); + gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (gtk_info_bar_get_content_area (GTK_INFO_BAR (priv->read_later))), + vbox, + FALSE, FALSE, 0); + priv->read_later_vbox = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), priv->read_later_vbox, FALSE, FALSE, 0); + g_signal_connect (priv->view, "external-uri", G_CALLBACK (view_external_uri), window); g_signal_connect (priv->view, "new-view-requested", G_CALLBACK (view_new_window), window); g_signal_connect (priv->view, "loaded", G_CALLBACK (view_loaded), window); @@ -1139,6 +1183,126 @@ window_open_location (GtkAction *action, YelpWindow *window) } } +static void +read_later_resolved (YelpUri *uri, + YelpWindow *window) +{ + gchar *fulluri; + const gchar *text = (const gchar *) g_object_get_data ((GObject *) uri, "link-text"); + YelpWindowPrivate *priv = GET_PRIV (window); + YelpUri *base; + gchar *doc_uri; + + g_object_get (priv->view, "yelp-uri", &base, NULL); + doc_uri = yelp_uri_get_document_uri (uri); + fulluri = yelp_uri_get_canonical_uri (uri); + + yelp_application_add_read_later (priv->application, doc_uri, fulluri, text); + + g_object_unref (base); + g_free (doc_uri); + g_free (fulluri); +} + +static void +window_read_later (GtkAction *action, + YelpWindow *window) +{ + YelpWindowPrivate *priv = GET_PRIV (window); + YelpUri *uri; + gchar *text; + + uri = yelp_view_get_active_link_uri (priv->view); + text = yelp_view_get_active_link_text (priv->view); + + g_object_set_data_full ((GObject *) uri, "link-text", text, g_free); + + if (!yelp_uri_is_resolved (uri)) { + g_signal_connect (uri, "resolved", + G_CALLBACK (read_later_resolved), + window); + yelp_uri_resolve (uri); + } + else { + read_later_resolved (uri, window); + } +} + +static void +read_later_clicked (GtkLinkButton *button, + YelpWindow *window) +{ + YelpWindowPrivate *priv = GET_PRIV (window); + YelpUri *base; + gchar *doc_uri; + gchar *fulluri; + + fulluri = g_strdup (gtk_link_button_get_uri (button)); + + g_object_get (priv->view, "yelp-uri", &base, NULL); + doc_uri = yelp_uri_get_document_uri (base); + + yelp_application_remove_read_later (priv->application, doc_uri, fulluri); + + g_object_unref (base); + g_free (doc_uri); + + yelp_view_load (priv->view, fulluri); + + g_free (fulluri); +} + +static void +app_read_later_changed (YelpApplication *app, + const gchar *doc_uri, + YelpWindow *window) +{ + GVariant *value; + GVariantIter *viter; + gchar *uri, *title; /* do not free */ + GList *children; + gboolean has_children = FALSE; + YelpWindowPrivate *priv = GET_PRIV (window); + + children = gtk_container_get_children (GTK_CONTAINER (priv->read_later_vbox)); + while (children) { + gtk_container_remove (GTK_CONTAINER (priv->read_later_vbox), + GTK_WIDGET (children->data)); + children = g_list_delete_link (children, children); + } + + value = yelp_application_get_read_later (priv->application, doc_uri); + g_variant_get (value, "a(ss)", &viter); + while (g_variant_iter_loop (viter, "(&s&s)", &uri, &title)) { + GtkWidget *align, *link; + + align = gtk_alignment_new (0.0, 0.0, 0.0, 0.0); + g_object_set (align, "left-padding", 6, NULL); + gtk_box_pack_start (GTK_BOX (priv->read_later_vbox), align, FALSE, FALSE, 0); + + link = gtk_link_button_new_with_label (uri, title); + g_object_set (link, "xalign", 0.0, NULL); + g_signal_connect (link, "clicked", G_CALLBACK (read_later_clicked), window); + gtk_container_add (GTK_CONTAINER (align), link); + + gtk_widget_show_all (align); + has_children = TRUE; + } + g_variant_iter_free (viter); + g_variant_unref (value); + + if (has_children) { + if (gtk_widget_get_parent (priv->read_later) == NULL) { + gtk_box_pack_end (GTK_BOX (priv->vbox_full), priv->read_later, FALSE, FALSE, 0); + gtk_widget_show_all (priv->read_later); + } + } + else { + if (gtk_widget_get_parent (priv->read_later) != NULL) + gtk_container_remove (GTK_CONTAINER (priv->vbox_full), priv->read_later); + } +} + static gboolean find_animate_open (YelpWindow *window) { YelpWindowPrivate *priv = GET_PRIV (window); @@ -1172,7 +1336,7 @@ find_animate_close (YelpWindow *window) { priv->find_cur_height = 0; g_object_set (priv->find_bar, "height-request", -1, NULL); g_object_set (priv->find_entry, "height-request", -1, NULL); - gtk_container_remove (GTK_CONTAINER (priv->vbox), priv->find_bar); + gtk_container_remove (GTK_CONTAINER (priv->vbox_view), priv->find_bar); priv->find_animate = 0; return FALSE; } @@ -1206,7 +1370,7 @@ window_find_in_page (GtkAction *action, g_object_set (priv->find_entry, "width-request", 2 * priv->width / 3, NULL); - gtk_box_pack_end (GTK_BOX (priv->vbox), priv->find_bar, FALSE, FALSE, 0); + gtk_box_pack_end (GTK_BOX (priv->vbox_view), priv->find_bar, FALSE, FALSE, 0); g_object_set (priv->find_bar, "height-request", -1, NULL); g_object_set (priv->find_entry, "height-request", -1, NULL); gtk_widget_show_all (priv->find_bar); @@ -1529,6 +1693,8 @@ view_loaded (YelpView *view, g_free (icon); g_free (title); + app_read_later_changed (priv->application, doc_uri, window); + completion = (GtkTreeModel *) g_hash_table_lookup (completions, doc_uri); if (completion == NULL) { GtkListStore *base = gtk_list_store_new (5, -- cgit v1.2.1