diff --git a/configure.ac b/configure.ac index d68285a..f1d158f 100644 --- a/configure.ac +++ b/configure.ac @@ -28,7 +28,10 @@ dnl **************************************************************************** GTK_REQUIRED=2.24.0 LIBXML2_REQUIRED=2.7.8 VALA_REQUIRED=0.14.0 +WEBKIT_REQUIRED=1.8.1 +LIBWNCK_REQUIRED=2.30.0 GEANY_REQUIRED=1.22 +GCONF2_REQUIRED=2.30.0 dnl **************************************************************************** dnl Check for vala @@ -49,7 +52,10 @@ dnl **************************************************************************** dnl Check for packages dnl **************************************************************************** PKG_CHECK_MODULES(GEANY_VALA_TOYS, [gtk+-2.0 >= $GTK_REQUIRED + webkit-1.0 >= $WEBKIT_REQUIRED + libwnck-1.0 >= $LIBWNCK_REQUIRED libxml-2.0 >= $LIBXML2_REQUIRED + gconf-2.0 >= $GCONF2_REQUIRED geany >= $GEANY_REQUIRED]) AC_SUBST(GEANY_VALA_TOYS_CFLAGS) AC_SUBST(GEANY_VALA_TOYS_LIBS) @@ -67,9 +73,10 @@ Makefile plugins/Makefile plugins/dock/Makefile plugins/dock/gdl/Makefile +plugins/help/Makefile +plugins/help/devhelp/Makefile plugins/prj/Makefile po/Makefile.in ]) AC_OUTPUT - diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 321578b..15e531a 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -1 +1 @@ -SUBDIRS = dock prj +SUBDIRS = dock prj help diff --git a/plugins/dock/dock-plugin.vala b/plugins/dock/dock-plugin.vala index a280324..129f137 100644 --- a/plugins/dock/dock-plugin.vala +++ b/plugins/dock/dock-plugin.vala @@ -330,6 +330,7 @@ public class GVT.DockPlugin : GLib.Object int num = notebook.page_num (m_DocumentDock); if (num != notebook.get_current_page ()) { + debug("activate editor"); notebook.set_current_page (num); } } @@ -789,6 +790,8 @@ plugin_set_info (Geany.Plugin.Info inInfo) public void plugin_init (Geany.Data inData) { + geany_plugin.module_make_resident (); + // Delay creation to let geany original creation terminate before // relayout it if (s_IdCreate == 0) diff --git a/plugins/help/Makefile.am b/plugins/help/Makefile.am new file mode 100644 index 0000000..0a8f852 --- /dev/null +++ b/plugins/help/Makefile.am @@ -0,0 +1,27 @@ +SUBDIRS = devhelp + +include $(top_srcdir)/build/plugins.am + +INCLUDES += \ + -I${top_srcdir}/plugins/help/devhelp + +plugins_LTLIBRARIES = libgvt-help-plugin.la + +libgvt_help_plugin_la_VALAFLAGS = \ + --vapidir=${top_srcdir}/plugins/help/devhelp \ + --pkg=libdevhelp-2.0 + +libgvt_help_plugin_la_SOURCES = \ + help-plugin.vala \ + devhelp.vala + +libgvt_help_plugin_la_LIBADD = \ + $(GEANY_VALA_TOYS_LIBS) \ + devhelp/libdevhelp-2.la + +VALAFILES = \ + $(filter %.vala,$(libgvt_help_plugin_la_SOURCES)) + +CLEANFILES += \ + libgvt_help_plugin_la_vala.stamp \ + $(VALAFILES:.vala=.c) diff --git a/plugins/help/devhelp.vala b/plugins/help/devhelp.vala new file mode 100644 index 0000000..0e2d184 --- /dev/null +++ b/plugins/help/devhelp.vala @@ -0,0 +1,322 @@ +/* -*- Mode: Vala; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ +/* + * help-manager.vala + * Copyright (C) Nicolas Bruguier 2010-2012 + * + * geany-vala-toys is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * geany-vala-toys is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +public Geany.Plugin geany_plugin; +public Geany.Data geany_data; +public Geany.Functions geany_functions; + +const string MAN2HTML = "/usr/lib/cgi-bin/man/man2html"; + +public class GVT.Devhelp : GLib.Object +{ + // types + private class View : Gtk.Frame + { + // properties + private WebKit.WebView m_DHView; + private Gtk.ToolButton m_BackButton; + private Gtk.ToolButton m_ForwardButton; + + // accessors + public int page_id { get; set; } + + // methods + public View () + { + set_shadow_type(Gtk.ShadowType.NONE); + + m_DHView = new WebKit.WebView(); + m_DHView.show (); + + Gtk.VBox vbox = new Gtk.VBox (false, 0); + vbox.show (); + add (vbox); + + Gtk.Toolbar toolbar = new Gtk.Toolbar (); + toolbar.show (); + vbox.pack_start (toolbar, false, false); + + m_BackButton = new Gtk.ToolButton.from_stock (Gtk.STOCK_GO_BACK); + m_BackButton.show (); + m_BackButton.clicked.connect (() => { + m_DHView.go_back (); + }); + toolbar.insert (m_BackButton, -1); + + m_ForwardButton = new Gtk.ToolButton.from_stock (Gtk.STOCK_GO_FORWARD); + m_ForwardButton.show (); + m_BackButton.clicked.connect (() => { + m_DHView.go_forward (); + }); + toolbar.insert (m_ForwardButton, -1); + + Gtk.SeparatorToolItem separator = new Gtk.SeparatorToolItem (); + separator.show (); + toolbar.insert (separator, -1); + + Gtk.ToolButton zoom_in = new Gtk.ToolButton.from_stock (Gtk.STOCK_ZOOM_IN); + zoom_in.show (); + zoom_in.clicked.connect (() => { + m_DHView.zoom_in (); + }); + toolbar.insert (zoom_in, -1); + + Gtk.ToolButton zoom_out = new Gtk.ToolButton.from_stock (Gtk.STOCK_ZOOM_OUT); + zoom_out.show (); + zoom_out.clicked.connect (() => { + m_DHView.zoom_out (); + }); + toolbar.insert (zoom_out, -1); + + Gtk.ScrolledWindow swview = new Gtk.ScrolledWindow(null, null); + swview.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC); + swview.show (); + + swview.add (m_DHView); + vbox.pack_start (swview); + + m_DHView.document_load_finished.connect (() => { + update_history_buttons (); + }); + m_DHView.notify["uri"].connect (() => { + update_history_buttons (); + }); + m_DHView.notify["load-status"].connect (() => { + update_history_buttons (); + }); + + m_DHView.open("about:blank"); + } + + private void + update_history_buttons () + { + m_BackButton.set_sensitive (m_DHView.can_go_back ()); + m_ForwardButton.set_sensitive (m_DHView.can_go_forward ()); + } + + private string? + get_man_html_page (string[] inArgs) + { + string cmd = MAN2HTML; + + foreach (string arg in inArgs) + { + cmd += " " + arg; + } + try + { + string html; + int ret; + debug ("launch %s", cmd); + GLib.Process.spawn_command_line_sync (cmd, out html, null, out ret); + if (ret == 0) + { + GLib.FileUtils.set_contents ("/tmp/gvt-help-plugin.html", html); + return "/tmp/gvt-help-plugin.html"; + } + } + catch (GLib.Error err) + { + warning ("%s", err.message); + } + + return null; + } + + public void + open (string inUri) + { + debug ("open %s", inUri); + int current = geany_data.main_widgets.message_window_notebook.get_current_page (); + if (current != page_id) + geany_data.main_widgets.message_window_notebook.set_current_page (page_id); + + if (inUri.has_prefix ("/cgi-bin/man/man2html?")) + { + string[] args = inUri.substring ("/cgi-bin/man/man2html?".length).split("+"); + string? page = get_man_html_page (args); + if (page != null) + m_DHView.open (page); + } + else + { + m_DHView.open (inUri); + } + } + + public void + open_man_page (string inTag) + { + string[] args = {}; + args += inTag; + string? page = get_man_html_page (args); + + if (page != null) + { + open (page); + } + } + } + + class Sidebar : Gtk.Notebook + { + // properties + private Dh.Base m_DHBase; + private Dh.BookTree m_DHBookTree; + private Dh.Search m_DHSearch; + private View m_View; + + // accessors + public int page_id { get; set; } + + // methods + public Sidebar (View inView) + { + m_DHBase = new Dh.Base(); + + m_View = inView; + + Dh.BookManager book_manager = m_DHBase.get_book_manager(); + + m_DHBookTree = new Dh.BookTree(book_manager); + m_DHBookTree.show (); + m_DHSearch = new Dh.Search(book_manager); + m_DHSearch.show (); + + Gtk.ScrolledWindow swbooktree = new Gtk.ScrolledWindow(null, null); + swbooktree.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); + swbooktree.border_width = 5; + swbooktree.show (); + swbooktree.add(m_DHBookTree); + + Gtk.Frame frasearch = new Gtk.Frame(null); + frasearch.set_shadow_type(Gtk.ShadowType.NONE); + frasearch.border_width = 5; + frasearch.show (); + frasearch.add(m_DHSearch); + + append_page(swbooktree, new Gtk.Label("Contents")); + append_page(frasearch, new Gtk.Label("Search")); + + m_DHBookTree.link_selected.connect(on_link_selected); + m_DHSearch.link_selected.connect(on_link_selected); + } + + private void + on_link_selected (void* inLink) + { + Dh.Link link = (Dh.Link)inLink; + string uri = link.get_uri(); + m_View.open (uri); + } + + public void + search (string inTag) + { + debug ("search: %s", inTag); + int current = geany_data.main_widgets.sidebar_notebook.get_current_page (); + if (current != page_id) + geany_data.main_widgets.sidebar_notebook.set_current_page (page_id); + set_current_page (1); + + m_DHSearch.set_search_string (inTag, null); + } + } + + // properties + private View m_View; + private Sidebar m_Sidebar; + + // static methods + static construct + { + geany_plugin.module_make_resident (); + } + + // methods + public Devhelp () + { + // Create help tabs + m_View = new View (); + m_View.show (); + m_Sidebar = new Sidebar (m_View); + m_Sidebar.show (); + + // Add sidebar + m_Sidebar.page_id = geany_data.main_widgets.sidebar_notebook.append_page (m_Sidebar, + new Gtk.Label ("GVT Help")); + + // Add view + m_View.page_id = geany_data.main_widgets.message_window_notebook.append_page (m_View, + new Gtk.Label ("GVT Documentation")); + + unowned Geany.KeyGroup key_group = geany_plugin.set_key_group ("GVT Help Manager", GVT.HelpKeyBinding.COUNT); + Geany.Keybindings.set_item (key_group, GVT.HelpKeyBinding.SEARCH_SYMBOL, plugin_kb_activate, 0, 0, + "gvt_help_search_symbol", "Find symbol in help", null); + Geany.Keybindings.set_item (key_group, GVT.HelpKeyBinding.SEARCH_MAN_SYMBOL, plugin_kb_activate, 0, 0, + "gvt_help_search_man_symbol", "Find symbol in man pages", null); + } + + public string? + get_current_word () + { + unowned Geany.Document? document = Geany.Document.get_current (); + + if (document == null || document.editor == null || document.editor.scintilla == null) + return null; + + if (document.editor.scintilla.has_selection ()) + { + string val = document.editor.scintilla.get_selection_contents (); + val.canon (Geany.Editor.WORD_CHARS, ' '); + return val.strip (); + } + + int pos = document.editor.scintilla.get_current_position (); + string val = document.editor.get_word_at_pos (pos); + if (val != null && val.length > 0) + { + val.canon (Geany.Editor.WORD_CHARS, ' '); + return val.strip (); + } + + return null; + } + + public void + search_symbol () + { + string? tag = get_current_word (); + if (tag != null) + { + m_Sidebar.search (tag); + } + } + + public void + search_man_symbol () + { + string? tag = get_current_word (); + if (tag != null) + { + m_View.open_man_page (tag); + } + } +} diff --git a/plugins/help/devhelp/Makefile.am b/plugins/help/devhelp/Makefile.am new file mode 100644 index 0000000..0d2e527 --- /dev/null +++ b/plugins/help/devhelp/Makefile.am @@ -0,0 +1,79 @@ +include $(top_srcdir)/build/common.am + +INCLUDES += \ + -DG_LOG_DOMAIN=\"Devhelp\" \ + -I.. + +noinst_LTLIBRARIES = libdevhelp-2.la + +dh_headers = \ + dh-assistant.h \ + dh-assistant-view.h \ + dh-base.h \ + dh-book-manager.h \ + dh-book.h \ + dh-book-tree.h \ + dh-error.h \ + dh-keyword-model.h \ + dh-link.h \ + dh-search.h \ + dh-window.h + +dh-enum-types.h: dh-enum-types.h.template $(dh_headers) + $(AM_V_GEN) (cd $(srcdir) && @GLIB_MKENUMS@ --template dh-enum-types.h.template $(dh_headers)) > $@ + +dh-enum-types.c: dh-enum-types.c.template $(dh_headers) + $(AM_V_GEN) (cd $(srcdir) && @GLIB_MKENUMS@ --template dh-enum-types.c.template $(dh_headers)) > $@ + +BUILT_SOURCES = \ + dh-marshal.h \ + dh-marshal.c \ + dh-enum-types.h \ + dh-enum-types.c + +EXTRA_DIST += \ + dh-marshal.list \ + dh-enum-types.c.template \ + dh-enum-types.h.template \ + libdevhelp-2.0.vapi \ + libdevhelp-2.0.deps + +libdevhelp_2_la_SOURCES = \ + dh-assistant.c \ + dh-assistant-view.c \ + dh-base.c \ + dh-book.c \ + dh-book-manager.c \ + dh-book-tree.c \ + dh-enum-types.c \ + dh-enum-types.h \ + dh-error.c \ + dh-keyword-model.c \ + dh-link.c \ + dh-marshal.c \ + dh-marshal.h \ + dh-parser.c \ + dh-parser.h \ + dh-preferences.c \ + dh-preferences.h \ + dh-search.c \ + dh-util.c \ + dh-util.h \ + dh-window.c \ + eggfindbar.c \ + eggfindbar.h \ + ige-conf.c \ + ige-conf-gconf.c \ + ige-conf.h \ + ige-conf-private.h \ + $(dh_headers) + +libdevhelp_2_la_LIBADD = \ + $(GEANY_VALA_TOYS_LIBS) + +dh-marshal.h: dh-marshal.list + $(AM_V_GEN) $(GLIB_GENMARSHAL) $< --header --prefix=_dh_marshal dh-marshal.list > $@ + +dh-marshal.c: dh-marshal.list + $(AM_V_GEN) echo "#include \"dh-marshal.h\"" > $@ && \ + $(GLIB_GENMARSHAL) $< --body --prefix=_dh_marshal dh-marshal.list >> $@ diff --git a/plugins/help/devhelp/dh-assistant-view.c b/plugins/help/devhelp/dh-assistant-view.c new file mode 100644 index 0000000..7430b73 --- /dev/null +++ b/plugins/help/devhelp/dh-assistant-view.c @@ -0,0 +1,465 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2008 Imendio AB + * Copyright (C) 2008 Sven Herzberg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include "config.h" +#include +#include +#include +#include "dh-assistant-view.h" +#include "dh-link.h" +#include "dh-util.h" +#include "dh-book-manager.h" +#include "dh-book.h" +#include "dh-window.h" + +typedef struct { + DhBase *base; + DhLink *link; + gchar *current_search; + gboolean snippet_loaded; +} DhAssistantViewPriv; + +G_DEFINE_TYPE (DhAssistantView, dh_assistant_view, WEBKIT_TYPE_WEB_VIEW); + +#define GET_PRIVATE(instance) G_TYPE_INSTANCE_GET_PRIVATE \ + (instance, DH_TYPE_ASSISTANT_VIEW, DhAssistantViewPriv) + +static void +view_finalize (GObject *object) +{ + DhAssistantViewPriv *priv = GET_PRIVATE (object); + + if (priv->link) { + g_object_unref (priv->link); + } + + if (priv->base) { + g_object_unref (priv->base); + } + + g_free (priv->current_search); + + G_OBJECT_CLASS (dh_assistant_view_parent_class)->finalize (object); +} + +static WebKitNavigationResponse +assistant_navigation_requested (WebKitWebView *web_view, + WebKitWebFrame *frame, + WebKitNetworkRequest *request) +{ + DhAssistantViewPriv *priv; + const gchar *uri; + + priv = GET_PRIVATE (web_view); + + uri = webkit_network_request_get_uri (request); + if (strcmp (uri, "about:blank") == 0) { + return WEBKIT_NAVIGATION_RESPONSE_ACCEPT; + } + else if (! priv->snippet_loaded) { + priv->snippet_loaded = TRUE; + return WEBKIT_NAVIGATION_RESPONSE_ACCEPT; + } + else if (g_str_has_prefix (uri, "file://")) { + GtkWidget *window; + + window = dh_base_get_window (priv->base); + _dh_window_display_uri (DH_WINDOW (window), uri); + } + + return WEBKIT_NAVIGATION_RESPONSE_IGNORE; +} + +static gboolean +assistant_button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + /* Block webkit's builtin context menu. */ + if (event->button != 1) { + return TRUE; + } + + return GTK_WIDGET_CLASS (dh_assistant_view_parent_class)->button_press_event (widget, event); +} + +static void +dh_assistant_view_class_init (DhAssistantViewClass* klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + WebKitWebViewClass *web_view_class = WEBKIT_WEB_VIEW_CLASS (klass); + + object_class->finalize = view_finalize; + + widget_class->button_press_event = assistant_button_press_event; + + web_view_class->navigation_requested = assistant_navigation_requested; + + g_type_class_add_private (klass, sizeof (DhAssistantViewPriv)); +} + +static void +dh_assistant_view_init (DhAssistantView *view) +{ +} + +DhBase* +dh_assistant_view_get_base (DhAssistantView *view) +{ + DhAssistantViewPriv *priv; + + g_return_val_if_fail (DH_IS_ASSISTANT_VIEW (view), NULL); + + priv = GET_PRIVATE (view); + + return priv->base; +} + +GtkWidget* +dh_assistant_view_new (void) +{ + return g_object_new (DH_TYPE_ASSISTANT_VIEW, NULL); +} + +static const gchar * +find_in_buffer (const gchar *buffer, + const gchar *key, + gsize length, + gsize key_length) +{ + gsize m = 0; + gsize i = 0; + + while (i < length) { + if (key[m] == buffer[i]) { + m++; + if (m == key_length) { + return buffer + i - m + 1; + } + } else { + m = 0; + } + i++; + } + + return NULL; +} + +/** + * dh_assistant_view_set_link: + * @view: an devhelp assistant view + * @link: the #DhLink + * + * Open @link in the assistant view, if %NULL the view will be blanked. + * + * Return value: %TRUE if the requested link is open, %FALSE otherwise. + **/ +gboolean +dh_assistant_view_set_link (DhAssistantView *view, + DhLink *link) +{ + DhAssistantViewPriv *priv; + gchar *uri; + const gchar *anchor; + gchar *filename; + GMappedFile *file; + const gchar *contents; + gsize length; + gchar *key; + gsize key_length; + gsize offset = 0; + const gchar *start; + const gchar *end; + + g_return_val_if_fail (DH_IS_ASSISTANT_VIEW (view), FALSE); + + priv = GET_PRIVATE (view); + + if (priv->link == link) { + return TRUE; + } + + if (priv->link) { + dh_link_unref (priv->link); + priv->link = NULL; + } + + if (link) { + link = dh_link_ref (link); + } else { + webkit_web_view_load_uri (WEBKIT_WEB_VIEW (view), "about:blank"); + return TRUE; + } + + uri = dh_link_get_uri (link); + anchor = strrchr (uri, '#'); + if (anchor) { + filename = g_strndup (uri, anchor - uri); + anchor++; + g_free (uri); + } else { + g_free (uri); + return FALSE; + } + + if (g_str_has_prefix (filename, "file://")) + offset = 7; + + file = g_mapped_file_new (filename + offset, FALSE, NULL); + if (!file) { + g_free (filename); + return FALSE; + } + + contents = g_mapped_file_get_contents (file); + length = g_mapped_file_get_length (file); + + key = g_strdup_printf (""; + + start = find_in_buffer (start, + start_key, + length, + strlen (start_key)); + + end_key = "
buf) { + name[-1] = '\n'; + } + } + + stylesheet = dh_util_build_data_filename ("devhelp", + "assistant", + "assistant.css", + NULL); + javascript = dh_util_build_data_filename ("devhelp", + "assistant", + "assistant.js", + NULL); + + html = g_strdup_printf ( + "" + "" + "" + "" + "" + "" + "" + "
%s %s
" + "
%s
" + "" + "", + stylesheet, + javascript, + function, + dh_link_get_type_as_string (link), + dh_link_get_uri (link), + dh_link_get_name (link), + _("Book:"), + dh_link_get_book_name (link), + buf); + g_free (buf); + + g_free (stylesheet); + g_free (javascript); + + priv->snippet_loaded = FALSE; + webkit_web_view_load_string ( + WEBKIT_WEB_VIEW (view), + html, + "text/html", + NULL, + filename); + + g_free (html); + } else { + webkit_web_view_load_uri (WEBKIT_WEB_VIEW (view), "about:blank"); + } + +#if GLIB_CHECK_VERSION(2,21,3) + g_mapped_file_unref (file); +#else + g_mapped_file_free (file); +#endif + + g_free (filename); + + return TRUE; +} + +gboolean +dh_assistant_view_search (DhAssistantView *view, + const gchar *str) +{ + DhAssistantViewPriv *priv; + const gchar *name; + DhLink *link; + DhLink *exact_link; + DhLink *prefix_link; + DhBookManager *book_manager; + GList *books; + + g_return_val_if_fail (DH_IS_ASSISTANT_VIEW (view), FALSE); + g_return_val_if_fail (str, FALSE); + + priv = GET_PRIVATE (view); + + /* Filter out very short strings. */ + if (strlen (str) < 4) { + return FALSE; + } + + if (priv->current_search && strcmp (priv->current_search, str) == 0) { + return FALSE; + } + g_free (priv->current_search); + priv->current_search = g_strdup (str); + + book_manager = dh_base_get_book_manager (dh_assistant_view_get_base (view)); + + prefix_link = NULL; + exact_link = NULL; + + for (books = dh_book_manager_get_books (book_manager); + !exact_link && books; + books = g_list_next (books)) { + GList *l; + + for (l = dh_book_get_keywords (DH_BOOK (books->data)); + l && exact_link == NULL; + l = l->next) { + DhLinkType type; + + link = l->data; + + type = dh_link_get_link_type (link); + + if (type == DH_LINK_TYPE_BOOK || + type == DH_LINK_TYPE_PAGE || + type == DH_LINK_TYPE_KEYWORD) { + continue; + } + + name = dh_link_get_name (link); + if (strcmp (name, str) == 0) { + exact_link = link; + } + else if (g_str_has_prefix (name, str)) { + /* Prefer shorter prefix matches. */ + if (!prefix_link) { + prefix_link = link; + } + else if (strlen (dh_link_get_name (prefix_link)) > strlen (name)) { + prefix_link = link; + } + } + } + } + + if (exact_link) { + /*g_print ("exact hit: '%s' '%s'\n", exact_link->name, str);*/ + dh_assistant_view_set_link (view, exact_link); + } + else if (prefix_link) { + /*g_print ("prefix hit: '%s' '%s'\n", prefix_link->name, str);*/ + dh_assistant_view_set_link (view, prefix_link); + } else { + /*g_print ("no hit\n");*/ + /*assistant_view_set_link (view, NULL);*/ + return FALSE; + } + + return TRUE; +} + +void +dh_assistant_view_set_base (DhAssistantView *view, + DhBase *base) +{ + DhAssistantViewPriv *priv; + + g_return_if_fail (DH_IS_ASSISTANT_VIEW (view)); + g_return_if_fail (DH_IS_BASE (base)); + + priv = GET_PRIVATE (view); + + priv->base = g_object_ref (base); +} diff --git a/plugins/help/devhelp/dh-assistant-view.h b/plugins/help/devhelp/dh-assistant-view.h new file mode 100644 index 0000000..2d70eac --- /dev/null +++ b/plugins/help/devhelp/dh-assistant-view.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2008 Sven Herzberg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#ifndef __DH_ASSISTANT_VIEW_H__ +#define __DH_ASSISTANT_VIEW_H__ + +#include +#include "dh-base.h" +#include "dh-link.h" + +G_BEGIN_DECLS + +#define DH_TYPE_ASSISTANT_VIEW (dh_assistant_view_get_type ()) +#define DH_ASSISTANT_VIEW(i) (G_TYPE_CHECK_INSTANCE_CAST ((i), DH_TYPE_ASSISTANT_VIEW, DhAssistantView)) +#define DH_ASSISTANT_VIEW_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), DH_TYPE_ASSISTANT_VIEW, DhAssistantViewClass)) +#define DH_IS_ASSISTANT_VIEW(i) (G_TYPE_CHECK_INSTANCE_TYPE ((i), DH_TYPE_ASSISTANT_VIEW)) +#define DH_IS_ASSISTANT_VIEW_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), DH_ASSISTANT_VIEW)) +#define DH_ASSISTANT_VIEW_GET_CLASS(i) (G_TYPE_INSTANCE_GET_CLASS ((i), DH_TYPE_ASSISTANT_VIEW, DhAssistantView)) + +typedef struct _DhAssistantView DhAssistantView; +typedef struct _DhAssistantViewClass DhAssistantViewClass; + +struct _DhAssistantView { + WebKitWebView parent_instance; +}; + +struct _DhAssistantViewClass { + WebKitWebViewClass parent_class; +}; + +GType dh_assistant_view_get_type (void) G_GNUC_CONST; +GtkWidget* dh_assistant_view_new (void); +gboolean dh_assistant_view_search (DhAssistantView *view, + const gchar *str); +DhBase* dh_assistant_view_get_base (DhAssistantView *view); +void dh_assistant_view_set_base (DhAssistantView *view, + DhBase *base); +gboolean dh_assistant_view_set_link (DhAssistantView *view, + DhLink *link); +G_END_DECLS + +#endif /* __DH_ASSISTANT_VIEW_H__ */ diff --git a/plugins/help/devhelp/dh-assistant.c b/plugins/help/devhelp/dh-assistant.c new file mode 100644 index 0000000..bd19d88 --- /dev/null +++ b/plugins/help/devhelp/dh-assistant.c @@ -0,0 +1,130 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include +#include +#include +#include "dh-window.h" +#include "dh-util.h" +#include "dh-assistant-view.h" +#include "dh-assistant.h" + +typedef struct { + GtkWidget *main_box; + GtkWidget *view; +} DhAssistantPriv; + +static void dh_assistant_class_init (DhAssistantClass *klass); +static void dh_assistant_init (DhAssistant *assistant); + +G_DEFINE_TYPE (DhAssistant, dh_assistant, GTK_TYPE_WINDOW); + +#define GET_PRIVATE(instance) G_TYPE_INSTANCE_GET_PRIVATE \ + (instance, DH_TYPE_ASSISTANT, DhAssistantPriv) + +static gboolean +assistant_key_press_event_cb (GtkWidget *widget, + GdkEventKey *event, + DhAssistant *assistant) +{ + if (event->keyval == GDK_Escape) { + gtk_widget_destroy (GTK_WIDGET (assistant)); + return TRUE; + } + + return FALSE; +} + +static void +dh_assistant_class_init (DhAssistantClass *klass) +{ + g_type_class_add_private (klass, sizeof (DhAssistantPriv)); +} + +static void +dh_assistant_init (DhAssistant *assistant) +{ + DhAssistantPriv *priv = GET_PRIVATE (assistant); + GtkWidget *scrolled_window; + + priv->main_box = gtk_vbox_new (FALSE, 0); + gtk_widget_show (priv->main_box); + gtk_container_add (GTK_CONTAINER (assistant), priv->main_box); + + /* i18n: Please don't translate "Devhelp". */ + gtk_window_set_title (GTK_WINDOW (assistant), _("Devhelp — Assistant")); + gtk_window_set_icon_name (GTK_WINDOW (assistant), "devhelp"); + + priv->view = dh_assistant_view_new (); + + g_signal_connect (assistant, "key-press-event", + G_CALLBACK (assistant_key_press_event_cb), + assistant); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + + gtk_container_add (GTK_CONTAINER (scrolled_window), priv->view); + + gtk_widget_show_all (scrolled_window); + + gtk_box_pack_start (GTK_BOX (priv->main_box), + scrolled_window, TRUE, TRUE, 0); + + dh_util_state_manage_window (GTK_WINDOW (assistant), + "assistant/window"); +} + +GtkWidget * +dh_assistant_new (DhBase *base) +{ + GtkWidget *assistant; + DhAssistantPriv *priv; + + assistant = g_object_new (DH_TYPE_ASSISTANT, NULL); + + priv = GET_PRIVATE (assistant); + + dh_assistant_view_set_base (DH_ASSISTANT_VIEW (priv->view), base); + + return assistant; +} + +gboolean +dh_assistant_search (DhAssistant *assistant, + const gchar *str) +{ + DhAssistantPriv *priv; + + g_return_val_if_fail (DH_IS_ASSISTANT (assistant), FALSE); + g_return_val_if_fail (str != NULL, FALSE); + + priv = GET_PRIVATE (assistant); + + if (dh_assistant_view_search (DH_ASSISTANT_VIEW (priv->view), str)) { + gtk_widget_show (GTK_WIDGET (assistant)); + return TRUE; + } + + return FALSE; +} diff --git a/plugins/help/devhelp/dh-assistant.h b/plugins/help/devhelp/dh-assistant.h new file mode 100644 index 0000000..771ecbe --- /dev/null +++ b/plugins/help/devhelp/dh-assistant.h @@ -0,0 +1,54 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DH_ASSISTANT_H__ +#define __DH_ASSISTANT_H__ + +#include +#include "dh-base.h" + +G_BEGIN_DECLS + +#define DH_TYPE_ASSISTANT (dh_assistant_get_type ()) +#define DH_ASSISTANT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), DH_TYPE_ASSISTANT, DhAssistant)) +#define DH_ASSISTANT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), DH_TYPE_ASSISTANT, DhAssistantClass)) +#define DH_IS_ASSISTANT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), DH_TYPE_ASSISTANT)) +#define DH_IS_ASSISTANT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), DH_TYPE_ASSISTANT)) +#define DH_ASSISTANT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), DH_TYPE_ASSISTANT, DhAssistantClass)) + +typedef struct _DhAssistant DhAssistant; +typedef struct _DhAssistantClass DhAssistantClass; + +struct _DhAssistant { + GtkWindow parent_instance; +}; + +struct _DhAssistantClass { + GtkWindowClass parent_class; +}; + +GType dh_assistant_get_type (void) G_GNUC_CONST; +GtkWidget *dh_assistant_new (DhBase *base); +gboolean dh_assistant_search (DhAssistant *assistant, + const gchar *str); + +G_END_DECLS + +#endif /* __DH_ASSISTANT_H__ */ diff --git a/plugins/help/devhelp/dh-base.c b/plugins/help/devhelp/dh-base.c new file mode 100644 index 0000000..caf2193 --- /dev/null +++ b/plugins/help/devhelp/dh-base.c @@ -0,0 +1,307 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002 Mikael Hallendal + * Copyright (C) 2004-2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include + +#ifdef GDK_WINDOWING_X11 +#include +#include +#define WNCK_I_KNOW_THIS_IS_UNSTABLE +#include +#endif + +#include "dh-window.h" +#include "dh-link.h" +#include "dh-parser.h" +#include "dh-preferences.h" +#include "dh-assistant.h" +#include "dh-util.h" +#include "ige-conf.h" +#include "dh-base.h" +#include "dh-book-manager.h" + +typedef struct { + GSList *windows; + GSList *assistants; + DhBookManager *book_manager; +} DhBasePriv; + +G_DEFINE_TYPE (DhBase, dh_base, G_TYPE_OBJECT); + +#define GET_PRIVATE(instance) G_TYPE_INSTANCE_GET_PRIVATE \ + (instance, DH_TYPE_BASE, DhBasePriv) + +static void dh_base_init (DhBase *base); +static void dh_base_class_init (DhBaseClass *klass); + +static DhBase *base_instance; + +static void +base_finalize (GObject *object) +{ + G_OBJECT_CLASS (dh_base_parent_class)->finalize (object); +} + +static void +base_dispose (GObject *object) +{ + DhBasePriv *priv; + + priv = GET_PRIVATE (object); + + if (priv->book_manager) { + g_object_unref (priv->book_manager); + priv->book_manager = NULL; + } +} + + +static void +dh_base_class_init (DhBaseClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = base_finalize; + object_class->dispose = base_dispose; + + g_type_class_add_private (klass, sizeof (DhBasePriv)); +} + +static void +dh_base_init (DhBase *base) +{ + DhBasePriv *priv = GET_PRIVATE (base); + IgeConf *conf; + gchar *path; + + conf = ige_conf_get (); + path = dh_util_build_data_filename ("devhelp", "devhelp.defaults", NULL); + ige_conf_add_defaults (conf, path); + g_free (path); + + priv->book_manager = dh_book_manager_new (); + dh_book_manager_populate (priv->book_manager); + +#ifdef GDK_WINDOWING_X11 + { + gint n_screens, i; + + /* For some reason, libwnck doesn't seem to update its list of + * workspaces etc if we don't do this. + */ + n_screens = gdk_display_get_n_screens (gdk_display_get_default ()); + for (i = 0; i < n_screens; i++) + wnck_screen_get (i); + } +#endif +} + +static void +base_window_or_assistant_finalized_cb (DhBase *base, + gpointer window_or_assistant) +{ + DhBasePriv *priv = GET_PRIVATE (base); + + priv->windows = g_slist_remove (priv->windows, window_or_assistant); + priv->assistants = g_slist_remove (priv->assistants, window_or_assistant); + + if (priv->windows == NULL && priv->assistants == NULL) { + gtk_main_quit (); + } +} + +DhBase * +dh_base_get (void) +{ + if (!base_instance) { + base_instance = g_object_new (DH_TYPE_BASE, NULL); + } + + return base_instance; +} + +DhBase * +dh_base_new (void) +{ + if (base_instance) { + g_error ("You can only have one DhBase instance."); + } + + return dh_base_get (); +} + +GtkWidget * +dh_base_new_window (DhBase *base) +{ + DhBasePriv *priv; + GtkWidget *window; + + g_return_val_if_fail (DH_IS_BASE (base), NULL); + + priv = GET_PRIVATE (base); + + window = dh_window_new (base); + + priv->windows = g_slist_prepend (priv->windows, window); + + g_object_weak_ref (G_OBJECT (window), + (GWeakNotify) base_window_or_assistant_finalized_cb, + base); + + return window; +} + +GtkWidget * +dh_base_new_assistant (DhBase *base) +{ + DhBasePriv *priv; + GtkWidget *assistant; + + g_return_val_if_fail (DH_IS_BASE (base), NULL); + + priv = GET_PRIVATE (base); + + assistant = dh_assistant_new (base); + + priv->assistants = g_slist_prepend (priv->assistants, assistant); + + g_object_weak_ref (G_OBJECT (assistant), + (GWeakNotify) base_window_or_assistant_finalized_cb, + base); + + return assistant; +} + +DhBookManager * +dh_base_get_book_manager (DhBase *base) +{ + DhBasePriv *priv; + + g_return_val_if_fail (DH_IS_BASE (base), NULL); + + priv = GET_PRIVATE (base); + + return priv->book_manager; +} + +GtkWidget * +dh_base_get_window_on_current_workspace (DhBase *base) +{ + DhBasePriv *priv; + + g_return_val_if_fail (DH_IS_BASE (base), NULL); + + priv = GET_PRIVATE (base); + + if (!priv->windows) { + return NULL; + } + +#ifdef GDK_WINDOWING_X11 + { + WnckWorkspace *workspace; + WnckScreen *screen; + GtkWidget *window; + GList *windows, *w; + GSList *l; + gulong xid; + pid_t pid; + + screen = wnck_screen_get (0); + if (!screen) { + return NULL; + } + + workspace = wnck_screen_get_active_workspace (screen); + if (!workspace) { + return NULL; + } + + xid = 0; + pid = getpid (); + + /* Use _stacked so we can use the one on top. */ + windows = wnck_screen_get_windows_stacked (screen); + windows = g_list_last (windows); + + for (w = windows; w; w = w->prev) { + if (wnck_window_is_on_workspace (w->data, workspace) && + wnck_window_get_pid (w->data) == pid) { + xid = wnck_window_get_xid (w->data); + break; + } + } + + if (!xid) { + return NULL; + } + + /* Return the first matching window we have. */ + for (l = priv->windows; l; l = l->next) { + window = l->data; + +#if GTK_CHECK_VERSION (2,14,0) + if (GDK_WINDOW_XID (gtk_widget_get_window (window)) == xid) { +#else + if (GDK_WINDOW_XID (window->window) == xid) { +#endif + return window; + } + } + } + + return NULL; +#else + return priv->windows->data; +#endif +} + +GtkWidget * +dh_base_get_window (DhBase *base) +{ + GtkWidget *window; + + g_return_val_if_fail (DH_IS_BASE (base), NULL); + + window = dh_base_get_window_on_current_workspace (base); + if (!window) { + window = dh_base_new_window (base); + gtk_window_present (GTK_WINDOW (window)); + } + + return window; +} + +void +dh_base_quit (DhBase *base) +{ + DhBasePriv *priv = GET_PRIVATE (base); + + /* Make sure all of the windows get a chance to release their resources + * properly. As they get destroyed, + * base_window_or_assistant_finalized_cb() will be called, and when the + * last one is removed, we will quit */ + g_slist_foreach (priv->windows, (GFunc)gtk_widget_destroy, NULL); + g_slist_foreach (priv->assistants, (GFunc)gtk_widget_destroy, NULL); +} diff --git a/plugins/help/devhelp/dh-base.h b/plugins/help/devhelp/dh-base.h new file mode 100644 index 0000000..56f2605 --- /dev/null +++ b/plugins/help/devhelp/dh-base.h @@ -0,0 +1,62 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002 Mikael Hallendal + * Copyright (C) 2005-2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DH_BASE_H__ +#define __DH_BASE_H__ + +#include + +#include "dh-book-manager.h" + +G_BEGIN_DECLS + +typedef struct _DhBase DhBase; +typedef struct _DhBaseClass DhBaseClass; + +#define DH_TYPE_BASE (dh_base_get_type ()) +#define DH_BASE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), DH_TYPE_BASE, DhBase)) +#define DH_BASE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), DH_TYPE_BASE, DhBaseClass)) +#define DH_IS_BASE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), DH_TYPE_BASE)) +#define DH_IS_BASE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), DH_TYPE_BASE)) +#define DH_BASE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), DH_TYPE_BASE, DhBaseClass)) + +struct _DhBase { + GObject parent_instance; +}; + +struct _DhBaseClass { + GObjectClass parent_class; +}; + +GType dh_base_get_type (void) G_GNUC_CONST; +DhBase * dh_base_get (void); +DhBase * dh_base_new (void); +GtkWidget * dh_base_new_window (DhBase *base); +GtkWidget * dh_base_new_assistant (DhBase *base); +GtkWidget * dh_base_get_window (DhBase *base); +GtkWidget * dh_base_get_window_on_current_workspace (DhBase *base); +DhBookManager *dh_base_get_book_manager (DhBase *base); +void dh_base_quit (DhBase *base); + +G_END_DECLS + +#endif /* __DH_BASE_H__ */ diff --git a/plugins/help/devhelp/dh-book-manager.c b/plugins/help/devhelp/dh-book-manager.c new file mode 100644 index 0000000..5726439 --- /dev/null +++ b/plugins/help/devhelp/dh-book-manager.c @@ -0,0 +1,408 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002 Mikael Hallendal + * Copyright (C) 2004-2008 Imendio AB + * Copyright (C) 2010 Lanedo GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include + +#include "dh-link.h" +#include "dh-util.h" +#include "dh-book.h" +#include "dh-book-manager.h" +#include "dh-marshal.h" + +typedef struct { + /* The list of all DhBooks found in the system */ + GList *books; +} DhBookManagerPriv; + +enum { + DISABLED_BOOK_LIST_UPDATED, + LAST_SIGNAL +}; + +static gint signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE (DhBookManager, dh_book_manager, G_TYPE_OBJECT); + +#define GET_PRIVATE(instance) G_TYPE_INSTANCE_GET_PRIVATE \ + (instance, DH_TYPE_BOOK_MANAGER, DhBookManagerPriv) + +static void dh_book_manager_init (DhBookManager *book_manager); +static void dh_book_manager_class_init (DhBookManagerClass *klass); + +static void book_manager_add_from_filepath (DhBookManager *book_manager, + const gchar *book_path); +static void book_manager_add_from_dir (DhBookManager *book_manager, + const gchar *dir_path); + +#ifdef GDK_WINDOWING_QUARTZ +static void book_manager_add_from_xcode_docset (DhBookManager *book_manager, + const gchar *dir_path); +#endif + +static void +book_manager_finalize (GObject *object) +{ + DhBookManagerPriv *priv; + GList *l; + + priv = GET_PRIVATE (object); + + /* Destroy all books */ + for (l = priv->books; l; l = g_list_next (l)) { + g_object_unref (l->data); + } + g_list_free (priv->books); + + G_OBJECT_CLASS (dh_book_manager_parent_class)->finalize (object); +} + +static void +dh_book_manager_class_init (DhBookManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = book_manager_finalize; + + signals[DISABLED_BOOK_LIST_UPDATED] = + g_signal_new ("disabled-book-list-updated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (DhBookManagerClass, disabled_book_list_updated), + NULL, NULL, + _dh_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + g_type_class_add_private (klass, sizeof (DhBookManagerPriv)); +} + +static void +dh_book_manager_init (DhBookManager *book_manager) +{ + DhBookManagerPriv *priv = GET_PRIVATE (book_manager); + + priv->books = NULL; +} + +static void +book_manager_clean_list_of_books_disabled (GSList *books_disabled) +{ + GSList *sl; + + for (sl = books_disabled; sl; sl = g_slist_next (sl)) { + g_free (sl->data); + } + g_slist_free (sl); +} + +static void +book_manager_check_status_from_conf (DhBookManager *book_manager) +{ + GSList *books_disabled, *sl; + + books_disabled = dh_util_state_load_books_disabled (); + + for (sl = books_disabled; sl; sl = g_slist_next (sl)) { + DhBook *book; + + book = dh_book_manager_get_book_by_name (book_manager, + (const gchar *)sl->data); + if (book) { + dh_book_set_enabled (book, FALSE); + } + } + + book_manager_clean_list_of_books_disabled (books_disabled); +} + +static void +book_manager_add_books_in_data_dir (DhBookManager *book_manager, + const gchar *data_dir) +{ + gchar *dir; + + dir = g_build_filename (data_dir, "gtk-doc", "html", NULL); + book_manager_add_from_dir (book_manager, dir); + g_free (dir); + + dir = g_build_filename (data_dir, "devhelp", "books", NULL); + book_manager_add_from_dir (book_manager, dir); + g_free (dir); +} + +void +dh_book_manager_populate (DhBookManager *book_manager) +{ + const gchar * const * system_dirs; + + book_manager_add_books_in_data_dir (book_manager, + g_get_user_data_dir ()); + + system_dirs = g_get_system_data_dirs (); + while (*system_dirs) { + book_manager_add_books_in_data_dir (book_manager, + *system_dirs); + system_dirs++; + } + +#ifdef GDK_WINDOWING_QUARTZ + book_manager_add_from_xcode_docset ( + book_manager, + "/Library/Developer/Shared/Documentation/DocSets"); +#endif + + /* Once all books are loaded, check enabled status from conf */ + book_manager_check_status_from_conf (book_manager); +} + +static gchar * +book_manager_get_book_path (const gchar *base_path, + const gchar *name) +{ + static const gchar *suffixes[] = { + "devhelp2", + "devhelp2.gz", + "devhelp", + "devhelp.gz", + NULL + }; + gchar *tmp; + gchar *book_path; + guint i; + + for (i = 0; suffixes[i]; i++) { + tmp = g_build_filename (base_path, name, name, NULL); + book_path = g_strconcat (tmp, ".", suffixes[i], NULL); + g_free (tmp); + + if (g_file_test (book_path, G_FILE_TEST_EXISTS)) { + return book_path;; + } + g_free (book_path); + } + return NULL; +} + +static void +book_manager_add_from_dir (DhBookManager *book_manager, + const gchar *dir_path) +{ + GDir *dir; + const gchar *name; + + g_return_if_fail (book_manager); + g_return_if_fail (dir_path); + + /* Open directory */ + dir = g_dir_open (dir_path, 0, NULL); + if (!dir) { + return; + } + + /* And iterate it */ + while ((name = g_dir_read_name (dir)) != NULL) { + gchar *book_path; + + book_path = book_manager_get_book_path (dir_path, name); + if (book_path) { + /* Add book from filepath */ + book_manager_add_from_filepath (book_manager, + book_path); + g_free (book_path); + } + } + + g_dir_close (dir); +} + +#ifdef GDK_WINDOWING_QUARTZ +static gboolean +seems_docset_dir (const gchar *path) +{ + gchar *tmp; + gboolean seems_like_devhelp = FALSE; + + g_return_val_if_fail (path, FALSE); + + /* Do some sanity checking on the directory first so we don't have + * to go through several hundreds of files in every docset. + */ + tmp = g_build_filename (path, "style.css", NULL); + if (g_file_test (tmp, G_FILE_TEST_EXISTS)) { + gchar *tmp; + + tmp = g_build_filename (path, "index.sgml", NULL); + if (g_file_test (tmp, G_FILE_TEST_EXISTS)) { + seems_like_devhelp = TRUE; + } + g_free (tmp); + } + g_free (tmp); + + return seems_like_devhelp; +} + +static void +book_manager_add_from_xcode_docset (DhBookManager *book_manager, + const gchar *dir_path) +{ + GDir *dir; + const gchar *name; + + g_return_if_fail (book_manager); + g_return_if_fail (dir_path); + + if (!seems_docset_dir (dir_path)) { + return; + } + + /* Open directory */ + dir = g_dir_open (dir_path, 0, NULL); + if (!dir) { + return; + } + + /* And iterate it, looking for files ending with .devhelp2 */ + while ((name = g_dir_read_name (dir)) != NULL) { + if (g_strcmp0 (strrchr (name, '.'), + ".devhelp2") == 0) { + gchar *book_path; + + book_path = g_build_filename (path, name, NULL); + /* Add book from filepath */ + book_manager_add_from_filepath (book_manager, + book_path); + g_free (book_path); + } + } + + g_dir_close (dir); +} +#endif + +static void +book_manager_add_from_filepath (DhBookManager *book_manager, + const gchar *book_path) +{ + DhBookManagerPriv *priv; + DhBook *book; + + g_return_if_fail (book_manager); + g_return_if_fail (book_path); + + priv = GET_PRIVATE (book_manager); + + /* Allocate new book struct */ + book = dh_book_new (book_path); + + /* Check if book with same path was already loaded in the manager */ + if (g_list_find_custom (priv->books, + book, + (GCompareFunc)dh_book_cmp_by_path)) { + g_object_unref (book); + return; + } + + /* Check if book with same bookname was already loaded in the manager + * (we need to force unique book names) */ + if (g_list_find_custom (priv->books, + book, + (GCompareFunc)dh_book_cmp_by_name)) { + g_object_unref (book); + return; + } + + /* Add the book to the book list */ + priv->books = g_list_insert_sorted (priv->books, + book, + (GCompareFunc)dh_book_cmp_by_title); +} + +GList * +dh_book_manager_get_books (DhBookManager *book_manager) +{ + g_return_val_if_fail (book_manager, NULL); + + return GET_PRIVATE (book_manager)->books; +} + +DhBook * +dh_book_manager_get_book_by_name (DhBookManager *book_manager, + const gchar *name) +{ + DhBook *book = NULL; + GList *l; + + g_return_val_if_fail (book_manager, NULL); + + for (l = GET_PRIVATE (book_manager)->books; + l && !book; + l = g_list_next (l)) { + if (g_strcmp0 (name, + dh_book_get_name (DH_BOOK (l->data))) == 0) { + book = l->data; + } + } + + return book; +} + +void +dh_book_manager_update (DhBookManager *book_manager) +{ + DhBookManagerPriv *priv; + GSList *books_disabled = NULL; + GList *l; + + g_return_if_fail (book_manager); + + priv = GET_PRIVATE (book_manager); + + /* Create list of disabled books */ + for (l = priv->books; l; l = g_list_next (l)) { + DhBook *book = DH_BOOK (l->data); + + if (!dh_book_get_enabled (book)) { + books_disabled = g_slist_append (books_disabled, + g_strdup (dh_book_get_name (book))); + } + } + + /* Store in conf */ + dh_util_state_store_books_disabled (books_disabled); + + /* Emit signal to notify others */ + g_signal_emit (book_manager, + signals[DISABLED_BOOK_LIST_UPDATED], + 0); + + book_manager_clean_list_of_books_disabled (books_disabled); +} + +DhBookManager * +dh_book_manager_new (void) +{ + return g_object_new (DH_TYPE_BOOK_MANAGER, NULL); +} + diff --git a/plugins/help/devhelp/dh-book-manager.h b/plugins/help/devhelp/dh-book-manager.h new file mode 100644 index 0000000..3fec290 --- /dev/null +++ b/plugins/help/devhelp/dh-book-manager.h @@ -0,0 +1,61 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2010 Lanedo GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DH_BOOK_MANAGER_H__ +#define __DH_BOOK_MANAGER_H__ + +#include + +#include "dh-book.h" + +G_BEGIN_DECLS + +typedef struct _DhBookManager DhBookManager; +typedef struct _DhBookManagerClass DhBookManagerClass; + +#define DH_TYPE_BOOK_MANAGER (dh_book_manager_get_type ()) +#define DH_BOOK_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), DH_TYPE_BOOK_MANAGER, DhBookManager)) +#define DH_BOOK_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), DH_TYPE_BOOK_MANAGER, DhBookManagerClass)) +#define DH_IS_BOOK_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), DH_TYPE_BOOK_MANAGER)) +#define DH_IS_BOOK_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), DH_TYPE_BOOK_MANAGER)) +#define DH_BOOK_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), DH_TYPE_BOOK_MANAGER, DhBookManagerClass)) + +struct _DhBookManager { + GObject parent_instance; +}; + +struct _DhBookManagerClass { + GObjectClass parent_class; + + /* Signals */ + void (* disabled_book_list_updated) (DhBookManager *book_manager); +}; + +GType dh_book_manager_get_type (void) G_GNUC_CONST; +DhBookManager *dh_book_manager_new (void); +void dh_book_manager_populate (DhBookManager *book_manager); +GList *dh_book_manager_get_books (DhBookManager *book_manager); +DhBook *dh_book_manager_get_book_by_name (DhBookManager *book_manager, + const gchar *name); +void dh_book_manager_update (DhBookManager *book_manager); + +G_END_DECLS + +#endif /* __DH_BOOK_MANAGER_H__ */ diff --git a/plugins/help/devhelp/dh-book-tree.c b/plugins/help/devhelp/dh-book-tree.c new file mode 100644 index 0000000..d6be62e --- /dev/null +++ b/plugins/help/devhelp/dh-book-tree.c @@ -0,0 +1,385 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001-2003 Mikael Hallendal + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include +#include + +#include "dh-marshal.h" +#include "dh-book-tree.h" +#include "dh-book.h" + +typedef struct { + const gchar *uri; + gboolean found; + GtkTreeIter iter; + GtkTreePath *path; +} FindURIData; + +typedef struct { + GtkTreeStore *store; + DhBookManager *book_manager; + DhLink *selected_link; +} DhBookTreePriv; + +static void dh_book_tree_class_init (DhBookTreeClass *klass); +static void dh_book_tree_init (DhBookTree *tree); +static void book_tree_add_columns (DhBookTree *tree); +static void book_tree_setup_selection (DhBookTree *tree); +static void book_tree_populate_tree (DhBookTree *tree); +static void book_tree_insert_node (DhBookTree *tree, + GNode *node, + GtkTreeIter *parent_iter); +static void book_tree_selection_changed_cb (GtkTreeSelection *selection, + DhBookTree *tree); + +enum { + LINK_SELECTED, + LAST_SIGNAL +}; + +enum { + COL_TITLE, + COL_LINK, + COL_WEIGHT, + N_COLUMNS +}; + +G_DEFINE_TYPE (DhBookTree, dh_book_tree, GTK_TYPE_TREE_VIEW); + +#define GET_PRIVATE(instance) G_TYPE_INSTANCE_GET_PRIVATE \ + (instance, DH_TYPE_BOOK_TREE, DhBookTreePriv); + +static gint signals[LAST_SIGNAL] = { 0 }; + +static void +book_tree_finalize (GObject *object) +{ + DhBookTreePriv *priv = GET_PRIVATE (object); + + g_object_unref (priv->store); + g_object_unref (priv->book_manager); + + G_OBJECT_CLASS (dh_book_tree_parent_class)->finalize (object); +} + +static void +dh_book_tree_class_init (DhBookTreeClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = book_tree_finalize; + + signals[LINK_SELECTED] = + g_signal_new ("link-selected", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _dh_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + g_type_class_add_private (klass, sizeof (DhBookTreePriv)); +} + +static void +dh_book_tree_init (DhBookTree *tree) +{ + DhBookTreePriv *priv; + + priv = GET_PRIVATE (tree); + + priv->store = gtk_tree_store_new (N_COLUMNS, + G_TYPE_STRING, + G_TYPE_POINTER, + PANGO_TYPE_WEIGHT); + priv->selected_link = NULL; + gtk_tree_view_set_model (GTK_TREE_VIEW (tree), + GTK_TREE_MODEL (priv->store)); + + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree), FALSE); + + book_tree_add_columns (tree); + + book_tree_setup_selection (tree); +} + +static void +book_tree_add_columns (DhBookTree *tree) +{ + GtkCellRenderer *cell; + GtkTreeViewColumn *column; + + column = gtk_tree_view_column_new (); + + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", COL_TITLE, + "weight", COL_WEIGHT, + NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column); +} + +static void +book_tree_setup_selection (DhBookTree *tree) +{ + GtkTreeSelection *selection; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); + + gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); + + g_signal_connect (selection, "changed", + G_CALLBACK (book_tree_selection_changed_cb), + tree); +} + +static void +book_tree_populate_tree (DhBookTree *tree) +{ + DhBookTreePriv *priv = GET_PRIVATE (tree); + GList *l; + + gtk_tree_store_clear (priv->store); + + for (l = dh_book_manager_get_books (priv->book_manager); + l; + l = g_list_next (l)) { + DhBook *book = DH_BOOK (l->data); + GNode *node; + + node = dh_book_get_tree (book); + while(node) { + book_tree_insert_node (tree, node, NULL); + node = g_node_next_sibling (node); + } + } +} + +static void +book_manager_disabled_book_list_changed_cb (DhBookManager *book_manager, + gpointer user_data) +{ + DhBookTree *tree = user_data; + book_tree_populate_tree (tree); +} + +static void +book_tree_insert_node (DhBookTree *tree, + GNode *node, + GtkTreeIter *parent_iter) + +{ + DhBookTreePriv *priv = GET_PRIVATE (tree); + DhLink *link; + GtkTreeIter iter; + PangoWeight weight; + GNode *child; + + link = node->data; + + gtk_tree_store_append (priv->store, &iter, parent_iter); + + if (dh_link_get_link_type (link) == DH_LINK_TYPE_BOOK) { + weight = PANGO_WEIGHT_BOLD; + } else { + weight = PANGO_WEIGHT_NORMAL; + } + + gtk_tree_store_set (priv->store, &iter, + COL_TITLE, dh_link_get_name (link), + COL_LINK, link, + COL_WEIGHT, weight, + -1); + + for (child = g_node_first_child (node); + child; + child = g_node_next_sibling (child)) { + book_tree_insert_node (tree, child, &iter); + } +} + +static void +book_tree_selection_changed_cb (GtkTreeSelection *selection, + DhBookTree *tree) +{ + DhBookTreePriv *priv = GET_PRIVATE (tree); + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) { + DhLink *link; + + gtk_tree_model_get (GTK_TREE_MODEL (priv->store), + &iter, + COL_LINK, &link, + -1); + if (link != priv->selected_link) { + g_signal_emit (tree, signals[LINK_SELECTED], 0, link); + } + priv->selected_link = link; + } +} + +GtkWidget * +dh_book_tree_new (DhBookManager *book_manager) +{ + DhBookTree *tree; + DhBookTreePriv *priv; + GtkTreeSelection *selection; + GtkTreeIter iter; + DhLink *link; + + tree = g_object_new (DH_TYPE_BOOK_TREE, NULL); + priv = GET_PRIVATE (tree); + + priv->book_manager = g_object_ref (book_manager); + g_signal_connect (priv->book_manager, + "disabled-book-list-updated", + G_CALLBACK (book_manager_disabled_book_list_changed_cb), + tree); + + book_tree_populate_tree (tree); + + /* Mark the first item as selected, or it would get automatically + * selected when the treeview will get focus; but that's not even + * enough as a selection changed would still be emitted when there + * is no change, hence the manual tracking of selection in + * selected_link. + * https://bugzilla.gnome.org/show_bug.cgi?id=492206 + */ + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); + g_signal_handlers_block_by_func (selection, + book_tree_selection_changed_cb, + tree); + gtk_tree_model_get_iter_first ( GTK_TREE_MODEL (priv->store), &iter); + gtk_tree_model_get (GTK_TREE_MODEL (priv->store), + &iter, COL_LINK, &link, -1); + priv->selected_link = link; + gtk_tree_selection_select_iter (selection, &iter); + g_signal_handlers_unblock_by_func (selection, + book_tree_selection_changed_cb, + tree); + + return GTK_WIDGET (tree); +} + +static gboolean +book_tree_find_uri_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + FindURIData *data) +{ + DhLink *link; + gchar *link_uri; + + gtk_tree_model_get (model, iter, + COL_LINK, &link, + -1); + + link_uri = dh_link_get_uri (link); + if (g_str_has_prefix (data->uri, link_uri)) { + data->found = TRUE; + data->iter = *iter; + data->path = gtk_tree_path_copy (path); + } + g_free (link_uri); + + return data->found; +} + +void +dh_book_tree_select_uri (DhBookTree *tree, + const gchar *uri) +{ + DhBookTreePriv *priv = GET_PRIVATE (tree); + GtkTreeSelection *selection; + FindURIData data; + + data.found = FALSE; + data.uri = uri; + + gtk_tree_model_foreach (GTK_TREE_MODEL (priv->store), + (GtkTreeModelForeachFunc) book_tree_find_uri_foreach, + &data); + + if (!data.found) { + return; + } + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); + + g_signal_handlers_block_by_func (selection, + book_tree_selection_changed_cb, + tree); + + gtk_tree_view_expand_to_path (GTK_TREE_VIEW (tree), data.path); + gtk_tree_selection_select_iter (selection, &data.iter); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (tree), data.path, NULL, 0); + + g_signal_handlers_unblock_by_func (selection, + book_tree_selection_changed_cb, + tree); + + gtk_tree_path_free (data.path); +} + +const gchar * +dh_book_tree_get_selected_book_title (DhBookTree *tree) +{ + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path; + DhLink *link; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) { + return NULL; + } + + path = gtk_tree_model_get_path (model, &iter); + + /* Get the book node for this link. */ + while (1) { + if (gtk_tree_path_get_depth (path) <= 1) { + break; + } + + gtk_tree_path_up (path); + } + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_path_free (path); + + gtk_tree_model_get (model, &iter, + COL_LINK, &link, + -1); + + return dh_link_get_name (link); +} diff --git a/plugins/help/devhelp/dh-book-tree.h b/plugins/help/devhelp/dh-book-tree.h new file mode 100644 index 0000000..885b09c --- /dev/null +++ b/plugins/help/devhelp/dh-book-tree.h @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001 Mikael Hallendal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DH_BOOK_TREE_H__ +#define __DH_BOOK_TREE_H__ + +#include +#include "dh-link.h" +#include "dh-book-manager.h" + +G_BEGIN_DECLS + +#define DH_TYPE_BOOK_TREE (dh_book_tree_get_type ()) +#define DH_BOOK_TREE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DH_TYPE_BOOK_TREE, DhBookTree)) +#define DH_BOOK_TREE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), DH_TYPE_BOOK_TREE, DhBookTreeClass)) +#define DH_IS_BOOK_TREE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DH_TYPE_BOOK_TREE)) +#define DH_IS_BOOK_TREE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), DH_TYPE_BOOK_TREE)) + +typedef struct _DhBookTree DhBookTree; +typedef struct _DhBookTreeClass DhBookTreeClass; + +struct _DhBookTree { + GtkTreeView parent_instance; +}; + +struct _DhBookTreeClass { + GtkTreeViewClass parent_class; +}; + +GType dh_book_tree_get_type (void) G_GNUC_CONST; +GtkWidget * dh_book_tree_new (DhBookManager *book_manager); +void dh_book_tree_select_uri (DhBookTree *book_tree, + const gchar *uri); +const gchar *dh_book_tree_get_selected_book_title (DhBookTree *tree); + +G_END_DECLS + +#endif /* __DH_BOOK_TREE_H__ */ diff --git a/plugins/help/devhelp/dh-book.c b/plugins/help/devhelp/dh-book.c new file mode 100644 index 0000000..179fdd2 --- /dev/null +++ b/plugins/help/devhelp/dh-book.c @@ -0,0 +1,246 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002 Mikael Hallendal + * Copyright (C) 2004-2008 Imendio AB + * Copyright (C) 2010 Lanedo GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include + +#include "dh-link.h" +#include "dh-parser.h" +#include "dh-book.h" + +/* Structure defining basic contents to store about every book */ +typedef struct { + /* File path of the book */ + gchar *path; + /* Enable or disabled? */ + gboolean enabled; + /* Book name */ + gchar *name; + /* Book title */ + gchar *title; + /* Generated book tree */ + GNode *tree; + /* Generated list of keywords in the book */ + GList *keywords; +} DhBookPriv; + +G_DEFINE_TYPE (DhBook, dh_book, G_TYPE_OBJECT); + +#define GET_PRIVATE(instance) G_TYPE_INSTANCE_GET_PRIVATE \ + (instance, DH_TYPE_BOOK, DhBookPriv) + +static void dh_book_init (DhBook *book); +static void dh_book_class_init (DhBookClass *klass); + +static void unref_node_link (GNode *node, + gpointer data); + +static void +book_finalize (GObject *object) +{ + DhBookPriv *priv; + + priv = GET_PRIVATE (object); + + if (priv->tree) { + g_node_traverse (priv->tree, + G_IN_ORDER, + G_TRAVERSE_ALL, + -1, + (GNodeTraverseFunc)unref_node_link, + NULL); + g_node_destroy (priv->tree); + } + + if (priv->keywords) { + g_list_foreach (priv->keywords, (GFunc)dh_link_unref, NULL); + g_list_free (priv->keywords); + } + + g_free (priv->title); + + g_free (priv->path); + + G_OBJECT_CLASS (dh_book_parent_class)->finalize (object); +} + +static void +dh_book_class_init (DhBookClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = book_finalize; + + g_type_class_add_private (klass, sizeof (DhBookPriv)); +} + +static void +dh_book_init (DhBook *book) +{ + DhBookPriv *priv = GET_PRIVATE (book); + + priv->name = NULL; + priv->path = NULL; + priv->title = NULL; + priv->enabled = TRUE; + priv->tree = NULL; + priv->keywords = NULL; +} + +static void +unref_node_link (GNode *node, + gpointer data) +{ + dh_link_unref (node->data); +} + +DhBook * +dh_book_new (const gchar *book_path) +{ + DhBookPriv *priv; + DhBook *book; + GError *error = NULL; + + g_return_val_if_fail (book_path, NULL); + + book = g_object_new (DH_TYPE_BOOK, NULL); + priv = GET_PRIVATE (book); + + /* Parse file storing contents in the book struct */ + if (!dh_parser_read_file (book_path, + &priv->tree, + &priv->keywords, + &error)) { + g_warning ("Failed to read '%s': %s", + priv->path, error->message); + g_error_free (error); + + /* Deallocate the book, as we are not going to add it + * in the manager */ + g_object_unref (book); + return NULL; + } + + /* Store path */ + priv->path = g_strdup (book_path); + + /* Setup title */ + priv->title = g_strdup (dh_link_get_name ((DhLink *)priv->tree->data)); + + /* Setup name */ + priv->name = g_strdup (dh_link_get_book_id ((DhLink *)priv->tree->data)); + + return book; +} + +GList * +dh_book_get_keywords (DhBook *book) +{ + DhBookPriv *priv; + + g_return_val_if_fail (DH_IS_BOOK (book), NULL); + + priv = GET_PRIVATE (book); + + return priv->enabled ? priv->keywords : NULL; +} + +GNode * +dh_book_get_tree (DhBook *book) +{ + DhBookPriv *priv; + + g_return_val_if_fail (DH_IS_BOOK (book), NULL); + + priv = GET_PRIVATE (book); + + return priv->enabled ? priv->tree : NULL; +} + +const gchar * +dh_book_get_name (DhBook *book) +{ + DhBookPriv *priv; + + g_return_val_if_fail (DH_IS_BOOK (book), NULL); + + priv = GET_PRIVATE (book); + + return priv->name; +} + +const gchar * +dh_book_get_title (DhBook *book) +{ + DhBookPriv *priv; + + g_return_val_if_fail (DH_IS_BOOK (book), NULL); + + priv = GET_PRIVATE (book); + + return priv->title; +} + +gboolean +dh_book_get_enabled (DhBook *book) +{ + g_return_val_if_fail (DH_IS_BOOK (book), FALSE); + + return GET_PRIVATE (book)->enabled; +} + +void +dh_book_set_enabled (DhBook *book, + gboolean enabled) +{ + g_return_if_fail (DH_IS_BOOK (book)); + + GET_PRIVATE (book)->enabled = enabled; +} + +gint +dh_book_cmp_by_path (const DhBook *a, + const DhBook *b) +{ + return ((a && b) ? + g_strcmp0 (GET_PRIVATE (a)->path, GET_PRIVATE (b)->path) : + -1); +} + +gint +dh_book_cmp_by_name (const DhBook *a, + const DhBook *b) +{ + return ((a && b) ? + g_ascii_strcasecmp (GET_PRIVATE (a)->name, GET_PRIVATE (b)->name) : + -1); +} + +gint +dh_book_cmp_by_title (const DhBook *a, + const DhBook *b) +{ + return ((a && b) ? + g_utf8_collate (GET_PRIVATE (a)->title, GET_PRIVATE (b)->title) : + -1); +} diff --git a/plugins/help/devhelp/dh-book.h b/plugins/help/devhelp/dh-book.h new file mode 100644 index 0000000..198f2b3 --- /dev/null +++ b/plugins/help/devhelp/dh-book.h @@ -0,0 +1,67 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002 Mikael Hallendal + * Copyright (C) 2005-2008 Imendio AB + * Copyright (C) 2010 Lanedo GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _DH_BOOK_H_ +#define _DH_BOOK_H_ + +#include + +G_BEGIN_DECLS + +typedef struct _DhBook DhBook; +typedef struct _DhBookClass DhBookClass; + +#define DH_TYPE_BOOK (dh_book_get_type ()) +#define DH_BOOK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), DH_TYPE_BOOK, DhBook)) +#define DH_BOOK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), DH_TYPE_BOOK, DhBookClass)) +#define DH_IS_BOOK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), DH_TYPE_BOOK)) +#define DH_IS_BOOK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), DH_TYPE_BOOK)) +#define DH_BOOK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), DH_TYPE_BOOK, DhBookClass)) + +struct _DhBook { + GObject parent_instance; +}; + +struct _DhBookClass { + GObjectClass parent_class; +}; + +GType dh_book_get_type (void) G_GNUC_CONST; +DhBook *dh_book_new (const gchar *book_path); +GList *dh_book_get_keywords (DhBook *book); +GNode *dh_book_get_tree (DhBook *book); +const gchar *dh_book_get_name (DhBook *book); +const gchar *dh_book_get_title (DhBook *book); +gboolean dh_book_get_enabled (DhBook *book); +void dh_book_set_enabled (DhBook *book, + gboolean enabled); +gint dh_book_cmp_by_path (const DhBook *a, + const DhBook *b); +gint dh_book_cmp_by_name (const DhBook *a, + const DhBook *b); +gint dh_book_cmp_by_title (const DhBook *a, + const DhBook *b); + +G_END_DECLS + +#endif /* _DH_BOOK_H_ */ diff --git a/plugins/help/devhelp/dh-enum-types.c.template b/plugins/help/devhelp/dh-enum-types.c.template new file mode 100644 index 0000000..4fe8186 --- /dev/null +++ b/plugins/help/devhelp/dh-enum-types.c.template @@ -0,0 +1,47 @@ +/*** BEGIN file-header ***/ +#include "dh-enum-types.h" +#include "dh-assistant.h" +#include "dh-assistant-view.h" +#include "dh-base.h" +#include "dh-book-manager.h" +#include "dh-book.h" +#include "dh-book-tree.h" +#include "dh-error.h" +#include "dh-keyword-model.h" +#include "dh-link.h" +#include "dh-search.h" +#include "dh-window.h" +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType +@enum_name@_get_type (void) +{ + static GType the_type = 0; + + if (the_type == 0) + { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, + "@VALUENAME@", + "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + the_type = g_@type@_register_static ( + g_intern_static_string ("@EnumName@"), + values); + } + return the_type; +} + +/*** END value-tail ***/ diff --git a/plugins/help/devhelp/dh-enum-types.h.template b/plugins/help/devhelp/dh-enum-types.h.template new file mode 100644 index 0000000..fc97c74 --- /dev/null +++ b/plugins/help/devhelp/dh-enum-types.h.template @@ -0,0 +1,27 @@ +/*** BEGIN file-header ***/ +#ifndef __DH_ENUM_TYPES_H__ +#define __DH_ENUM_TYPES_H__ + +#include + +G_BEGIN_DECLS + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* Enumerations from "@filename@" */ + +/*** END file-production ***/ + +/*** BEGIN enumeration-production ***/ +#define DH_TYPE_@ENUMSHORT@ (@enum_name@_get_type()) +GType @enum_name@_get_type (void) G_GNUC_CONST; + +/*** END enumeration-production ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* __DH_ENUM_TYPES_H__ */ +/*** END file-tail ***/ + diff --git a/plugins/help/devhelp/dh-error.c b/plugins/help/devhelp/dh-error.c new file mode 100644 index 0000000..790eec7 --- /dev/null +++ b/plugins/help/devhelp/dh-error.c @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002 Mikael Hallendal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "dh-error.h" + +GQuark +dh_error_quark (void) +{ + static GQuark q = 0; + + if (q == 0) { + q = g_quark_from_static_string ("dh-error-quark"); + } + + return q; +} diff --git a/plugins/help/devhelp/dh-error.h b/plugins/help/devhelp/dh-error.h new file mode 100644 index 0000000..5d4825c --- /dev/null +++ b/plugins/help/devhelp/dh-error.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002 Mikael Hallendal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DH_ERROR_H__ +#define __DH_ERROR_H__ + +#include + +G_BEGIN_DECLS + +#define DH_ERROR dh_error_quark () + +typedef enum { + DH_ERROR_FILE_NOT_FOUND, + DH_ERROR_MALFORMED_BOOK, + DH_ERROR_INVALID_BOOK_TYPE, + DH_ERROR_INTERNAL_ERROR +} DhError; + +GQuark dh_error_quark (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __DH_ERROR_H__ */ diff --git a/plugins/help/devhelp/dh-keyword-model.c b/plugins/help/devhelp/dh-keyword-model.c new file mode 100644 index 0000000..3ff7b7a --- /dev/null +++ b/plugins/help/devhelp/dh-keyword-model.c @@ -0,0 +1,545 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002 Mikael Hallendal + * Copyright (C) 2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include + +#include "dh-link.h" +#include "dh-book.h" +#include "dh-keyword-model.h" + +struct _DhKeywordModelPriv { + DhBookManager *book_manager; + + GList *keyword_words; + gint keyword_words_length; + + gint stamp; +}; + +#define G_LIST(x) ((GList *) x) +#define MAX_HITS 100 + +static void dh_keyword_model_init (DhKeywordModel *list_store); +static void dh_keyword_model_class_init (DhKeywordModelClass *class); +static void dh_keyword_model_tree_model_init (GtkTreeModelIface *iface); + +G_DEFINE_TYPE_WITH_CODE (DhKeywordModel, dh_keyword_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, + dh_keyword_model_tree_model_init)); + +static void +keyword_model_dispose (GObject *object) +{ + DhKeywordModel *model = DH_KEYWORD_MODEL (object); + DhKeywordModelPriv *priv = model->priv; + + if (priv->book_manager) { + g_object_unref (priv->book_manager); + priv->book_manager = NULL; + } + + G_OBJECT_CLASS (dh_keyword_model_parent_class)->dispose (object); +} + +static void +keyword_model_finalize (GObject *object) +{ + DhKeywordModel *model = DH_KEYWORD_MODEL (object); + DhKeywordModelPriv *priv = model->priv; + + g_list_free (priv->keyword_words); + + g_free (model->priv); + + G_OBJECT_CLASS (dh_keyword_model_parent_class)->finalize (object); +} + +static void +dh_keyword_model_class_init (DhKeywordModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass);; + + object_class->finalize = keyword_model_finalize; + object_class->dispose = keyword_model_dispose; +} + +static void +dh_keyword_model_init (DhKeywordModel *model) +{ + DhKeywordModelPriv *priv; + + priv = g_new0 (DhKeywordModelPriv, 1); + model->priv = priv; + + do { + priv->stamp = g_random_int (); + } while (priv->stamp == 0); +} + +static GtkTreeModelFlags +keyword_model_get_flags (GtkTreeModel *tree_model) +{ + return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY; +} + +static gint +keyword_model_get_n_columns (GtkTreeModel *tree_model) +{ + return DH_KEYWORD_MODEL_NUM_COLS; +} + +static GType +keyword_model_get_column_type (GtkTreeModel *tree_model, + gint column) +{ + switch (column) { + case DH_KEYWORD_MODEL_COL_NAME: + return G_TYPE_STRING; + break; + case DH_KEYWORD_MODEL_COL_LINK: + return G_TYPE_POINTER; + default: + return G_TYPE_INVALID; + } +} + +static gboolean +keyword_model_get_iter (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreePath *path) +{ + DhKeywordModel *model; + DhKeywordModelPriv *priv; + GList *node; + const gint *indices; + + model = DH_KEYWORD_MODEL (tree_model); + priv = model->priv; + + indices = gtk_tree_path_get_indices (path); + + if (indices == NULL) { + return FALSE; + } + + if (indices[0] >= priv->keyword_words_length) { + return FALSE; + } + + node = g_list_nth (priv->keyword_words, indices[0]); + + iter->stamp = priv->stamp; + iter->user_data = node; + + return TRUE; +} + +static GtkTreePath * +keyword_model_get_path (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + DhKeywordModel *model = DH_KEYWORD_MODEL (tree_model); + DhKeywordModelPriv *priv; + GtkTreePath *path; + gint i = 0; + + g_return_val_if_fail (iter->stamp == model->priv->stamp, NULL); + + priv = model->priv; + + i = g_list_position (priv->keyword_words, iter->user_data); + if (i < 0) { + return NULL; + } + + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, i); + + return path; +} + +static void +keyword_model_get_value (GtkTreeModel *tree_model, + GtkTreeIter *iter, + gint column, + GValue *value) +{ + DhLink *link; + + link = G_LIST (iter->user_data)->data; + + switch (column) { + case DH_KEYWORD_MODEL_COL_NAME: + g_value_init (value, G_TYPE_STRING); + g_value_set_string (value, dh_link_get_name (link)); + break; + case DH_KEYWORD_MODEL_COL_LINK: + g_value_init (value, G_TYPE_POINTER); + g_value_set_pointer (value, link); + break; + default: + g_warning ("Bad column %d requested", column); + } +} + +static gboolean +keyword_model_iter_next (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + DhKeywordModel *model = DH_KEYWORD_MODEL (tree_model); + + g_return_val_if_fail (model->priv->stamp == iter->stamp, FALSE); + + iter->user_data = G_LIST (iter->user_data)->next; + + return (iter->user_data != NULL); +} + +static gboolean +keyword_model_iter_children (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent) +{ + DhKeywordModelPriv *priv; + + priv = DH_KEYWORD_MODEL (tree_model)->priv; + + /* This is a list, nodes have no children. */ + if (parent) { + return FALSE; + } + + /* But if parent == NULL we return the list itself as children of + * the "root". + */ + if (priv->keyword_words) { + iter->stamp = priv->stamp; + iter->user_data = priv->keyword_words; + return TRUE; + } + + return FALSE; +} + +static gboolean +keyword_model_iter_has_child (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + return FALSE; +} + +static gint +keyword_model_iter_n_children (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + DhKeywordModelPriv *priv; + + priv = DH_KEYWORD_MODEL (tree_model)->priv; + + if (iter == NULL) { + return priv->keyword_words_length; + } + + g_return_val_if_fail (priv->stamp == iter->stamp, -1); + + return 0; +} + +static gboolean +keyword_model_iter_nth_child (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n) +{ + DhKeywordModelPriv *priv; + GList *child; + + priv = DH_KEYWORD_MODEL (tree_model)->priv; + + if (parent) { + return FALSE; + } + + child = g_list_nth (priv->keyword_words, n); + + if (child) { + iter->stamp = priv->stamp; + iter->user_data = child; + return TRUE; + } + + return FALSE; +} + +static gboolean +keyword_model_iter_parent (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *child) +{ + return FALSE; +} + +static void +dh_keyword_model_tree_model_init (GtkTreeModelIface *iface) +{ + iface->get_flags = keyword_model_get_flags; + iface->get_n_columns = keyword_model_get_n_columns; + iface->get_column_type = keyword_model_get_column_type; + iface->get_iter = keyword_model_get_iter; + iface->get_path = keyword_model_get_path; + iface->get_value = keyword_model_get_value; + iface->iter_next = keyword_model_iter_next; + iface->iter_children = keyword_model_iter_children; + iface->iter_has_child = keyword_model_iter_has_child; + iface->iter_n_children = keyword_model_iter_n_children; + iface->iter_nth_child = keyword_model_iter_nth_child; + iface->iter_parent = keyword_model_iter_parent; +} + +DhKeywordModel * +dh_keyword_model_new (void) +{ + DhKeywordModel *model; + + model = g_object_new (DH_TYPE_KEYWORD_MODEL, NULL); + + return model; +} + +void +dh_keyword_model_set_words (DhKeywordModel *model, + DhBookManager *book_manager) +{ + g_return_if_fail (DH_IS_KEYWORD_MODEL (model)); + + model->priv->book_manager = g_object_ref (book_manager); +} + +static GList * +keyword_model_search (DhKeywordModel *model, + const gchar *string, + gchar **stringv, + const gchar *book_id, + gboolean case_sensitive, + DhLink **exact_link) +{ + DhKeywordModelPriv *priv; + GList *new_list = NULL, *b; + gint hits = 0; + gchar *page_id = NULL; + gchar *page_filename_prefix = NULL; + + priv = model->priv; + + /* The search string may be prefixed by a page:foobar qualifier, it + * will be matched against the filenames of the hits to limit the + * search to pages whose filename is prefixed by "foobar. + */ + if (stringv && g_str_has_prefix(stringv[0], "page:")) { + page_id = stringv[0] + 5; + page_filename_prefix = g_strdup_printf("%s.", page_id); + stringv++; + } + + for (b = dh_book_manager_get_books (priv->book_manager); + b && hits < MAX_HITS; + b = g_list_next (b)) { + DhBook *book; + GList *l; + + book = DH_BOOK (b->data); + + for (l = dh_book_get_keywords (book); + l && hits < MAX_HITS; + l = g_list_next (l)) { + DhLink *link; + gboolean found; + gchar *name; + + link = l->data; + found = FALSE; + + if (book_id && + dh_link_get_book_id (link) && + strcmp (dh_link_get_book_id (link), book_id) != 0) { + continue; + } + + if (page_id && + (dh_link_get_link_type (link) != DH_LINK_TYPE_PAGE && + !g_str_has_prefix (dh_link_get_file_name (link), page_filename_prefix))) { + continue; + } + + if (!case_sensitive) { + name = g_ascii_strdown (dh_link_get_name (link), -1); + } else { + name = g_strdup (dh_link_get_name (link)); + } + + if (!found) { + gint i; + + if (stringv[0] == NULL) { + /* means only a page was specified, no keyword */ + if (g_strrstr (dh_link_get_name(link), page_id)) + found = TRUE; + } else { + found = TRUE; + for (i = 0; stringv[i] != NULL; i++) { + if (!g_strrstr (name, stringv[i])) { + found = FALSE; + break; + } + } + } + } + + g_free (name); + + if (found) { + /* Include in the new list. */ + new_list = g_list_prepend (new_list, link); + hits++; + + if (!*exact_link && + dh_link_get_name (link) && ( + (dh_link_get_link_type (link) == DH_LINK_TYPE_PAGE && + page_id && strcmp (dh_link_get_name (link), page_id) == 0) || + (strcmp (dh_link_get_name (link), string) == 0))) { + *exact_link = link; + } + } + } + } + + g_free (page_filename_prefix); + + return g_list_sort (new_list, dh_link_compare); +} + +DhLink * +dh_keyword_model_filter (DhKeywordModel *model, + const gchar *string, + const gchar *book_id) +{ + DhKeywordModelPriv *priv; + GList *new_list = NULL; + gint old_length; + DhLink *exact_link = NULL; + gint hits; + gint i; + GtkTreePath *path; + GtkTreeIter iter; + + g_return_val_if_fail (DH_IS_KEYWORD_MODEL (model), NULL); + g_return_val_if_fail (string != NULL, NULL); + + priv = model->priv; + + /* Do the minimum amount of work: call update on all rows that are + * kept and remove the rest. + */ + old_length = priv->keyword_words_length; + new_list = NULL; + hits = 0; + + if (string[0] != '\0') { + gchar **stringv; + gboolean case_sensitive; + + stringv = g_strsplit (string, " ", -1); + + case_sensitive = FALSE; + + /* Search for any parameters and position search cursor to + * the next element in the search string. + */ + for (i = 0; stringv[i] != NULL; i++) { + gchar *lower; + + /* Searches are case sensitive when any uppercase + * letter is used in the search terms, matching vim + * smartcase behaviour. + */ + lower = g_ascii_strdown (stringv[i], -1); + if (strcmp (lower, stringv[i]) != 0) { + case_sensitive = TRUE; + g_free (lower); + break; + } + g_free (lower); + } + + new_list = keyword_model_search (model, + string, + stringv, + book_id, + case_sensitive, + &exact_link); + hits = g_list_length (new_list); + + g_strfreev (stringv); + } + + /* Update the list of hits. */ + g_list_free (priv->keyword_words); + priv->keyword_words = new_list; + priv->keyword_words_length = hits; + + /* Update model: rows 0 -> hits. */ + for (i = 0; i < hits; ++i) { + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, i); + keyword_model_get_iter (GTK_TREE_MODEL (model), &iter, path); + gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter); + gtk_tree_path_free (path); + } + + if (old_length > hits) { + /* Update model: remove rows hits -> old_length. */ + for (i = old_length - 1; i >= hits; i--) { + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, i); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + } + } + else if (old_length < hits) { + /* Update model: add rows old_length -> hits. */ + for (i = old_length; i < hits; i++) { + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, i); + keyword_model_get_iter (GTK_TREE_MODEL (model), &iter, path); + gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); + gtk_tree_path_free (path); + } + } + + if (hits == 1) { + return priv->keyword_words->data; + } + + return exact_link; +} diff --git a/plugins/help/devhelp/dh-keyword-model.h b/plugins/help/devhelp/dh-keyword-model.h new file mode 100644 index 0000000..d2ba70d --- /dev/null +++ b/plugins/help/devhelp/dh-keyword-model.h @@ -0,0 +1,69 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2002 Mikael Hallendal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DH_KEYWORD_MODEL_H__ +#define __DH_KEYWORD_MODEL_H__ + +#include +#include "dh-link.h" +#include "dh-book-manager.h" + +G_BEGIN_DECLS + +#define DH_TYPE_KEYWORD_MODEL (dh_keyword_model_get_type ()) +#define DH_KEYWORD_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DH_TYPE_KEYWORD_MODEL, DhKeywordModel)) +#define DH_KEYWORD_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), DH_TYPE_KEYWORD_MODEL, DhKeywordModelClass)) +#define DH_IS_KEYWORD_MODEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DH_TYPE_KEYWORD_MODEL)) +#define DH_IS_KEYWORD_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DH_TYPE_KEYWORD_MODEL)) +#define DH_KEYWORD_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), DH_TYPE_KEYWORD_MODEL, DhKeywordModelClass)) + +typedef struct _DhKeywordModel DhKeywordModel; +typedef struct _DhKeywordModelClass DhKeywordModelClass; +typedef struct _DhKeywordModelPriv DhKeywordModelPriv; + +struct _DhKeywordModel +{ + GObject parent_instance; + DhKeywordModelPriv *priv; +}; + +struct _DhKeywordModelClass +{ + GObjectClass parent_class; +}; + +enum { + DH_KEYWORD_MODEL_COL_NAME, + DH_KEYWORD_MODEL_COL_LINK, + DH_KEYWORD_MODEL_NUM_COLS +}; + +GType dh_keyword_model_get_type (void); +DhKeywordModel *dh_keyword_model_new (void); +void dh_keyword_model_set_words (DhKeywordModel *model, + DhBookManager *book_manager); +DhLink * dh_keyword_model_filter (DhKeywordModel *model, + const gchar *string, + const gchar *book_id); + +G_END_DECLS + +#endif /* __DH_KEYWORD_MODEL_H__ */ diff --git a/plugins/help/devhelp/dh-link.c b/plugins/help/devhelp/dh-link.c new file mode 100644 index 0000000..a93b052 --- /dev/null +++ b/plugins/help/devhelp/dh-link.c @@ -0,0 +1,291 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001-2002 Mikael Hallendal + * Copyright (C) 2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include +#include +#include "dh-link.h" + +struct _DhLink { + /* FIXME: Those two could exist only for book to save some + * memory. + */ + gchar *id; + gchar *base; + + gchar *name; + gchar *filename; + + DhLink *book; + DhLink *page; + + guint ref_count; + + DhLinkType type : 8; + DhLinkFlags flags : 8; +}; + +GType +dh_link_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) { + type = g_boxed_type_register_static ( + "DhLink", + (GBoxedCopyFunc) dh_link_ref, + (GBoxedFreeFunc) dh_link_unref); + } + return type; +} + +static void +link_free (DhLink *link) +{ + g_free (link->base); + g_free (link->id); + g_free (link->name); + g_free (link->filename); + + if (link->book) { + dh_link_unref (link->book); + } + if (link->page) { + dh_link_unref (link->page); + } + + g_slice_free (DhLink, link); +} + +DhLink * +dh_link_new (DhLinkType type, + const gchar *base, + const gchar *id, + const gchar *name, + DhLink *book, + DhLink *page, + const gchar *filename) +{ + DhLink *link; + + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (filename != NULL, NULL); + + if (type == DH_LINK_TYPE_BOOK) { + g_return_val_if_fail (base != NULL, NULL); + g_return_val_if_fail (id != NULL, NULL); + } + if (type != DH_LINK_TYPE_BOOK && type != DH_LINK_TYPE_PAGE) { + g_return_val_if_fail (book != NULL, NULL); + g_return_val_if_fail (page != NULL, NULL); + } + + link = g_slice_new0 (DhLink); + + link->ref_count = 1; + link->type = type; + + if (type == DH_LINK_TYPE_BOOK) { + link->base = g_strdup (base); + link->id = g_strdup (id); + } + + link->name = g_strdup (name); + link->filename = g_strdup (filename); + + if (book) { + link->book = dh_link_ref (book); + } + if (page) { + link->page = dh_link_ref (page); + } + + return link; +} + +gint +dh_link_compare (gconstpointer a, + gconstpointer b) +{ + DhLink *la = (DhLink *) a; + DhLink *lb = (DhLink *) b; + gint flags_diff; + + /* Sort deprecated hits last. */ + flags_diff = (la->flags & DH_LINK_FLAGS_DEPRECATED) - + (lb->flags & DH_LINK_FLAGS_DEPRECATED); + if (flags_diff != 0) { + return flags_diff; + } + + return strcmp (la->name, lb->name); +} + +DhLink * +dh_link_ref (DhLink *link) +{ + g_return_val_if_fail (link != NULL, NULL); + + link->ref_count++; + + return link; +} + +void +dh_link_unref (DhLink *link) +{ + g_return_if_fail (link != NULL); + + link->ref_count--; + + if (link->ref_count == 0) { + link_free (link); + } +} + +const gchar * +dh_link_get_name (DhLink *link) +{ + return link->name; +} + +const gchar * +dh_link_get_book_name (DhLink *link) +{ + if (link->book) { + return link->book->name; + } + + return ""; +} + +const gchar * +dh_link_get_page_name (DhLink *link) +{ + if (link->page) { + return link->page->name; + } + + return ""; +} + +const gchar * +dh_link_get_file_name (DhLink *link) +{ + if (link->page) { + return link->filename; + } + + return ""; +} + +const gchar * +dh_link_get_book_id (DhLink *link) +{ + if (link->type == DH_LINK_TYPE_BOOK) { + return link->id; + } + + if (link->book) { + return link->book->id; + } + + return ""; +} + +gchar * +dh_link_get_uri (DhLink *link) +{ + gchar *base, *uri; + + if (link->type == DH_LINK_TYPE_BOOK) + base = link->base; + else + base = link->book->base; + + uri = g_strconcat ("file://", base, "/", link->filename, NULL, NULL); + + return uri; +} + +DhLinkType +dh_link_get_link_type (DhLink *link) +{ + return link->type; +} + +DhLinkFlags +dh_link_get_flags (DhLink *link) +{ + return link->flags; +} + +void +dh_link_set_flags (DhLink *link, + DhLinkFlags flags) +{ + link->flags = flags; +} + +const gchar * +dh_link_get_type_as_string (DhLink *link) +{ + switch (link->type) { + case DH_LINK_TYPE_BOOK: + /* i18n: a documentation book */ + return _("Book"); + case DH_LINK_TYPE_PAGE: + /* i18n: a "page" in a documentation book */ + return _("Page"); + case DH_LINK_TYPE_KEYWORD: + /* i18n: a search hit in the documentation, could be a + * function, macro, struct, etc */ + return _("Keyword"); + case DH_LINK_TYPE_FUNCTION: + /* i18n: in the programming language context, if you don't + * have an ESTABLISHED term for it, leave it + * untranslated. */ + return _("Function"); + case DH_LINK_TYPE_STRUCT: + /* i18n: in the programming language context, if you don't + * have an ESTABLISHED term for it, leave it + * untranslated. */ + return _("Struct"); + case DH_LINK_TYPE_MACRO: + /* i18n: in the programming language context, if you don't + * have an ESTABLISHED term for it, leave it + * untranslated. */ + return _("Macro"); + case DH_LINK_TYPE_ENUM: + /* i18n: in the programming language context, if you don't + * have an ESTABLISHED term for it, leave it + * untranslated. */ + return _("Enum"); + case DH_LINK_TYPE_TYPEDEF: + /* i18n: in the programming language context, if you don't + * have an ESTABLISHED term for it, leave it + * untranslated. */ + return _("Type"); + } + + return ""; +} diff --git a/plugins/help/devhelp/dh-link.h b/plugins/help/devhelp/dh-link.h new file mode 100644 index 0000000..ff47b94 --- /dev/null +++ b/plugins/help/devhelp/dh-link.h @@ -0,0 +1,72 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 Mikael Hallendal + * Copyright (C) 2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DH_LINK_H__ +#define __DH_LINK_H__ + +#include + +typedef enum { + DH_LINK_TYPE_BOOK, + DH_LINK_TYPE_PAGE, + DH_LINK_TYPE_KEYWORD, + DH_LINK_TYPE_FUNCTION, + DH_LINK_TYPE_STRUCT, + DH_LINK_TYPE_MACRO, + DH_LINK_TYPE_ENUM, + DH_LINK_TYPE_TYPEDEF +} DhLinkType; + +typedef enum { + DH_LINK_FLAGS_NONE = 0, + DH_LINK_FLAGS_DEPRECATED = 1 << 0 +} DhLinkFlags; + +typedef struct _DhLink DhLink; + +#define DH_TYPE_LINK (dh_link_get_type ()) + +GType dh_link_get_type (void); +DhLink * dh_link_new (DhLinkType type, + const gchar *base, + const gchar *id, + const gchar *name, + DhLink *book, + DhLink *page, + const gchar *filename); +void dh_link_free (DhLink *link); +gint dh_link_compare (gconstpointer a, + gconstpointer b); +DhLink * dh_link_ref (DhLink *link); +void dh_link_unref (DhLink *link); +const gchar *dh_link_get_name (DhLink *link); +const gchar *dh_link_get_book_name (DhLink *link); +const gchar *dh_link_get_page_name (DhLink *link); +const gchar *dh_link_get_file_name (DhLink *link); +const gchar *dh_link_get_book_id (DhLink *link); +gchar *dh_link_get_uri (DhLink *link); +DhLinkFlags dh_link_get_flags (DhLink *link); +void dh_link_set_flags (DhLink *link, + DhLinkFlags flags); +DhLinkType dh_link_get_link_type (DhLink *link); +const gchar *dh_link_get_type_as_string (DhLink *link); + +#endif /* __DH_LINK_H__ */ diff --git a/plugins/help/devhelp/dh-marshal.list b/plugins/help/devhelp/dh-marshal.list new file mode 100644 index 0000000..2023eaf --- /dev/null +++ b/plugins/help/devhelp/dh-marshal.list @@ -0,0 +1,6 @@ +VOID:BOOLEAN +VOID:POINTER +VOID:STRING +VOID:VOID +BOOLEAN:STRING +VOID:STRING,FLAGS diff --git a/plugins/help/devhelp/dh-parser.c b/plugins/help/devhelp/dh-parser.c new file mode 100644 index 0000000..456d275 --- /dev/null +++ b/plugins/help/devhelp/dh-parser.c @@ -0,0 +1,611 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (c) 2002-2003 Mikael Hallendal + * Copyright (c) 2002-2003 CodeFactory AB + * Copyright (C) 2005,2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include +#include +#include + +#include "dh-error.h" +#include "dh-link.h" +#include "dh-parser.h" + +#define NAMESPACE "http://www.devhelp.net/book" +#define BYTES_PER_READ 4096 + +typedef struct { + GMarkupParser *m_parser; + GMarkupParseContext *context; + + const gchar *path; + + /* Top node of book */ + GNode *book_node; + + /* Current sub section node */ + GNode *parent; + + gboolean parsing_chapters; + gboolean parsing_keywords; + + GNode **book_tree; + GList **keywords; + + /* Version 2 uses instead of . */ + gint version; +} DhParser; + +static void +dh_parser_free (DhParser *parser) +{ + // NOTE: priv->book_tree and priv->keywords do not need to be freed + // because they're only used to store the locations for the return + // params of dh_parser_read_file() + + g_markup_parse_context_free (parser->context); + g_free (parser->m_parser); + g_free (parser); +} + +static void +parser_start_node_book (DhParser *parser, + GMarkupParseContext *context, + const gchar *node_name, + const gchar **attribute_names, + const gchar **attribute_values, + GError **error) +{ + gint i, j; + gint line, col; + gchar *title = NULL; + gchar *base = NULL; + const gchar *name = NULL; + const gchar *uri = NULL; + DhLink *link; + + if (g_ascii_strcasecmp (node_name, "book") != 0) { + g_markup_parse_context_get_position (context, &line, &col); + g_set_error (error, + DH_ERROR, + DH_ERROR_MALFORMED_BOOK, + _("Expected '%s', got '%s' at line %d, column %d"), + "book", node_name, line, col); + return; + } + + for (i = 0; attribute_names[i]; ++i) { + const gchar *xmlns; + + if (g_ascii_strcasecmp (attribute_names[i], "xmlns") == 0) { + xmlns = attribute_values[i]; + if (g_ascii_strcasecmp (xmlns, NAMESPACE) != 0) { + g_markup_parse_context_get_position (context, + &line, + &col); + g_set_error (error, + DH_ERROR, + DH_ERROR_MALFORMED_BOOK, + _("Invalid namespace '%s' at" + " line %d, column %d"), + xmlns, line, col); + return; + } + } + else if (g_ascii_strcasecmp (attribute_names[i], "name") == 0) { + name = attribute_values[i]; + } + else if (g_ascii_strcasecmp (attribute_names[i], "title") == 0) { + title = g_strdup(attribute_values[i]); + for (j = 0; title[j]; j++) { + if (title[j] == '\n') title[j] = ' '; + } + } + else if (g_ascii_strcasecmp (attribute_names[i], "base") == 0) { + base = g_strdup (attribute_values[i]); + } + else if (g_ascii_strcasecmp (attribute_names[i], "link") == 0) { + uri = attribute_values[i]; + } + } + + if (!title || !name || !uri) { + g_markup_parse_context_get_position (context, &line, &col); + g_set_error (error, + DH_ERROR, + DH_ERROR_MALFORMED_BOOK, + _("\"title\", \"name\" and \"link\" elements are " + "required at line %d, column %d"), + line, col); + g_free (title); + return; + } + + if (!base) { + base = g_path_get_dirname (parser->path); + } + + link = dh_link_new (DH_LINK_TYPE_BOOK, + base, + name, + title, + NULL, + NULL, + uri); + g_free (base); + + *parser->keywords = g_list_prepend (*parser->keywords, dh_link_ref (link)); + + parser->book_node = g_node_new (dh_link_ref (link)); + *parser->book_tree = parser->book_node; + parser->parent = parser->book_node; + g_free (title); + dh_link_unref (link); +} + +static void +parser_start_node_chapter (DhParser *parser, + GMarkupParseContext *context, + const gchar *node_name, + const gchar **attribute_names, + const gchar **attribute_values, + GError **error) +{ + gint i; + gint line, col; + const gchar *name = NULL; + const gchar *uri = NULL; + DhLink *link; + GNode *node; + + if (g_ascii_strcasecmp (node_name, "sub") != 0) { + g_markup_parse_context_get_position (context, &line, &col); + g_set_error (error, + DH_ERROR, + DH_ERROR_MALFORMED_BOOK, + _("Expected '%s', got '%s' at line %d, column %d"), + "sub", node_name, line, col); + return; + } + + for (i = 0; attribute_names[i]; ++i) { + if (g_ascii_strcasecmp (attribute_names[i], "name") == 0) { + name = attribute_values[i]; + } + else if (g_ascii_strcasecmp (attribute_names[i], "link") == 0) { + uri = attribute_values[i]; + } + } + + if (!name || !uri) { + g_markup_parse_context_get_position (context, &line, &col); + g_set_error (error, + DH_ERROR, + DH_ERROR_MALFORMED_BOOK, + _("\"name\" and \"link\" elements are required " + "inside on line %d, column %d"), + line, col); + return; + } + + link = dh_link_new (DH_LINK_TYPE_PAGE, + NULL, + NULL, + name, + parser->book_node->data, + NULL, + uri); + + *parser->keywords = g_list_prepend (*parser->keywords, link); + + node = g_node_new (link); + g_node_prepend (parser->parent, node); + parser->parent = node; +} + +static void +parser_start_node_keyword (DhParser *parser, + GMarkupParseContext *context, + const gchar *node_name, + const gchar **attribute_names, + const gchar **attribute_values, + GError **error) +{ + gint i; + gint line, col; + const gchar *name = NULL; + const gchar *uri = NULL; + const gchar *type = NULL; + const gchar *deprecated = NULL; + DhLinkType link_type; + DhLink *link; + gchar *tmp; + + if (parser->version == 2 && + g_ascii_strcasecmp (node_name, "keyword") != 0) { + g_markup_parse_context_get_position (context, &line, &col); + g_set_error (error, + DH_ERROR, + DH_ERROR_MALFORMED_BOOK, + _("Expected '%s', got '%s' at line %d, column %d"), + "keyword", node_name, line, col); + return; + } + else if (parser->version == 1 && + g_ascii_strcasecmp (node_name, "function") != 0) { + g_markup_parse_context_get_position (context, &line, &col); + g_set_error (error, + DH_ERROR, + DH_ERROR_MALFORMED_BOOK, + _("Expected '%s', got '%s' at line %d, column %d"), + "function", node_name, line, col); + return; + } + + for (i = 0; attribute_names[i]; ++i) { + if (g_ascii_strcasecmp (attribute_names[i], "type") == 0) { + type = attribute_values[i]; + } + else if (g_ascii_strcasecmp (attribute_names[i], "name") == 0) { + name = attribute_values[i]; + } + else if (g_ascii_strcasecmp (attribute_names[i], "link") == 0) { + uri = attribute_values[i]; + } + else if (g_ascii_strcasecmp (attribute_names[i], "deprecated") == 0) { + deprecated = attribute_values[i]; + } + } + + if (!name || !uri) { + g_markup_parse_context_get_position (context, &line, &col); + g_set_error (error, + DH_ERROR, + DH_ERROR_MALFORMED_BOOK, + _("\"name\" and \"link\" elements are required " + "inside '%s' on line %d, column %d"), + parser->version == 2 ? "keyword" : "function", + line, col); + return; + } + + if (parser->version == 2 && !type) { + /* Required */ + g_markup_parse_context_get_position (context, &line, &col); + g_set_error (error, + DH_ERROR, + DH_ERROR_MALFORMED_BOOK, + _("\"type\" element is required " + "inside on line %d, column %d"), + line, col); + return; + } + + if (parser->version == 2) { + if (strcmp (type, "function") == 0) { + link_type = DH_LINK_TYPE_FUNCTION; + } + else if (strcmp (type, "struct") == 0) { + link_type = DH_LINK_TYPE_STRUCT; + } + else if (strcmp (type, "macro") == 0) { + link_type = DH_LINK_TYPE_MACRO; + } + else if (strcmp (type, "enum") == 0) { + link_type = DH_LINK_TYPE_ENUM; + } + else if (strcmp (type, "typedef") == 0) { + link_type = DH_LINK_TYPE_TYPEDEF; + } else { + link_type = DH_LINK_TYPE_KEYWORD; + } + } else { + link_type = DH_LINK_TYPE_KEYWORD; + } + + /* Strip out trailing " () or "()". */ + if (g_str_has_suffix (name, " ()")) { + tmp = g_strndup (name, strlen (name) - 3); + + if (link_type == DH_LINK_TYPE_KEYWORD) { + link_type = DH_LINK_TYPE_FUNCTION; + } + name = tmp; + } + else if (g_str_has_suffix (name, "()")) { + tmp = g_strndup (name, strlen (name) - 2); + + /* With old devhelp format, take a guess that this is a + * macro. + */ + if (link_type == DH_LINK_TYPE_KEYWORD) { + link_type = DH_LINK_TYPE_MACRO; + } + name = tmp; + } else { + tmp = NULL; + } + + /* Strip out prefixing "struct", "union", "enum", to make searching + * easier. Also fix up the link type (only applies for old devhelp + * format). + */ + if (g_str_has_prefix (name, "struct ")) { + name = name + 7; + if (link_type == DH_LINK_TYPE_KEYWORD) { + link_type = DH_LINK_TYPE_STRUCT; + } + } + else if (g_str_has_prefix (name, "union ")) { + name = name + 6; + if (link_type == DH_LINK_TYPE_KEYWORD) { + link_type = DH_LINK_TYPE_STRUCT; + } + } + else if (g_str_has_prefix (name, "enum ")) { + name = name + 5; + if (link_type == DH_LINK_TYPE_KEYWORD) { + link_type = DH_LINK_TYPE_ENUM; + } + } + + link = dh_link_new (link_type, + NULL, + NULL, + name, + parser->book_node->data, + parser->parent->data, + uri); + + g_free (tmp); + + if (deprecated) { + dh_link_set_flags ( + link, + dh_link_get_flags (link) | DH_LINK_FLAGS_DEPRECATED); + } + + *parser->keywords = g_list_prepend (*parser->keywords, link); +} + +static void +parser_start_node_cb (GMarkupParseContext *context, + const gchar *node_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + DhParser *parser = user_data; + + if (parser->parsing_keywords) { + parser_start_node_keyword (parser, + context, + node_name, + attribute_names, + attribute_values, + error); + return; + } + else if (parser->parsing_chapters) { + parser_start_node_chapter (parser, + context, + node_name, + attribute_names, + attribute_values, + error); + return; + } + else if (g_ascii_strcasecmp (node_name, "functions") == 0) { + parser->parsing_keywords = TRUE; + } + else if (g_ascii_strcasecmp (node_name, "chapters") == 0) { + parser->parsing_chapters = TRUE; + } + if (!parser->book_node) { + parser_start_node_book (parser, + context, + node_name, + attribute_names, + attribute_values, + error); + return; + } +} + +static void +parser_end_node_cb (GMarkupParseContext *context, + const gchar *node_name, + gpointer user_data, + GError **error) +{ + DhParser *parser = user_data; + + if (parser->parsing_keywords) { + if (g_ascii_strcasecmp (node_name, "functions") == 0) { + parser->parsing_keywords = FALSE; + } + } + else if (parser->parsing_chapters) { + g_node_reverse_children (parser->parent); + if (g_ascii_strcasecmp (node_name, "sub") == 0) { + parser->parent = parser->parent->parent; + /* Move up in the tree */ + } + else if (g_ascii_strcasecmp (node_name, "chapters") == 0) { + parser->parsing_chapters = FALSE; + } + } +} + +static void +parser_error_cb (GMarkupParseContext *context, + GError *error, + gpointer user_data) +{ + DhParser *parser = user_data; + + g_markup_parse_context_free (parser->context); + parser->context = NULL; +} + +static gboolean +parser_read_gz_file (DhParser *parser, + const gchar *path, + GError **error) +{ + gchar buf[BYTES_PER_READ]; + gzFile file; + + file = gzopen (path, "r"); + if (!file) { + g_set_error (error, + DH_ERROR, + DH_ERROR_FILE_NOT_FOUND, + "%s", g_strerror (errno)); + return FALSE; + } + + while (TRUE) { + gssize bytes_read; + + bytes_read = gzread (file, buf, BYTES_PER_READ); + if (bytes_read == -1) { + gint err; + const gchar *message; + + message = gzerror (file, &err); + g_set_error (error, + DH_ERROR, + DH_ERROR_INTERNAL_ERROR, + _("Cannot uncompress book '%s': %s"), + path, message); + return FALSE; + } + + g_markup_parse_context_parse (parser->context, buf, + bytes_read, error); + if (error != NULL && *error != NULL) { + return FALSE; + } + if (bytes_read < BYTES_PER_READ) { + break; + } + } + + gzclose (file); + + return TRUE; +} + +gboolean +dh_parser_read_file (const gchar *path, + GNode **book_tree, + GList **keywords, + GError **error) +{ + DhParser *parser; + gboolean gz; + GIOChannel *io = NULL; + gchar buf[BYTES_PER_READ]; + gboolean result = TRUE; + + parser = g_new0 (DhParser, 1); + + if (g_str_has_suffix (path, ".devhelp2")) { + parser->version = 2; + gz = FALSE; + } + else if (g_str_has_suffix (path, ".devhelp")) { + parser->version = 1; + gz = FALSE; + } + else if (g_str_has_suffix (path, ".devhelp2.gz")) { + parser->version = 2; + gz = TRUE; + } else { + parser->version = 1; + gz = TRUE; + } + + parser->m_parser = g_new0 (GMarkupParser, 1); + + parser->m_parser->start_element = parser_start_node_cb; + parser->m_parser->end_element = parser_end_node_cb; + parser->m_parser->error = parser_error_cb; + + parser->context = g_markup_parse_context_new (parser->m_parser, 0, + parser, NULL); + + parser->path = path; + parser->book_tree = book_tree; + parser->keywords = keywords; + + if (gz) { + if (!parser_read_gz_file (parser, + path, + error)) { + result = FALSE; + } + goto exit; + } else { + io = g_io_channel_new_file (path, "r", error); + if (!io) { + result = FALSE; + goto exit; + } + + while (TRUE) { + GIOStatus io_status; + gsize bytes_read; + + io_status = g_io_channel_read_chars (io, buf, BYTES_PER_READ, + &bytes_read, error); + if (io_status == G_IO_STATUS_ERROR) { + result = FALSE; + goto exit; + } + if (io_status != G_IO_STATUS_NORMAL) { + break; + } + + g_markup_parse_context_parse (parser->context, buf, + bytes_read, error); + if (error != NULL && *error != NULL) { + result = FALSE; + goto exit; + } + + if (bytes_read < BYTES_PER_READ) { + break; + } + } + } + + exit: + if (io) { + g_io_channel_unref (io); + } + dh_parser_free (parser); + + return result; +} diff --git a/plugins/help/devhelp/dh-parser.h b/plugins/help/devhelp/dh-parser.h new file mode 100644 index 0000000..9de1409 --- /dev/null +++ b/plugins/help/devhelp/dh-parser.h @@ -0,0 +1,36 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2003 CodeFactory AB + * Copyright (C) 2003 Mikael Hallendal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DH_PARSER_H__ +#define __DH_PARSER_H__ + +#include + +G_BEGIN_DECLS + +gboolean dh_parser_read_file (const gchar *path, + GNode **book_tree, + GList **keywords, + GError **error); + +G_END_DECLS + +#endif /* __DH_PARSER_H__ */ diff --git a/plugins/help/devhelp/dh-preferences.c b/plugins/help/devhelp/dh-preferences.c new file mode 100644 index 0000000..4d50a6d --- /dev/null +++ b/plugins/help/devhelp/dh-preferences.c @@ -0,0 +1,417 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2004-2008 Imendio AB + * Copyright (C) 2010 Lanedo GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include +#include "dh-util.h" +#include "dh-preferences.h" +#include "ige-conf.h" +#include "dh-base.h" + +typedef struct { + GtkWidget *dialog; + + /* Fonts tab */ + GtkWidget *system_fonts_button; + GtkWidget *fonts_table; + GtkWidget *variable_font_button; + GtkWidget *fixed_font_button; + guint use_system_fonts_id; + guint system_var_id; + guint system_fixed_id; + guint var_id; + guint fixed_id; + + /* Book Shelf tab */ + DhBookManager *book_manager; + GtkTreeView *booklist_treeview; + GtkListStore *booklist_store; +} DhPreferences; + +/* Fonts-tab related */ +static void preferences_fonts_font_set_cb (GtkFontButton *button, + gpointer user_data); +static void preferences_fonts_system_fonts_toggled_cb (GtkToggleButton *button, + gpointer user_data); +#if 0 +static void preferences_fonts_var_font_notify_cb (IgeConf *client, + const gchar *path, + gpointer user_data); +static void preferences_fonts_fixed_font_notify_cb (IgeConf *client, + const gchar *path, + gpointer user_data); +static void preferences_fonts_use_system_font_notify_cb (IgeConf *client, + const gchar *path, + gpointer user_data); +static void preferences_connect_conf_listeners (void); +#endif +static void preferences_fonts_get_font_names (gboolean use_system_fonts, + gchar **variable, + gchar **fixed); + +/* Bookshelf-tab related */ +static void preferences_bookshelf_tree_selection_toggled_cb (GtkCellRendererToggle *cell_renderer, + gchar *path, + gpointer user_data); +static void preferences_bookshelf_populate_store (void); + +/* Common */ +static void preferences_close_cb (GtkButton *button, + gpointer user_data); + +#define DH_CONF_PATH "/apps/devhelp" +#define DH_CONF_USE_SYSTEM_FONTS DH_CONF_PATH "/ui/use_system_fonts" +#define DH_CONF_VARIABLE_FONT DH_CONF_PATH "/ui/variable_font" +#define DH_CONF_FIXED_FONT DH_CONF_PATH "/ui/fixed_font" +#define DH_CONF_SYSTEM_VARIABLE_FONT "/desktop/gnome/interface/font_name" +#define DH_CONF_SYSTEM_FIXED_FONT "/desktop/gnome/interface/monospace_font_name" + +/* Book list store columns... */ +#define LTCOLUMN_ENABLED 0 +#define LTCOLUMN_TITLE 1 +#define LTCOLUMN_BOOK 2 + +static DhPreferences *prefs; + +static void +preferences_init (void) +{ + if (!prefs) { + prefs = g_new0 (DhPreferences, 1); + prefs->book_manager = dh_base_get_book_manager (dh_base_get ()); + } +} + +static void +preferences_close_cb (GtkButton *button, gpointer user_data) +{ + DhPreferences *prefs = user_data; + + gtk_widget_destroy (GTK_WIDGET (prefs->dialog)); + prefs->dialog = NULL; + + prefs->system_fonts_button = NULL; + prefs->fonts_table = NULL; + prefs->variable_font_button = NULL; + prefs->fixed_font_button = NULL; + + prefs->booklist_treeview = NULL; + prefs->booklist_store = NULL; +} + +static void +preferences_fonts_font_set_cb (GtkFontButton *button, + gpointer user_data) +{ + DhPreferences *prefs = user_data; + const gchar *font_name; + const gchar *key; + + font_name = gtk_font_button_get_font_name (button); + + if (GTK_WIDGET (button) == prefs->variable_font_button) { + key = DH_CONF_VARIABLE_FONT; + } else { + key = DH_CONF_FIXED_FONT; + } + + ige_conf_set_string (ige_conf_get (), key, font_name); +} + +static void +preferences_fonts_system_fonts_toggled_cb (GtkToggleButton *button, + gpointer user_data) +{ + DhPreferences *prefs = user_data; + gboolean active; + + active = gtk_toggle_button_get_active (button); + + ige_conf_set_bool (ige_conf_get (), + DH_CONF_USE_SYSTEM_FONTS, + active); + + gtk_widget_set_sensitive (prefs->fonts_table, !active); +} + +#if 0 +static void +preferences_fonts_var_font_notify_cb (IgeConf *client, + const gchar *path, + gpointer user_data) +{ + DhPreferences *prefs = user_data; + gboolean use_system_fonts; + gchar *font_name; + + ige_conf_get_bool (ige_conf_get (), + DH_CONF_USE_SYSTEM_FONTS, + &use_system_fonts); + + if (prefs->variable_font_button) { + ige_conf_get_string (ige_conf_get (), path, &font_name); + gtk_font_button_set_font_name (GTK_FONT_BUTTON (prefs->variable_font_button), + font_name); + g_free (font_name); + } +} + +static void +preferences_fonts_fixed_font_notify_cb (IgeConf *client, + const gchar *path, + gpointer user_data) +{ + DhPreferences *prefs = user_data; + gboolean use_system_fonts; + gchar *font_name; + + ige_conf_get_bool (ige_conf_get (), + DH_CONF_USE_SYSTEM_FONTS, + &use_system_fonts); + + if (prefs->fixed_font_button) { + ige_conf_get_string (ige_conf_get (), path, &font_name); + gtk_font_button_set_font_name (GTK_FONT_BUTTON (prefs->fixed_font_button), + font_name); + g_free (font_name); + } +} + +static void +preferences_fonts_use_system_font_notify_cb (IgeConf *client, + const gchar *path, + gpointer user_data) +{ + DhPreferences *prefs = user_data; + gboolean use_system_fonts; + + ige_conf_get_bool (ige_conf_get (), path, &use_system_fonts); + + if (prefs->system_fonts_button) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (prefs->system_fonts_button), + use_system_fonts); + } + + if (prefs->fonts_table) { + gtk_widget_set_sensitive (prefs->fonts_table, !use_system_fonts); + } +} + +/* FIXME: This is not hooked up yet (to update the dialog if the values are + * changed outside of devhelp). + */ +static void +preferences_connect_conf_listeners (void) +{ + IgeConf *conf; + + conf = ige_conf_get (); + + prefs->use_system_fonts_id = + ige_conf_notify_add (conf, + DH_CONF_USE_SYSTEM_FONTS, + preferences_use_system_font_notify_cb, + prefs); + prefs->system_var_id = + ige_conf_notify_add (conf, + DH_CONF_SYSTEM_VARIABLE_FONT, + preferences_var_font_notify_cb, + prefs); + prefs->system_fixed_id = + ige_conf_notify_add (conf, + DH_CONF_SYSTEM_FIXED_FONT, + preferences_fixed_font_notify_cb, + prefs); + prefs->var_id = + ige_conf_notify_add (conf, + DH_CONF_VARIABLE_FONT, + preferences_var_font_notify_cb, + prefs); + prefs->fixed_id = + ige_conf_notify_add (conf, + DH_CONF_FIXED_FONT, + preferences_fixed_font_notify_cb, + prefs); +} +#endif + +/* FIXME: Use the functions in dh-util.c for this. */ +static void +preferences_fonts_get_font_names (gboolean use_system_fonts, + gchar **variable, + gchar **fixed) +{ + gchar *var_font_name, *fixed_font_name; + IgeConf *conf; + + conf = ige_conf_get (); + + if (use_system_fonts) { +#ifdef GDK_WINDOWING_QUARTZ + var_font_name = g_strdup ("Lucida Grande 14"); + fixed_font_name = g_strdup ("Monaco 14"); +#else + ige_conf_get_string (conf, + DH_CONF_SYSTEM_VARIABLE_FONT, + &var_font_name); + ige_conf_get_string (conf, + DH_CONF_SYSTEM_FIXED_FONT, + &fixed_font_name); +#endif + } else { + ige_conf_get_string (conf, + DH_CONF_VARIABLE_FONT, + &var_font_name); + ige_conf_get_string (conf, + DH_CONF_FIXED_FONT, + &fixed_font_name); + } + + *variable = var_font_name; + *fixed = fixed_font_name; +} + +static void +preferences_bookshelf_tree_selection_toggled_cb (GtkCellRendererToggle *cell_renderer, + gchar *path, + gpointer user_data) +{ + GtkTreeIter iter; + + if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (prefs->booklist_store), + &iter, + path)) + { + gpointer book = NULL; + gboolean enabled; + + gtk_tree_model_get (GTK_TREE_MODEL (prefs->booklist_store), + &iter, + LTCOLUMN_BOOK, &book, + LTCOLUMN_ENABLED, &enabled, + -1); + + if (book) { + /* Update book conf */ + dh_book_set_enabled (book, !enabled); + + gtk_list_store_set (prefs->booklist_store, &iter, + LTCOLUMN_ENABLED, !enabled, + -1); + + dh_book_manager_update (prefs->book_manager); + } + } +} + +static void +preferences_bookshelf_populate_store (void) +{ + GList *l; + + for (l = dh_book_manager_get_books (prefs->book_manager); + l; + l = g_list_next (l)) { + GtkTreeIter iter; + DhBook *book; + + book = DH_BOOK (l->data); + + gtk_list_store_append (prefs->booklist_store, &iter); + gtk_list_store_set (prefs->booklist_store, &iter, + LTCOLUMN_ENABLED, dh_book_get_enabled (book), + LTCOLUMN_TITLE, dh_book_get_title (book), + LTCOLUMN_BOOK, book, + -1); + } +} + +void +dh_preferences_show_dialog (GtkWindow *parent) +{ + gchar *path; + GtkBuilder *builder; + gboolean use_system_fonts; + gchar *var_font_name, *fixed_font_name; + + preferences_init (); + + if (prefs->dialog != NULL) { + gtk_window_present (GTK_WINDOW (prefs->dialog)); + return; + } + + path = dh_util_build_data_filename ("devhelp", "ui", + "devhelp.builder", + NULL); + builder = dh_util_builder_get_file ( + path, + "preferences_dialog", + NULL, + "preferences_dialog", &prefs->dialog, + "fonts_table", &prefs->fonts_table, + "system_fonts_button", &prefs->system_fonts_button, + "variable_font_button", &prefs->variable_font_button, + "fixed_font_button", &prefs->fixed_font_button, + "book_manager_store", &prefs->booklist_store, + "book_manager_treeview", &prefs->booklist_treeview, + NULL); + g_free (path); + + dh_util_builder_connect ( + builder, + prefs, + "variable_font_button", "font_set", preferences_fonts_font_set_cb, + "fixed_font_button", "font_set", preferences_fonts_font_set_cb, + "system_fonts_button", "toggled", preferences_fonts_system_fonts_toggled_cb, + "book_manager_toggle", "toggled", preferences_bookshelf_tree_selection_toggled_cb, + "preferences_close_button", "clicked", preferences_close_cb, + NULL); + + ige_conf_get_bool (ige_conf_get (), + DH_CONF_USE_SYSTEM_FONTS, + &use_system_fonts); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (prefs->system_fonts_button), + use_system_fonts); + gtk_widget_set_sensitive (prefs->fonts_table, !use_system_fonts); + + preferences_fonts_get_font_names (FALSE, &var_font_name, &fixed_font_name); + + if (var_font_name) { + gtk_font_button_set_font_name (GTK_FONT_BUTTON (prefs->variable_font_button), + var_font_name); + g_free (var_font_name); + } + + if (fixed_font_name) { + gtk_font_button_set_font_name (GTK_FONT_BUTTON (prefs->fixed_font_button), + fixed_font_name); + g_free (fixed_font_name); + } + + preferences_bookshelf_populate_store (); + + g_object_unref (builder); + + gtk_window_set_transient_for (GTK_WINDOW (prefs->dialog), parent); + gtk_widget_show_all (prefs->dialog); +} diff --git a/plugins/help/devhelp/dh-preferences.h b/plugins/help/devhelp/dh-preferences.h new file mode 100644 index 0000000..f7f13d4 --- /dev/null +++ b/plugins/help/devhelp/dh-preferences.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2004-2008 Imendio AB + * Copyright (C) 2010 Lanedo GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DH_PREFERENCES_H__ +#define __DH_PREFERENCES_H__ + +#include + +G_BEGIN_DECLS + +void dh_preferences_show_dialog (GtkWindow *parent); + +G_END_DECLS + +#endif /* __DH_PREFERENCES_H__ */ + diff --git a/plugins/help/devhelp/dh-search.c b/plugins/help/devhelp/dh-search.c new file mode 100644 index 0000000..f554f69 --- /dev/null +++ b/plugins/help/devhelp/dh-search.c @@ -0,0 +1,725 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001-2003 CodeFactory AB + * Copyright (C) 2001-2003 Mikael Hallendal + * Copyright (C) 2005-2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include +#include +#include +#include "dh-marshal.h" +#include "dh-keyword-model.h" +#include "dh-search.h" +#include "dh-preferences.h" +#include "dh-base.h" +#include "dh-util.h" +#include "dh-book-manager.h" +#include "dh-book.h" + +typedef struct { + DhKeywordModel *model; + + DhBookManager *book_manager; + + DhLink *selected_link; + + GtkWidget *book_combo; + GtkWidget *entry; + GtkWidget *hitlist; + + GCompletion *completion; + + guint idle_complete; + guint idle_filter; +} DhSearchPriv; + +static void dh_search_init (DhSearch *search); +static void dh_search_class_init (DhSearchClass *klass); +static void search_grab_focus (GtkWidget *widget); +static void search_selection_changed_cb (GtkTreeSelection *selection, + DhSearch *content); +static gboolean search_tree_button_press_cb (GtkTreeView *view, + GdkEventButton *event, + DhSearch *search); +static gboolean search_entry_key_press_event_cb (GtkEntry *entry, + GdkEventKey *event, + DhSearch *search); +static void search_combo_changed_cb (GtkComboBox *combo, + DhSearch *search); +static void search_entry_changed_cb (GtkEntry *entry, + DhSearch *search); +static void search_entry_activated_cb (GtkEntry *entry, + DhSearch *search); +static void search_entry_text_inserted_cb (GtkEntry *entry, + const gchar *text, + gint length, + gint *position, + DhSearch *search); +static gboolean search_complete_idle (DhSearch *search); +static gboolean search_filter_idle (DhSearch *search); +static const gchar *search_complete_func (DhLink *link); + +enum { + LINK_SELECTED, + LAST_SIGNAL +}; + +G_DEFINE_TYPE (DhSearch, dh_search, GTK_TYPE_VBOX); + +#define GET_PRIVATE(instance) G_TYPE_INSTANCE_GET_PRIVATE \ + (instance, DH_TYPE_SEARCH, DhSearchPriv); + +static gint signals[LAST_SIGNAL] = { 0 }; + +static void +search_finalize (GObject *object) +{ + DhSearchPriv *priv; + + priv = GET_PRIVATE (object); + + g_completion_free (priv->completion); + g_object_unref (priv->book_manager); + + G_OBJECT_CLASS (dh_search_parent_class)->finalize (object); +} + +static void +dh_search_class_init (DhSearchClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass;; + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;; + + object_class->finalize = search_finalize; + + widget_class->grab_focus = search_grab_focus; + + signals[LINK_SELECTED] = + g_signal_new ("link_selected", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (DhSearchClass, link_selected), + NULL, NULL, + _dh_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, G_TYPE_POINTER); + + g_type_class_add_private (klass, sizeof (DhSearchPriv)); +} + +static void +dh_search_init (DhSearch *search) +{ + DhSearchPriv *priv = GET_PRIVATE (search); + + priv->completion = g_completion_new ( + (GCompletionFunc) search_complete_func); + + priv->hitlist = gtk_tree_view_new (); + priv->model = dh_keyword_model_new (); + + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->hitlist), + GTK_TREE_MODEL (priv->model)); + + gtk_tree_view_set_enable_search (GTK_TREE_VIEW (priv->hitlist), FALSE); + + gtk_box_set_spacing (GTK_BOX (search), 4); +} + +static void +search_grab_focus (GtkWidget *widget) +{ + DhSearchPriv *priv = GET_PRIVATE (widget); + + gtk_widget_grab_focus (priv->entry); +} + +static void +search_selection_changed_cb (GtkTreeSelection *selection, + DhSearch *search) +{ + DhSearchPriv *priv; + GtkTreeIter iter; + + priv = GET_PRIVATE (search); + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) { + DhLink *link; + + gtk_tree_model_get (GTK_TREE_MODEL (priv->model), &iter, + DH_KEYWORD_MODEL_COL_LINK, &link, + -1); + + if (link != priv->selected_link) { + priv->selected_link = link; + g_signal_emit (search, signals[LINK_SELECTED], 0, link); + } + } +} + +/* Make it possible to jump back to the currently selected item, useful when the + * html view has been scrolled away. + */ +static gboolean +search_tree_button_press_cb (GtkTreeView *view, + GdkEventButton *event, + DhSearch *search) +{ + GtkTreePath *path; + GtkTreeIter iter; + DhSearchPriv *priv; + DhLink *link; + + priv = GET_PRIVATE (search); + + gtk_tree_view_get_path_at_pos (view, event->x, event->y, &path, + NULL, NULL, NULL); + if (!path) { + return FALSE; + } + + gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->model), &iter, path); + gtk_tree_path_free (path); + + gtk_tree_model_get (GTK_TREE_MODEL (priv->model), + &iter, + DH_KEYWORD_MODEL_COL_LINK, &link, + -1); + + priv->selected_link = link; + + g_signal_emit (search, signals[LINK_SELECTED], 0, link); + + /* Always return FALSE so the tree view gets the event and can update + * the selection etc. + */ + return FALSE; +} + +static gboolean +search_entry_key_press_event_cb (GtkEntry *entry, + GdkEventKey *event, + DhSearch *search) +{ + DhSearchPriv *priv = GET_PRIVATE (search); + + if (event->keyval == GDK_Tab) { + if (event->state & GDK_CONTROL_MASK) { + gtk_widget_grab_focus (priv->hitlist); + } else { + gtk_editable_set_position (GTK_EDITABLE (entry), -1); + gtk_editable_select_region (GTK_EDITABLE (entry), -1, -1); + } + return TRUE; + } + + if (event->keyval == GDK_Return || + event->keyval == GDK_KP_Enter) { + GtkTreeIter iter; + DhLink *link; + gchar *name; + + /* Get the first entry found. */ + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->model), &iter)) { + gtk_tree_model_get (GTK_TREE_MODEL (priv->model), + &iter, + DH_KEYWORD_MODEL_COL_LINK, &link, + DH_KEYWORD_MODEL_COL_NAME, &name, + -1); + + gtk_entry_set_text (GTK_ENTRY (entry), name); + g_free (name); + + gtk_editable_set_position (GTK_EDITABLE (entry), -1); + gtk_editable_select_region (GTK_EDITABLE (entry), -1, -1); + + g_signal_emit (search, signals[LINK_SELECTED], 0, link); + + return TRUE; + } + } + + return FALSE; +} + +static void +search_combo_set_active_id (DhSearch *search, + const gchar *book_id) +{ + DhSearchPriv *priv = GET_PRIVATE (search); + GtkTreeIter iter; + GtkTreeModel *model; + gboolean has_next; + + g_signal_handlers_block_by_func (priv->book_combo, + search_combo_changed_cb, + search); + + if (book_id != NULL) { + model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->book_combo)); + + has_next = gtk_tree_model_get_iter_first (model, &iter); + while (has_next) { + gchar *id; + + gtk_tree_model_get (model, &iter, + 1, &id, + -1); + + if (id && strcmp (book_id, id) == 0) { + g_free (id); + + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->book_combo), + &iter); + break; + } + + g_free (id); + + has_next = gtk_tree_model_iter_next (model, &iter); + } + } else { + gtk_combo_box_set_active (GTK_COMBO_BOX (priv->book_combo), 0); + } + + g_signal_handlers_unblock_by_func (priv->book_combo, + search_combo_changed_cb, + search); +} + +static gchar * +search_combo_get_active_id (DhSearch *search) +{ + DhSearchPriv *priv = GET_PRIVATE (search); + GtkTreeIter iter; + GtkTreeModel *model; + gchar *id; + + if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (priv->book_combo), + &iter)) { + return NULL; + } + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->book_combo)); + + gtk_tree_model_get (model, &iter, + 1, &id, + -1); + + return id; +} + +static void +search_combo_changed_cb (GtkComboBox *combo, + DhSearch *search) +{ + DhSearchPriv *priv = GET_PRIVATE (search); + + if (!priv->idle_filter) { + priv->idle_filter = + g_idle_add ((GSourceFunc) search_filter_idle, search); + } +} + +static void +search_entry_changed_cb (GtkEntry *entry, + DhSearch *search) +{ + DhSearchPriv *priv = GET_PRIVATE (search); + + if (!priv->idle_filter) { + priv->idle_filter = + g_idle_add ((GSourceFunc) search_filter_idle, search); + } +} + +static void +search_entry_activated_cb (GtkEntry *entry, + DhSearch *search) +{ + DhSearchPriv *priv = GET_PRIVATE (search); + gchar *id; + const gchar *str; + + id = search_combo_get_active_id (search); + str = gtk_entry_get_text (GTK_ENTRY (priv->entry)); + dh_keyword_model_filter (priv->model, str, id); + g_free (id); +} + +static void +search_entry_text_inserted_cb (GtkEntry *entry, + const gchar *text, + gint length, + gint *position, + DhSearch *search) +{ + DhSearchPriv *priv = GET_PRIVATE (search); + + if (!priv->idle_complete) { + priv->idle_complete = + g_idle_add ((GSourceFunc) search_complete_idle, + search); + } +} + +static gboolean +search_complete_idle (DhSearch *search) +{ + DhSearchPriv *priv = GET_PRIVATE (search); + const gchar *str; + gchar *completed = NULL; + gsize length; + + str = gtk_entry_get_text (GTK_ENTRY (priv->entry)); + + g_completion_complete (priv->completion, str, &completed); + if (completed) { + length = strlen (str); + + gtk_entry_set_text (GTK_ENTRY (priv->entry), completed); + gtk_editable_set_position (GTK_EDITABLE (priv->entry), length); + gtk_editable_select_region (GTK_EDITABLE (priv->entry), + length, -1); + g_free (completed); + } + + priv->idle_complete = 0; + + return FALSE; +} + +static gboolean +search_filter_idle (DhSearch *search) +{ + DhSearchPriv *priv = GET_PRIVATE (search); + const gchar *str; + gchar *id; + DhLink *link; + + str = gtk_entry_get_text (GTK_ENTRY (priv->entry)); + id = search_combo_get_active_id (search); + link = dh_keyword_model_filter (priv->model, str, id); + g_free (id); + + priv->idle_filter = 0; + + if (link) { + g_signal_emit (search, signals[LINK_SELECTED], 0, link); + } + + return FALSE; +} + +static const gchar * +search_complete_func (DhLink *link) +{ + return dh_link_get_name (link); +} + +static void +search_cell_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gpointer data) +{ + DhLink *link; + PangoStyle style; + + gtk_tree_model_get (tree_model, iter, + DH_KEYWORD_MODEL_COL_LINK, &link, + -1); + + style = PANGO_STYLE_NORMAL; + + if (dh_link_get_flags (link) & DH_LINK_FLAGS_DEPRECATED) { + style |= PANGO_STYLE_ITALIC; + } + + g_object_set (cell, + "text", dh_link_get_name (link), + "style", style, + NULL); +} + +static gboolean +search_combo_row_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + char *label; + char *link; + gboolean result; + + gtk_tree_model_get (model, iter, 0, &label, 1, &link, -1); + + result = (link == NULL && label == NULL); + g_free (label); + g_free (link); + + return result; +} + +static void +search_combo_populate (DhSearch *search) +{ + DhSearchPriv *priv; + GtkListStore *store; + GtkTreeIter iter; + GList *l; + + priv = GET_PRIVATE (search); + + store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (priv->book_combo))); + + gtk_list_store_clear (store); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + 0, _("All books"), + 1, NULL, + -1); + + /* Add a separator */ + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + 0, NULL, + 1, NULL, + -1); + + for (l = dh_book_manager_get_books (priv->book_manager); + l; + l = g_list_next (l)) { + DhBook *book = DH_BOOK (l->data); + GNode *node; + + node = dh_book_get_tree (book); + if (node) { + DhLink *link; + + link = node->data; + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + 0, dh_link_get_name (link), + 1, dh_link_get_book_id (link), + -1); + } + } + + gtk_combo_box_set_active (GTK_COMBO_BOX (priv->book_combo), 0); +} + + +static void +search_combo_create (DhSearch *search) +{ + GtkListStore *store; + GtkCellRenderer *cell; + DhSearchPriv *priv; + + priv = GET_PRIVATE (search); + + store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING); + priv->book_combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store)); + g_object_unref (store); + + search_combo_populate (search); + + gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (priv->book_combo), + search_combo_row_separator_func, + NULL, NULL); + + cell = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->book_combo), + cell, + TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->book_combo), + cell, + "text", 0); +} + +static void +completion_add_items (DhSearch *search) +{ + DhSearchPriv *priv; + GList *l; + + priv = GET_PRIVATE (search); + + for (l = dh_book_manager_get_books (priv->book_manager); + l; + l = g_list_next (l)) { + DhBook *book = DH_BOOK (l->data); + GList *keywords; + + keywords = dh_book_get_keywords(book); + + if (keywords) { + g_completion_add_items (priv->completion, + keywords); + } + } +} + +static void +book_manager_disabled_book_list_changed_cb (DhBookManager *book_manager, + gpointer user_data) +{ + DhSearch *search = user_data; + search_combo_populate (search); +} + +GtkWidget * +dh_search_new (DhBookManager *book_manager) +{ + DhSearch *search; + DhSearchPriv *priv; + GtkTreeSelection *selection; + GtkWidget *list_sw; + GtkWidget *hbox; + GtkWidget *book_label; + GtkCellRenderer *cell; + + search = g_object_new (DH_TYPE_SEARCH, NULL); + + priv = GET_PRIVATE (search); + + priv->book_manager = g_object_ref (book_manager); + g_signal_connect (priv->book_manager, + "disabled-book-list-updated", + G_CALLBACK (book_manager_disabled_book_list_changed_cb), + search); + + gtk_container_set_border_width (GTK_CONTAINER (search), 2); + + search_combo_create (search); + g_signal_connect (priv->book_combo, "changed", + G_CALLBACK (search_combo_changed_cb), + search); + + book_label = gtk_label_new_with_mnemonic (_("Search in:")); + gtk_label_set_mnemonic_widget (GTK_LABEL (book_label), priv->book_combo); + + hbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (hbox), book_label, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), priv->book_combo, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (search), hbox, FALSE, FALSE, 0); + + /* Setup the keyword box. */ + priv->entry = gtk_entry_new (); + g_signal_connect (priv->entry, "key-press-event", + G_CALLBACK (search_entry_key_press_event_cb), + search); + + g_signal_connect (priv->hitlist, "button-press-event", + G_CALLBACK (search_tree_button_press_cb), + search); + + g_signal_connect (priv->entry, "changed", + G_CALLBACK (search_entry_changed_cb), + search); + + g_signal_connect (priv->entry, "activate", + G_CALLBACK (search_entry_activated_cb), + search); + + g_signal_connect (priv->entry, "insert-text", + G_CALLBACK (search_entry_text_inserted_cb), + search); + + gtk_box_pack_start (GTK_BOX (search), priv->entry, FALSE, FALSE, 0); + + /* Setup the hitlist */ + list_sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (list_sw), GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (list_sw), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + + gtk_tree_view_insert_column_with_data_func ( + GTK_TREE_VIEW (priv->hitlist), + -1, + NULL, + cell, + search_cell_data_func, + search, NULL); + + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->hitlist), + FALSE); + gtk_tree_view_set_search_column (GTK_TREE_VIEW (priv->hitlist), + DH_KEYWORD_MODEL_COL_NAME); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->hitlist)); + + g_signal_connect (selection, "changed", + G_CALLBACK (search_selection_changed_cb), + search); + + gtk_container_add (GTK_CONTAINER (list_sw), priv->hitlist); + + gtk_box_pack_end (GTK_BOX (search), list_sw, TRUE, TRUE, 0); + + completion_add_items (search); + dh_keyword_model_set_words (priv->model, book_manager); + + gtk_widget_show_all (GTK_WIDGET (search)); + + return GTK_WIDGET (search); +} + +void +dh_search_set_search_string (DhSearch *search, + const gchar *str, + const gchar *book_id) +{ + DhSearchPriv *priv; + + g_return_if_fail (DH_IS_SEARCH (search)); + + priv = GET_PRIVATE (search); + + g_signal_handlers_block_by_func (priv->entry, + search_entry_changed_cb, + search); + + gtk_entry_set_text (GTK_ENTRY (priv->entry), str); + + gtk_editable_set_position (GTK_EDITABLE (priv->entry), -1); + gtk_editable_select_region (GTK_EDITABLE (priv->entry), -1, -1); + + g_signal_handlers_unblock_by_func (priv->entry, + search_entry_changed_cb, + search); + + search_combo_set_active_id (search, book_id); + + if (!priv->idle_filter) { + priv->idle_filter = + g_idle_add ((GSourceFunc) search_filter_idle, search); + } +} diff --git a/plugins/help/devhelp/dh-search.h b/plugins/help/devhelp/dh-search.h new file mode 100644 index 0000000..33af9cb --- /dev/null +++ b/plugins/help/devhelp/dh-search.h @@ -0,0 +1,60 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001-2002 CodeFactory AB + * Copyright (C) 2001-2002 Mikael Hallendal + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DH_SEARCH_H__ +#define __DH_SEARCH_H__ + +#include +#include "dh-link.h" +#include "dh-book-manager.h" + +G_BEGIN_DECLS + +#define DH_TYPE_SEARCH (dh_search_get_type ()) +#define DH_SEARCH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), DH_TYPE_SEARCH, DhSearch)) +#define DH_SEARCH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), DH_TYPE_SEARCH, DhSearchClass)) +#define DH_IS_SEARCH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DH_TYPE_SEARCH)) +#define DH_IS_SEARCH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), DH_TYPE_SEARCH)) + +typedef struct _DhSearch DhSearch; +typedef struct _DhSearchClass DhSearchClass; + +struct _DhSearch { + GtkVBox parent_instance; +}; + +struct _DhSearchClass { + GtkVBoxClass parent_class; + + /* Signals */ + void (*link_selected) (DhSearch *search, + DhLink *link); +}; + +GType dh_search_get_type (void); +GtkWidget *dh_search_new (DhBookManager *book_manager); +void dh_search_set_search_string (DhSearch *search, + const gchar *str, + const gchar *book_id); + +G_END_DECLS + +#endif /* __DH_SEARCH_H__ */ diff --git a/plugins/help/devhelp/dh-util.c b/plugins/help/devhelp/dh-util.c new file mode 100644 index 0000000..fe81a84 --- /dev/null +++ b/plugins/help/devhelp/dh-util.c @@ -0,0 +1,811 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001 Mikael Hallendal + * Copyright (C) 2004,2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include +#include +#ifdef GDK_WINDOWING_QUARTZ +#include +#endif +#include "ige-conf.h" +#include "dh-util.h" + +static GList *views; + +static GtkBuilder * +get_builder_file (const gchar *filename, + const gchar *root, + const gchar *domain, + const gchar *first_required_widget, + va_list args) +{ + GtkBuilder *builder; + const char *name; + GObject **object_ptr; + + builder = gtk_builder_new (); + if (!gtk_builder_add_from_file (builder, filename, NULL)) { + g_warning ("Couldn't find necessary UI file '%s'", filename); + g_object_unref (builder); + return NULL; + } + + for (name = first_required_widget; name; name = va_arg (args, char *)) { + object_ptr = va_arg (args, void *); + *object_ptr = gtk_builder_get_object (builder, name); + + if (!*object_ptr) { + g_warning ("UI file '%s' is missing widget '%s'.", + filename, name); + continue; + } + } + + return builder; +} + +GtkBuilder * +dh_util_builder_get_file (const gchar *filename, + const gchar *root, + const gchar *domain, + const gchar *first_required_widget, + ...) +{ + va_list args; + GtkBuilder *builder; + + va_start (args, first_required_widget); + builder = get_builder_file (filename, + root, + domain, + first_required_widget, + args); + va_end (args); + + return builder; +} + +void +dh_util_builder_connect (GtkBuilder *builder, + gpointer user_data, + gchar *first_widget, + ...) +{ + va_list args; + const gchar *name; + const gchar *signal; + GObject *object; + gpointer *callback; + + va_start (args, first_widget); + + for (name = first_widget; name; name = va_arg (args, char *)) { + signal = va_arg (args, void *); + callback = va_arg (args, void *); + + object = gtk_builder_get_object (builder, name); + if (!object) { + g_warning ("UI file is missing widget '%s', aborting", + name); + continue; + } + + g_signal_connect (object, + signal, + G_CALLBACK (callback), + user_data); + } + + va_end (args); +} + +#ifdef GDK_WINDOWING_QUARTZ +static gchar * +cf_string_to_utf8 (CFStringRef str) +{ + CFIndex len; + gchar *ret; + + len = CFStringGetMaximumSizeForEncoding (CFStringGetLength (str), + kCFStringEncodingUTF8) + 1; + + ret = g_malloc (len); + ret[len] = '\0'; + + if (CFStringGetCString (str, ret, len, kCFStringEncodingUTF8)) + return ret; + + g_free (ret); + return NULL; +} + +static gchar * +util_get_mac_data_dir (void) +{ + const gchar *env; + CFBundleRef cf_bundle; + UInt32 type; + UInt32 creator; + CFURLRef cf_url; + CFStringRef cf_string; + gchar *ret, *tmp; + + /* The environment variable overrides all. */ + env = g_getenv ("DEVHELP_DATADIR"); + if (env) { + return g_strdup (env); + } + + cf_bundle = CFBundleGetMainBundle (); + if (!cf_bundle) { + return NULL; + } + + /* Only point into the bundle if it's an application. */ + CFBundleGetPackageInfo (cf_bundle, &type, &creator); + if (type != 'APPL') { + return NULL; + } + + cf_url = CFBundleCopyBundleURL (cf_bundle); + cf_string = CFURLCopyFileSystemPath (cf_url, kCFURLPOSIXPathStyle); + ret = cf_string_to_utf8 (cf_string); + CFRelease (cf_string); + CFRelease (cf_url); + + tmp = g_build_filename (ret, "Contents", "Resources", NULL); + g_free (ret); + + return tmp; +} +#endif + +gchar * +dh_util_build_data_filename (const gchar *first_part, + ...) +{ + gchar *datadir = NULL; + va_list args; + const gchar *part; + gchar **strv; + gint i; + gchar *ret; + + va_start (args, first_part); + +#ifdef GDK_WINDOWING_QUARTZ + datadir = util_get_mac_data_dir (); +#endif + + if (datadir == NULL) { + datadir = g_strdup (PACKAGE_DATA_DIR); + } + + /* 2 = 1 initial component + terminating NULL element. */ + strv = g_malloc (sizeof (gchar *) * 2); + strv[0] = (gchar *) datadir; + + i = 1; + for (part = first_part; part; part = va_arg (args, char *), i++) { + /* +2 = 1 new element + terminating NULL element. */ + strv = g_realloc (strv, sizeof (gchar*) * (i + 2)); + strv[i] = (gchar *) part; + } + + strv[i] = NULL; + ret = g_build_filenamev (strv); + g_free (strv); + + g_free (datadir); + + va_end (args); + + return ret; +} + +typedef struct { + gchar *name; + guint timeout_id; +} DhUtilStateItem; + +static void +util_state_item_free (DhUtilStateItem *item) +{ + g_free (item->name); + if (item->timeout_id) { + g_source_remove (item->timeout_id); + } + g_slice_free (DhUtilStateItem, item); +} + +static void +util_state_setup_widget (GtkWidget *widget, + const gchar *name) +{ + DhUtilStateItem *item; + + item = g_slice_new0 (DhUtilStateItem); + item->name = g_strdup (name); + + g_object_set_data_full (G_OBJECT (widget), + "dh-util-state", + item, + (GDestroyNotify) util_state_item_free); +} + +static gchar * +util_state_get_key (const gchar *name, + const gchar *key) +{ + return g_strdup_printf ("/apps/devhelp/state/%s/%s", name, key); +} + +static void +util_state_schedule_save (GtkWidget *widget, + GSourceFunc func) + +{ + DhUtilStateItem *item; + + item = g_object_get_data (G_OBJECT (widget), "dh-util-state"); + if (item->timeout_id) { + g_source_remove (item->timeout_id); + } + + item->timeout_id = g_timeout_add (500, + func, + widget); +} + +static void +util_state_save_window (GtkWindow *window, + const gchar *name) +{ + gchar *key; + GdkWindowState state; + gboolean maximized; + gint width, height; + gint x, y; + +#if GTK_CHECK_VERSION (2,14,0) + state = gdk_window_get_state (gtk_widget_get_window (GTK_WIDGET (window))); +#else + state = gdk_window_get_state (GTK_WIDGET (window)->window); +#endif + if (state & GDK_WINDOW_STATE_MAXIMIZED) { + maximized = TRUE; + } else { + maximized = FALSE; + } + + key = util_state_get_key (name, "maximized"); + ige_conf_set_bool (ige_conf_get (), key, maximized); + g_free (key); + + /* If maximized don't save the size and position. */ + if (maximized) { + return; + } + + gtk_window_get_size (GTK_WINDOW (window), &width, &height); + + key = util_state_get_key (name, "width"); + ige_conf_set_int (ige_conf_get (), key, width); + g_free (key); + + key = util_state_get_key (name, "height"); + ige_conf_set_int (ige_conf_get (), key, height); + g_free (key); + + gtk_window_get_position (GTK_WINDOW (window), &x, &y); + + key = util_state_get_key (name, "x_position"); + ige_conf_set_int (ige_conf_get (), key, x); + g_free (key); + + key = util_state_get_key (name, "y_position"); + ige_conf_set_int (ige_conf_get (), key, y); + g_free (key); +} + +static void +util_state_restore_window (GtkWindow *window, + const gchar *name) +{ + gchar *key; + gboolean maximized; + gint width, height; + gint x, y; + GdkScreen *screen; + gint max_width, max_height; + + key = util_state_get_key (name, "width"); + ige_conf_get_int (ige_conf_get (), key, &width); + g_free (key); + + key = util_state_get_key (name, "height"); + ige_conf_get_int (ige_conf_get (), key, &height); + g_free (key); + + key = util_state_get_key (name, "x_position"); + ige_conf_get_int (ige_conf_get (), key, &x); + g_free (key); + + key = util_state_get_key (name, "y_position"); + ige_conf_get_int (ige_conf_get (), key, &y); + g_free (key); + + if (width > 1 && height > 1) { + screen = gtk_widget_get_screen (GTK_WIDGET (window)); + max_width = gdk_screen_get_width (screen); + max_height = gdk_screen_get_height (screen); + + width = CLAMP (width, 0, max_width); + height = CLAMP (height, 0, max_height); + + x = CLAMP (x, 0, max_width - width); + y = CLAMP (y, 0, max_height - height); + + gtk_window_set_default_size (window, width, height); + } + + gtk_window_move (window, x, y); + + key = util_state_get_key (name, "maximized"); + ige_conf_get_bool (ige_conf_get (), key, &maximized); + g_free (key); + + if (maximized) { + gtk_window_maximize (window); + } +} + +static gboolean +util_state_window_timeout_cb (gpointer window) +{ + DhUtilStateItem *item; + + item = g_object_get_data (window, "dh-util-state"); + if (item) { + item->timeout_id = 0; + util_state_save_window (window, item->name); + } + + return FALSE; +} + +static gboolean +util_state_window_configure_event_cb (GtkWidget *window, + GdkEventConfigure *event, + gpointer user_data) +{ + util_state_schedule_save (window, util_state_window_timeout_cb); + return FALSE; +} + +static gboolean +util_state_paned_timeout_cb (gpointer paned) +{ + DhUtilStateItem *item; + + item = g_object_get_data (paned, "dh-util-state"); + if (item) { + gchar *key; + + item->timeout_id = 0; + + key = util_state_get_key (item->name, "position"); + ige_conf_set_int (ige_conf_get (), + key, + gtk_paned_get_position (paned)); + g_free (key); + } + + return FALSE; +} + +static gboolean +util_state_paned_changed_cb (GtkWidget *paned, + gpointer user_data) +{ + util_state_schedule_save (paned, util_state_paned_timeout_cb); + return FALSE; +} + +void +dh_util_state_manage_window (GtkWindow *window, + const gchar *name) +{ + util_state_setup_widget (GTK_WIDGET (window), name); + + g_signal_connect (window, "configure-event", + G_CALLBACK (util_state_window_configure_event_cb), + NULL); + + util_state_restore_window (window, name); +} + +void +dh_util_state_manage_paned (GtkPaned *paned, + const gchar *name) +{ + gchar *key; + gint position; + + util_state_setup_widget (GTK_WIDGET (paned), name); + + key = util_state_get_key (name, "position"); + if (ige_conf_get_int (ige_conf_get (), key, &position)) { + gtk_paned_set_position (paned, position); + } + g_free (key); + + g_signal_connect (paned, "notify::position", + G_CALLBACK (util_state_paned_changed_cb), + NULL); +} + +GSList * +dh_util_state_load_books_disabled (void) +{ + gchar *key; + GSList *books_disabled = NULL; + + key = util_state_get_key ("main/contents", "books_disabled"); + ige_conf_get_string_list (ige_conf_get (), key, &books_disabled); + g_free(key); + + return books_disabled; +} + +void +dh_util_state_store_books_disabled (GSList *books_disabled) +{ + gchar *key; + + key = util_state_get_key ("main/contents", "books_disabled"); + ige_conf_set_string_list (ige_conf_get (), key, books_disabled); + g_free(key); +} + +static gboolean +util_state_notebook_timeout_cb (gpointer notebook) +{ + DhUtilStateItem *item; + + item = g_object_get_data (notebook, "dh-util-state"); + if (item) { + GtkWidget *page; + const gchar *page_name; + + item->timeout_id = 0; + + page = gtk_notebook_get_nth_page ( + notebook, + gtk_notebook_get_current_page (notebook)); + page_name = dh_util_state_get_notebook_page_name (page); + if (page_name) { + gchar *key; + + key = util_state_get_key (item->name, "selected_tab"); + ige_conf_set_string (ige_conf_get (), key, page_name); + g_free (key); + } + } + + return FALSE; +} + +static void +util_state_notebook_switch_page_cb (GtkWidget *notebook, + gpointer page, + guint page_num, + gpointer user_data) +{ + util_state_schedule_save (notebook, util_state_notebook_timeout_cb); +} + +void +dh_util_state_set_notebook_page_name (GtkWidget *page, + const gchar *page_name) +{ + g_object_set_data_full (G_OBJECT (page), + "dh-util-state-tab-name", + g_strdup (page_name), + g_free); +} + +const gchar * +dh_util_state_get_notebook_page_name (GtkWidget *page) +{ + return g_object_get_data (G_OBJECT (page), + "dh-util-state-tab-name"); +} + +void +dh_util_state_manage_notebook (GtkNotebook *notebook, + const gchar *name, + const gchar *default_tab) +{ + gchar *key; + gchar *tab; + gint i; + + util_state_setup_widget (GTK_WIDGET (notebook), name); + + key = util_state_get_key (name, "selected_tab"); + if (!ige_conf_get_string (ige_conf_get (), key, &tab)) { + tab = g_strdup (default_tab); + } + g_free (key); + + for (i = 0; i < gtk_notebook_get_n_pages (notebook); i++) { + GtkWidget *page; + const gchar *page_name; + + page = gtk_notebook_get_nth_page (notebook, i); + page_name = dh_util_state_get_notebook_page_name (page); + if (page_name && strcmp (page_name, tab) == 0) { + gtk_notebook_set_current_page (notebook, i); + gtk_widget_grab_focus (page); + break; + } + } + + g_free (tab); + + g_signal_connect (notebook, "switch-page", + G_CALLBACK (util_state_notebook_switch_page_cb), + NULL); +} + +static gboolean +split_font_string (const gchar *name_and_size, + gchar **name, + gdouble *size) +{ + PangoFontDescription *desc; + PangoFontMask mask; + gboolean retval = FALSE; + + desc = pango_font_description_from_string (name_and_size); + if (!desc) { + return FALSE; + } + + mask = (PANGO_FONT_MASK_FAMILY | PANGO_FONT_MASK_SIZE); + if ((pango_font_description_get_set_fields (desc) & mask) == mask) { + *size = PANGO_PIXELS (pango_font_description_get_size (desc)); + *name = g_strdup (pango_font_description_get_family (desc)); + retval = TRUE; + } + + pango_font_description_free (desc); + + return retval; +} + +#define DH_CONF_PATH "/apps/devhelp" +#define DH_CONF_USE_SYSTEM_FONTS DH_CONF_PATH "/ui/use_system_fonts" +#define DH_CONF_VARIABLE_FONT DH_CONF_PATH "/ui/variable_font" +#define DH_CONF_FIXED_FONT DH_CONF_PATH "/ui/fixed_font" +#define DH_CONF_SYSTEM_VARIABLE_FONT "/desktop/gnome/interface/font_name" +#define DH_CONF_SYSTEM_FIXED_FONT "/desktop/gnome/interface/monospace_font_name" + +void +dh_util_font_get_variable (gchar **name, + gdouble *size, + gboolean use_system_fonts) +{ + IgeConf *conf; + gchar *name_and_size; + + conf = ige_conf_get (); + + if (use_system_fonts) { +#ifdef GDK_WINDOWING_QUARTZ + name_and_size = g_strdup ("Lucida Grande 14"); +#else + ige_conf_get_string (conf, + DH_CONF_SYSTEM_VARIABLE_FONT, + &name_and_size); +#endif + } else { + ige_conf_get_string (conf, + DH_CONF_VARIABLE_FONT, + &name_and_size); + } + + if (!split_font_string (name_and_size, name, size)) { + *name = g_strdup ("sans"); + *size = 12; + } + + g_free (name_and_size); +} + +void +dh_util_font_get_fixed (gchar **name, + gdouble *size, + gboolean use_system_fonts) +{ + IgeConf *conf; + gchar *name_and_size; + + conf = ige_conf_get (); + + if (use_system_fonts) { +#ifdef GDK_WINDOWING_QUARTZ + name_and_size = g_strdup ("Monaco 14"); +#else + ige_conf_get_string (conf, + DH_CONF_SYSTEM_FIXED_FONT, + &name_and_size); +#endif + } else { + ige_conf_get_string (conf, + DH_CONF_FIXED_FONT, + &name_and_size); + } + + if (!split_font_string (name_and_size, name, size)) { + *name = g_strdup ("monospace"); + *size = 12; + } + + g_free (name_and_size); +} + +static void +view_destroy_cb (GtkWidget *view, + gpointer user_data) +{ + views = g_list_remove (views, view); +} + +static void +view_setup_fonts (WebKitWebView *view) +{ + IgeConf *conf; + WebKitWebSettings *settings; + gboolean use_system_fonts; + gchar *variable_name; + gdouble variable_size; + gchar *fixed_name; + gdouble fixed_size; + + conf = ige_conf_get (); + + settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view)); + + ige_conf_get_bool (conf, + DH_CONF_USE_SYSTEM_FONTS, + &use_system_fonts); + + dh_util_font_get_variable (&variable_name, &variable_size, + use_system_fonts); + dh_util_font_get_fixed (&fixed_name, &fixed_size, + use_system_fonts); + + g_object_set (settings, + "monospace-font-family", fixed_name, + "default-monospace-font-size", (guint) fixed_size, + "sans-serif-font-family", variable_name, + "serif-font-family", variable_name, + "default-font-size", (guint) variable_size, + NULL); + + g_free (variable_name); + g_free (fixed_name); +} + +static void +font_notify_cb (IgeConf *conf, + const gchar *path, + gpointer user_data) +{ + GList *l; + + for (l = views; l; l = l->next) { + view_setup_fonts (l->data); + } +} + +void +dh_util_font_add_web_view (WebKitWebView *view) +{ + static gboolean setup; + + if (!setup) { + IgeConf *conf; + + conf = ige_conf_get (); + + ige_conf_notify_add (conf, + DH_CONF_USE_SYSTEM_FONTS, + font_notify_cb, + NULL); + ige_conf_notify_add (conf, + DH_CONF_SYSTEM_VARIABLE_FONT, + font_notify_cb, + NULL); + ige_conf_notify_add (conf, + DH_CONF_SYSTEM_FIXED_FONT, + font_notify_cb, + NULL); + ige_conf_notify_add (conf, + DH_CONF_VARIABLE_FONT, + font_notify_cb, + NULL); + ige_conf_notify_add (conf, + DH_CONF_FIXED_FONT, + font_notify_cb, + NULL); + + setup = TRUE; + } + + views = g_list_prepend (views, view); + + g_signal_connect (view, "destroy", + G_CALLBACK (view_destroy_cb), + NULL); + + view_setup_fonts (view); +} + +gint +dh_util_cmp_book (DhLink *a, DhLink *b) +{ + const gchar *name_a; + const gchar *name_b; + gchar *name_a_casefold; + gchar *name_b_casefold; + int rc; + + name_a = dh_link_get_name (a); + if (!name_a) { + name_a = ""; + } + + name_b = dh_link_get_name (b); + if (!name_b) { + name_b = ""; + } + + if (g_ascii_strncasecmp (name_a, "the ", 4) == 0) { + name_a += 4; + } + if (g_ascii_strncasecmp (name_b, "the ", 4) == 0) { + name_b += 4; + } + + name_a_casefold = g_utf8_casefold (name_a, -1); + name_b_casefold = g_utf8_casefold (name_b, -1); + + rc = strcmp (name_a_casefold, name_b_casefold); + + g_free (name_a_casefold); + g_free (name_b_casefold); + + return rc; +} diff --git a/plugins/help/devhelp/dh-util.h b/plugins/help/devhelp/dh-util.h new file mode 100644 index 0000000..6f126d5 --- /dev/null +++ b/plugins/help/devhelp/dh-util.h @@ -0,0 +1,67 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001-2002 Mikael Hallendal + * Copyright (C) 2004,2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DH_UTIL_H__ +#define __DH_UTIL_H__ + +#include +#include +#include "dh-link.h" + +G_BEGIN_DECLS + +GtkBuilder * dh_util_builder_get_file (const gchar *filename, + const gchar *root, + const gchar *domain, + const gchar *first_required_widget, + ...); +void dh_util_builder_connect (GtkBuilder *gui, + gpointer user_data, + gchar *first_widget, + ...); +gchar * dh_util_build_data_filename (const gchar *first_part, + ...); +void dh_util_state_manage_window (GtkWindow *window, + const gchar *name); +void dh_util_state_manage_paned (GtkPaned *paned, + const gchar *name); +void dh_util_state_manage_notebook (GtkNotebook *notebook, + const gchar *name, + const gchar *default_tab); +void dh_util_state_set_notebook_page_name (GtkWidget *page, + const gchar *page_name); +const gchar *dh_util_state_get_notebook_page_name (GtkWidget *page); +GSList * dh_util_state_load_books_disabled (void); +void dh_util_state_store_books_disabled (GSList *books_disabled); + +void dh_util_font_get_variable (gchar **name, + gdouble *size, + gboolean use_system_font); +void dh_util_font_get_fixed (gchar **name, + gdouble *size, + gboolean use_system_font); +void dh_util_font_add_web_view (WebKitWebView *view); +gint dh_util_cmp_book (DhLink *a, + DhLink *b); + +G_END_DECLS + +#endif /* __DH_UTIL_H__ */ diff --git a/plugins/help/devhelp/dh-window.c b/plugins/help/devhelp/dh-window.c new file mode 100644 index 0000000..f1dcf9b --- /dev/null +++ b/plugins/help/devhelp/dh-window.c @@ -0,0 +1,1982 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001-2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Fullscreen mode code adapted from gedit + * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence + * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi + * Copyright (C) 2002-2005 Paolo Maggi + */ + +#include "config.h" +#include +#include +#include +#include +#include + +#ifdef GDK_WINDOWING_QUARTZ +#include +#endif + +#include "dh-book-tree.h" +#include "dh-book-manager.h" +#include "dh-book.h" +#include "dh-preferences.h" +#include "dh-search.h" +#include "dh-window.h" +#include "dh-util.h" +#include "dh-marshal.h" +#include "dh-enum-types.h" +#include "eggfindbar.h" +#include "ige-conf.h" + +#define FULLSCREEN_ANIMATION_SPEED 4 + +struct _DhWindowPriv { + DhBase *base; + + GtkWidget *main_box; + GtkWidget *menu_box; + GtkWidget *hpaned; + GtkWidget *control_notebook; + GtkWidget *book_tree; + GtkWidget *search; + GtkWidget *notebook; + + GtkWidget *vbox; + GtkWidget *findbar; + + GtkWidget *fullscreen_controls; + guint fullscreen_animation_timeout_id; + gboolean fullscreen_animation_enter; + + GtkUIManager *manager; + GtkActionGroup *action_group; + + DhLink *selected_search_link; + guint find_source_id; +}; + +enum { + OPEN_LINK, + LAST_SIGNAL +}; + +static gint signals[LAST_SIGNAL] = { 0 }; + +static guint tab_accel_keys[] = { + GDK_1, GDK_2, GDK_3, GDK_4, GDK_5, + GDK_6, GDK_7, GDK_8, GDK_9, GDK_0 +}; + +static const +struct +{ + gchar *name; + int level; +} +zoom_levels[] = +{ + { N_("50%"), 70 }, + { N_("75%"), 84 }, + { N_("100%"), 100 }, + { N_("125%"), 119 }, + { N_("150%"), 141 }, + { N_("175%"), 168 }, + { N_("200%"), 200 }, + { N_("300%"), 283 }, + { N_("400%"), 400 } +}; + +#define ZOOM_MINIMAL (zoom_levels[0].level) +#define ZOOM_MAXIMAL (zoom_levels[8].level) +#define ZOOM_DEFAULT (zoom_levels[2].level) + +#if GTK_CHECK_VERSION (2,17,5) +#define ERRORS_IN_INFOBAR +#endif + +static void dh_window_class_init (DhWindowClass *klass); +static void dh_window_init (DhWindow *window); +static void window_populate (DhWindow *window); +static void window_tree_link_selected_cb (GObject *ignored, + DhLink *link, + DhWindow *window); +static void window_search_link_selected_cb (GObject *ignored, + DhLink *link, + DhWindow *window); +static void window_check_history (DhWindow *window, + WebKitWebView *web_view); +static void window_web_view_tab_accel_cb (GtkAccelGroup *accel_group, + GObject *object, + guint key, + GdkModifierType mod, + DhWindow *window); +static void window_find_search_changed_cb (GObject *object, + GParamSpec *arg1, + DhWindow *window); +static void window_find_case_changed_cb (GObject *object, + GParamSpec *arg1, + DhWindow *window); +static void window_find_previous_cb (GtkEntry *entry, + DhWindow *window); +static void window_find_next_cb (GtkEntry *entry, + DhWindow *window); +static void window_findbar_close_cb (GtkWidget *widget, + DhWindow *window); +static GtkWidget * window_new_tab_label (DhWindow *window, + const gchar *label, + const GtkWidget *parent); +static int window_open_new_tab (DhWindow *window, + const gchar *location, + gboolean switch_focus); +static WebKitWebView *window_get_active_web_view (DhWindow *window); +#ifdef ERRORS_IN_INFOBAR +static GtkWidget * window_get_active_info_bar (DhWindow *window); +#endif +static void window_update_title (DhWindow *window, + WebKitWebView *web_view, + const gchar *title); +static void window_tab_set_title (DhWindow *window, + WebKitWebView *web_view, + const gchar *title); +static void window_close_tab (DhWindow *window, + gint page_num); + +G_DEFINE_TYPE (DhWindow, dh_window, GTK_TYPE_WINDOW); + +#define GET_PRIVATE(instance) G_TYPE_INSTANCE_GET_PRIVATE \ + (instance, DH_TYPE_WINDOW, DhWindowPriv); + +static void +window_activate_new_window (GtkAction *action, + DhWindow *window) +{ + DhWindowPriv *priv; + GtkWidget *new_window; + + priv = window->priv; + + new_window = dh_base_new_window (priv->base); + gtk_widget_show (new_window); +} + +static void +window_activate_new_tab (GtkAction *action, + DhWindow *window) +{ + window_open_new_tab (window, NULL, TRUE); +} + +static void +window_activate_print (GtkAction *action, + DhWindow *window) +{ + WebKitWebView *web_view; + + web_view = window_get_active_web_view (window); + webkit_web_view_execute_script (web_view, "print();"); +} + +static void +window_close_tab (DhWindow *window, + gint page_num) +{ + DhWindowPriv *priv; + gint pages; + + priv = window->priv; + + gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), page_num); + + pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook)); + + if (pages == 0) { + gtk_widget_destroy (GTK_WIDGET (window)); + } + else if (pages == 1) { + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (priv->notebook), FALSE); + } +} + +static void +window_activate_close (GtkAction *action, + DhWindow *window) +{ + gint page_num; + + page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (window->priv->notebook)); + window_close_tab (window, page_num); +} + +static void +window_activate_quit (GtkAction *action, + DhWindow *window) +{ + dh_base_quit (window->priv->base); +} + +static void +window_activate_copy (GtkAction *action, + DhWindow *window) +{ + GtkWidget *widget; + DhWindowPriv *priv; + + priv = window->priv; + + widget = gtk_window_get_focus (GTK_WINDOW (window)); + + if (GTK_IS_EDITABLE (widget)) { + gtk_editable_copy_clipboard (GTK_EDITABLE (widget)); + } else if (GTK_IS_TREE_VIEW (widget) && + gtk_widget_is_ancestor (widget, priv->search) && + priv->selected_search_link) { + GtkClipboard *clipboard; + clipboard = gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text (clipboard, + dh_link_get_name(priv->selected_search_link), -1); + } else { + WebKitWebView *web_view; + + web_view = window_get_active_web_view (window); + webkit_web_view_copy_clipboard (web_view); + } +} + +static void +window_activate_find (GtkAction *action, + DhWindow *window) +{ + DhWindowPriv *priv; + WebKitWebView *web_view; + + priv = window->priv; + web_view = window_get_active_web_view (window); + + gtk_widget_show (priv->findbar); + gtk_widget_grab_focus (priv->findbar); + + webkit_web_view_set_highlight_text_matches (web_view, TRUE); +} + +static int +window_get_current_zoom_level_index (DhWindow *window) +{ + WebKitWebView *web_view; + float zoom_level; + int zoom_level_as_int = ZOOM_DEFAULT; + int i; + + web_view = window_get_active_web_view (window); + if (web_view) { + g_object_get (web_view, "zoom-level", &zoom_level, NULL); + zoom_level_as_int = (int)(zoom_level*100); + } + + for (i=0; zoom_levels[i].level != ZOOM_MAXIMAL; i++) { + if (zoom_levels[i].level == zoom_level_as_int) + return i; + } + return i; +} + +static void +window_update_zoom_actions_sensitiveness (DhWindow *window) +{ + DhWindowPriv *priv; + GtkAction *zoom_in, *zoom_out, *zoom_default; + int zoom_level_idx; + + priv = window->priv; + zoom_in = gtk_action_group_get_action (priv->action_group, "ZoomIn"); + zoom_out = gtk_action_group_get_action (priv->action_group, "ZoomOut"); + zoom_default = gtk_action_group_get_action (priv->action_group, "ZoomDefault"); + + zoom_level_idx = window_get_current_zoom_level_index (window); + + gtk_action_set_sensitive (zoom_in, + zoom_levels[zoom_level_idx].level < ZOOM_MAXIMAL); + gtk_action_set_sensitive (zoom_out, + zoom_levels[zoom_level_idx].level > ZOOM_MINIMAL); + gtk_action_set_sensitive (zoom_default, + zoom_levels[zoom_level_idx].level != ZOOM_DEFAULT); +} + +static void +window_activate_zoom_in (GtkAction *action, + DhWindow *window) +{ + int zoom_level_idx; + + zoom_level_idx = window_get_current_zoom_level_index (window); + if (zoom_levels[zoom_level_idx].level < ZOOM_MAXIMAL) { + WebKitWebView *web_view; + + web_view = window_get_active_web_view (window); + g_object_set (web_view, + "zoom-level", (float)(zoom_levels[zoom_level_idx+1].level)/100, + NULL); + window_update_zoom_actions_sensitiveness (window); + } + +} + +static void +window_activate_zoom_out (GtkAction *action, + DhWindow *window) +{ + int zoom_level_idx; + + zoom_level_idx = window_get_current_zoom_level_index (window); + if (zoom_levels[zoom_level_idx].level > ZOOM_MINIMAL) { + WebKitWebView *web_view; + + web_view = window_get_active_web_view (window); + g_object_set (web_view, + "zoom-level", (float)(zoom_levels[zoom_level_idx-1].level)/100, + NULL); + window_update_zoom_actions_sensitiveness (window); + } +} + +static void +window_activate_zoom_default (GtkAction *action, + DhWindow *window) +{ + WebKitWebView *web_view; + + web_view = window_get_active_web_view (window); + g_object_set (web_view, "zoom-level", (float)(ZOOM_DEFAULT)/100, NULL); + window_update_zoom_actions_sensitiveness (window); +} + +static gboolean +run_fullscreen_animation (gpointer data) +{ + DhWindow *window = DH_WINDOW (data); + GdkScreen *screen; + GdkRectangle fs_rect; + gint x, y; + + screen = gtk_window_get_screen (GTK_WINDOW (window)); + gdk_screen_get_monitor_geometry (screen, + gdk_screen_get_monitor_at_window (screen, + gtk_widget_get_window (GTK_WIDGET (window))), + &fs_rect); + + gtk_window_get_position (GTK_WINDOW (window->priv->fullscreen_controls), + &x, &y); + + if (window->priv->fullscreen_animation_enter) + { + if (y == fs_rect.y) + { + window->priv->fullscreen_animation_timeout_id = 0; + return FALSE; + } + else + { + gtk_window_move (GTK_WINDOW (window->priv->fullscreen_controls), + x, y + 1); + return TRUE; + } + } + else + { + gint w, h; + + gtk_window_get_size (GTK_WINDOW (window->priv->fullscreen_controls), + &w, &h); + + if (y == fs_rect.y - h + 1) + { + window->priv->fullscreen_animation_timeout_id = 0; + return FALSE; + } + else + { + gtk_window_move (GTK_WINDOW (window->priv->fullscreen_controls), + x, y - 1); + return TRUE; + } + } +} + +static void +show_hide_fullscreen_toolbar (DhWindow *window, + gboolean show, + gint height) +{ + GtkSettings *settings; + gboolean enable_animations; + + settings = gtk_widget_get_settings (GTK_WIDGET (window)); + g_object_get (G_OBJECT (settings), + "gtk-enable-animations", + &enable_animations, + NULL); + + if (enable_animations) + { + window->priv->fullscreen_animation_enter = show; + + if (window->priv->fullscreen_animation_timeout_id == 0) + { + window->priv->fullscreen_animation_timeout_id = + g_timeout_add (FULLSCREEN_ANIMATION_SPEED, + (GSourceFunc) run_fullscreen_animation, + window); + } + } + else + { + GdkRectangle fs_rect; + GdkScreen *screen; + + screen = gtk_window_get_screen (GTK_WINDOW (window)); + gdk_screen_get_monitor_geometry (screen, + gdk_screen_get_monitor_at_window (screen, + gtk_widget_get_window (GTK_WIDGET (window))), + &fs_rect); + + if (show) + gtk_window_move (GTK_WINDOW (window->priv->fullscreen_controls), + fs_rect.x, fs_rect.y); + else + gtk_window_move (GTK_WINDOW (window->priv->fullscreen_controls), + fs_rect.x, fs_rect.y - height + 1); + } + +} + + +static gboolean +on_fullscreen_controls_enter_notify_event (GtkWidget *widget, + GdkEventCrossing *event, + DhWindow *window) +{ + show_hide_fullscreen_toolbar (window, TRUE, 0); + + return FALSE; +} + +static gboolean +on_fullscreen_controls_leave_notify_event (GtkWidget *widget, + GdkEventCrossing *event, + DhWindow *window) +{ + GdkDisplay *display; + GdkScreen *screen; + gint w, h; + gint x, y; + + display = gdk_display_get_default (); + screen = gtk_window_get_screen (GTK_WINDOW (window)); + + gtk_window_get_size (GTK_WINDOW (window->priv->fullscreen_controls), &w, &h); + gdk_display_get_pointer (display, &screen, &x, &y, NULL); + + /* gtk seems to emit leave notify when clicking on tool items, + * work around it by checking the coordinates + */ + if (y >= h) + { + show_hide_fullscreen_toolbar (window, FALSE, h); + } + + return FALSE; +} + +static gboolean +window_is_fullscreen (DhWindow *window) +{ + GdkWindowState state; + + g_return_val_if_fail (DH_IS_WINDOW (window), FALSE); + +#if GTK_CHECK_VERSION (2,14,0) + state = gdk_window_get_state (gtk_widget_get_window (GTK_WIDGET (window))); +#else + state = gdk_window_get_state (GTK_WIDGET (window)->window); +#endif + + return state & GDK_WINDOW_STATE_FULLSCREEN; +} + +static void +window_fullscreen_controls_build (DhWindow *window) +{ + GtkWidget *toolbar; + GtkAction *action; + DhWindowPriv *priv; + + priv = window->priv; + if (priv->fullscreen_controls != NULL) + return; + + priv->fullscreen_controls = gtk_window_new (GTK_WINDOW_POPUP); + gtk_window_set_transient_for (GTK_WINDOW (priv->fullscreen_controls), + GTK_WINDOW (window)); + + toolbar = gtk_ui_manager_get_widget (priv->manager, "/FullscreenToolBar"); + gtk_container_add (GTK_CONTAINER (priv->fullscreen_controls), + toolbar); + action = gtk_action_group_get_action (priv->action_group, + "LeaveFullscreen"); + g_object_set (action, "is-important", TRUE, NULL); + + /* Set the toolbar style */ + gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), + GTK_TOOLBAR_BOTH_HORIZ); + + g_signal_connect (priv->fullscreen_controls, "enter-notify-event", + G_CALLBACK (on_fullscreen_controls_enter_notify_event), + window); + g_signal_connect (priv->fullscreen_controls, "leave-notify-event", + G_CALLBACK (on_fullscreen_controls_leave_notify_event), + window); +} + +static void +window_fullscreen_controls_show (DhWindow *window) +{ + GdkScreen *screen; + GdkRectangle fs_rect; + gint w, h; + + screen = gtk_window_get_screen (GTK_WINDOW (window)); + gdk_screen_get_monitor_geometry (screen, + gdk_screen_get_monitor_at_window ( + screen, + gtk_widget_get_window (GTK_WIDGET (window))), + &fs_rect); + + gtk_window_get_size (GTK_WINDOW (window->priv->fullscreen_controls), &w, &h); + + gtk_window_resize (GTK_WINDOW (window->priv->fullscreen_controls), + fs_rect.width, h); + + gtk_window_move (GTK_WINDOW (window->priv->fullscreen_controls), + fs_rect.x, fs_rect.y - h + 1); + + gtk_widget_show_all (window->priv->fullscreen_controls); +} + +static void +window_fullscreen (DhWindow *window) +{ + if (window_is_fullscreen (window)) + return; + + gtk_window_fullscreen (GTK_WINDOW (window)); + gtk_widget_hide (gtk_ui_manager_get_widget (window->priv->manager, "/MenuBar")); + gtk_widget_hide (gtk_ui_manager_get_widget (window->priv->manager, "/Toolbar")); + + window_fullscreen_controls_build (window); + window_fullscreen_controls_show (window); +} + +static void +window_unfullscreen (DhWindow *window) +{ + if (! window_is_fullscreen (window)) + return; + + gtk_window_unfullscreen (GTK_WINDOW (window)); + gtk_widget_show (gtk_ui_manager_get_widget (window->priv->manager, "/MenuBar")); + gtk_widget_show (gtk_ui_manager_get_widget (window->priv->manager, "/Toolbar")); + + gtk_widget_hide (window->priv->fullscreen_controls); +} + + +static void +window_toggle_fullscreen_mode (GtkAction *action, + DhWindow *window) +{ + if (window_is_fullscreen (window)) { + window_unfullscreen (window); + } else { + window_fullscreen (window); + } +} + +static void +window_leave_fullscreen_mode (GtkAction *action, + DhWindow *window) +{ + GtkAction *view_action; + + view_action = gtk_action_group_get_action (window->priv->action_group, + "ViewFullscreen"); + g_signal_handlers_block_by_func (view_action, + G_CALLBACK (window_toggle_fullscreen_mode), + window); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (view_action), + FALSE); + window_unfullscreen (window); + g_signal_handlers_unblock_by_func (view_action, + G_CALLBACK (window_toggle_fullscreen_mode), + window); +} + +static void +window_activate_preferences (GtkAction *action, + DhWindow *window) +{ + dh_preferences_show_dialog (GTK_WINDOW (window)); +} + +static void +window_activate_back (GtkAction *action, + DhWindow *window) +{ + DhWindowPriv *priv; + WebKitWebView *web_view; + GtkWidget *frame; + + priv = window->priv; + + frame = gtk_notebook_get_nth_page ( + GTK_NOTEBOOK (priv->notebook), + gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook))); + web_view = g_object_get_data (G_OBJECT (frame), "web_view"); + + webkit_web_view_go_back (web_view); +} + +static void +window_activate_forward (GtkAction *action, + DhWindow *window) +{ + DhWindowPriv *priv; + WebKitWebView *web_view; + GtkWidget *frame; + + priv = window->priv; + + frame = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), + gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook)) + ); + web_view = g_object_get_data (G_OBJECT (frame), "web_view"); + + webkit_web_view_go_forward (web_view); +} + +static void +window_activate_show_contents (GtkAction *action, + DhWindow *window) +{ + DhWindowPriv *priv; + + priv = window->priv; + + gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->control_notebook), 0); + gtk_widget_grab_focus (priv->book_tree); +} + +static void +window_activate_show_search (GtkAction *action, + DhWindow *window) +{ + DhWindowPriv *priv; + + priv = window->priv; + + gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->control_notebook), 1); + gtk_widget_grab_focus (priv->search); +} + +static void +window_activate_about (GtkAction *action, + DhWindow *window) +{ + const gchar *authors[] = { + "Mikael Hallendal ", + "Richard Hult ", + "Johan Dahlin ", + "Ross Burton ", + "Aleksander Morgado ", + NULL + }; + const gchar **documenters = NULL; + const gchar *translator_credits = _("translator_credits"); + + /* i18n: Please don't translate "Devhelp" (it's marked as translatable + * for transliteration only) */ + gtk_show_about_dialog (GTK_WINDOW (window), + "name", _("Devhelp"), + "version", PACKAGE_VERSION, + "comments", _("A developers' help browser for GNOME"), + "authors", authors, + "documenters", documenters, + "translator-credits", + strcmp (translator_credits, "translator_credits") != 0 ? + translator_credits : NULL, + "website", "http://live.gnome.org/devhelp", + "logo-icon-name", "devhelp", + NULL); +} + +static void +window_open_link_cb (DhWindow *window, + const char *location, + DhOpenLinkFlags flags) +{ + DhWindowPriv *priv; + priv = window->priv; + + if (flags & DH_OPEN_LINK_NEW_TAB) { + window_open_new_tab (window, location, FALSE); + } + else if (flags & DH_OPEN_LINK_NEW_WINDOW) { + GtkWidget *new_window; + new_window = dh_base_new_window (priv->base); + gtk_widget_show (new_window); + } +} + +static const GtkActionEntry actions[] = { + { "FileMenu", NULL, N_("_File"), NULL, NULL, NULL }, + { "EditMenu", NULL, N_("_Edit"), NULL, NULL, NULL }, + { "ViewMenu", NULL, N_("_View"), NULL, NULL, NULL }, + { "GoMenu", NULL, N_("_Go"), NULL, NULL, NULL }, + { "HelpMenu", NULL, N_("_Help"), NULL, NULL, NULL }, + + /* File menu */ + { "NewWindow", GTK_STOCK_NEW, N_("_New Window"), "N", NULL, + G_CALLBACK (window_activate_new_window) }, + { "NewTab", GTK_STOCK_NEW, N_("New _Tab"), "T", NULL, + G_CALLBACK (window_activate_new_tab) }, + { "Print", GTK_STOCK_PRINT, N_("_Print…"), "P", NULL, + G_CALLBACK (window_activate_print) }, + { "Close", GTK_STOCK_CLOSE, NULL, NULL, NULL, + G_CALLBACK (window_activate_close) }, + { "Quit", GTK_STOCK_QUIT, NULL, NULL, NULL, + G_CALLBACK (window_activate_quit) }, + + /* Edit menu */ + { "Copy", GTK_STOCK_COPY, NULL, "C", NULL, + G_CALLBACK (window_activate_copy) }, + { "Find", GTK_STOCK_FIND, NULL, "F", NULL, + G_CALLBACK (window_activate_find) }, + { "Find Next", GTK_STOCK_GO_FORWARD, N_("Find Next"), "G", NULL, + G_CALLBACK (window_find_next_cb) }, + { "Find Previous", GTK_STOCK_GO_BACK, N_("Find Previous"), "G", NULL, + G_CALLBACK (window_find_previous_cb) }, + { "Preferences", GTK_STOCK_PREFERENCES, NULL, NULL, NULL, + G_CALLBACK (window_activate_preferences) }, + + /* Go menu */ + { "Back", GTK_STOCK_GO_BACK, NULL, "Left", + N_("Go to the previous page"), + G_CALLBACK (window_activate_back) }, + { "Forward", GTK_STOCK_GO_FORWARD, NULL, "Right", + N_("Go to the next page"), + G_CALLBACK (window_activate_forward) }, + + { "ShowContentsTab", NULL, N_("_Contents Tab"), "B", NULL, + G_CALLBACK (window_activate_show_contents) }, + + { "ShowSearchTab", NULL, N_("_Search Tab"), "S", NULL, + G_CALLBACK (window_activate_show_search) }, + + /* View menu */ + { "ZoomIn", GTK_STOCK_ZOOM_IN, N_("_Larger Text"), "plus", + N_("Increase the text size"), + G_CALLBACK (window_activate_zoom_in) }, + { "ZoomOut", GTK_STOCK_ZOOM_OUT, N_("S_maller Text"), "minus", + N_("Decrease the text size"), + G_CALLBACK (window_activate_zoom_out) }, + { "ZoomDefault", GTK_STOCK_ZOOM_100, N_("_Normal Size"), "0", + N_("Use the normal text size"), + G_CALLBACK (window_activate_zoom_default) }, + + /* About menu */ + { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, + G_CALLBACK (window_activate_about) }, + + /* Fullscreen toolbar */ + { "LeaveFullscreen", GTK_STOCK_LEAVE_FULLSCREEN, NULL, + NULL, N_("Leave fullscreen mode"), + G_CALLBACK (window_leave_fullscreen_mode) } +}; + +static const GtkToggleActionEntry always_sensitive_toggle_menu_entries[] = +{ + { "ViewFullscreen", GTK_STOCK_FULLSCREEN, NULL, "F11", + N_("Display in full screen"), + G_CALLBACK (window_toggle_fullscreen_mode), FALSE }, +}; + +static const gchar* important_actions[] = { + "Back", + "Forward" +}; + +static void +window_finalize (GObject *object) +{ + DhWindowPriv *priv = GET_PRIVATE (object); + + g_object_unref (priv->base); + + G_OBJECT_CLASS (dh_window_parent_class)->finalize (object); +} + +static void +dh_window_class_init (DhWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = window_finalize; + + signals[OPEN_LINK] = + g_signal_new ("open-link", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (DhWindowClass, open_link), + NULL, NULL, + _dh_marshal_VOID__STRING_FLAGS, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + DH_TYPE_OPEN_LINK_FLAGS); + + gtk_rc_parse_string ("style \"devhelp-tab-close-button-style\"\n" + "{\n" + "GtkWidget::focus-padding = 0\n" + "GtkWidget::focus-line-width = 0\n" + "xthickness = 0\n" + "ythickness = 0\n" + "}\n" + "widget \"*.devhelp-tab-close-button\" " + "style \"devhelp-tab-close-button-style\""); + + g_type_class_add_private (klass, sizeof (DhWindowPriv)); +} + +static void +dh_window_init (DhWindow *window) +{ + DhWindowPriv *priv; + GtkAction *action; + GtkAccelGroup *accel_group; + GClosure *closure; + guint i; + + priv = GET_PRIVATE (window); + window->priv = priv; + + priv->selected_search_link = NULL; + + priv->manager = gtk_ui_manager_new (); + + accel_group = gtk_ui_manager_get_accel_group (priv->manager); + gtk_window_add_accel_group (GTK_WINDOW (window), accel_group); + + priv->main_box = gtk_vbox_new (FALSE, 0); + gtk_widget_show (priv->main_box); + + priv->menu_box = gtk_vbox_new (FALSE, 0); + gtk_widget_show (priv->menu_box); + gtk_container_set_border_width (GTK_CONTAINER (priv->menu_box), 0); + gtk_box_pack_start (GTK_BOX (priv->main_box), priv->menu_box, + FALSE, TRUE, 0); + + gtk_container_add (GTK_CONTAINER (window), priv->main_box); + + g_signal_connect (window, + "open-link", + G_CALLBACK (window_open_link_cb), + window); + + priv->action_group = gtk_action_group_new ("MainWindow"); + + gtk_action_group_set_translation_domain (priv->action_group, + GETTEXT_PACKAGE); + + gtk_action_group_add_actions (priv->action_group, + actions, + G_N_ELEMENTS (actions), + window); + gtk_action_group_add_toggle_actions (priv->action_group, + always_sensitive_toggle_menu_entries, + G_N_ELEMENTS (always_sensitive_toggle_menu_entries), + window); + + for (i = 0; i < G_N_ELEMENTS (important_actions); i++) { + action = gtk_action_group_get_action (priv->action_group, + important_actions[i]); + g_object_set (action, "is-important", TRUE, NULL); + } + + gtk_ui_manager_insert_action_group (priv->manager, + priv->action_group, + 0); + + action = gtk_action_group_get_action (priv->action_group, + "Back"); + g_object_set (action, "sensitive", FALSE, NULL); + + action = gtk_action_group_get_action (priv->action_group, + "Forward"); + g_object_set (action, "sensitive", FALSE, NULL); + + action = gtk_action_group_get_action (priv->action_group, "ZoomIn"); + /* Translators: This refers to text size */ + g_object_set (action, "short_label", _("Larger"), NULL); + action = gtk_action_group_get_action (priv->action_group, "ZoomOut"); + /* Translators: This refers to text size */ + g_object_set (action, "short_label", _("Smaller"), NULL); + + accel_group = gtk_accel_group_new (); + gtk_window_add_accel_group (GTK_WINDOW (window), accel_group); + + for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) { + closure = g_cclosure_new (G_CALLBACK (window_web_view_tab_accel_cb), + window, + NULL); + gtk_accel_group_connect (accel_group, + tab_accel_keys[i], + GDK_MOD1_MASK, + 0, + closure); + } +} + +/* The ugliest hack. When switching tabs, the selection and cursor is changed + * for the tree view so the web_view content is changed. Block the signal during + * switch. + */ +static void +window_control_switch_page_cb (GtkWidget *notebook, + gpointer page, + guint page_num, + DhWindow *window) +{ + DhWindowPriv *priv; + + priv = window->priv; + + g_signal_handlers_block_by_func (priv->book_tree, + window_tree_link_selected_cb, + window); +} + +static void +window_control_after_switch_page_cb (GtkWidget *notebook, + gpointer page, + guint page_num, + DhWindow *window) +{ + DhWindowPriv *priv; + + priv = window->priv; + + g_signal_handlers_unblock_by_func (priv->book_tree, + window_tree_link_selected_cb, + window); +} + +static void +window_web_view_switch_page_cb (GtkNotebook *notebook, + gpointer page, + guint new_page_num, + DhWindow *window) +{ + DhWindowPriv *priv; + GtkWidget *new_page; + + priv = window->priv; + + new_page = gtk_notebook_get_nth_page (notebook, new_page_num); + if (new_page) { + WebKitWebView *new_web_view; + WebKitWebFrame *web_frame; + const gchar *location; + + new_web_view = g_object_get_data (G_OBJECT (new_page), "web_view"); + + /* Sync the book tree. */ + web_frame = webkit_web_view_get_main_frame (new_web_view); + location = webkit_web_frame_get_uri (web_frame); + + if (location) { + dh_book_tree_select_uri (DH_BOOK_TREE (priv->book_tree), + location); + } + window_check_history (window, new_web_view); + + window_update_title (window, new_web_view, NULL); + } else { + /* i18n: Please don't translate "Devhelp" (it's marked as translatable + * for transliteration only) */ + gtk_window_set_title (GTK_WINDOW (window), _("Devhelp")); + window_check_history (window, NULL); + } +} + +static void +window_web_view_switch_page_after_cb (GtkNotebook *notebook, + gpointer page, + guint new_page_num, + DhWindow *window) +{ + window_update_zoom_actions_sensitiveness (window); +} + +static void +window_populate (DhWindow *window) +{ + DhWindowPriv *priv; + gchar *path; + GtkWidget *book_tree_sw; + DhBookManager *book_manager; + GtkWidget *menubar; + GtkWidget *toolbar; + + priv = window->priv; + + path = dh_util_build_data_filename ("devhelp", "ui", "window.ui", NULL); + gtk_ui_manager_add_ui_from_file (priv->manager, + path, + NULL); + g_free (path); + gtk_ui_manager_ensure_update (priv->manager); + + menubar = gtk_ui_manager_get_widget (priv->manager, "/MenuBar"); + gtk_box_pack_start (GTK_BOX (priv->menu_box), menubar, + FALSE, FALSE, 0); + toolbar = gtk_ui_manager_get_widget (priv->manager, "/Toolbar"); + gtk_box_pack_start (GTK_BOX (priv->menu_box), toolbar, + FALSE, FALSE, 0); + +#ifdef GDK_WINDOWING_QUARTZ + { + GtkWidget *widget; + IgeMacMenuGroup *group; + + /* Hide toolbar labels. */ + gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_ICONS); + + /* Setup menubar. */ + ige_mac_menu_set_menu_bar (GTK_MENU_SHELL (menubar)); + gtk_widget_hide (menubar); + + widget = gtk_ui_manager_get_widget (priv->manager, "/MenuBar/FileMenu/Quit"); + ige_mac_menu_set_quit_menu_item (GTK_MENU_ITEM (widget)); + + group = ige_mac_menu_add_app_menu_group (); + widget = gtk_ui_manager_get_widget (priv->manager, "/MenuBar/HelpMenu/About"); + ige_mac_menu_add_app_menu_item (group, GTK_MENU_ITEM (widget), + /* i18n: please don't translate + * "Devhelp", it's a name, not a + * generic word. */ + _("About Devhelp")); + + group = ige_mac_menu_add_app_menu_group (); + widget = gtk_ui_manager_get_widget (priv->manager, "/MenuBar/EditMenu/Preferences"); + ige_mac_menu_add_app_menu_item (group, GTK_MENU_ITEM (widget), + _("Preferences…")); + + ige_mac_menu_set_global_key_handler_enabled (TRUE); + } +#endif + + priv->hpaned = gtk_hpaned_new (); + + gtk_box_pack_start (GTK_BOX (priv->main_box), priv->hpaned, TRUE, TRUE, 0); + + /* Search and contents notebook. */ + priv->control_notebook = gtk_notebook_new (); + + gtk_paned_add1 (GTK_PANED (priv->hpaned), priv->control_notebook); + + g_signal_connect (priv->control_notebook, + "switch-page", + G_CALLBACK (window_control_switch_page_cb), + window); + + g_signal_connect_after (priv->control_notebook, + "switch-page", + G_CALLBACK (window_control_after_switch_page_cb), + window); + + book_tree_sw = gtk_scrolled_window_new (NULL, NULL); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (book_tree_sw), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (book_tree_sw), + GTK_SHADOW_IN); + gtk_container_set_border_width (GTK_CONTAINER (book_tree_sw), 2); + + book_manager = dh_base_get_book_manager (priv->base); + + priv->book_tree = dh_book_tree_new (book_manager); + gtk_container_add (GTK_CONTAINER (book_tree_sw), + priv->book_tree); + dh_util_state_set_notebook_page_name (book_tree_sw, "content"); + gtk_notebook_append_page (GTK_NOTEBOOK (priv->control_notebook), + book_tree_sw, + gtk_label_new (_("Contents"))); + g_signal_connect (priv->book_tree, + "link-selected", + G_CALLBACK (window_tree_link_selected_cb), + window); + + priv->search = dh_search_new (book_manager); + dh_util_state_set_notebook_page_name (priv->search, "search"); + gtk_notebook_append_page (GTK_NOTEBOOK (priv->control_notebook), + priv->search, + gtk_label_new (_("Search"))); + g_signal_connect (priv->search, + "link-selected", + G_CALLBACK (window_search_link_selected_cb), + window); + + priv->vbox = gtk_vbox_new (FALSE, 0); + gtk_paned_add2 (GTK_PANED (priv->hpaned), priv->vbox); + + /* HTML tabs notebook. */ + priv->notebook = gtk_notebook_new (); + gtk_container_set_border_width (GTK_CONTAINER (priv->notebook), 0); + gtk_notebook_set_show_border (GTK_NOTEBOOK (priv->notebook), FALSE); + gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE); + gtk_box_pack_start (GTK_BOX (priv->vbox), priv->notebook, TRUE, TRUE, 0); + + g_signal_connect (priv->notebook, + "switch-page", + G_CALLBACK (window_web_view_switch_page_cb), + window); + g_signal_connect_after (priv->notebook, + "switch-page", + G_CALLBACK (window_web_view_switch_page_after_cb), + window); + + + /* Create findbar. */ + priv->findbar = egg_find_bar_new (); + gtk_widget_set_no_show_all (priv->findbar, TRUE); + gtk_box_pack_start (GTK_BOX (priv->vbox), priv->findbar, FALSE, FALSE, 0); + + g_signal_connect (priv->findbar, + "notify::search-string", + G_CALLBACK(window_find_search_changed_cb), + window); + g_signal_connect (priv->findbar, + "notify::case-sensitive", + G_CALLBACK (window_find_case_changed_cb), + window); + g_signal_connect (priv->findbar, + "previous", + G_CALLBACK (window_find_previous_cb), + window); + g_signal_connect (priv->findbar, + "next", + G_CALLBACK (window_find_next_cb), + window); + g_signal_connect (priv->findbar, + "close", + G_CALLBACK (window_findbar_close_cb), + window); + + gtk_widget_show_all (priv->hpaned); + + window_update_zoom_actions_sensitiveness (window); + window_open_new_tab (window, NULL, TRUE); +} + + +static gchar * +find_library_equivalent (DhWindow *window, + const gchar *uri) +{ + DhWindowPriv *priv; + gchar **components; + GList *iter; + DhLink *link; + DhBookManager *book_manager; + gchar *book_id; + gchar *filename; + gchar *local_uri = NULL; + GList *books; + + components = g_strsplit (uri, "/", 0); + book_id = components[4]; + filename = components[6]; + + priv = window->priv; + book_manager = dh_base_get_book_manager (priv->base); + + /* use list pointer to iterate */ + for (books = dh_book_manager_get_books (book_manager); + !local_uri && books; + books = g_list_next (books)) { + DhBook *book = DH_BOOK (books->data); + + for (iter = dh_book_get_keywords (book); + iter; + iter = g_list_next (iter)) { + link = iter->data; + if (g_strcmp0 (dh_link_get_book_id (link), book_id) != 0) { + continue; + } + if (g_strcmp0 (dh_link_get_file_name (link), filename) != 0) { + continue; + } + local_uri = dh_link_get_uri (link); + break; + } + } + + g_strfreev (components); + + return local_uri; +} + + +static gboolean +window_web_view_navigation_policy_decision_requested (WebKitWebView *web_view, + WebKitWebFrame *frame, + WebKitNetworkRequest *request, + WebKitWebNavigationAction *navigation_action, + WebKitWebPolicyDecision *policy_decision, + DhWindow *window) +{ + DhWindowPriv *priv; + const char *uri; + + priv = window->priv; + + uri = webkit_network_request_get_uri (request); + +#ifdef ERRORS_IN_INFOBAR + /* make sure to hide the info bar on page change */ + gtk_widget_hide (window_get_active_info_bar (window)); +#endif + + if (webkit_web_navigation_action_get_button (navigation_action) == 2) { /* middle click */ + webkit_web_policy_decision_ignore (policy_decision); + g_signal_emit (window, signals[OPEN_LINK], 0, uri, DH_OPEN_LINK_NEW_TAB); + return TRUE; + } + + if (strcmp (uri, "about:blank") == 0) { + return FALSE; + } + + if (strncmp (uri, "http://library.gnome.org/devel/", 31) == 0) { + gchar *local_uri = find_library_equivalent (window, uri); + if (local_uri) { + webkit_web_policy_decision_ignore (policy_decision); + _dh_window_display_uri (window, local_uri); + g_free (local_uri); + return TRUE; + } + } + + if (strncmp (uri, "file://", 7) != 0) { + webkit_web_policy_decision_ignore (policy_decision); + gtk_show_uri (NULL, uri, GDK_CURRENT_TIME, NULL); + return TRUE; + } + + if (web_view == window_get_active_web_view (window)) { + dh_book_tree_select_uri (DH_BOOK_TREE (priv->book_tree), uri); + window_check_history (window, web_view); + } + + return FALSE; +} + + +static gboolean +window_web_view_load_error_cb (WebKitWebView *web_view, + WebKitWebFrame *frame, + gchar *uri, + gpointer *web_error, + DhWindow *window) +{ +#ifdef ERRORS_IN_INFOBAR + GtkWidget *info_bar; + GtkWidget *content_area; + GtkWidget *message_label; + GList *children; + gchar *markup; + + info_bar = window_get_active_info_bar (window); + markup = g_strdup_printf ("%s", + _("Error opening the requested link.")); + message_label = gtk_label_new (markup); + gtk_misc_set_alignment (GTK_MISC (message_label), 0, 0.5); + gtk_label_set_use_markup (GTK_LABEL (message_label), TRUE); + content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar)); + children = gtk_container_get_children (GTK_CONTAINER (content_area)); + if (children) { + gtk_container_remove (GTK_CONTAINER (content_area), children->data); + g_list_free (children); + } + gtk_container_add (GTK_CONTAINER (content_area), message_label); + gtk_widget_show (message_label); + + gtk_widget_show (info_bar); + g_free (markup); + + return TRUE; +#else + return FALSE; +#endif +} + +static void +window_tree_link_selected_cb (GObject *ignored, + DhLink *link, + DhWindow *window) +{ + WebKitWebView *view; + gchar *uri; + + view = window_get_active_web_view (window); + + uri = dh_link_get_uri (link); + webkit_web_view_load_uri (view, uri); + g_free (uri); + + window_check_history (window, view); +} + +static void +window_search_link_selected_cb (GObject *ignored, + DhLink *link, + DhWindow *window) +{ + DhWindowPriv *priv; + WebKitWebView *view; + gchar *uri; + + priv = window->priv; + + priv->selected_search_link = link; + + view = window_get_active_web_view (window); + + uri = dh_link_get_uri (link); + webkit_web_view_load_uri (view, uri); + g_free (uri); + + window_check_history (window, view); +} + +static void +window_check_history (DhWindow *window, + WebKitWebView *web_view) +{ + DhWindowPriv *priv; + GtkAction *action; + + priv = window->priv; + + action = gtk_action_group_get_action (priv->action_group, "Forward"); + g_object_set (action, + "sensitive", web_view ? webkit_web_view_can_go_forward (web_view) : FALSE, + NULL); + + action = gtk_action_group_get_action (priv->action_group, "Back"); + g_object_set (action, + "sensitive", web_view ? webkit_web_view_can_go_back (web_view) : FALSE, + NULL); +} + +static void +window_web_view_title_changed_cb (WebKitWebView *web_view, + WebKitWebFrame *web_frame, + const gchar *title, + DhWindow *window) +{ + if (web_view == window_get_active_web_view (window)) { + window_update_title (window, web_view, title); + } + + window_tab_set_title (window, web_view, title); +} + +static gboolean +window_web_view_button_press_event_cb (WebKitWebView *web_view, + GdkEventButton *event, + DhWindow *window) +{ + if (event->button == 3) { + return TRUE; + } + + return FALSE; +} + +static gboolean +do_search (DhWindow *window) +{ + DhWindowPriv *priv = window->priv; + WebKitWebView *web_view; + + priv->find_source_id = 0; + + web_view = window_get_active_web_view (window); + + webkit_web_view_unmark_text_matches (web_view); + webkit_web_view_mark_text_matches ( + web_view, + egg_find_bar_get_search_string (EGG_FIND_BAR (priv->findbar)), + egg_find_bar_get_case_sensitive (EGG_FIND_BAR (priv->findbar)), 0); + webkit_web_view_set_highlight_text_matches (web_view, TRUE); + + webkit_web_view_search_text ( + web_view, egg_find_bar_get_search_string (EGG_FIND_BAR (priv->findbar)), + egg_find_bar_get_case_sensitive (EGG_FIND_BAR (priv->findbar)), + TRUE, TRUE); + + return FALSE; +} + +static void +window_find_search_changed_cb (GObject *object, + GParamSpec *pspec, + DhWindow *window) +{ + DhWindowPriv *priv = window->priv; + + if (priv->find_source_id != 0) { + g_source_remove (priv->find_source_id); + priv->find_source_id = 0; + } + + priv->find_source_id = g_timeout_add (300, (GSourceFunc)do_search, window); +} + +static void +window_find_case_changed_cb (GObject *object, + GParamSpec *pspec, + DhWindow *window) +{ + DhWindowPriv *priv = window->priv;; + WebKitWebView *view; + const gchar *string; + gboolean case_sensitive; + + view = window_get_active_web_view (window); + + string = egg_find_bar_get_search_string (EGG_FIND_BAR (priv->findbar)); + case_sensitive = egg_find_bar_get_case_sensitive (EGG_FIND_BAR (priv->findbar)); + + webkit_web_view_unmark_text_matches (view); + webkit_web_view_mark_text_matches (view, string, case_sensitive, 0); + webkit_web_view_set_highlight_text_matches (view, TRUE); +} + +static void +window_find_next_cb (GtkEntry *entry, + DhWindow *window) +{ + DhWindowPriv *priv = window->priv; + WebKitWebView *view; + const gchar *string; + gboolean case_sensitive; + + view = window_get_active_web_view (window); + + gtk_widget_show (priv->findbar); + + string = egg_find_bar_get_search_string (EGG_FIND_BAR (priv->findbar)); + case_sensitive = egg_find_bar_get_case_sensitive (EGG_FIND_BAR (priv->findbar)); + + webkit_web_view_search_text (view, string, case_sensitive, TRUE, TRUE); +} + +static void +window_find_previous_cb (GtkEntry *entry, + DhWindow *window) +{ + DhWindowPriv *priv = window->priv; + WebKitWebView *view; + const gchar *string; + gboolean case_sensitive; + + view = window_get_active_web_view (window); + + gtk_widget_show (priv->findbar); + + string = egg_find_bar_get_search_string (EGG_FIND_BAR (priv->findbar)); + case_sensitive = egg_find_bar_get_case_sensitive (EGG_FIND_BAR (priv->findbar)); + + webkit_web_view_search_text (view, string, case_sensitive, FALSE, TRUE); +} + +static void +window_findbar_close_cb (GtkWidget *widget, + DhWindow *window) +{ + DhWindowPriv *priv = window->priv; + WebKitWebView *view; + + view = window_get_active_web_view (window); + + gtk_widget_hide (priv->findbar); + + webkit_web_view_set_highlight_text_matches (view, FALSE); +} + +#if 0 +static void +window_web_view_open_new_tab_cb (WebKitWebView *web_view, + const gchar *location, + DhWindow *window) +{ + window_open_new_tab (window, location); +} +#endif + +static void +window_web_view_tab_accel_cb (GtkAccelGroup *accel_group, + GObject *object, + guint key, + GdkModifierType mod, + DhWindow *window) +{ + DhWindowPriv *priv; + gint i, num; + + priv = window->priv; + + num = -1; + for (i = 0; i < (gint) G_N_ELEMENTS (tab_accel_keys); i++) { + if (tab_accel_keys[i] == key) { + num = i; + break; + } + } + + if (num != -1) { + gtk_notebook_set_current_page ( + GTK_NOTEBOOK (priv->notebook), num); + } +} + +static int +window_open_new_tab (DhWindow *window, + const gchar *location, + gboolean switch_focus) +{ + DhWindowPriv *priv; + GtkWidget *view; + GtkWidget *vbox; + GtkWidget *scrolled_window; + GtkWidget *label; + gint num; +#ifdef ERRORS_IN_INFOBAR + GtkWidget *info_bar; +#endif + + priv = window->priv; + + /* Prepare the web view */ + view = webkit_web_view_new (); + gtk_widget_show (view); + dh_util_font_add_web_view (WEBKIT_WEB_VIEW (view)); + +#ifdef ERRORS_IN_INFOBAR + /* Prepare the info bar */ + info_bar = gtk_info_bar_new (); + gtk_widget_set_no_show_all (info_bar, TRUE); + gtk_info_bar_add_button (GTK_INFO_BAR (info_bar), + GTK_STOCK_CLOSE, GTK_RESPONSE_OK); + gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), + GTK_MESSAGE_ERROR); + g_signal_connect (info_bar, "response", + G_CALLBACK (gtk_widget_hide), NULL); +#endif + +#if 0 + /* Leave this in for now to make it easier to experiment. */ + { + WebKitWebSettings *settings; + settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view)); + + g_object_set (settings, + "user-stylesheet-uri", "file://" DATADIR "/devhelp/devhelp.css", + NULL); + } +#endif + + vbox = gtk_vbox_new (0, FALSE); + gtk_widget_show (vbox); + + /* XXX: Really it would be much better to use real structures */ + g_object_set_data (G_OBJECT (vbox), "web_view", view); +#ifdef ERRORS_IN_INFOBAR + g_object_set_data (G_OBJECT (vbox), "info_bar", info_bar); + + gtk_box_pack_start (GTK_BOX(vbox), info_bar, FALSE, TRUE, 0); +#endif + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_container_add (GTK_CONTAINER (scrolled_window), view); + gtk_widget_show (scrolled_window); + gtk_box_pack_start (GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0); + + label = window_new_tab_label (window, _("Empty Page"), vbox); + gtk_widget_show_all (label); + + g_signal_connect (view, "title-changed", + G_CALLBACK (window_web_view_title_changed_cb), + window); + g_signal_connect (view, "button-press-event", + G_CALLBACK (window_web_view_button_press_event_cb), + window); + g_signal_connect (view, "navigation-policy-decision-requested", + G_CALLBACK (window_web_view_navigation_policy_decision_requested), + window); + g_signal_connect (view, "load-error", + G_CALLBACK (window_web_view_load_error_cb), + window); + + num = gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), + vbox, NULL); + + gtk_notebook_set_tab_label (GTK_NOTEBOOK (priv->notebook), + vbox, label); + + if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook)) > 1) { + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (priv->notebook), TRUE); + } else { + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (priv->notebook), FALSE); + } + + if (location) { + webkit_web_view_load_uri (WEBKIT_WEB_VIEW (view), location); + } else { + webkit_web_view_load_uri (WEBKIT_WEB_VIEW (view), "about:blank"); + } + + if (switch_focus) { + gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num); + } + + return num; +} + +#ifndef GDK_WINDOWING_QUARTZ +static void +close_button_clicked_cb (GtkButton *button, + DhWindow *window) +{ + GtkWidget *parent_tab; + gint pages; + gint i; + + parent_tab = g_object_get_data (G_OBJECT (button), "parent_tab"); + pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (window->priv->notebook)); + for (i=0; ipriv->notebook), i) == parent_tab) { + window_close_tab (window, i); + break; + } + } +} + +static void +tab_label_style_set_cb (GtkWidget *hbox, + GtkStyle *previous_style, + gpointer user_data) +{ + PangoFontMetrics *metrics; + PangoContext *context; + GtkWidget *button; + GtkStyle *style; + gint char_width; + gint h, w; + + context = gtk_widget_get_pango_context (hbox); + style = gtk_widget_get_style (hbox); + metrics = pango_context_get_metrics (context, + style->font_desc, + pango_context_get_language (context)); + + char_width = pango_font_metrics_get_approximate_digit_width (metrics); + pango_font_metrics_unref (metrics); + + gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (hbox), + GTK_ICON_SIZE_MENU, &w, &h); + + gtk_widget_set_size_request (hbox, 15 * PANGO_PIXELS (char_width) + 2 * w, -1); + + button = g_object_get_data (G_OBJECT (hbox), "close-button"); + gtk_widget_set_size_request (button, w + 2, h + 2); +} +#endif + +/* Don't create a close button on quartz, it looks very much out of + * place. + */ +static GtkWidget* +window_new_tab_label (DhWindow *window, + const gchar *str, + const GtkWidget *parent) +{ + GtkWidget *label; +#ifndef GDK_WINDOWING_QUARTZ + GtkWidget *hbox; + GtkWidget *close_button; + GtkWidget *image; + + hbox = gtk_hbox_new (FALSE, 4); + + label = gtk_label_new (str); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0); + + close_button = gtk_button_new (); + gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE); + gtk_button_set_focus_on_click (GTK_BUTTON (close_button), FALSE); + gtk_widget_set_name (close_button, "devhelp-tab-close-button"); + g_object_set_data (G_OBJECT (close_button), "parent_tab", (gpointer) parent); + + image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU); + g_signal_connect (close_button, "clicked", + G_CALLBACK (close_button_clicked_cb), + window); + gtk_container_add (GTK_CONTAINER (close_button), image); + + gtk_box_pack_start (GTK_BOX (hbox), close_button, FALSE, FALSE, 0); + + /* Set minimal size */ + g_signal_connect (hbox, "style-set", + G_CALLBACK (tab_label_style_set_cb), + NULL); + + g_object_set_data (G_OBJECT (hbox), "label", label); + g_object_set_data (G_OBJECT (hbox), "close-button", close_button); + + return hbox; +#else + label = gtk_label_new (str); + g_object_set_data (G_OBJECT (label), "label", label); + + return label; +#endif +} + +static WebKitWebView * +window_get_active_web_view (DhWindow *window) +{ + DhWindowPriv *priv; + gint page_num; + GtkWidget *page; + + priv = window->priv; + + page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook)); + if (page_num == -1) { + return NULL; + } + + page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), page_num); + + return g_object_get_data (G_OBJECT (page), "web_view"); +} + +#ifdef ERRORS_IN_INFOBAR +static GtkWidget * +window_get_active_info_bar (DhWindow *window) +{ + DhWindowPriv *priv; + gint page_num; + GtkWidget *page; + + priv = window->priv; + + page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook)); + if (page_num == -1) { + return NULL; + } + + page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), page_num); + + return g_object_get_data (G_OBJECT (page), "info_bar"); +} +#endif + +static void +window_update_title (DhWindow *window, + WebKitWebView *web_view, + const gchar *web_view_title) +{ + DhWindowPriv *priv; + const gchar *book_title; + + priv = window->priv; + + if (!web_view_title) { + WebKitWebFrame *web_frame; + + web_frame = webkit_web_view_get_main_frame (web_view); + web_view_title = webkit_web_frame_get_title (web_frame); + } + + if (web_view_title && *web_view_title == '\0') { + web_view_title = NULL; + } + + book_title = dh_book_tree_get_selected_book_title (DH_BOOK_TREE (priv->book_tree)); + + /* Don't use both titles if they are the same. */ + if (book_title && web_view_title && strcmp (book_title, web_view_title) == 0) { + web_view_title = NULL; + } + + if (!book_title) { + /* i18n: Please don't translate "Devhelp" (it's marked as translatable + * for transliteration only) */ + book_title = _("Devhelp"); + } + + if (web_view_title) { + gchar *full_title; + full_title = g_strdup_printf ("%s - %s", book_title, web_view_title); + gtk_window_set_title (GTK_WINDOW (window), full_title); + g_free (full_title); + } else { + gtk_window_set_title (GTK_WINDOW (window), book_title); + } +} + +static void +window_tab_set_title (DhWindow *window, + WebKitWebView *web_view, + const gchar *title) +{ + DhWindowPriv *priv; + gint num_pages, i; + GtkWidget *page; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *page_web_view; + + priv = window->priv; + + if (!title || title[0] == '\0') { + title = _("Empty Page"); + } + + num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook)); + for (i = 0; i < num_pages; i++) { + page = gtk_notebook_get_nth_page ( + GTK_NOTEBOOK (priv->notebook), i); + page_web_view = g_object_get_data (G_OBJECT (page), "web_view"); + + /* The web_view widget is inside a frame. */ + if (page_web_view == GTK_WIDGET (web_view)) { + hbox = gtk_notebook_get_tab_label ( + GTK_NOTEBOOK (priv->notebook), page); + + if (hbox) { + label = g_object_get_data (G_OBJECT (hbox), "label"); + gtk_label_set_text (GTK_LABEL (label), title); + } + break; + } + } +} + +GtkWidget * +dh_window_new (DhBase *base) +{ + DhWindow *window; + DhWindowPriv *priv; + + window = g_object_new (DH_TYPE_WINDOW, NULL); + priv = window->priv; + + priv->base = g_object_ref (base); + + window_populate (window); + + gtk_window_set_icon_name (GTK_WINDOW (window), "devhelp"); + + dh_util_state_manage_window (GTK_WINDOW (window), "main/window"); + dh_util_state_manage_paned (GTK_PANED (priv->hpaned), "main/paned"); + dh_util_state_manage_notebook (GTK_NOTEBOOK (priv->control_notebook), + "main/search_notebook", + "content"); + + return GTK_WIDGET (window); +} + +void +dh_window_search (DhWindow *window, + const gchar *str, + const gchar *book_id) +{ + DhWindowPriv *priv; + + g_return_if_fail (DH_IS_WINDOW (window)); + + priv = window->priv; + + gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->control_notebook), 1); + dh_search_set_search_string (DH_SEARCH (priv->search), str, book_id); +} + +void +dh_window_focus_search (DhWindow *window) +{ + DhWindowPriv *priv; + + g_return_if_fail (DH_IS_WINDOW (window)); + + priv = window->priv; + + gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->control_notebook), 1); + gtk_widget_grab_focus (priv->search); +} + +/* Only call this with a URI that is known to be in the docs. */ +void +_dh_window_display_uri (DhWindow *window, + const gchar *uri) +{ + DhWindowPriv *priv; + WebKitWebView *web_view; + + g_return_if_fail (DH_IS_WINDOW (window)); + g_return_if_fail (uri != NULL); + + priv = window->priv; + + web_view = window_get_active_web_view (window); + webkit_web_view_load_uri (web_view, uri); + dh_book_tree_select_uri (DH_BOOK_TREE (priv->book_tree), uri); +} diff --git a/plugins/help/devhelp/dh-window.h b/plugins/help/devhelp/dh-window.h new file mode 100644 index 0000000..8003806 --- /dev/null +++ b/plugins/help/devhelp/dh-window.h @@ -0,0 +1,73 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 CodeFactory AB + * Copyright (C) 2001-2002 Mikael Hallendal + * Copyright (C) 2005 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __DH_WINDOW_H__ +#define __DH_WINDOW_H__ + +#include +#include "dh-base.h" + +G_BEGIN_DECLS + +#define DH_TYPE_WINDOW (dh_window_get_type ()) +#define DH_WINDOW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), DH_TYPE_WINDOW, DhWindow)) +#define DH_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), DH_TYPE_WINDOW, DhWindowClass)) +#define DH_IS_WINDOW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), DH_TYPE_WINDOW)) +#define DH_IS_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), DH_TYPE_WINDOW)) +#define DH_WINDOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), DH_TYPE_WINDOW, DhWindowClass)) + +typedef struct _DhWindow DhWindow; +typedef struct _DhWindowClass DhWindowClass; +typedef struct _DhWindowPriv DhWindowPriv; + +typedef enum +{ + DH_OPEN_LINK_NEW_WINDOW = 1 << 0, + DH_OPEN_LINK_NEW_TAB = 1 << 1 +} DhOpenLinkFlags; + +struct _DhWindow { + GtkWindow parent_instance; + DhWindowPriv *priv; +}; + +struct _DhWindowClass { + GtkWindowClass parent_class; + + /* Signals */ + void (*open_link) (DhWindow *window, + const char *location, + DhOpenLinkFlags flags); +}; + +GType dh_window_get_type (void) G_GNUC_CONST; +GtkWidget *dh_window_new (DhBase *base); +void dh_window_search (DhWindow *window, + const gchar *str, + const gchar *book_id); +void dh_window_focus_search (DhWindow *window); +void _dh_window_display_uri (DhWindow *window, + const gchar *uri); + +G_END_DECLS + +#endif /* __DH_WINDOW_H__ */ diff --git a/plugins/help/devhelp/eggfindbar.c b/plugins/help/devhelp/eggfindbar.c new file mode 100644 index 0000000..f6e1dba --- /dev/null +++ b/plugins/help/devhelp/eggfindbar.c @@ -0,0 +1,755 @@ +/* Copyright (C) 2004 Red Hat, Inc. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with the Gnome Library; see the file COPYING.LIB. If not, +write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. +*/ + +#include "config.h" + +#include + +#include +#include +#include + +#include "eggfindbar.h" + +struct _EggFindBarPrivate +{ + gchar *search_string; + + GtkToolItem *next_button; + GtkToolItem *previous_button; + GtkToolItem *status_separator; + GtkToolItem *status_item; + GtkToolItem *case_button; + + GtkWidget *find_entry; + GtkWidget *status_label; + + gulong set_focus_handler; + guint case_sensitive : 1; +}; + +#define EGG_FIND_BAR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EGG_TYPE_FIND_BAR, EggFindBarPrivate)) + +enum { + PROP_0, + PROP_SEARCH_STRING, + PROP_CASE_SENSITIVE +}; + +static void egg_find_bar_finalize (GObject *object); +static void egg_find_bar_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void egg_find_bar_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void egg_find_bar_show (GtkWidget *widget); +static void egg_find_bar_hide (GtkWidget *widget); +static void egg_find_bar_grab_focus (GtkWidget *widget); + +G_DEFINE_TYPE (EggFindBar, egg_find_bar, GTK_TYPE_TOOLBAR); + +enum + { + NEXT, + PREVIOUS, + CLOSE, + SCROLL, + LAST_SIGNAL + }; + +static guint find_bar_signals[LAST_SIGNAL] = { 0 }; + +static void +egg_find_bar_class_init (EggFindBarClass *klass) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkBindingSet *binding_set; + + egg_find_bar_parent_class = g_type_class_peek_parent (klass); + + object_class = (GObjectClass *)klass; + widget_class = (GtkWidgetClass *)klass; + + object_class->set_property = egg_find_bar_set_property; + object_class->get_property = egg_find_bar_get_property; + + object_class->finalize = egg_find_bar_finalize; + + widget_class->show = egg_find_bar_show; + widget_class->hide = egg_find_bar_hide; + + widget_class->grab_focus = egg_find_bar_grab_focus; + + find_bar_signals[NEXT] = + g_signal_new ("next", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (EggFindBarClass, next), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + find_bar_signals[PREVIOUS] = + g_signal_new ("previous", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (EggFindBarClass, previous), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + find_bar_signals[CLOSE] = + g_signal_new ("close", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EggFindBarClass, close), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + find_bar_signals[SCROLL] = + g_signal_new ("scroll", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (EggFindBarClass, scroll), + NULL, NULL, + g_cclosure_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + GTK_TYPE_SCROLL_TYPE); + + /** + * EggFindBar:search_string: + * + * The current string to search for. NULL or empty string + * both mean no current string. + * + */ + g_object_class_install_property (object_class, + PROP_SEARCH_STRING, + g_param_spec_string ("search_string", + ("Search string"), + ("The name of the string to be found"), + NULL, + G_PARAM_READWRITE)); + + /** + * EggFindBar:case_sensitive: + * + * TRUE for a case sensitive search. + * + */ + g_object_class_install_property (object_class, + PROP_CASE_SENSITIVE, + g_param_spec_boolean ("case_sensitive", + ("Case sensitive"), + ("TRUE for a case sensitive search"), + FALSE, + G_PARAM_READWRITE)); + + /* Style properties */ + gtk_widget_class_install_style_property (widget_class, + g_param_spec_boxed ("all_matches_color", + ("Highlight color"), + ("Color of highlight for all matches"), + GDK_TYPE_COLOR, + G_PARAM_READABLE)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_boxed ("current_match_color", + ("Current color"), + ("Color of highlight for the current match"), + GDK_TYPE_COLOR, + G_PARAM_READABLE)); + + g_type_class_add_private (object_class, sizeof (EggFindBarPrivate)); + + binding_set = gtk_binding_set_by_class (klass); + + gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0, + "close", 0); + + gtk_binding_entry_add_signal (binding_set, GDK_Up, 0, + "scroll", 1, + GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_STEP_BACKWARD); + gtk_binding_entry_add_signal (binding_set, GDK_Down, 0, + "scroll", 1, + GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_STEP_FORWARD); + gtk_binding_entry_add_signal (binding_set, GDK_Page_Up, 0, + "scroll", 1, + GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_BACKWARD); + gtk_binding_entry_add_signal (binding_set, GDK_KP_Page_Up, 0, + "scroll", 1, + GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_BACKWARD); + gtk_binding_entry_add_signal (binding_set, GDK_Page_Down, 0, + "scroll", 1, + GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_FORWARD); + gtk_binding_entry_add_signal (binding_set, GDK_KP_Page_Down, 0, + "scroll", 1, + GTK_TYPE_SCROLL_TYPE, GTK_SCROLL_PAGE_FORWARD); +} + +static void +egg_find_bar_emit_next (EggFindBar *find_bar) +{ + g_signal_emit (find_bar, find_bar_signals[NEXT], 0); +} + +static void +egg_find_bar_emit_previous (EggFindBar *find_bar) +{ + g_signal_emit (find_bar, find_bar_signals[PREVIOUS], 0); +} + +static void +next_clicked_callback (GtkButton *button, + void *data) +{ + EggFindBar *find_bar = EGG_FIND_BAR (data); + + egg_find_bar_emit_next (find_bar); +} + +static void +previous_clicked_callback (GtkButton *button, + void *data) +{ + EggFindBar *find_bar = EGG_FIND_BAR (data); + + egg_find_bar_emit_previous (find_bar); +} + +static void +case_sensitive_toggled_callback (GtkCheckButton *button, + void *data) +{ + EggFindBar *find_bar = EGG_FIND_BAR (data); + + egg_find_bar_set_case_sensitive (find_bar, + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))); +} + +static void +entry_activate_callback (GtkEntry *entry, + void *data) +{ + EggFindBar *find_bar = EGG_FIND_BAR (data); + + if (find_bar->priv->search_string != NULL) + egg_find_bar_emit_next (find_bar); +} + +static void +entry_changed_callback (GtkEntry *entry, + void *data) +{ + EggFindBar *find_bar = EGG_FIND_BAR (data); + char *text; + + /* paranoid strdup because set_search_string() sets + * the entry text + */ + text = g_strdup (gtk_entry_get_text (entry)); + + egg_find_bar_set_search_string (find_bar, text); + + g_free (text); +} + +static void +set_focus_cb (GtkWidget *window, + GtkWidget *widget, + EggFindBar *bar) +{ + GtkWidget *wbar = GTK_WIDGET (bar); + + while (widget != NULL && widget != wbar) + { + widget = gtk_widget_get_parent (widget); + } + + /* if widget == bar, the new focus widget is in the bar, so we + * don't deactivate. + */ + if (widget != wbar) + { + g_signal_emit (bar, find_bar_signals[CLOSE], 0); + } +} + +static void +egg_find_bar_init (EggFindBar *find_bar) +{ + EggFindBarPrivate *priv; + GtkWidget *label; + GtkWidget *alignment; + GtkWidget *box; + GtkToolItem *item; + GtkWidget *arrow; + + /* Data */ + priv = EGG_FIND_BAR_GET_PRIVATE (find_bar); + + find_bar->priv = priv; + priv->search_string = NULL; + + gtk_toolbar_set_style (GTK_TOOLBAR (find_bar), GTK_TOOLBAR_BOTH_HORIZ); + + /* Find: |_____| */ + item = gtk_tool_item_new (); + box = gtk_hbox_new (FALSE, 12); + + alignment = gtk_alignment_new (0.0, 0.5, 1.0, 0.0); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 2, 2); + + label = gtk_label_new_with_mnemonic (_("Find:")); + + priv->find_entry = gtk_entry_new (); + gtk_entry_set_width_chars (GTK_ENTRY (priv->find_entry), 32); + gtk_entry_set_max_length (GTK_ENTRY (priv->find_entry), 512); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->find_entry); + + /* Prev */ + arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE); + priv->previous_button = gtk_tool_button_new (arrow, Q_("Find Previous")); + gtk_tool_item_set_is_important (priv->previous_button, TRUE); +#if GTK_CHECK_VERSION (2, 11, 5) + gtk_widget_set_tooltip_text (GTK_WIDGET (priv->previous_button), + _("Find previous occurrence of the search string")); +#else + gtk_tool_item_set_tooltip (priv->previous_button, GTK_TOOLBAR (find_bar)->tooltips, + _("Find previous occurrence of the search string"), + NULL); +#endif + + /* Next */ + arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE); + priv->next_button = gtk_tool_button_new (arrow, Q_("Find Next")); + gtk_tool_item_set_is_important (priv->next_button, TRUE); +#if GTK_CHECK_VERSION (2, 11, 5) + gtk_widget_set_tooltip_text (GTK_WIDGET (priv->next_button), + _("Find next occurrence of the search string")); +#else + gtk_tool_item_set_tooltip (priv->next_button, GTK_TOOLBAR (find_bar)->tooltips, + _("Find next occurrence of the search string"), + NULL); +#endif + + /* Separator*/ + priv->status_separator = gtk_separator_tool_item_new(); + + /* Case button */ + priv->case_button = gtk_toggle_tool_button_new (); + g_object_set (G_OBJECT (priv->case_button), "label", _("C_ase Sensitive"), NULL); + gtk_tool_item_set_is_important (priv->case_button, TRUE); +#if GTK_CHECK_VERSION (2, 11, 5) + gtk_widget_set_tooltip_text (GTK_WIDGET (priv->case_button), + _("Toggle case sensitive search")); +#else + gtk_tool_item_set_tooltip (priv->case_button, GTK_TOOLBAR (find_bar)->tooltips, + _("Toggle case sensitive search"), + NULL); +#endif + /* Status */ + priv->status_item = gtk_tool_item_new(); + gtk_tool_item_set_expand (priv->status_item, TRUE); + priv->status_label = gtk_label_new (NULL); + gtk_label_set_ellipsize (GTK_LABEL (priv->status_label), + PANGO_ELLIPSIZE_END); + gtk_misc_set_alignment (GTK_MISC (priv->status_label), 0.0, 0.5); + + + g_signal_connect (priv->find_entry, "changed", + G_CALLBACK (entry_changed_callback), + find_bar); + g_signal_connect (priv->find_entry, "activate", + G_CALLBACK (entry_activate_callback), + find_bar); + g_signal_connect (priv->next_button, "clicked", + G_CALLBACK (next_clicked_callback), + find_bar); + g_signal_connect (priv->previous_button, "clicked", + G_CALLBACK (previous_clicked_callback), + find_bar); + g_signal_connect (priv->case_button, "toggled", + G_CALLBACK (case_sensitive_toggled_callback), + find_bar); + + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (box), priv->find_entry, TRUE, TRUE, 0); + gtk_container_add (GTK_CONTAINER (alignment), box); + gtk_container_add (GTK_CONTAINER (item), alignment); + gtk_toolbar_insert (GTK_TOOLBAR (find_bar), item, -1); + gtk_toolbar_insert (GTK_TOOLBAR (find_bar), priv->previous_button, -1); + gtk_toolbar_insert (GTK_TOOLBAR (find_bar), priv->next_button, -1); + gtk_toolbar_insert (GTK_TOOLBAR (find_bar), priv->case_button, -1); + gtk_toolbar_insert (GTK_TOOLBAR (find_bar), priv->status_separator, -1); + gtk_container_add (GTK_CONTAINER (priv->status_item), priv->status_label); + gtk_toolbar_insert (GTK_TOOLBAR (find_bar), priv->status_item, -1); + + /* don't show status separator/label until they are set */ + + gtk_widget_show_all (GTK_WIDGET (item)); + gtk_widget_show_all (GTK_WIDGET (priv->next_button)); + gtk_widget_show_all (GTK_WIDGET (priv->previous_button)); + gtk_widget_show (priv->status_label); +} + +static void +egg_find_bar_finalize (GObject *object) +{ + EggFindBar *find_bar = EGG_FIND_BAR (object); + EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->priv; + + g_free (priv->search_string); + + G_OBJECT_CLASS (egg_find_bar_parent_class)->finalize (object); +} + +static void +egg_find_bar_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EggFindBar *find_bar = EGG_FIND_BAR (object); + + switch (prop_id) + { + case PROP_SEARCH_STRING: + egg_find_bar_set_search_string (find_bar, g_value_get_string (value)); + break; + case PROP_CASE_SENSITIVE: + egg_find_bar_set_case_sensitive (find_bar, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +egg_find_bar_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EggFindBar *find_bar = EGG_FIND_BAR (object); + EggFindBarPrivate *priv = (EggFindBarPrivate *)find_bar->priv; + + switch (prop_id) + { + case PROP_SEARCH_STRING: + g_value_set_string (value, priv->search_string); + break; + case PROP_CASE_SENSITIVE: + g_value_set_boolean (value, priv->case_sensitive); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +egg_find_bar_show (GtkWidget *widget) +{ + EggFindBar *bar = EGG_FIND_BAR (widget); + EggFindBarPrivate *priv = bar->priv; + + GTK_WIDGET_CLASS (egg_find_bar_parent_class)->show (widget); + + if (priv->set_focus_handler == 0) + { + GtkWidget *toplevel; + + toplevel = gtk_widget_get_toplevel (widget); + + priv->set_focus_handler = + g_signal_connect (toplevel, "set-focus", + G_CALLBACK (set_focus_cb), bar); + } +} + +static void +egg_find_bar_hide (GtkWidget *widget) +{ + EggFindBar *bar = EGG_FIND_BAR (widget); + EggFindBarPrivate *priv = bar->priv; + + if (priv->set_focus_handler != 0) + { + GtkWidget *toplevel; + + toplevel = gtk_widget_get_toplevel (widget); + + g_signal_handlers_disconnect_by_func + (toplevel, (void (*)) G_CALLBACK (set_focus_cb), bar); + priv->set_focus_handler = 0; + } + + GTK_WIDGET_CLASS (egg_find_bar_parent_class)->hide (widget); +} + +static void +egg_find_bar_grab_focus (GtkWidget *widget) +{ + EggFindBar *find_bar = EGG_FIND_BAR (widget); + EggFindBarPrivate *priv = find_bar->priv; + + gtk_widget_grab_focus (priv->find_entry); +} + +/** + * egg_find_bar_new: + * + * Creates a new #EggFindBar. + * + * Returns: a newly created #EggFindBar + * + * Since: 2.6 + */ +GtkWidget * +egg_find_bar_new (void) +{ + EggFindBar *find_bar; + + find_bar = g_object_new (EGG_TYPE_FIND_BAR, NULL); + + return GTK_WIDGET (find_bar); +} + +/** + * egg_find_bar_set_search_string: + * + * Sets the string that should be found/highlighted in the document. + * Empty string is converted to NULL. + * + * Since: 2.6 + */ +void +egg_find_bar_set_search_string (EggFindBar *find_bar, + const char *search_string) +{ + EggFindBarPrivate *priv; + + g_return_if_fail (EGG_IS_FIND_BAR (find_bar)); + + priv = (EggFindBarPrivate *)find_bar->priv; + + g_object_freeze_notify (G_OBJECT (find_bar)); + + if (priv->search_string != search_string) + { + char *old; + + old = priv->search_string; + + if (search_string && *search_string == '\0') + search_string = NULL; + + /* Only update if the string has changed; setting the entry + * will emit changed on the entry which will re-enter + * this function, but we'll handle that fine with this + * short-circuit. + */ + if ((old && search_string == NULL) || + (old == NULL && search_string) || + (old && search_string && + strcmp (old, search_string) != 0)) + { + gboolean not_empty; + + priv->search_string = g_strdup (search_string); + g_free (old); + + gtk_entry_set_text (GTK_ENTRY (priv->find_entry), + priv->search_string ? + priv->search_string : + ""); + + not_empty = (search_string == NULL) ? FALSE : TRUE; + + gtk_widget_set_sensitive (GTK_WIDGET (find_bar->priv->next_button), not_empty); + gtk_widget_set_sensitive (GTK_WIDGET (find_bar->priv->previous_button), not_empty); + + g_object_notify (G_OBJECT (find_bar), + "search_string"); + } + } + + g_object_thaw_notify (G_OBJECT (find_bar)); +} + + +/** + * egg_find_bar_get_search_string: + * + * Gets the string that should be found/highlighted in the document. + * + * Returns: the string + * + * Since: 2.6 + */ +const char* +egg_find_bar_get_search_string (EggFindBar *find_bar) +{ + EggFindBarPrivate *priv; + + g_return_val_if_fail (EGG_IS_FIND_BAR (find_bar), NULL); + + priv = find_bar->priv; + + return priv->search_string ? priv->search_string : ""; +} + +/** + * egg_find_bar_set_case_sensitive: + * + * Sets whether the search is case sensitive + * + * Since: 2.6 + */ +void +egg_find_bar_set_case_sensitive (EggFindBar *find_bar, + gboolean case_sensitive) +{ + EggFindBarPrivate *priv; + + g_return_if_fail (EGG_IS_FIND_BAR (find_bar)); + + priv = (EggFindBarPrivate *)find_bar->priv; + + g_object_freeze_notify (G_OBJECT (find_bar)); + + case_sensitive = case_sensitive != FALSE; + + if (priv->case_sensitive != case_sensitive) + { + priv->case_sensitive = case_sensitive; + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->case_button), + priv->case_sensitive); + + g_object_notify (G_OBJECT (find_bar), + "case_sensitive"); + } + + g_object_thaw_notify (G_OBJECT (find_bar)); +} + +/** + * egg_find_bar_get_case_sensitive: + * + * Gets whether the search is case sensitive + * + * Returns: TRUE if it's case sensitive + * + * Since: 2.6 + */ +gboolean +egg_find_bar_get_case_sensitive (EggFindBar *find_bar) +{ + EggFindBarPrivate *priv; + + g_return_val_if_fail (EGG_IS_FIND_BAR (find_bar), FALSE); + + priv = (EggFindBarPrivate *)find_bar->priv; + + return priv->case_sensitive; +} + +static void +get_style_color (EggFindBar *find_bar, + const char *style_prop_name, + GdkColor *color) +{ + GdkColor *style_color; + + gtk_widget_ensure_style (GTK_WIDGET (find_bar)); + gtk_widget_style_get (GTK_WIDGET (find_bar), + "color", &style_color, NULL); + if (style_color) + { + *color = *style_color; + gdk_color_free (style_color); + } +} + +/** + * egg_find_bar_get_all_matches_color: + * + * Gets the color to use to highlight all the + * known matches. + * + * Since: 2.6 + */ +void +egg_find_bar_get_all_matches_color (EggFindBar *find_bar, + GdkColor *color) +{ + GdkColor found_color = { 0, 0, 0, 0x0f0f }; + + get_style_color (find_bar, "all_matches_color", &found_color); + + *color = found_color; +} + +/** + * egg_find_bar_get_current_match_color: + * + * Gets the color to use to highlight the match + * we're currently on. + * + * Since: 2.6 + */ +void +egg_find_bar_get_current_match_color (EggFindBar *find_bar, + GdkColor *color) +{ + GdkColor found_color = { 0, 0, 0, 0xffff }; + + get_style_color (find_bar, "current_match_color", &found_color); + + *color = found_color; +} + +/** + * egg_find_bar_set_status_text: + * + * Sets some text to display if there's space; typical text would + * be something like "5 results on this page" or "No results" + * + * @text: the text to display + * + * Since: 2.6 + */ +void +egg_find_bar_set_status_text (EggFindBar *find_bar, + const char *text) +{ + EggFindBarPrivate *priv; + + g_return_if_fail (EGG_IS_FIND_BAR (find_bar)); + + priv = (EggFindBarPrivate *)find_bar->priv; + + gtk_label_set_text (GTK_LABEL (priv->status_label), text); + g_object_set (priv->status_separator, "visible", text != NULL && *text != '\0', NULL); + g_object_set (priv->status_item, "visible", text != NULL && *text !='\0', NULL); +} diff --git a/plugins/help/devhelp/eggfindbar.h b/plugins/help/devhelp/eggfindbar.h new file mode 100644 index 0000000..eef5561 --- /dev/null +++ b/plugins/help/devhelp/eggfindbar.h @@ -0,0 +1,81 @@ +/* Copyright (C) 2004 Red Hat, Inc. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with the Gnome Library; see the file COPYING.LIB. If not, +write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. +*/ + +#ifndef __EGG_FIND_BAR_H__ +#define __EGG_FIND_BAR_H__ + +#include + +G_BEGIN_DECLS + +#define EGG_TYPE_FIND_BAR (egg_find_bar_get_type ()) +#define EGG_FIND_BAR(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), EGG_TYPE_FIND_BAR, EggFindBar)) +#define EGG_FIND_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_FIND_BAR, EggFindBarClass)) +#define EGG_IS_FIND_BAR(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), EGG_TYPE_FIND_BAR)) +#define EGG_IS_FIND_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_FIND_BAR)) +#define EGG_FIND_BAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_FIND_BAR, EggFindBarClass)) + +typedef struct _EggFindBar EggFindBar; +typedef struct _EggFindBarClass EggFindBarClass; +typedef struct _EggFindBarPrivate EggFindBarPrivate; + +struct _EggFindBar +{ + GtkToolbar parent; + + /*< private >*/ + EggFindBarPrivate *priv; +}; + +struct _EggFindBarClass +{ + GtkToolbarClass parent_class; + + void (* next) (EggFindBar *find_bar); + void (* previous) (EggFindBar *find_bar); + void (* close) (EggFindBar *find_bar); + void (* scroll) (EggFindBar *find_bar, GtkScrollType* scroll); + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); +}; + +GType egg_find_bar_get_type (void) G_GNUC_CONST; +GtkWidget *egg_find_bar_new (void); + +void egg_find_bar_set_search_string (EggFindBar *find_bar, + const char *search_string); +const char* egg_find_bar_get_search_string (EggFindBar *find_bar); +void egg_find_bar_set_case_sensitive (EggFindBar *find_bar, + gboolean case_sensitive); +gboolean egg_find_bar_get_case_sensitive (EggFindBar *find_bar); +void egg_find_bar_get_all_matches_color (EggFindBar *find_bar, + GdkColor *color); +void egg_find_bar_get_current_match_color (EggFindBar *find_bar, + GdkColor *color); +void egg_find_bar_set_status_text (EggFindBar *find_bar, + const char *text); + +G_END_DECLS + +#endif /* __EGG_FIND_BAR_H__ */ + + diff --git a/plugins/help/devhelp/ige-conf-gconf.c b/plugins/help/devhelp/ige-conf-gconf.c new file mode 100644 index 0000000..6672204 --- /dev/null +++ b/plugins/help/devhelp/ige-conf-gconf.c @@ -0,0 +1,387 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include +#include "ige-conf-private.h" + +typedef struct { + GConfClient *gconf_client; + GList *defaults; +} IgeConfPriv; + +typedef struct { + IgeConf *conf; + IgeConfNotifyFunc func; + gpointer user_data; +} IgeConfNotifyData; + +G_DEFINE_TYPE (IgeConf, ige_conf, G_TYPE_OBJECT); + +#define GET_PRIVATE(instance) G_TYPE_INSTANCE_GET_PRIVATE \ + (instance, IGE_TYPE_CONF, IgeConfPriv); + +static IgeConf *global_conf = NULL; + +static void +conf_finalize (GObject *object) +{ + IgeConfPriv *priv; + + priv = GET_PRIVATE (object); + + /* FIXME: Remove added dirs. + gconf_client_remove_dir (priv->gconf_client, + CONF_PATH, + NULL); + */ + + g_object_unref (priv->gconf_client); + + _ige_conf_defaults_free_list (priv->defaults); + + G_OBJECT_CLASS (ige_conf_parent_class)->finalize (object); +} + +static void +ige_conf_class_init (IgeConfClass *class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (class); + + object_class->finalize = conf_finalize; + + g_type_class_add_private (object_class, sizeof (IgeConfPriv)); +} + +static void +ige_conf_init (IgeConf *conf) +{ + IgeConfPriv *priv; + + priv = GET_PRIVATE (conf); + + priv->gconf_client = gconf_client_get_default (); +} + +IgeConf * +ige_conf_get (void) +{ + if (!global_conf) { + global_conf = g_object_new (IGE_TYPE_CONF, NULL); + } + + return global_conf; +} + +void +ige_conf_add_defaults (IgeConf *conf, + const gchar *path) +{ + IgeConfPriv *priv = GET_PRIVATE (conf); + gchar *root; + + priv->defaults = _ige_conf_defaults_read_file (path, NULL); + root = _ige_conf_defaults_get_root (priv->defaults); + + gconf_client_add_dir (priv->gconf_client, + root, + GCONF_CLIENT_PRELOAD_ONELEVEL, + NULL); + + g_free (root); +} + +static GConfEntry * +conf_get_entry (IgeConf *conf, + const gchar *key) +{ + IgeConfPriv *priv; + + priv = GET_PRIVATE (conf); + + return gconf_client_get_entry (priv->gconf_client, key, + NULL, TRUE, NULL); +} + +gboolean +ige_conf_set_int (IgeConf *conf, + const gchar *key, + gint value) +{ + IgeConfPriv *priv; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + return gconf_client_set_int (priv->gconf_client, + key, + value, + NULL); +} + +gboolean +ige_conf_get_int (IgeConf *conf, + const gchar *key, + gint *value) +{ + IgeConfPriv *priv; + GConfEntry *entry; + gboolean got_value = FALSE; + + *value = 0; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + priv = GET_PRIVATE (conf); + + entry = conf_get_entry (conf, key); + if (entry) { + GConfValue *v; + + v = gconf_entry_get_value (entry); + if (v) { + *value = gconf_value_get_int (v); + got_value = TRUE; + } + } + + gconf_entry_free (entry); + + if (!got_value) { + *value = _ige_conf_defaults_get_int (priv->defaults, key); + } + + return TRUE; +} + +gboolean +ige_conf_set_bool (IgeConf *conf, + const gchar *key, + gboolean value) +{ + IgeConfPriv *priv; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + return gconf_client_set_bool (priv->gconf_client, + key, + value, + NULL); +} + +gboolean +ige_conf_get_bool (IgeConf *conf, + const gchar *key, + gboolean *value) +{ + IgeConfPriv *priv; + GConfEntry *entry; + gboolean got_value = FALSE; + + *value = 0; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + g_return_val_if_fail (value != NULL, FALSE); + + priv = GET_PRIVATE (conf); + + entry = conf_get_entry (conf, key); + if (entry) { + GConfValue *v; + + v = gconf_entry_get_value (entry); + if (v) { + *value = gconf_value_get_bool (v); + got_value = TRUE; + } + } + + gconf_entry_free (entry); + + if (!got_value) { + *value = _ige_conf_defaults_get_bool (priv->defaults, key); + } + + return TRUE; +} + +gboolean +ige_conf_set_string (IgeConf *conf, + const gchar *key, + const gchar *value) +{ + IgeConfPriv *priv; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + return gconf_client_set_string (priv->gconf_client, + key, + value, + NULL); +} + +gboolean +ige_conf_get_string (IgeConf *conf, + const gchar *key, + gchar **value) +{ + IgeConfPriv *priv; + GError *error = NULL; + + *value = NULL; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + *value = gconf_client_get_string (priv->gconf_client, + key, + &error); + + if (error) { + g_error_free (error); + return FALSE; + } + + if (*value == NULL) { + *value = g_strdup (_ige_conf_defaults_get_string (priv->defaults, key)); + } + + return TRUE; +} + +gboolean +ige_conf_set_string_list (IgeConf *conf, + const gchar *key, + GSList *value) +{ + IgeConfPriv *priv; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + return gconf_client_set_list (priv->gconf_client, + key, + GCONF_VALUE_STRING, + value, + NULL); +} + +gboolean +ige_conf_get_string_list (IgeConf *conf, + const gchar *key, + GSList **value) +{ + IgeConfPriv *priv; + GError *error = NULL; + + *value = NULL; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + *value = gconf_client_get_list (priv->gconf_client, + key, + GCONF_VALUE_STRING, + &error); + if (error) { + g_error_free (error); + return FALSE; + } + + return TRUE; +} + +static void +conf_notify_data_free (IgeConfNotifyData *data) +{ + g_object_unref (data->conf); + g_slice_free (IgeConfNotifyData, data); +} + +static void +conf_notify_func (GConfClient *client, + guint id, + GConfEntry *entry, + gpointer user_data) +{ + IgeConfNotifyData *data; + + data = user_data; + + data->func (data->conf, + gconf_entry_get_key (entry), + data->user_data); +} + +guint +ige_conf_notify_add (IgeConf *conf, + const gchar *key, + IgeConfNotifyFunc func, + gpointer user_data) +{ + IgeConfPriv *priv; + guint id; + IgeConfNotifyData *data; + + g_return_val_if_fail (IGE_IS_CONF (conf), 0); + + priv = GET_PRIVATE (conf); + + data = g_slice_new (IgeConfNotifyData); + data->func = func; + data->user_data = user_data; + data->conf = g_object_ref (conf); + + id = gconf_client_notify_add (priv->gconf_client, + key, + conf_notify_func, + data, + (GFreeFunc) conf_notify_data_free, + NULL); + + return id; +} + +gboolean +ige_conf_notify_remove (IgeConf *conf, + guint id) +{ + IgeConfPriv *priv; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + gconf_client_notify_remove (priv->gconf_client, id); + + return TRUE; +} diff --git a/plugins/help/devhelp/ige-conf-mac.c b/plugins/help/devhelp/ige-conf-mac.c new file mode 100644 index 0000000..5b1302a --- /dev/null +++ b/plugins/help/devhelp/ige-conf-mac.c @@ -0,0 +1,342 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#import +#include +#include "ige-conf-private.h" + +typedef struct { + NSUserDefaults *defaults; +} IgeConfPriv; + +typedef struct { + IgeConf *conf; + IgeConfNotifyFunc func; + gpointer user_data; +} IgeConfNotifyData; + +G_DEFINE_TYPE (IgeConf, ige_conf, G_TYPE_OBJECT); + +#define GET_PRIVATE(instance) G_TYPE_INSTANCE_GET_PRIVATE \ + (instance, IGE_TYPE_CONF, IgeConfPriv); + +static IgeConf *global_conf = NULL; + +static void +conf_finalize (GObject *object) +{ + IgeConfPriv *priv = GET_PRIVATE (object); + + [priv->defaults synchronize]; + + if (IGE_CONF (object) == global_conf) { + global_conf = NULL; + } + + G_OBJECT_CLASS (ige_conf_parent_class)->finalize (object); +} + +static void +ige_conf_class_init (IgeConfClass *class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (class); + + object_class->finalize = conf_finalize; + + g_type_class_add_private (object_class, sizeof (IgeConfPriv)); +} + +static void +ige_conf_init (IgeConf *conf) +{ +} + +static void +conf_atexit (void) +{ + if (global_conf) { + IgeConfPriv *priv = GET_PRIVATE (global_conf); + + [priv->defaults synchronize]; + } +} + +IgeConf * +ige_conf_get (void) +{ + if (!global_conf) { + global_conf = g_object_new (IGE_TYPE_CONF, NULL); + g_atexit (conf_atexit); + } + + return global_conf; +} + +void +ige_conf_add_defaults (IgeConf *conf, + const gchar *path) +{ + IgeConfPriv *priv = GET_PRIVATE (conf); + NSDictionary *dict; + GList *defaults, *l; + + priv->defaults = [NSUserDefaults standardUserDefaults]; + + dict = [NSMutableDictionary dictionaryWithCapacity: 10]; + + defaults = _ige_conf_defaults_read_file (path, NULL); + for (l = defaults; l; l = l->next) { + IgeConfDefaultItem *item = l->data; + NSString *key; + NSString *value; + + key = [NSString stringWithUTF8String: item->key]; + value = [NSString stringWithUTF8String: item->value]; + [dict setValue:value forKey:key]; + } + + _ige_conf_defaults_free_list (defaults); + + [priv->defaults registerDefaults: dict]; +} + +gboolean +ige_conf_set_int (IgeConf *conf, + const gchar *key, + gint value) +{ + IgeConfPriv *priv; + NSString *string; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + string = [NSString stringWithUTF8String: key]; + [priv->defaults setInteger: value forKey: string]; + + return TRUE; +} + +gboolean +ige_conf_get_int (IgeConf *conf, + const gchar *key, + gint *value) +{ + IgeConfPriv *priv; + NSString *string; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + string = [NSString stringWithUTF8String: key]; + *value = [priv->defaults integerForKey: string]; + + return TRUE; +} + +gboolean +ige_conf_set_bool (IgeConf *conf, + const gchar *key, + gboolean value) +{ + IgeConfPriv *priv; + NSString *string; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + string = [NSString stringWithUTF8String: key]; + [priv->defaults setBool: value forKey: string]; + + return TRUE; +} + +gboolean +ige_conf_get_bool (IgeConf *conf, + const gchar *key, + gboolean *value) +{ + IgeConfPriv *priv; + NSString *string; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + string = [NSString stringWithUTF8String: key]; + *value = [priv->defaults boolForKey: string]; + + return TRUE; +} + +gboolean +ige_conf_set_string (IgeConf *conf, + const gchar *key, + const gchar *value) +{ + IgeConfPriv *priv; + NSString *string, *nsvalue; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + string = [NSString stringWithUTF8String: key]; + nsvalue = [NSString stringWithUTF8String: value]; + + [priv->defaults setObject: nsvalue forKey: string]; + + return TRUE; +} + +gboolean +ige_conf_get_string (IgeConf *conf, + const gchar *key, + gchar **value) +{ + IgeConfPriv *priv; + NSString *string, *nsvalue; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + *value = NULL; + + string = [NSString stringWithUTF8String: key]; + nsvalue = [priv->defaults stringForKey: string]; + if (nsvalue == NULL) { + return FALSE; + } + + *value = g_strdup ([nsvalue UTF8String]); + + return TRUE; +} + +gboolean +ige_conf_set_string_list (IgeConf *conf, + const gchar *key, + GSList *value) +{ + IgeConfPriv *priv; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + return TRUE; /*gconf_client_set_string_list (priv->gconf_client, + key, + value, + NULL); + */ +} + +gboolean +ige_conf_get_string_list (IgeConf *conf, + const gchar *key, + GSList **value) +{ + IgeConfPriv *priv; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + *value = NULL; /*gconf_client_get_string_list (priv->gconf_client, + key, + &error); + */ + return TRUE; +} + +/* +static void +conf_notify_data_free (IgeConfNotifyData *data) +{ + g_object_unref (data->conf); + g_slice_free (IgeConfNotifyData, data); +} + +static void +conf_notify_func (GConfClient *client, + guint id, + GConfEntry *entry, + gpointer user_data) +{ + IgeConfNotifyData *data; + + data = user_data; + + data->func (data->conf, + gconf_entry_get_key (entry), + data->user_data); +} +*/ + +guint +ige_conf_notify_add (IgeConf *conf, + const gchar *key, + IgeConfNotifyFunc func, + gpointer user_data) +{ + IgeConfPriv *priv; + guint id; + IgeConfNotifyData *data; + + g_return_val_if_fail (IGE_IS_CONF (conf), 0); + + priv = GET_PRIVATE (conf); + + data = g_slice_new (IgeConfNotifyData); + data->func = func; + data->user_data = user_data; + data->conf = g_object_ref (conf); + + id = 0; /*gconf_client_notify_add (priv->gconf_client, + key, + conf_notify_func, + data, + (GFreeFunc) conf_notify_data_free, + NULL); + */ + return id; +} + +gboolean +ige_conf_notify_remove (IgeConf *conf, + guint id) +{ + IgeConfPriv *priv; + + g_return_val_if_fail (IGE_IS_CONF (conf), FALSE); + + priv = GET_PRIVATE (conf); + + /*gconf_client_notify_remove (priv->gconf_client, id);*/ + + return TRUE; +} diff --git a/plugins/help/devhelp/ige-conf-private.h b/plugins/help/devhelp/ige-conf-private.h new file mode 100644 index 0000000..b4348d5 --- /dev/null +++ b/plugins/help/devhelp/ige-conf-private.h @@ -0,0 +1,54 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __IGE_CONF_PRIVATE_H__ +#define __IGE_CONF_PRIVATE_H__ + +#include +#include "ige-conf.h" + +G_BEGIN_DECLS + +typedef enum { + IGE_CONF_TYPE_INT, + IGE_CONF_TYPE_BOOLEAN, + IGE_CONF_TYPE_STRING +} IgeConfType; + +typedef struct { + IgeConfType type; + gchar *key; + gchar *value; +} IgeConfDefaultItem; + +GList * _ige_conf_defaults_read_file (const gchar *path, + GError **error); +void _ige_conf_defaults_free_list (GList *defaults); +gchar * _ige_conf_defaults_get_root (GList *defaults); +const gchar *_ige_conf_defaults_get_string (GList *defaults, + const gchar *key); +gint _ige_conf_defaults_get_int (GList *defaults, + const gchar *key); +gboolean _ige_conf_defaults_get_bool (GList *defaults, + const gchar *key); + +G_END_DECLS + +#endif /* __IGE_CONF_PRIVATE_H__ */ diff --git a/plugins/help/devhelp/ige-conf.c b/plugins/help/devhelp/ige-conf.c new file mode 100644 index 0000000..2574ee4 --- /dev/null +++ b/plugins/help/devhelp/ige-conf.c @@ -0,0 +1,337 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include +#include "ige-conf-private.h" + +typedef struct { + GString *text; + + gchar *current_key; + gchar *current_value; + IgeConfType current_type; + + GList *defaults; +} DefaultData; + +#define BYTES_PER_READ 4096 + +static void +parser_start_cb (GMarkupParseContext *context, + const gchar *node_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + DefaultData *data = user_data; + + if (g_ascii_strcasecmp (node_name, "applyto") == 0) { + data->text = g_string_new (NULL); + } + else if (g_ascii_strcasecmp (node_name, "type") == 0) { + data->text = g_string_new (NULL); + } + else if (g_ascii_strcasecmp (node_name, "default") == 0) { + data->text = g_string_new (NULL); + } +} + +static void +parser_end_cb (GMarkupParseContext *context, + const gchar *node_name, + gpointer user_data, + GError **error) +{ + DefaultData *data = user_data; + + if (g_ascii_strcasecmp (node_name, "schema") == 0) { + IgeConfDefaultItem *item; + + item = g_slice_new0 (IgeConfDefaultItem); + item->key = data->current_key; + item->type = data->current_type; + + switch (item->type) { + case IGE_CONF_TYPE_INT: + case IGE_CONF_TYPE_STRING: + item->value = g_strdup (data->current_value); + break; + case IGE_CONF_TYPE_BOOLEAN: + if (strcmp (data->current_value, "true") == 0) { + item->value = g_strdup ("YES"); + } else { + item->value = g_strdup ("NO"); + } + break; + } + + data->defaults = g_list_prepend (data->defaults, item); + + data->current_key = NULL; + + g_free (data->current_value); + data->current_value = NULL; + } + else if (g_ascii_strcasecmp (node_name, "applyto") == 0) { + data->current_key = g_string_free (data->text, FALSE); + data->text = NULL; + } + else if (g_ascii_strcasecmp (node_name, "type") == 0) { + gchar *str; + + str = g_string_free (data->text, FALSE); + if (strcmp (str, "int") == 0) { + data->current_type = IGE_CONF_TYPE_INT; + } + else if (strcmp (str, "bool") == 0) { + data->current_type = IGE_CONF_TYPE_BOOLEAN; + } + else if (strcmp (str, "string") == 0) { + data->current_type = IGE_CONF_TYPE_STRING; + } + + g_free (str); + data->text = NULL; + } + else if (g_ascii_strcasecmp (node_name, "default") == 0) { + data->current_value = g_string_free (data->text, FALSE); + data->text = NULL; + } +} + +static void +parser_text_cb (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + DefaultData *data = user_data; + + if (data->text) { + g_string_append_len (data->text, text, text_len); + } +} + +static void +parser_error_cb (GMarkupParseContext *context, + GError *error, + gpointer user_data) +{ + g_warning ("Error: %s\n", error->message); +} + +GList * +_ige_conf_defaults_read_file (const gchar *path, + GError **error) +{ + DefaultData data; + GMarkupParser *parser; + GMarkupParseContext *context; + GIOChannel *io = NULL; + gchar buf[BYTES_PER_READ]; + + io = g_io_channel_new_file (path, "r", error); + if (!io) { + return NULL; + } + + parser = g_new0 (GMarkupParser, 1); + + parser->start_element = parser_start_cb; + parser->end_element = parser_end_cb; + parser->text = parser_text_cb; + parser->error = parser_error_cb; + + memset (&data, 0, sizeof (DefaultData)); + + context = g_markup_parse_context_new (parser, + 0, + &data, + NULL); + + while (TRUE) { + GIOStatus io_status; + gsize bytes_read; + + io_status = g_io_channel_read_chars (io, buf, BYTES_PER_READ, + &bytes_read, error); + if (io_status == G_IO_STATUS_ERROR) { + goto exit; + } + if (io_status != G_IO_STATUS_NORMAL) { + break; + } + + g_markup_parse_context_parse (context, buf, bytes_read, error); + if (error != NULL && *error != NULL) { + goto exit; + } + + if (bytes_read < BYTES_PER_READ) { + break; + } + } + + exit: + g_io_channel_unref (io); + g_markup_parse_context_free (context); + g_free (parser); + + return data.defaults; +} + +void +_ige_conf_defaults_free_list (GList *defaults) +{ + GList *l; + + for (l = defaults; l; l = l->next) { + IgeConfDefaultItem *item = l->data; + + g_free (item->value); + g_slice_free (IgeConfDefaultItem, item); + } + + g_list_free (defaults); +} + +gchar * +_ige_conf_defaults_get_root (GList *defaults) +{ + GList *l; + gchar *root; + gchar **strv_prev = NULL; + gint i; + gint last_common = G_MAXINT; + + for (l = defaults; l; l = l->next) { + IgeConfDefaultItem *item = l->data; + gchar **strv; + + strv = g_strsplit (item->key, "/", 0); + if (strv_prev == NULL) { + strv_prev = strv; + continue; + } + + i = 0; + while (strv[i] && strv_prev[i] && i < last_common) { + if (strcmp (strv[i], strv_prev[i]) != 0) { + last_common = i; + break; + } + i++; + } + + g_strfreev (strv_prev); + strv_prev = strv; + } + + if (strv_prev) { + GString *str; + + str = g_string_new (NULL); + i = 0; + while (strv_prev[i] && i < last_common) { + if (strv_prev[i][0] != '\0') { + g_string_append_c (str, '/'); + g_string_append (str, strv_prev[i]); + } + i++; + } + root = g_string_free (str, FALSE); + g_strfreev (strv_prev); + } else { + root = g_strdup ("/"); + } + + return root; +} + +static IgeConfDefaultItem * +defaults_get_item (GList *defaults, + const gchar *key) +{ + GList *l; + + for (l = defaults; l; l = l->next) { + IgeConfDefaultItem *item = l->data; + + if (strcmp (item->key, key) == 0) { + return item; + } + } + + return NULL; +} + +const gchar * +_ige_conf_defaults_get_string (GList *defaults, + const gchar *key) +{ + IgeConfDefaultItem *item; + + item = defaults_get_item (defaults, key); + + if (item) { + return item->value; + } + + return NULL; +} + +gint +_ige_conf_defaults_get_int (GList *defaults, + const gchar *key) +{ + IgeConfDefaultItem *item; + + item = defaults_get_item (defaults, key); + + if (item) { + return strtol (item->value, NULL, 10); + } + + return 0; +} + +gboolean +_ige_conf_defaults_get_bool (GList *defaults, + const gchar *key) +{ + IgeConfDefaultItem *item; + + item = defaults_get_item (defaults, key); + + if (item) { + if (strcmp (item->value, "false") == 0) { + return FALSE; + } + else if (strcmp (item->value, "true") == 0) { + return TRUE; + } + } + + return FALSE; +} diff --git a/plugins/help/devhelp/ige-conf.h b/plugins/help/devhelp/ige-conf.h new file mode 100644 index 0000000..333e913 --- /dev/null +++ b/plugins/help/devhelp/ige-conf.h @@ -0,0 +1,87 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2008 Imendio AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __IGE_CONF_H__ +#define __IGE_CONF_H__ + +#include + +G_BEGIN_DECLS + +#define IGE_TYPE_CONF (ige_conf_get_type ()) +#define IGE_CONF(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), IGE_TYPE_CONF, IgeConf)) +#define IGE_CONF_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), IGE_TYPE_CONF, IgeConfClass)) +#define IGE_IS_CONF(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), IGE_TYPE_CONF)) +#define IGE_IS_CONF_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), IGE_TYPE_CONF)) +#define IGE_CONF_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), IGE_TYPE_CONF, IgeConfClass)) + +typedef struct _IgeConf IgeConf; +typedef struct _IgeConfClass IgeConfClass; + +struct _IgeConf { + GObject parent_instance; +}; + +struct _IgeConfClass { + GObjectClass parent_class; +}; + +typedef void (*IgeConfNotifyFunc) (IgeConf *conf, + const gchar *key, + gpointer user_data); + +GType ige_conf_get_type (void); +IgeConf *ige_conf_get (void); +void ige_conf_add_defaults (IgeConf *conf, + const gchar *path); +guint ige_conf_notify_add (IgeConf *conf, + const gchar *key, + IgeConfNotifyFunc func, + gpointer data); +gboolean ige_conf_notify_remove (IgeConf *conf, + guint id); +gboolean ige_conf_set_int (IgeConf *conf, + const gchar *key, + gint value); +gboolean ige_conf_get_int (IgeConf *conf, + const gchar *key, + gint *value); +gboolean ige_conf_set_bool (IgeConf *conf, + const gchar *key, + gboolean value); +gboolean ige_conf_get_bool (IgeConf *conf, + const gchar *key, + gboolean *value); +gboolean ige_conf_set_string (IgeConf *conf, + const gchar *key, + const gchar *value); +gboolean ige_conf_get_string (IgeConf *conf, + const gchar *key, + gchar **value); +gboolean ige_conf_set_string_list (IgeConf *conf, + const gchar *key, + GSList *value); +gboolean ige_conf_get_string_list (IgeConf *conf, + const gchar *key, + GSList **value); + +G_END_DECLS + +#endif /* __IGE_CONF_H__ */ diff --git a/plugins/help/devhelp/libdevhelp-2.0.deps b/plugins/help/devhelp/libdevhelp-2.0.deps new file mode 100644 index 0000000..2e8b4f7 --- /dev/null +++ b/plugins/help/devhelp/libdevhelp-2.0.deps @@ -0,0 +1,2 @@ +gtk+-2.0 +webkit-1.0 diff --git a/plugins/help/devhelp/libdevhelp-2.0.vapi b/plugins/help/devhelp/libdevhelp-2.0.vapi new file mode 100644 index 0000000..fb44cfe --- /dev/null +++ b/plugins/help/devhelp/libdevhelp-2.0.vapi @@ -0,0 +1,143 @@ +/* libdevhelp-2.0.vapi generated by vapigen, do not modify. */ + +[CCode (cprefix = "Dh", lower_case_cprefix = "dh_")] +namespace Dh { + [CCode (cheader_filename = "devhelp/dh-assistant.h")] + public class Assistant : Gtk.Window, Atk.Implementor, Gtk.Buildable { + [CCode (type = "GtkWidget*", has_construct_function = false)] + public Assistant (Dh.Base @base); + public bool search (string str); + } + [CCode (cheader_filename = "devhelp/dh-assistant-view.h")] + public class AssistantView : WebKit.WebView, Atk.Implementor, Gtk.Buildable { + [CCode (type = "GtkWidget*", has_construct_function = false)] + public AssistantView (); + public unowned Dh.Base get_base (); + public bool search (string str); + public void set_base (Dh.Base @base); + public bool set_link (Dh.Link link); + } + [CCode (cheader_filename = "devhelp/dh-base.h")] + public class Base : GLib.Object { + [CCode (has_construct_function = false)] + public Base (); + [CCode (type = "GtkWidget*", has_construct_function = false)] + public Base.assistant (Dh.Base @base); + public static unowned Dh.Base @get (); + public unowned Dh.BookManager get_book_manager (); + public unowned Gtk.Widget get_window (); + public unowned Gtk.Widget get_window_on_current_workspace (); + public void quit (); + [CCode (type = "GtkWidget*", has_construct_function = false)] + public Base.window (Dh.Base @base); + } + [CCode (cheader_filename = "devhelp/dh-book.h")] + public class Book : GLib.Object { + [CCode (has_construct_function = false)] + public Book (string book_path); + public int cmp_by_name (Dh.Book b); + public int cmp_by_path (Dh.Book b); + public int cmp_by_title (Dh.Book b); + public bool get_enabled (); + public unowned GLib.List get_keywords (); + public unowned string get_name (); + public unowned string get_title (); + public unowned GLib.Node get_tree (); + public void set_enabled (bool enabled); + } + [CCode (cheader_filename = "devhelp/dh-book-manager.h")] + public class BookManager : GLib.Object { + [CCode (has_construct_function = false)] + public BookManager (); + public unowned Dh.Book get_book_by_name (string name); + public unowned GLib.List get_books (); + public void populate (); + public void update (); + public virtual signal void disabled_book_list_updated (); + } + [CCode (cheader_filename = "devhelp/dh-book-tree.h")] + public class BookTree : Gtk.TreeView, Atk.Implementor, Gtk.Buildable { + [CCode (type = "GtkWidget*", has_construct_function = false)] + public BookTree (Dh.BookManager book_manager); + public unowned string get_selected_book_title (); + public void select_uri (string uri); + public virtual signal void link_selected (void* p0); + } + [CCode (cheader_filename = "devhelp/dh-keyword-model.h")] + public class KeywordModel : GLib.Object, Gtk.TreeModel { + [CCode (has_construct_function = false)] + public KeywordModel (); + public unowned Dh.Link filter (string str, string book_id); + public void set_words (Dh.BookManager book_manager); + } + [Compact] + [CCode (cheader_filename = "devhelp/dh-keyword-model.h")] + public class KeywordModelPriv { + } + [Compact] + [CCode (ref_function = "dh_link_ref", unref_function = "dh_link_unref", type_id = "DH_TYPE_LINK", cheader_filename = "devhelp/dh-link.h")] + public class Link { + [CCode (has_construct_function = false)] + public Link (Dh.LinkType type, string @base, string id, string name, Dh.Link book, Dh.Link page, string filename); + public static int compare (void* a, void* b); + public unowned string get_book_id (); + public unowned string get_book_name (); + public unowned string get_file_name (); + public Dh.LinkFlags get_flags (); + public Dh.LinkType get_link_type (); + public unowned string get_name (); + public unowned string get_page_name (); + public unowned string get_type_as_string (); + public unowned string get_uri (); + public void set_flags (Dh.LinkFlags flags); + } + [CCode (cheader_filename = "devhelp/dh-search.h")] + public class Search : Gtk.VBox, Atk.Implementor, Gtk.Buildable, Gtk.Orientable { + [CCode (type = "GtkWidget*", has_construct_function = false)] + public Search (Dh.BookManager book_manager); + public void set_search_string (string str, string? book_id); + public virtual signal void link_selected (void* link); + } + [CCode (cheader_filename = "devhelp/dh-window.h")] + public class Window : Gtk.Window, Atk.Implementor, Gtk.Buildable { + [CCode (type = "GtkWidget*", has_construct_function = false)] + public Window (Dh.Base @base); + public void focus_search (); + public void search (string str, string book_id); + public virtual signal void open_link (string location, Dh.OpenLinkFlags flags); + } + [Compact] + [CCode (cheader_filename = "devhelp/dh-window.h")] + public class WindowPriv { + } + [CCode (cprefix = "DH_ERROR_", has_type_id = false, cheader_filename = "devhelp/dh-error.h")] + public enum Error { + FILE_NOT_FOUND, + MALFORMED_BOOK, + INVALID_BOOK_TYPE, + INTERNAL_ERROR + } + [CCode (cprefix = "DH_LINK_FLAGS_", has_type_id = false, cheader_filename = "devhelp/dh-link.h")] + public enum LinkFlags { + NONE, + DEPRECATED + } + [CCode (cprefix = "DH_LINK_TYPE_", has_type_id = false, cheader_filename = "devhelp/dh-link.h")] + public enum LinkType { + BOOK, + PAGE, + KEYWORD, + FUNCTION, + STRUCT, + MACRO, + ENUM, + TYPEDEF + } + [CCode (cprefix = "DH_OPEN_LINK_NEW_", has_type_id = false, cheader_filename = "devhelp/dh-window.h")] + public enum OpenLinkFlags { + WINDOW, + TAB + } + [CCode (cheader_filename = "dl-error.h")] + public static GLib.Quark error_quark (); +} diff --git a/plugins/help/help-plugin.vala b/plugins/help/help-plugin.vala new file mode 100644 index 0000000..b12ffe7 --- /dev/null +++ b/plugins/help/help-plugin.vala @@ -0,0 +1,70 @@ +/* -*- Mode: Vala; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ +/* + * help-plugin.vala + * Copyright (C) Nicolas Bruguier 2010-2012 + * + * geany-vala-toys is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * geany-vala-toys is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +GVT.Devhelp s_Manager = null; + +public enum GVT.HelpKeyBinding +{ + SEARCH_SYMBOL, + SEARCH_MAN_SYMBOL, + + COUNT +} + +public void +plugin_kb_activate (uint inKbId) +{ + if (s_Manager != null) + { + switch (inKbId) + { + case GVT.HelpKeyBinding.SEARCH_SYMBOL: + s_Manager.search_symbol (); + break; + + case GVT.HelpKeyBinding.SEARCH_MAN_SYMBOL: + s_Manager.search_man_symbol (); + break; + } + } +} + +public int +plugin_version_check (int inABIVersion) +{ + return Geany.Plugin.version_check (inABIVersion, 185); +} + +public void +plugin_set_info (Geany.Plugin.Info inInfo) +{ + inInfo.set ("GVT Help Manager", "Geany Vala Toys Help Manager", "0.1.0", "Nicolas Bruguier"); +} + +public void +plugin_init (Geany.Data inData) +{ + s_Manager = new GVT.Devhelp (); +} + +public void +plugin_cleanup () +{ + s_Manager = null; +} diff --git a/plugins/prj/prj-plugin.vala b/plugins/prj/prj-plugin.vala index 6291819..cea5b52 100644 --- a/plugins/prj/prj-plugin.vala +++ b/plugins/prj/prj-plugin.vala @@ -1,7 +1,7 @@ /* -*- Mode: Vala; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ /* * prj-plugin.vala - * Copyright (C) Nicolas Bruguier 2010-2011 + * Copyright (C) Nicolas Bruguier 2010-2012 * * geany-vala-toys is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published diff --git a/po/POTFILES.in b/po/POTFILES.in index 3060653..118b1f4 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -11,3 +11,10 @@ plugins/dock/gdl/gdl-dock-placeholder.c plugins/dock/gdl/gdl-dock-tablabel.c plugins/dock/gdl/gdl-dock.c plugins/dock/gdl/gdl-switcher.c +plugins/help/devhelp/dh-assistant-view.c +plugins/help/devhelp/dh-assistant.c +plugins/help/devhelp/dh-link.c +plugins/help/devhelp/dh-parser.c +plugins/help/devhelp/dh-search.c +plugins/help/devhelp/dh-window.c +plugins/help/devhelp/eggfindbar.c