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