1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * Copyright (C) 2010 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>
28 #include <gio/gdesktopappinfo.h>
29 #include <glib/gi18n.h>
31 #include <libxml/parser.h>
32 #include <libxml/xinclude.h>
33 #include <libxml/xpath.h>
35 #include "yelp-help-list.h"
36 #include "yelp-settings.h"
38 typedef struct _HelpListEntry HelpListEntry
;
40 static void yelp_help_list_class_init (YelpHelpListClass
*klass
);
41 static void yelp_help_list_init (YelpHelpList
*list
);
42 static void yelp_help_list_dispose (GObject
*object
);
43 static void yelp_help_list_finalize (GObject
*object
);
45 static gboolean
help_list_request_page (YelpDocument
*document
,
47 GCancellable
*cancellable
,
48 YelpDocumentCallback callback
,
50 static void help_list_think (YelpHelpList
*list
);
51 static void help_list_handle_page (YelpHelpList
*list
,
52 const gchar
*page_id
);
53 static void help_list_process_docbook (YelpHelpList
*list
,
54 HelpListEntry
*entry
);
55 static void help_list_process_mallard (YelpHelpList
*list
,
56 HelpListEntry
*entry
);
58 static const char*const known_vendor_prefixes
[] = { "gnome",
71 YelpUriDocumentType type
;
74 help_list_entry_free (HelpListEntry
*entry
)
77 g_free (entry
->title
);
82 help_list_entry_cmp (HelpListEntry
*a
, HelpListEntry
*b
)
85 as
= a
->title
? a
->title
: strchr (a
->id
, ':') + 1;
86 bs
= b
->title
? b
->title
: strchr (b
->id
, ':') + 1;
87 return g_utf8_collate (as
, bs
);
90 G_DEFINE_TYPE (YelpHelpList
, yelp_help_list
, YELP_TYPE_DOCUMENT
);
91 #define GET_PRIV(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), YELP_TYPE_HELP_LIST, YelpHelpListPrivate))
93 typedef struct _YelpHelpListPrivate YelpHelpListPrivate
;
94 struct _YelpHelpListPrivate
{
98 gboolean process_running
;
105 xmlXPathCompExprPtr get_docbook_title
;
106 xmlXPathCompExprPtr get_mallard_title
;
107 xmlXPathCompExprPtr get_mallard_desc
;
111 yelp_help_list_class_init (YelpHelpListClass
*klass
)
113 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
114 YelpDocumentClass
*document_class
= YELP_DOCUMENT_CLASS (klass
);
116 object_class
->dispose
= yelp_help_list_dispose
;
117 object_class
->finalize
= yelp_help_list_finalize
;
119 document_class
->request_page
= help_list_request_page
;
121 g_type_class_add_private (klass
, sizeof (YelpHelpListPrivate
));
125 yelp_help_list_init (YelpHelpList
*list
)
127 YelpHelpListPrivate
*priv
= GET_PRIV (list
);
129 priv
->mutex
= g_mutex_new ();
130 priv
->entries
= g_hash_table_new_full (g_str_hash
, g_str_equal
,
132 (GDestroyNotify
) help_list_entry_free
);
134 priv
->get_docbook_title
= xmlXPathCompile ("normalize-space("
135 "( /*/title | /*/db:title"
136 "| /*/articleinfo/title"
137 "| /*/bookinfo/title"
138 "| /*/db:info/db:title"
140 priv
->get_mallard_title
= xmlXPathCompile ("normalize-space((/mal:page/mal:info/mal:title[@type='text'] |"
141 " /mal:page/mal:title)[1])");
142 priv
->get_mallard_desc
= xmlXPathCompile ("normalize-space(/mal:page/mal:info/mal:desc[1])");
144 yelp_document_set_page_id ((YelpDocument
*) list
, NULL
, "index");
145 yelp_document_set_page_id ((YelpDocument
*) list
, "index", "index");
149 yelp_help_list_dispose (GObject
*object
)
151 G_OBJECT_CLASS (yelp_help_list_parent_class
)->dispose (object
);
155 yelp_help_list_finalize (GObject
*object
)
157 YelpHelpListPrivate
*priv
= GET_PRIV (object
);
159 g_hash_table_destroy (priv
->entries
);
160 g_mutex_free (priv
->mutex
);
162 if (priv
->get_docbook_title
)
163 xmlXPathFreeCompExpr (priv
->get_docbook_title
);
164 if (priv
->get_mallard_title
)
165 xmlXPathFreeCompExpr (priv
->get_mallard_title
);
166 if (priv
->get_mallard_desc
)
167 xmlXPathFreeCompExpr (priv
->get_mallard_desc
);
169 G_OBJECT_CLASS (yelp_help_list_parent_class
)->finalize (object
);
173 yelp_help_list_new (YelpUri
*uri
)
175 return g_object_new (YELP_TYPE_HELP_LIST
, NULL
);
178 /******************************************************************************/
181 help_list_request_page (YelpDocument
*document
,
182 const gchar
*page_id
,
183 GCancellable
*cancellable
,
184 YelpDocumentCallback callback
,
188 YelpHelpListPrivate
*priv
= GET_PRIV (document
);
194 YELP_DOCUMENT_CLASS (yelp_help_list_parent_class
)->request_page (document
,
203 g_mutex_lock (priv
->mutex
);
204 if (priv
->process_ran
) {
205 help_list_handle_page ((YelpHelpList
*) document
, page_id
);
209 if (!priv
->process_running
) {
210 priv
->process_running
= TRUE
;
211 g_object_ref (document
);
212 priv
->thread
= g_thread_create ((GThreadFunc
) help_list_think
,
213 document
, FALSE
, NULL
);
215 priv
->pending
= g_slist_prepend (priv
->pending
, g_strdup (page_id
));
216 g_mutex_unlock (priv
->mutex
);
221 help_list_think (YelpHelpList
*list
)
223 const gchar
* const *sdatadirs
= g_get_system_data_dirs ();
224 const gchar
* const *langs
= g_get_language_names ();
225 YelpHelpListPrivate
*priv
= GET_PRIV (list
);
226 /* The strings are still owned by GLib; we just own the array. */
228 gint datadir_i
, subdir_i
, lang_i
;
232 datadirs
= g_new0 (gchar
*, g_strv_length ((gchar
**) sdatadirs
) + 2);
233 datadirs
[0] = (gchar
*) g_get_user_data_dir ();
234 for (datadir_i
= 0; sdatadirs
[datadir_i
]; datadir_i
++)
235 datadirs
[datadir_i
+ 1] = (gchar
*) sdatadirs
[datadir_i
];
237 for (datadir_i
= 0; datadirs
[datadir_i
]; datadir_i
++) {
238 gchar
*helpdirname
= g_build_filename (datadirs
[datadir_i
], "gnome", "help", NULL
);
239 GFile
*helpdir
= g_file_new_for_path (helpdirname
);
240 GFileEnumerator
*children
= g_file_enumerate_children (helpdir
,
241 G_FILE_ATTRIBUTE_STANDARD_TYPE
","
242 G_FILE_ATTRIBUTE_STANDARD_NAME
,
243 G_FILE_QUERY_INFO_NONE
,
246 if (children
== NULL
) {
247 g_object_unref (helpdir
);
248 g_free (helpdirname
);
251 while (child
= g_file_enumerator_next_file (children
, NULL
, NULL
)) {
253 HelpListEntry
*entry
= NULL
;
255 if (g_file_info_get_file_type (child
) != G_FILE_TYPE_DIRECTORY
)
258 docid
= g_strconcat ("ghelp:", g_file_info_get_name (child
), NULL
);
259 if (g_hash_table_lookup (priv
->entries
, docid
)) {
261 g_object_unref (child
);
265 for (lang_i
= 0; langs
[lang_i
]; lang_i
++) {
266 gchar
*filename
, *tmp
;
268 filename
= g_build_filename (helpdirname
,
269 g_file_info_get_name (child
),
273 if (g_file_test (filename
, G_FILE_TEST_IS_REGULAR
)) {
274 entry
= g_new0 (HelpListEntry
, 1);
275 entry
->id
= g_strdup (docid
);
276 entry
->filename
= filename
;
277 entry
->type
= YELP_URI_DOCUMENT_TYPE_MALLARD
;
282 tmp
= g_strdup_printf ("%s.xml", g_file_info_get_name (child
));
283 filename
= g_build_filename (helpdirname
,
284 g_file_info_get_name (child
),
289 if (g_file_test (filename
, G_FILE_TEST_IS_REGULAR
)) {
290 entry
= g_new0 (HelpListEntry
, 1);
291 entry
->id
= g_strdup (docid
);
292 entry
->filename
= filename
;
293 entry
->type
= YELP_URI_DOCUMENT_TYPE_DOCBOOK
;
300 g_hash_table_insert (priv
->entries
, docid
, entry
);
301 priv
->all_entries
= g_list_prepend (priv
->all_entries
, entry
);
305 g_object_unref (child
);
307 g_object_unref (children
);
308 g_object_unref (helpdir
);
309 g_free (helpdirname
);
313 theme
= gtk_icon_theme_get_default ();
314 for (cur
= priv
->all_entries
; cur
!= NULL
; cur
= cur
->next
) {
315 GDesktopAppInfo
*app
;
317 HelpListEntry
*entry
= (HelpListEntry
*) cur
->data
;
318 const gchar
*entryid
= strchr (entry
->id
, ':') + 1;
320 if (entry
->type
== YELP_URI_DOCUMENT_TYPE_MALLARD
)
321 help_list_process_mallard (list
, entry
);
322 else if (entry
->type
== YELP_URI_DOCUMENT_TYPE_DOCBOOK
)
323 help_list_process_docbook (list
, entry
);
325 tmp
= g_strconcat (entryid
, ".desktop", NULL
);
326 app
= g_desktop_app_info_new (tmp
);
331 for (prefix
= (char **) known_vendor_prefixes
; *prefix
; prefix
++) {
332 tmp
= g_strconcat (*prefix
, "-", entryid
, ".desktop", NULL
);
333 app
= g_desktop_app_info_new (tmp
);
341 GIcon
*icon
= g_app_info_get_icon ((GAppInfo
*) app
);
343 GtkIconInfo
*info
= gtk_icon_theme_lookup_by_gicon (theme
,
345 GTK_ICON_LOOKUP_NO_SVG
);
347 const gchar
*iconfile
= gtk_icon_info_get_filename (info
);
349 entry
->icon
= g_filename_to_uri (iconfile
, NULL
, NULL
);
350 gtk_icon_info_free (info
);
353 g_object_unref (app
);
357 g_mutex_lock (priv
->mutex
);
358 priv
->process_running
= FALSE
;
359 priv
->process_ran
= TRUE
;
360 while (priv
->pending
) {
361 gchar
*page_id
= (gchar
*) priv
->pending
->data
;
362 help_list_handle_page (list
, page_id
);
364 priv
->pending
= g_slist_delete_link (priv
->pending
, priv
->pending
);
366 g_mutex_unlock (priv
->mutex
);
368 g_object_unref (list
);
371 /* This function expects to be called inside a locked mutex */
373 help_list_handle_page (YelpHelpList
*list
,
374 const gchar
*page_id
)
376 gchar
**colors
, *tmp
;
378 YelpHelpListPrivate
*priv
= GET_PRIV (list
);
379 GtkTextDirection direction
= gtk_widget_get_default_direction ();
380 GString
*string
= g_string_new
381 ("<html><head><style type='text/css'>\n"
382 "html { height: 100%; }\n"
383 "body { margin: 0; padding: 0; max-width: 100%;");
384 colors
= yelp_settings_get_colors (yelp_settings_get_default ());
386 tmp
= g_markup_printf_escaped (" background-color: %s; color: %s;"
387 " direction: %s; }\n",
388 colors
[YELP_SETTINGS_COLOR_BASE
],
389 colors
[YELP_SETTINGS_COLOR_TEXT
],
390 (direction
== GTK_TEXT_DIR_RTL
) ? "rtl" : "ltr");
391 g_string_append (string
, tmp
);
394 g_string_append (string
,
395 "div.body { margin: 0 12px 0 12px; padding: 0;"
396 " max-width: 60em; min-height: 20em; }\n"
397 "div.header { max-width: 100%; width: 100%;"
398 " padding: 0; margin: 0 0 1em 0; }\n"
399 "div.footer { max-width: 60em; }\n"
400 "div.sect { margin-top: 1.72em; }\n"
401 "div.trails { margin: 0; padding: 0.2em 12px 0 12px;");
403 tmp
= g_markup_printf_escaped (" background-color: %s;"
404 " border-bottom: solid 1px %s; }\n",
405 colors
[YELP_SETTINGS_COLOR_GRAY_BASE
],
406 colors
[YELP_SETTINGS_COLOR_GRAY_BORDER
]);
407 g_string_append (string
, tmp
);
410 g_string_append (string
,
411 "div.trail { margin: 0 1em 0.2em 1em; padding: 0; text-indent: -1em;");
413 tmp
= g_markup_printf_escaped (" color: %s; }\n",
414 colors
[YELP_SETTINGS_COLOR_TEXT_LIGHT
]);
415 g_string_append (string
, tmp
);
418 g_string_append (string
,
419 "a.trail { white-space: nowrap; }\n"
420 "div.hgroup { margin: 0 0 0.5em 0;");
422 tmp
= g_markup_printf_escaped (" color: %s;"
423 " border-bottom: solid 1px %s; }\n",
424 colors
[YELP_SETTINGS_COLOR_TEXT_LIGHT
],
425 colors
[YELP_SETTINGS_COLOR_GRAY_BORDER
]);
426 g_string_append (string
, tmp
);
429 tmp
= g_markup_printf_escaped ("div.title { margin: 0 0 0.2em 0; font-weight: bold; color: %s; }\n"
430 "div.desc { margin: 0 0 0.2em 0; }\n"
431 "div.linkdiv div.inner { padding-%s: 30px; min-height: 24px;"
432 " background-position: top %s; background-repeat: no-repeat;"
433 " -webkit-background-size: 22px 22px; }\n"
434 "div.linkdiv div.title {font-size: 1em; color: inherit; }\n"
435 "div.linkdiv div.desc { color: %s; }\n"
436 "div.linkdiv { margin: 0; padding: 0.5em; }\n"
437 "a:hover div.linkdiv {"
438 " text-decoration: none;"
439 " outline: solid 1px %s;"
440 " background: -webkit-gradient(linear, left top, left 80,"
441 " from(%s), to(%s)); }\n",
442 colors
[YELP_SETTINGS_COLOR_TEXT_LIGHT
],
443 ((direction
== GTK_TEXT_DIR_RTL
) ? "right" : "left"),
444 ((direction
== GTK_TEXT_DIR_RTL
) ? "right" : "left"),
445 colors
[YELP_SETTINGS_COLOR_TEXT_LIGHT
],
446 colors
[YELP_SETTINGS_COLOR_BLUE_BASE
],
447 colors
[YELP_SETTINGS_COLOR_BLUE_BASE
],
448 colors
[YELP_SETTINGS_COLOR_BASE
]);
449 g_string_append (string
, tmp
);
452 g_string_append (string
,
453 "h1, h2, h3, h4, h5, h6, h7 { margin: 0; padding: 0; font-weight: bold; }\n"
454 "h1 { font-size: 1.44em; }\n"
455 "h2 { font-size: 1.2em; }"
456 "h3.title, h4.title, h5.title, h6.title, h7.title { font-size: 1.2em; }"
457 "h3, h4, h5, h6, h7 { font-size: 1em; }"
458 "p { line-height: 1.72em; }"
459 "div, pre, p { margin: 1em 0 0 0; padding: 0; }"
460 "div:first-child, pre:first-child, p:first-child { margin-top: 0; }"
461 "div.inner, div.contents, pre.contents { margin-top: 0; }"
462 "p img { vertical-align: middle; }"
464 " text-decoration: none;");
466 tmp
= g_markup_printf_escaped (" color: %s; } a:visited { color: %s; }",
467 colors
[YELP_SETTINGS_COLOR_LINK
],
468 colors
[YELP_SETTINGS_COLOR_LINK_VISITED
]);
469 g_string_append (string
, tmp
);
472 g_string_append (string
,
473 "a:hover { text-decoration: underline; }\n"
474 "a img { border: none; }\n"
477 tmp
= g_markup_printf_escaped ("<title>%s</title>",
478 _("All Help Documents"));
479 g_string_append (string
, tmp
);
482 g_string_append (string
,
484 "<div class='header'></div>"
485 "<div class='body'><div class='hgroup'>");
486 tmp
= g_markup_printf_escaped ("<h1>%s</h1></div>\n",
487 _("All Help Documents"));
488 g_string_append (string
, tmp
);
491 priv
->all_entries
= g_list_sort (priv
->all_entries
,
492 (GCompareFunc
) help_list_entry_cmp
);
493 for (cur
= priv
->all_entries
; cur
!= NULL
; cur
= cur
->next
) {
494 HelpListEntry
*entry
= (HelpListEntry
*) cur
->data
;
495 gchar
*title
= entry
->title
? entry
->title
: (strchr (entry
->id
, ':') + 1);
496 gchar
*desc
= entry
->desc
? entry
->desc
: "";
498 tmp
= g_markup_printf_escaped ("<a href='%s'><div class='linkdiv'>",
500 g_string_append (string
, tmp
);
504 tmp
= g_markup_printf_escaped ("<div class='inner' style='background-image: url(%s);'>",
506 g_string_append (string
, tmp
);
510 g_string_append (string
, "<div class='inner'>");
512 tmp
= g_markup_printf_escaped ("<div class='title'>%s</div>"
513 "<div class='desc'>%s</div>"
516 g_string_append (string
, tmp
);
520 g_string_append (string
,
522 "<div class='footer'></div>"
525 yelp_document_give_contents (YELP_DOCUMENT (list
), page_id
,
529 g_string_free (string
, FALSE
);
530 yelp_document_signal (YELP_DOCUMENT (list
), page_id
,
531 YELP_DOCUMENT_SIGNAL_CONTENTS
, NULL
);
536 help_list_process_docbook (YelpHelpList
*list
,
537 HelpListEntry
*entry
)
539 xmlParserCtxtPtr parserCtxt
;
541 xmlXPathContextPtr xpath
;
542 xmlXPathObjectPtr obj
= NULL
;
543 YelpHelpListPrivate
*priv
= GET_PRIV (list
);
545 parserCtxt
= xmlNewParserCtxt ();
546 xmldoc
= xmlCtxtReadFile (parserCtxt
,
547 (const char *) entry
->filename
, NULL
,
548 XML_PARSE_DTDLOAD
| XML_PARSE_NOCDATA
|
549 XML_PARSE_NOENT
| XML_PARSE_NONET
);
550 xmlFreeParserCtxt (parserCtxt
);
554 xmlXIncludeProcessFlags (xmldoc
,
555 XML_PARSE_DTDLOAD
| XML_PARSE_NOCDATA
|
556 XML_PARSE_NOENT
| XML_PARSE_NONET
);
558 xpath
= xmlXPathNewContext (xmldoc
);
559 xmlXPathRegisterNs (xpath
, BAD_CAST
"db",
560 BAD_CAST
"http://docbook.org/ns/docbook");
561 obj
= xmlXPathCompiledEval (priv
->get_docbook_title
, xpath
);
564 entry
->title
= g_strdup (obj
->stringval
);
565 xmlXPathFreeObject (obj
);
571 xmlXPathFreeContext (xpath
);
575 help_list_process_mallard (YelpHelpList
*list
,
576 HelpListEntry
*entry
)
578 xmlParserCtxtPtr parserCtxt
;
580 xmlXPathContextPtr xpath
;
581 xmlXPathObjectPtr obj
= NULL
;
582 YelpHelpListPrivate
*priv
= GET_PRIV (list
);
584 parserCtxt
= xmlNewParserCtxt ();
585 xmldoc
= xmlCtxtReadFile (parserCtxt
,
586 (const char *) entry
->filename
, NULL
,
587 XML_PARSE_DTDLOAD
| XML_PARSE_NOCDATA
|
588 XML_PARSE_NOENT
| XML_PARSE_NONET
);
589 xmlFreeParserCtxt (parserCtxt
);
593 xmlXIncludeProcessFlags (xmldoc
,
594 XML_PARSE_DTDLOAD
| XML_PARSE_NOCDATA
|
595 XML_PARSE_NOENT
| XML_PARSE_NONET
);
597 xpath
= xmlXPathNewContext (xmldoc
);
598 xmlXPathRegisterNs (xpath
, BAD_CAST
"mal",
599 BAD_CAST
"http://projectmallard.org/1.0/");
601 obj
= xmlXPathCompiledEval (priv
->get_mallard_title
, xpath
);
604 entry
->title
= g_strdup (obj
->stringval
);
605 xmlXPathFreeObject (obj
);
608 obj
= xmlXPathCompiledEval (priv
->get_mallard_desc
, xpath
);
611 entry
->desc
= g_strdup (obj
->stringval
);
612 xmlXPathFreeObject (obj
);
618 xmlXPathFreeContext (xpath
);