1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * Copyright (C) 2010-2020 Shaun McCance <shaunm@gnome.org>
4  *
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.
9  *
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.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
17  *
18  * Author: Shaun McCance <shaunm@gnome.org>
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #define G_SETTINGS_ENABLE_BACKEND
26 
27 #include <gio/gio.h>
28 #include <gio/gsettingsbackend.h>
29 #include <glib/gi18n.h>
30 #include <gtk/gtk.h>
31 #ifdef GDK_WINDOWING_X11
32 #include <gdk/gdkx.h>
33 #endif
34 #include <stdlib.h>
35 
36 #include "yelp-bookmarks.h"
37 #include "yelp-settings.h"
38 #include "yelp-view.h"
39 
40 #include "yelp-application.h"
41 #include "yelp-window.h"
42 
43 #define DEFAULT_URI "help:gnome-help"
44 
45 static gboolean editor_mode = FALSE;
46 
47 G_GNUC_NORETURN static gboolean
option_version_cb(const gchar * option_name,const gchar * value,gpointer data,GError ** error)48 option_version_cb (const gchar *option_name,
49 	           const gchar *value,
50 	           gpointer     data,
51 	           GError     **error)
52 {
53 	g_print ("%s %s\n", PACKAGE, VERSION);
54 
55 	exit (0);
56 }
57 
58 static const GOptionEntry entries[] = {
59     {"editor-mode", 0, 0, G_OPTION_ARG_NONE, &editor_mode, N_("Turn on editor mode"), NULL},
60     { "version", 0, G_OPTION_FLAG_NO_ARG | G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, option_version_cb, NULL, NULL },
61     { NULL }
62 };
63 
64 typedef struct _YelpApplicationLoad YelpApplicationLoad;
65 struct _YelpApplicationLoad {
66     YelpApplication *app;
67     guint32 timestamp;
68     gboolean new;
69     gboolean fallback_help_list;
70 };
71 
72 static void          yelp_application_iface_init       (YelpBookmarksInterface *iface);
73 static void          yelp_application_dispose          (GObject                *object);
74 static void          yelp_application_finalize         (GObject                *object);
75 
76 static gboolean      yelp_application_cmdline          (GApplication          *app,
77                                                         gchar               ***arguments,
78                                                         gint                  *exit_status);
79 static void          yelp_application_startup          (GApplication          *app);
80 static int           yelp_application_command_line     (GApplication          *app,
81                                                         GApplicationCommandLine *cmdline);
82 static void          application_uri_resolved          (YelpUri               *uri,
83                                                         YelpApplicationLoad   *data);
84 static gboolean      application_window_deleted        (YelpWindow            *window,
85                                                         GdkEvent              *event,
86                                                         YelpApplication       *app);
87 GSettings *          application_get_doc_settings      (YelpApplication       *app,
88                                                         const gchar           *doc_uri);
89 static void          application_adjust_font           (GAction               *action,
90                                                         GVariant              *parameter,
91                                                         YelpApplication       *app);
92 static void          application_set_font_sensitivity  (YelpApplication       *app);
93 
94 static void          bookmarks_changed                 (GSettings             *settings,
95                                                         const gchar           *key,
96                                                         YelpApplication       *app);
97 static gboolean      window_resized                    (YelpWindow            *window,
98                                                         YelpApplication       *app);
99 
100 typedef struct _YelpApplicationPrivate YelpApplicationPrivate;
101 struct _YelpApplicationPrivate {
102     GSList *windows;
103     GHashTable *windows_by_document;
104 
105     GPropertyAction  *show_cursor_action;
106     GSimpleAction    *larger_text_action;
107     GSimpleAction    *smaller_text_action;
108 
109     GSettingsBackend *backend;
110     GSettings *gsettings;
111     GHashTable *docsettings;
112 };
113 
G_DEFINE_TYPE_WITH_CODE(YelpApplication,yelp_application,GTK_TYPE_APPLICATION,G_IMPLEMENT_INTERFACE (YELP_TYPE_BOOKMARKS,yelp_application_iface_init)G_ADD_PRIVATE (YelpApplication))114 G_DEFINE_TYPE_WITH_CODE (YelpApplication, yelp_application, GTK_TYPE_APPLICATION,
115                          G_IMPLEMENT_INTERFACE (YELP_TYPE_BOOKMARKS,
116                                                 yelp_application_iface_init)
117                          G_ADD_PRIVATE (YelpApplication) )
118 
119 static void
120 yelp_application_init (YelpApplication *app)
121 {
122     YelpApplicationPrivate *priv = yelp_application_get_instance_private (app);
123     priv->docsettings = g_hash_table_new_full (g_str_hash, g_str_equal,
124                                                (GDestroyNotify) g_free,
125                                                (GDestroyNotify) g_object_unref);
126 
127     gtk_application_set_accels_for_action (GTK_APPLICATION (app),
128                                            "app.yelp-application-show-cursor",
129                                            (const gchar*[]) {"F7", NULL});
130     gtk_application_set_accels_for_action (GTK_APPLICATION (app),
131                                            "app.yelp-application-larger-text",
132                                            (const gchar*[]) {"<Control>plus", NULL});
133     gtk_application_set_accels_for_action (GTK_APPLICATION (app),
134                                            "app.yelp-application-smaller-text",
135                                            (const gchar*[]) {"<Control>minus", NULL});
136 
137     gtk_application_set_accels_for_action (GTK_APPLICATION (app),
138                                            "win.yelp-window-find", (const gchar*[]) {"<Control>F", NULL});
139     gtk_application_set_accels_for_action (GTK_APPLICATION (app),
140                                            "win.yelp-window-search", (const gchar*[]) {"<Control>S", NULL});
141     gtk_application_set_accels_for_action (GTK_APPLICATION (app),
142                                            "win.yelp-window-new", (const gchar*[]) {"<Control>N", NULL});
143     gtk_application_set_accels_for_action (GTK_APPLICATION (app),
144                                            "win.yelp-window-close", (const gchar*[]) {"<Control>W", NULL});
145     gtk_application_set_accels_for_action (GTK_APPLICATION (app),
146                                            "win.yelp-window-ctrll", (const gchar*[]) {"<Control>L", NULL});
147     gtk_application_set_accels_for_action (GTK_APPLICATION (app),
148                                            "win.yelp-view-print", (const gchar*[]) {"<Control>P", NULL});
149 
150     gtk_application_set_accels_for_action (GTK_APPLICATION (app),
151                                            "win.yelp-view-go-back",
152                                            (const gchar*[]) {"<Alt>Left", NULL});
153     gtk_application_set_accels_for_action (GTK_APPLICATION (app),
154                                            "win.yelp-view-go-forward",
155                                            (const gchar*[]) {"<Alt>Right", NULL});
156     gtk_application_set_accels_for_action (GTK_APPLICATION (app),
157                                            "win.yelp-view-go-previous",
158                                            (const gchar*[]) {"<Control>Page_Up", NULL});
159     gtk_application_set_accels_for_action (GTK_APPLICATION (app),
160                                            "win.yelp-view-go-next",
161                                            (const gchar*[]) {"<Control>Page_Down", NULL});
162 }
163 
164 static void
yelp_application_class_init(YelpApplicationClass * klass)165 yelp_application_class_init (YelpApplicationClass *klass)
166 {
167     GApplicationClass *application_class = G_APPLICATION_CLASS (klass);
168     GObjectClass *object_class = G_OBJECT_CLASS (klass);
169 
170     application_class->local_command_line = yelp_application_cmdline;
171     application_class->startup = yelp_application_startup;
172     application_class->command_line = yelp_application_command_line;
173 
174     object_class->dispose = yelp_application_dispose;
175     object_class->finalize = yelp_application_finalize;
176 }
177 
178 static void
yelp_application_iface_init(YelpBookmarksInterface * iface)179 yelp_application_iface_init (YelpBookmarksInterface *iface)
180 {
181     iface->add_bookmark = yelp_application_add_bookmark;
182     iface->remove_bookmark = yelp_application_remove_bookmark;
183     iface->is_bookmarked = yelp_application_is_bookmarked;
184 }
185 
186 static void
yelp_application_dispose(GObject * object)187 yelp_application_dispose (GObject *object)
188 {
189     YelpApplicationPrivate *priv = yelp_application_get_instance_private (YELP_APPLICATION (object));
190 
191     if (priv->show_cursor_action) {
192         g_object_unref (priv->show_cursor_action);
193         priv->show_cursor_action = NULL;
194     }
195 
196     if (priv->larger_text_action) {
197         g_object_unref (priv->larger_text_action);
198         priv->larger_text_action = NULL;
199     }
200 
201     if (priv->smaller_text_action) {
202         g_object_unref (priv->smaller_text_action);
203         priv->smaller_text_action = NULL;
204     }
205 
206     if (priv->gsettings) {
207         g_object_unref (priv->gsettings);
208         priv->gsettings = NULL;
209     }
210 
211     G_OBJECT_CLASS (yelp_application_parent_class)->dispose (object);
212 }
213 
214 static void
yelp_application_finalize(GObject * object)215 yelp_application_finalize (GObject *object)
216 {
217     YelpApplicationPrivate *priv = yelp_application_get_instance_private (YELP_APPLICATION (object));
218 
219     g_hash_table_destroy (priv->windows_by_document);
220     g_hash_table_destroy (priv->docsettings);
221 
222     G_OBJECT_CLASS (yelp_application_parent_class)->finalize (object);
223 }
224 
225 
226 static gboolean
yelp_application_cmdline(GApplication * app,gchar *** arguments,gint * exit_status)227 yelp_application_cmdline (GApplication     *app,
228                           gchar          ***arguments,
229                           gint             *exit_status)
230 {
231     GOptionContext *context;
232     gint argc = g_strv_length (*arguments);
233     gint i;
234 
235     context = g_option_context_new (NULL);
236     g_option_context_add_group (context, gtk_get_option_group (FALSE));
237     g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
238     g_option_context_parse (context, &argc, arguments, NULL);
239 
240     for (i = 1; i < argc; i++) {
241         if (!strchr ((*arguments)[i], ':') && !((*arguments)[i][0] == '/')) {
242             GFile *base, *new;
243             gchar *cur, *newuri;
244             cur = g_get_current_dir ();
245             base = g_file_new_for_path (cur);
246             new = g_file_resolve_relative_path (base, (*arguments)[i]);
247             newuri = g_file_get_uri (new);
248             g_free ((*arguments)[i]);
249             (*arguments)[i] = newuri;
250             g_free (cur);
251             g_object_unref (new);
252             g_object_unref (base);
253         }
254     }
255 
256     return G_APPLICATION_CLASS (yelp_application_parent_class)
257         ->local_command_line (app, arguments, exit_status);
258 }
259 
260 static void
yelp_application_startup(GApplication * application)261 yelp_application_startup (GApplication *application)
262 {
263     YelpApplication *app = YELP_APPLICATION (application);
264     YelpApplicationPrivate *priv = yelp_application_get_instance_private (app);
265     gchar *keyfile;
266     YelpSettings *settings;
267 
268     g_set_application_name (_("Help"));
269 
270     /* chain up */
271     G_APPLICATION_CLASS (yelp_application_parent_class)->startup (application);
272 
273     settings = yelp_settings_get_default ();
274     if (editor_mode)
275         yelp_settings_set_editor_mode (settings, TRUE);
276     priv->windows_by_document = g_hash_table_new_full (g_str_hash,
277                                                        g_str_equal,
278                                                        g_free,
279                                                        NULL);
280     /* Use a config file for per-document settings, because
281        Ryan asked me to. */
282     keyfile = g_build_filename (g_get_user_config_dir (), "yelp", "yelp.cfg", NULL);
283     priv->backend = g_keyfile_settings_backend_new (keyfile, "/org/gnome/yelp/", "yelp");
284     g_free (keyfile);
285 
286     /* But the main settings are in dconf */
287     priv->gsettings = g_settings_new ("org.gnome.yelp");
288 
289     g_settings_bind (priv->gsettings, "show-cursor",
290                      settings, "show-text-cursor",
291                      G_SETTINGS_BIND_DEFAULT);
292     priv->show_cursor_action = g_property_action_new ("yelp-application-show-cursor",
293                                                       settings, "show-text-cursor");
294     g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (priv->show_cursor_action));
295 
296     g_settings_bind (priv->gsettings, "font-adjustment",
297                      settings, "font-adjustment",
298                      G_SETTINGS_BIND_DEFAULT);
299 
300     priv->larger_text_action = g_simple_action_new ("yelp-application-larger-text", NULL);
301     g_signal_connect (priv->larger_text_action,
302                       "activate",
303                       G_CALLBACK (application_adjust_font),
304                       app);
305     g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (priv->larger_text_action));
306 
307     priv->smaller_text_action = g_simple_action_new ("yelp-application-smaller-text", NULL);
308     g_signal_connect (priv->smaller_text_action,
309                       "activate",
310                       G_CALLBACK (application_adjust_font),
311                       app);
312     g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (priv->smaller_text_action));
313 
314     application_set_font_sensitivity (app);
315 }
316 
317 /******************************************************************************/
318 
319 static void
application_adjust_font(GAction * action,GVariant * parameter,YelpApplication * app)320 application_adjust_font (GAction         *action,
321                          GVariant        *parameter,
322                          YelpApplication *app)
323 {
324     YelpApplicationPrivate *priv = yelp_application_get_instance_private (app);
325     gint adjustment = g_settings_get_int (priv->gsettings, "font-adjustment");
326     gint adjust = g_str_equal (g_action_get_name (action), "yelp-application-larger-text") ? 1 : -1;
327 
328     adjustment += adjust;
329     g_settings_set_int (priv->gsettings, "font-adjustment", adjustment);
330 
331     application_set_font_sensitivity (app);
332 }
333 
334 static void
application_set_font_sensitivity(YelpApplication * app)335 application_set_font_sensitivity (YelpApplication *app)
336 {
337     YelpApplicationPrivate *priv = yelp_application_get_instance_private (app);
338     YelpSettings *settings = yelp_settings_get_default ();
339     GParamSpec *spec = g_object_class_find_property ((GObjectClass *) YELP_SETTINGS_GET_CLASS (settings),
340                                                      "font-adjustment");
341     gint adjustment = g_settings_get_int (priv->gsettings, "font-adjustment");
342     if (!G_PARAM_SPEC_INT (spec)) {
343         g_warning ("Expcected integer param spec for font-adjustment");
344         return;
345     }
346     g_simple_action_set_enabled (priv->larger_text_action,
347                                  adjustment < ((GParamSpecInt *) spec)->maximum);
348     g_simple_action_set_enabled (priv->smaller_text_action,
349                                  adjustment > ((GParamSpecInt *) spec)->minimum);
350 }
351 
352 /******************************************************************************/
353 
354 YelpApplication *
yelp_application_new(void)355 yelp_application_new (void)
356 {
357     YelpApplication *app;
358     char *app_id = NULL;
359     char *yelp = NULL;
360 
361     if (g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS)) {
362         GKeyFile *kf = g_key_file_new ();
363         g_key_file_load_from_file (kf, "/.flatpak-info", G_KEY_FILE_NONE, NULL);
364         if (g_key_file_has_group (kf, "Application"))
365           app_id = g_key_file_get_string (kf, "Application", "name", NULL);
366         else
367           app_id = g_key_file_get_string (kf, "Runtime", "name", NULL);
368         yelp = g_strconcat (app_id, ".Help", NULL);
369         g_key_file_unref (kf);
370     }
371     else {
372         yelp = g_strdup ("org.gnome.Yelp");
373     }
374 
375     app = g_object_new (YELP_TYPE_APPLICATION,
376                         "application-id", yelp,
377                         "flags", G_APPLICATION_HANDLES_COMMAND_LINE,
378                         "inactivity-timeout", 5000,
379                         NULL);
380     g_free (app_id);
381     g_free (yelp);
382 
383     return app;
384 }
385 
386 /* consumes the uri */
387 static void
open_uri(YelpApplication * app,YelpUri * uri,gboolean new_window,gboolean fallback_help_list)388 open_uri (YelpApplication *app,
389           YelpUri         *uri,
390           gboolean         new_window,
391           gboolean         fallback_help_list)
392 {
393     YelpApplicationLoad *data;
394     data = g_new (YelpApplicationLoad, 1);
395     data->app = app;
396     data->timestamp = gtk_get_current_event_time ();
397     data->new = new_window;
398     data->fallback_help_list = fallback_help_list;
399 
400     g_signal_connect (uri, "resolved",
401                       G_CALLBACK (application_uri_resolved),
402                       data);
403 
404     /* hold the app while resolving the uri so we don't exit while
405      * in the middle of the load
406      */
407     g_application_hold (G_APPLICATION (app));
408 
409     yelp_uri_resolve (uri);
410 }
411 
412 
413 static int
yelp_application_command_line(GApplication * application,GApplicationCommandLine * cmdline)414 yelp_application_command_line (GApplication            *application,
415                                GApplicationCommandLine *cmdline)
416 {
417     YelpApplication *app = YELP_APPLICATION (application);
418     gchar **argv;
419     int i;
420 
421     argv = g_application_command_line_get_arguments (cmdline, NULL);
422 
423     if (argv[1] == NULL)
424         open_uri (app, yelp_uri_new (DEFAULT_URI), FALSE, TRUE);
425 
426     for (i = 1; argv[i]; i++)
427         open_uri (app, yelp_uri_new (argv[i]), FALSE, FALSE);
428 
429     g_strfreev (argv);
430 
431     return 0;
432 }
433 
434 void
yelp_application_new_window(YelpApplication * app,const gchar * uri)435 yelp_application_new_window (YelpApplication  *app,
436                              const gchar      *uri)
437 {
438     if (uri)
439         open_uri (app, yelp_uri_new (uri), TRUE, FALSE);
440     else
441         open_uri (app, yelp_uri_new (DEFAULT_URI), TRUE, TRUE);
442 }
443 
444 void
yelp_application_new_window_uri(YelpApplication * app,YelpUri * uri)445 yelp_application_new_window_uri (YelpApplication  *app,
446                                  YelpUri          *uri)
447 {
448     open_uri (app, g_object_ref (uri), TRUE, FALSE);
449 }
450 
451 static void
application_uri_resolved(YelpUri * uri,YelpApplicationLoad * data)452 application_uri_resolved (YelpUri             *uri,
453                           YelpApplicationLoad *data)
454 {
455     YelpWindow *window;
456     gchar *doc_uri;
457     GdkWindow *gdk_window;
458     YelpApplicationPrivate *priv = yelp_application_get_instance_private (data->app);
459     GFile *gfile;
460 
461     /* We held the application while resolving the URI, so unhold now. */
462     g_application_release (G_APPLICATION (data->app));
463 
464     /* Get the GFile associated with the URI, or NULL if not available */
465     gfile = yelp_uri_get_file (uri);
466     if (gfile == NULL && data->fallback_help_list) {
467         /* There is no file associated to the default uri, so we'll fallback
468          * to help-list: if we're told to do so. */
469         open_uri (data->app, yelp_uri_new ("help-list:"), data->new, FALSE);
470         g_object_unref (uri);
471         g_free (data);
472         return;
473     }
474     g_clear_object (&gfile);
475 
476     doc_uri = yelp_uri_get_document_uri (uri);
477 
478     if (data->new || !doc_uri)
479         window = NULL;
480     else
481         window = g_hash_table_lookup (priv->windows_by_document, doc_uri);
482 
483     if (window == NULL) {
484         gint width, height;
485         GSettings *settings = application_get_doc_settings (data->app, doc_uri);
486 
487         g_settings_get (settings, "geometry", "(ii)", &width, &height);
488         window = yelp_window_new (data->app);
489         gtk_window_set_default_size (GTK_WINDOW (window), width, height);
490         g_signal_connect (window, "resized", G_CALLBACK (window_resized), data->app);
491         priv->windows = g_slist_prepend (priv->windows, window);
492 
493         if (!data->new) {
494             g_hash_table_insert (priv->windows_by_document, doc_uri, window);
495             g_object_set_data (G_OBJECT (window), "doc_uri", doc_uri);
496         }
497         else {
498             g_free (doc_uri);
499         }
500 
501         g_signal_connect (window, "delete-event",
502                           G_CALLBACK (application_window_deleted), data->app);
503         gtk_window_set_application (GTK_WINDOW (window),
504                                     GTK_APPLICATION (data->app));
505     }
506     else {
507         g_free (doc_uri);
508     }
509 
510     yelp_window_load_uri (window, uri);
511 
512     gtk_widget_show_all (GTK_WIDGET (window));
513 
514     /* Metacity no longer does anything useful with gtk_window_present */
515     gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
516 
517 #ifdef GDK_WINDOWING_X11
518     if (GDK_IS_X11_WINDOW (gdk_window)){
519         if (gdk_window)
520             gdk_x11_window_move_to_current_desktop (gdk_window);
521 
522         /* Ensure we actually present the window when invoked from the command
523          * line. This is somewhat evil, but the minor evil of Yelp stealing
524          * focus (after you requested it) is outweighed for me by the major
525          * evil of no help window appearing when you click Help.
526          */
527         if (data->timestamp == 0)
528             data->timestamp = gdk_x11_get_server_time (gtk_widget_get_window (GTK_WIDGET (window)));
529 
530         gtk_window_present_with_time (GTK_WINDOW (window), data->timestamp);
531     }
532     else
533 #endif
534         gtk_window_present (GTK_WINDOW (window));
535 
536     g_object_unref (uri);
537     g_free (data);
538 }
539 
540 static gboolean
application_window_deleted(YelpWindow * window,GdkEvent * event,YelpApplication * app)541 application_window_deleted (YelpWindow      *window,
542                             GdkEvent        *event,
543                             YelpApplication *app)
544 {
545     gchar *doc_uri; /* owned by windows_by_document */
546     YelpApplicationPrivate *priv = yelp_application_get_instance_private (app);
547 
548     priv->windows = g_slist_remove (priv->windows, window);
549     doc_uri = g_object_get_data (G_OBJECT (window), "doc_uri");
550     if (doc_uri)
551         g_hash_table_remove (priv->windows_by_document, doc_uri);
552 
553     return FALSE;
554 }
555 
556 GSettings *
application_get_doc_settings(YelpApplication * app,const gchar * doc_uri)557 application_get_doc_settings (YelpApplication *app, const gchar *doc_uri)
558 {
559     YelpApplicationPrivate *priv = yelp_application_get_instance_private (app);
560     GSettings *settings = g_hash_table_lookup (priv->docsettings, doc_uri);
561     if (settings == NULL) {
562         gchar *tmp, *key, *settings_path;
563         tmp = g_uri_escape_string (doc_uri, "", FALSE);
564         settings_path = g_strconcat ("/org/gnome/yelp/documents/", tmp, "/", NULL);
565         g_free (tmp);
566         if (priv->backend)
567             settings = g_settings_new_with_backend_and_path ("org.gnome.yelp.documents",
568                                                              priv->backend,
569                                                              settings_path);
570         else
571             settings = g_settings_new_with_path ("org.gnome.yelp.documents",
572                                                  settings_path);
573         key = g_strdup (doc_uri);
574         g_hash_table_insert (priv->docsettings, key, settings);
575         g_object_set_data ((GObject *) settings, "doc_uri", key);
576         g_signal_connect (settings, "changed::bookmarks",
577                           G_CALLBACK (bookmarks_changed), app);
578         g_free (settings_path);
579     }
580     return settings;
581 }
582 
583 /******************************************************************************/
584 
585 void
yelp_application_add_bookmark(YelpBookmarks * bookmarks,const gchar * doc_uri,const gchar * page_id,const gchar * icon,const gchar * title)586 yelp_application_add_bookmark (YelpBookmarks     *bookmarks,
587                                const gchar       *doc_uri,
588                                const gchar       *page_id,
589                                const gchar       *icon,
590                                const gchar       *title)
591 {
592     GSettings *settings;
593     YelpApplication *app = YELP_APPLICATION (bookmarks);
594 
595     g_return_if_fail (page_id);
596     g_return_if_fail (doc_uri);
597 
598     settings = application_get_doc_settings (app, doc_uri);
599 
600     if (settings) {
601         GVariantBuilder builder;
602         GVariantIter *iter;
603         gchar *this_id, *this_icon, *this_title;
604         gboolean broken = FALSE;
605         g_settings_get (settings, "bookmarks", "a(sss)", &iter);
606         g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sss)"));
607         while (g_variant_iter_loop (iter, "(&s&s&s)", &this_id, &this_icon, &this_title)) {
608             if (g_str_equal (page_id, this_id)) {
609                 /* Already have this bookmark */
610                 broken = TRUE;
611                 break;
612             }
613             g_variant_builder_add (&builder, "(sss)", this_id, this_icon, this_title);
614         }
615         g_variant_iter_free (iter);
616 
617         if (!broken) {
618             GVariant *value;
619             g_variant_builder_add (&builder, "(sss)", page_id, icon, title);
620             value = g_variant_builder_end (&builder);
621             g_settings_set_value (settings, "bookmarks", value);
622         }
623     }
624 }
625 
626 void
yelp_application_remove_bookmark(YelpBookmarks * bookmarks,const gchar * doc_uri,const gchar * page_id)627 yelp_application_remove_bookmark (YelpBookmarks     *bookmarks,
628                                   const gchar       *doc_uri,
629                                   const gchar       *page_id)
630 {
631     GSettings *settings;
632     YelpApplication *app = YELP_APPLICATION (bookmarks);
633 
634     g_return_if_fail (page_id);
635     g_return_if_fail (doc_uri);
636 
637     settings = application_get_doc_settings (app, doc_uri);
638 
639     if (settings) {
640         GVariantBuilder builder;
641         GVariantIter *iter;
642         gchar *this_id, *this_icon, *this_title;
643         g_settings_get (settings, "bookmarks", "a(sss)", &iter);
644         g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sss)"));
645         while (g_variant_iter_loop (iter, "(&s&s&s)", &this_id, &this_icon, &this_title)) {
646             if (!g_str_equal (page_id, this_id))
647                 g_variant_builder_add (&builder, "(sss)", this_id, this_icon, this_title);
648         }
649         g_variant_iter_free (iter);
650 
651         g_settings_set_value (settings, "bookmarks", g_variant_builder_end (&builder));
652     }
653 }
654 
655 gboolean
yelp_application_is_bookmarked(YelpBookmarks * bookmarks,const gchar * doc_uri,const gchar * page_id)656 yelp_application_is_bookmarked (YelpBookmarks     *bookmarks,
657                                 const gchar       *doc_uri,
658                                 const gchar       *page_id)
659 {
660     GVariant *stored_bookmarks;
661     GVariantIter *iter;
662     gboolean ret = FALSE;
663     gchar *this_id = NULL;
664     GSettings *settings;
665     YelpApplication *app = YELP_APPLICATION (bookmarks);
666 
667     g_return_val_if_fail (page_id, FALSE);
668     g_return_val_if_fail (doc_uri, FALSE);
669 
670     settings = application_get_doc_settings (app, doc_uri);
671     if (settings == NULL)
672         return FALSE;
673 
674     stored_bookmarks = g_settings_get_value (settings, "bookmarks");
675     g_settings_get (settings, "bookmarks", "a(sss)", &iter);
676     while (g_variant_iter_loop (iter, "(&s&s&s)", &this_id, NULL, NULL)) {
677         if (g_str_equal (page_id, this_id)) {
678             ret = TRUE;
679             break;
680         }
681     }
682 
683     g_variant_iter_free (iter);
684     g_variant_unref (stored_bookmarks);
685     return ret;
686 }
687 
688 void
yelp_application_update_bookmarks(YelpApplication * app,const gchar * doc_uri,const gchar * page_id,const gchar * icon,const gchar * title)689 yelp_application_update_bookmarks (YelpApplication   *app,
690                                    const gchar       *doc_uri,
691                                    const gchar       *page_id,
692                                    const gchar       *icon,
693                                    const gchar       *title)
694 {
695     GSettings *settings;
696 
697     g_return_if_fail (page_id);
698     g_return_if_fail (doc_uri);
699 
700     settings = application_get_doc_settings (app, doc_uri);
701 
702     if (settings) {
703         GVariantBuilder builder;
704         GVariantIter *iter;
705         gchar *this_id, *this_icon, *this_title;
706         gboolean updated = FALSE;
707         g_settings_get (settings, "bookmarks", "a(sss)", &iter);
708         g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sss)"));
709         while (g_variant_iter_loop (iter, "(&s&s&s)", &this_id, &this_icon, &this_title)) {
710             if (g_str_equal (page_id, this_id)) {
711                 if (icon && !g_str_equal (icon, this_icon)) {
712                     this_icon = (gchar *) icon;
713                     updated = TRUE;
714                 }
715                 if (title && !g_str_equal (title, this_title)) {
716                     this_title = (gchar *) title;
717                     updated = TRUE;
718                 }
719                 if (!updated)
720                     break;
721             }
722             g_variant_builder_add (&builder, "(sss)", this_id, this_icon, this_title);
723         }
724         g_variant_iter_free (iter);
725 
726         if (updated)
727             g_settings_set_value (settings, "bookmarks",
728                                   g_variant_builder_end (&builder));
729         else
730             g_variant_builder_clear (&builder);
731     }
732 }
733 
734 GVariant *
yelp_application_get_bookmarks(YelpApplication * app,const gchar * doc_uri)735 yelp_application_get_bookmarks (YelpApplication *app,
736                                 const gchar     *doc_uri)
737 {
738     GSettings *settings = application_get_doc_settings (app, doc_uri);
739 
740     return g_settings_get_value (settings, "bookmarks");
741 }
742 
743 static void
bookmarks_changed(GSettings * settings,const gchar * key,YelpApplication * app)744 bookmarks_changed (GSettings       *settings,
745                    const gchar     *key,
746                    YelpApplication *app)
747 {
748     const gchar *doc_uri = g_object_get_data ((GObject *) settings, "doc_uri");
749     if (doc_uri)
750         g_signal_emit_by_name (app, "bookmarks-changed", doc_uri);
751 }
752 
753 static gboolean
window_resized(YelpWindow * window,YelpApplication * app)754 window_resized (YelpWindow        *window,
755                 YelpApplication   *app)
756 {
757     YelpApplicationPrivate *priv = yelp_application_get_instance_private (app);
758     YelpUri *uri;
759     gchar *doc_uri;
760     GSettings *settings;
761 
762     uri = yelp_window_get_uri (window);
763     if (uri == NULL)
764         return FALSE;
765     doc_uri = yelp_uri_get_document_uri (uri);
766     if (doc_uri == NULL) {
767         g_object_unref (uri);
768         return FALSE;
769     }
770     settings = g_hash_table_lookup (priv->docsettings, doc_uri);
771 
772     if (settings) {
773         gint width, height;
774         yelp_window_get_geometry (window, &width, &height);
775         g_settings_set (settings, "geometry", "(ii)", width, height);
776     }
777 
778     g_free (doc_uri);
779     g_object_unref (uri);
780 
781     return FALSE;
782 }
783