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, see <http://www.gnu.org/licenses/>.
18 * Author: Shaun McCance <shaunm@gnome.org>
26 #include <gio/gdesktopappinfo.h>
27 #include <glib/gi18n.h>
29 #include <libxml/parser.h>
30 #include <libxml/xinclude.h>
31 #include <libxml/xpath.h>
32 #include <libxml/xpathInternals.h>
34 #include "yelp-help-list.h"
35 #include "yelp-settings.h"
37 typedef struct _HelpListEntry HelpListEntry
;
39 static void yelp_help_list_dispose (GObject
*object
);
40 static void yelp_help_list_finalize (GObject
*object
);
42 static gboolean
help_list_request_page (YelpDocument
*document
,
44 GCancellable
*cancellable
,
45 YelpDocumentCallback callback
,
47 static void help_list_think (YelpHelpList
*list
);
48 static void help_list_handle_page (YelpHelpList
*list
,
49 const gchar
*page_id
);
50 static void help_list_process_docbook (YelpHelpList
*list
,
51 HelpListEntry
*entry
);
52 static void help_list_process_mallard (YelpHelpList
*list
,
53 HelpListEntry
*entry
);
55 static const char*const known_vendor_prefixes
[] = { "gnome",
68 YelpUriDocumentType type
;
71 help_list_entry_free (HelpListEntry
*entry
)
74 g_free (entry
->title
);
79 help_list_entry_cmp (HelpListEntry
*a
, HelpListEntry
*b
)
82 as
= a
->title
? a
->title
: strchr (a
->id
, ':') + 1;
83 bs
= b
->title
? b
->title
: strchr (b
->id
, ':') + 1;
84 return g_utf8_collate (as
, bs
);
87 G_DEFINE_TYPE (YelpHelpList
, yelp_help_list
, YELP_TYPE_DOCUMENT
)
88 #define GET_PRIV(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), YELP_TYPE_HELP_LIST, YelpHelpListPrivate))
90 typedef struct _YelpHelpListPrivate YelpHelpListPrivate
;
91 struct _YelpHelpListPrivate
{
95 gboolean process_running
;
102 xmlXPathCompExprPtr get_docbook_title
;
103 xmlXPathCompExprPtr get_mallard_title
;
104 xmlXPathCompExprPtr get_mallard_desc
;
108 yelp_help_list_class_init (YelpHelpListClass
*klass
)
110 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
111 YelpDocumentClass
*document_class
= YELP_DOCUMENT_CLASS (klass
);
113 object_class
->dispose
= yelp_help_list_dispose
;
114 object_class
->finalize
= yelp_help_list_finalize
;
116 document_class
->request_page
= help_list_request_page
;
118 g_type_class_add_private (klass
, sizeof (YelpHelpListPrivate
));
122 yelp_help_list_init (YelpHelpList
*list
)
124 YelpHelpListPrivate
*priv
= GET_PRIV (list
);
126 g_mutex_init (&priv
->mutex
);
127 priv
->entries
= g_hash_table_new_full (g_str_hash
, g_str_equal
,
129 (GDestroyNotify
) help_list_entry_free
);
131 priv
->get_docbook_title
= xmlXPathCompile (BAD_CAST
"normalize-space("
132 "( /*/title | /*/db:title"
133 "| /*/articleinfo/title"
134 "| /*/bookinfo/title"
135 "| /*/db:info/db:title"
137 priv
->get_mallard_title
= xmlXPathCompile (BAD_CAST
"normalize-space((/mal:page/mal:info/mal:title[@type='text'] |"
138 " /mal:page/mal:title)[1])");
139 priv
->get_mallard_desc
= xmlXPathCompile (BAD_CAST
"normalize-space(/mal:page/mal:info/mal:desc[1])");
141 yelp_document_set_page_id ((YelpDocument
*) list
, NULL
, "index");
142 yelp_document_set_page_id ((YelpDocument
*) list
, "index", "index");
146 yelp_help_list_dispose (GObject
*object
)
148 G_OBJECT_CLASS (yelp_help_list_parent_class
)->dispose (object
);
152 yelp_help_list_finalize (GObject
*object
)
154 YelpHelpListPrivate
*priv
= GET_PRIV (object
);
156 g_hash_table_destroy (priv
->entries
);
157 g_mutex_clear (&priv
->mutex
);
159 if (priv
->get_docbook_title
)
160 xmlXPathFreeCompExpr (priv
->get_docbook_title
);
161 if (priv
->get_mallard_title
)
162 xmlXPathFreeCompExpr (priv
->get_mallard_title
);
163 if (priv
->get_mallard_desc
)
164 xmlXPathFreeCompExpr (priv
->get_mallard_desc
);
166 G_OBJECT_CLASS (yelp_help_list_parent_class
)->finalize (object
);
170 yelp_help_list_new (YelpUri
*uri
)
172 return g_object_new (YELP_TYPE_HELP_LIST
, NULL
);
175 /******************************************************************************/
178 help_list_request_page (YelpDocument
*document
,
179 const gchar
*page_id
,
180 GCancellable
*cancellable
,
181 YelpDocumentCallback callback
,
185 YelpHelpListPrivate
*priv
= GET_PRIV (document
);
191 YELP_DOCUMENT_CLASS (yelp_help_list_parent_class
)->request_page (document
,
200 g_mutex_lock (&priv
->mutex
);
201 if (priv
->process_ran
) {
202 help_list_handle_page ((YelpHelpList
*) document
, page_id
);
206 if (!priv
->process_running
) {
207 priv
->process_running
= TRUE
;
208 g_object_ref (document
);
209 priv
->thread
= g_thread_new ("helplist-page",
210 (GThreadFunc
) help_list_think
,
213 priv
->pending
= g_slist_prepend (priv
->pending
, g_strdup (page_id
));
214 g_mutex_unlock (&priv
->mutex
);
219 help_list_think (YelpHelpList
*list
)
221 const gchar
* const *sdatadirs
= g_get_system_data_dirs ();
222 const gchar
* const *langs
= g_get_language_names ();
223 YelpHelpListPrivate
*priv
= GET_PRIV (list
);
224 /* The strings are still owned by GLib; we just own the array. */
226 gint datadir_i
, lang_i
;
230 datadirs
= g_new0 (gchar
*, g_strv_length ((gchar
**) sdatadirs
) + 2);
231 datadirs
[0] = (gchar
*) g_get_user_data_dir ();
232 for (datadir_i
= 0; sdatadirs
[datadir_i
]; datadir_i
++)
233 datadirs
[datadir_i
+ 1] = (gchar
*) sdatadirs
[datadir_i
];
235 for (datadir_i
= 0; datadirs
[datadir_i
]; datadir_i
++) {
236 gchar
*helpdirname
= g_build_filename (datadirs
[datadir_i
], "gnome", "help", NULL
);
237 GFile
*helpdir
= g_file_new_for_path (helpdirname
);
238 GFileEnumerator
*children
= g_file_enumerate_children (helpdir
,
239 G_FILE_ATTRIBUTE_STANDARD_TYPE
","
240 G_FILE_ATTRIBUTE_STANDARD_NAME
,
241 G_FILE_QUERY_INFO_NONE
,
244 if (children
== NULL
) {
245 g_object_unref (helpdir
);
246 g_free (helpdirname
);
249 while ((child
= g_file_enumerator_next_file (children
, NULL
, NULL
))) {
251 HelpListEntry
*entry
= NULL
;
253 if (g_file_info_get_file_type (child
) != G_FILE_TYPE_DIRECTORY
) {
254 g_object_unref (child
);
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
);
311 for (datadir_i
= 0; datadirs
[datadir_i
]; datadir_i
++) {
312 for (lang_i
= 0; langs
[lang_i
]; lang_i
++) {
313 gchar
*langdirname
= g_build_filename (datadirs
[datadir_i
], "help", langs
[lang_i
], NULL
);
314 GFile
*langdir
= g_file_new_for_path (langdirname
);
315 GFileEnumerator
*children
= g_file_enumerate_children (langdir
,
316 G_FILE_ATTRIBUTE_STANDARD_TYPE
","
317 G_FILE_ATTRIBUTE_STANDARD_NAME
,
318 G_FILE_QUERY_INFO_NONE
,
321 if (children
== NULL
) {
322 g_object_unref (langdir
);
323 g_free (langdirname
);
326 while ((child
= g_file_enumerator_next_file (children
, NULL
, NULL
))) {
327 gchar
*docid
, *filename
;
328 HelpListEntry
*entry
= NULL
;
329 if (g_file_info_get_file_type (child
) != G_FILE_TYPE_DIRECTORY
) {
330 g_object_unref (child
);
334 docid
= g_strconcat ("help:", g_file_info_get_name (child
), NULL
);
335 if (g_hash_table_lookup (priv
->entries
, docid
) != NULL
) {
340 filename
= g_build_filename (langdirname
,
341 g_file_info_get_name (child
),
344 if (g_file_test (filename
, G_FILE_TEST_IS_REGULAR
)) {
345 entry
= g_new0 (HelpListEntry
, 1);
347 entry
->filename
= filename
;
348 entry
->type
= YELP_URI_DOCUMENT_TYPE_MALLARD
;
353 filename
= g_build_filename (langdirname
,
354 g_file_info_get_name (child
),
357 if (g_file_test (filename
, G_FILE_TEST_IS_REGULAR
)) {
358 entry
= g_new0 (HelpListEntry
, 1);
360 entry
->filename
= filename
;
361 entry
->type
= YELP_URI_DOCUMENT_TYPE_DOCBOOK
;
368 g_object_unref (child
);
370 g_hash_table_insert (priv
->entries
, docid
, entry
);
371 priv
->all_entries
= g_list_prepend (priv
->all_entries
, entry
);
375 g_object_unref (children
);
380 theme
= gtk_icon_theme_get_default ();
381 for (cur
= priv
->all_entries
; cur
!= NULL
; cur
= cur
->next
) {
382 GDesktopAppInfo
*app
;
384 HelpListEntry
*entry
= (HelpListEntry
*) cur
->data
;
385 const gchar
*entryid
= strchr (entry
->id
, ':') + 1;
387 if (entry
->type
== YELP_URI_DOCUMENT_TYPE_MALLARD
)
388 help_list_process_mallard (list
, entry
);
389 else if (entry
->type
== YELP_URI_DOCUMENT_TYPE_DOCBOOK
)
390 help_list_process_docbook (list
, entry
);
392 tmp
= g_strconcat (entryid
, ".desktop", NULL
);
393 app
= g_desktop_app_info_new (tmp
);
398 for (prefix
= (char **) known_vendor_prefixes
; *prefix
; prefix
++) {
399 tmp
= g_strconcat (*prefix
, "-", entryid
, ".desktop", NULL
);
400 app
= g_desktop_app_info_new (tmp
);
408 GIcon
*icon
= g_app_info_get_icon ((GAppInfo
*) app
);
410 GtkIconInfo
*info
= gtk_icon_theme_lookup_by_gicon (theme
,
412 GTK_ICON_LOOKUP_NO_SVG
);
414 const gchar
*iconfile
= gtk_icon_info_get_filename (info
);
416 entry
->icon
= g_filename_to_uri (iconfile
, NULL
, NULL
);
417 g_object_unref (info
);
420 g_object_unref (app
);
424 g_mutex_lock (&priv
->mutex
);
425 priv
->process_running
= FALSE
;
426 priv
->process_ran
= TRUE
;
427 while (priv
->pending
) {
428 gchar
*page_id
= (gchar
*) priv
->pending
->data
;
429 help_list_handle_page (list
, page_id
);
431 priv
->pending
= g_slist_delete_link (priv
->pending
, priv
->pending
);
433 g_mutex_unlock (&priv
->mutex
);
435 g_object_unref (list
);
438 /* This function expects to be called inside a locked mutex */
440 help_list_handle_page (YelpHelpList
*list
,
441 const gchar
*page_id
)
443 gchar
**colors
, *tmp
;
445 YelpHelpListPrivate
*priv
= GET_PRIV (list
);
446 GtkTextDirection direction
= gtk_widget_get_default_direction ();
447 GString
*string
= g_string_new
448 ("<html xmlns=\"http://www.w3.org/1999/xhtml\"><head><style type='text/css'>\n"
449 "html { height: 100%; }\n"
450 "body { margin: 0; padding: 0; max-width: 100%;");
451 colors
= yelp_settings_get_colors (yelp_settings_get_default ());
453 tmp
= g_markup_printf_escaped (" background-color: %s; color: %s;"
454 " direction: %s; }\n",
455 colors
[YELP_SETTINGS_COLOR_BASE
],
456 colors
[YELP_SETTINGS_COLOR_TEXT
],
457 (direction
== GTK_TEXT_DIR_RTL
) ? "rtl" : "ltr");
458 g_string_append (string
, tmp
);
461 g_string_append (string
,
462 "div.body { margin: 0 12px 0 12px; padding: 0;"
463 " max-width: 60em; min-height: 20em; }\n"
464 "div.header { max-width: 100%; width: 100%;"
465 " padding: 0; margin: 0 0 1em 0; }\n"
466 "div.footer { max-width: 60em; }\n"
467 "div.sect { margin-top: 1.72em; }\n"
468 "div.trails { margin: 0; padding: 0.2em 12px 0 12px;");
470 tmp
= g_markup_printf_escaped (" background-color: %s;"
471 " border-bottom: solid 1px %s; }\n",
472 colors
[YELP_SETTINGS_COLOR_GRAY_BASE
],
473 colors
[YELP_SETTINGS_COLOR_GRAY_BORDER
]);
474 g_string_append (string
, tmp
);
477 g_string_append (string
,
478 "div.trail { margin: 0 1em 0.2em 1em; padding: 0; text-indent: -1em;");
480 tmp
= g_markup_printf_escaped (" color: %s; }\n",
481 colors
[YELP_SETTINGS_COLOR_TEXT_LIGHT
]);
482 g_string_append (string
, tmp
);
485 g_string_append (string
,
486 "a.trail { white-space: nowrap; }\n"
487 "div.hgroup { margin: 0 0 0.5em 0;");
489 tmp
= g_markup_printf_escaped (" color: %s;"
490 " border-bottom: solid 1px %s; }\n",
491 colors
[YELP_SETTINGS_COLOR_TEXT_LIGHT
],
492 colors
[YELP_SETTINGS_COLOR_GRAY_BORDER
]);
493 g_string_append (string
, tmp
);
496 tmp
= g_markup_printf_escaped ("div.title { margin: 0 0 0.2em 0; font-weight: bold; color: %s; }\n"
497 "div.desc { margin: 0 0 0.2em 0; }\n"
498 "div.linkdiv div.inner { padding-%s: 30px; min-height: 24px;"
499 " background-position: top %s; background-repeat: no-repeat;"
500 " -webkit-background-size: 22px 22px; }\n"
501 "div.linkdiv div.title {font-size: 1em; color: inherit; }\n"
502 "div.linkdiv div.desc { color: %s; }\n"
503 "div.linkdiv { margin: 0; padding: 0.5em; }\n"
504 "a:hover div.linkdiv {"
505 " text-decoration: none;"
506 " outline: solid 1px %s;"
507 " background: -webkit-gradient(linear, left top, left 80,"
508 " from(%s), to(%s)); }\n",
509 colors
[YELP_SETTINGS_COLOR_TEXT_LIGHT
],
510 ((direction
== GTK_TEXT_DIR_RTL
) ? "right" : "left"),
511 ((direction
== GTK_TEXT_DIR_RTL
) ? "right" : "left"),
512 colors
[YELP_SETTINGS_COLOR_TEXT_LIGHT
],
513 colors
[YELP_SETTINGS_COLOR_BLUE_BASE
],
514 colors
[YELP_SETTINGS_COLOR_BLUE_BASE
],
515 colors
[YELP_SETTINGS_COLOR_BASE
]);
516 g_string_append (string
, tmp
);
519 g_string_append (string
,
520 "h1, h2, h3, h4, h5, h6, h7 { margin: 0; padding: 0; font-weight: bold; }\n"
521 "h1 { font-size: 1.44em; }\n"
522 "h2 { font-size: 1.2em; }"
523 "h3.title, h4.title, h5.title, h6.title, h7.title { font-size: 1.2em; }"
524 "h3, h4, h5, h6, h7 { font-size: 1em; }"
525 "p { line-height: 1.72em; }"
526 "div, pre, p { margin: 1em 0 0 0; padding: 0; }"
527 "div:first-child, pre:first-child, p:first-child { margin-top: 0; }"
528 "div.inner, div.contents, pre.contents { margin-top: 0; }"
529 "p img { vertical-align: middle; }"
531 " text-decoration: none;");
533 tmp
= g_markup_printf_escaped (" color: %s; } a:visited { color: %s; }",
534 colors
[YELP_SETTINGS_COLOR_LINK
],
535 colors
[YELP_SETTINGS_COLOR_LINK_VISITED
]);
536 g_string_append (string
, tmp
);
539 g_string_append (string
,
540 "a:hover { text-decoration: underline; }\n"
541 "a img { border: none; }\n"
544 tmp
= g_markup_printf_escaped ("<title>%s</title>",
545 _("All Help Documents"));
546 g_string_append (string
, tmp
);
549 g_string_append (string
,
551 "<div class='header'></div>"
552 "<div class='body'><div class='hgroup'>");
553 tmp
= g_markup_printf_escaped ("<h1>%s</h1></div>\n",
554 _("All Help Documents"));
555 g_string_append (string
, tmp
);
558 priv
->all_entries
= g_list_sort (priv
->all_entries
,
559 (GCompareFunc
) help_list_entry_cmp
);
560 for (cur
= priv
->all_entries
; cur
!= NULL
; cur
= cur
->next
) {
561 HelpListEntry
*entry
= (HelpListEntry
*) cur
->data
;
562 gchar
*title
= entry
->title
? entry
->title
: (strchr (entry
->id
, ':') + 1);
563 const gchar
*desc
= entry
->desc
? entry
->desc
: "";
565 tmp
= g_markup_printf_escaped ("<a href='%s'><div class='linkdiv'>",
567 g_string_append (string
, tmp
);
571 tmp
= g_markup_printf_escaped ("<div class='inner' style='background-image: url(%s);'>",
573 g_string_append (string
, tmp
);
577 g_string_append (string
, "<div class='inner'>");
579 tmp
= g_markup_printf_escaped ("<div class='title'>%s</div>"
580 "<div class='desc'>%s</div>"
583 g_string_append (string
, tmp
);
587 g_string_append (string
,
589 "<div class='footer'></div>"
592 yelp_document_give_contents (YELP_DOCUMENT (list
), page_id
,
594 "application/xhtml+xml");
596 g_string_free (string
, FALSE
);
597 yelp_document_signal (YELP_DOCUMENT (list
), page_id
,
598 YELP_DOCUMENT_SIGNAL_CONTENTS
, NULL
);
603 help_list_process_docbook (YelpHelpList
*list
,
604 HelpListEntry
*entry
)
606 xmlParserCtxtPtr parserCtxt
;
608 xmlXPathContextPtr xpath
;
609 xmlXPathObjectPtr obj
= NULL
;
610 YelpHelpListPrivate
*priv
= GET_PRIV (list
);
612 parserCtxt
= xmlNewParserCtxt ();
613 xmldoc
= xmlCtxtReadFile (parserCtxt
,
614 (const char *) entry
->filename
, NULL
,
615 XML_PARSE_DTDLOAD
| XML_PARSE_NOCDATA
|
616 XML_PARSE_NOENT
| XML_PARSE_NONET
);
617 xmlFreeParserCtxt (parserCtxt
);
621 xmlXIncludeProcessFlags (xmldoc
,
622 XML_PARSE_DTDLOAD
| XML_PARSE_NOCDATA
|
623 XML_PARSE_NOENT
| XML_PARSE_NONET
);
625 xpath
= xmlXPathNewContext (xmldoc
);
626 xmlXPathRegisterNs (xpath
, BAD_CAST
"db",
627 BAD_CAST
"http://docbook.org/ns/docbook");
628 obj
= xmlXPathCompiledEval (priv
->get_docbook_title
, xpath
);
631 entry
->title
= g_strdup ((const gchar
*) obj
->stringval
);
632 xmlXPathFreeObject (obj
);
638 xmlXPathFreeContext (xpath
);
642 help_list_process_mallard (YelpHelpList
*list
,
643 HelpListEntry
*entry
)
645 xmlParserCtxtPtr parserCtxt
;
647 xmlXPathContextPtr xpath
;
648 xmlXPathObjectPtr obj
= NULL
;
649 YelpHelpListPrivate
*priv
= GET_PRIV (list
);
651 parserCtxt
= xmlNewParserCtxt ();
652 xmldoc
= xmlCtxtReadFile (parserCtxt
,
653 (const char *) entry
->filename
, NULL
,
654 XML_PARSE_DTDLOAD
| XML_PARSE_NOCDATA
|
655 XML_PARSE_NOENT
| XML_PARSE_NONET
);
656 xmlFreeParserCtxt (parserCtxt
);
660 xmlXIncludeProcessFlags (xmldoc
,
661 XML_PARSE_DTDLOAD
| XML_PARSE_NOCDATA
|
662 XML_PARSE_NOENT
| XML_PARSE_NONET
);
664 xpath
= xmlXPathNewContext (xmldoc
);
665 xmlXPathRegisterNs (xpath
, BAD_CAST
"mal",
666 BAD_CAST
"http://projectmallard.org/1.0/");
668 obj
= xmlXPathCompiledEval (priv
->get_mallard_title
, xpath
);
671 entry
->title
= g_strdup ((const gchar
*) obj
->stringval
);
672 xmlXPathFreeObject (obj
);
675 obj
= xmlXPathCompiledEval (priv
->get_mallard_desc
, xpath
);
678 entry
->desc
= g_strdup ((const gchar
*) obj
->stringval
);
679 xmlXPathFreeObject (obj
);
685 xmlXPathFreeContext (xpath
);