/* The GIMP -- an image manipulation program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * The GIMP Help Browser * Copyright (C) 1999-2002 Sven Neumann * Michael Natterer * * Some code & ideas stolen from the GNOME help browser. * * 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 #if HAVE_UNISTD_H #include #endif #include #include #include #include #include #include "queue.h" #include "libgimp/stdplugins-intl.h" /* defines */ #ifdef __EMX__ #define chdir _chdir2 #endif #define GIMP_HELP_EXT_NAME "extension_gimp_help_browser" #define GIMP_HELP_TEMP_EXT_NAME "extension_gimp_help_browser_temp" #define GIMP_HELP_PREFIX "help" enum { BUTTON_HOME, BUTTON_INDEX, BUTTON_BACK, BUTTON_FORWARD }; /* structures */ typedef struct { const gchar *title; const gchar *ref; gint count; } HistoryItem; /* constant strings */ static gchar *doc_not_found_format_string = N_("Document not found" "" "
" "

" "%s" "

Couldn't find document

" "%s" "
" "

" "This either means that the help for this topic has not been written " "yet or that something is wrong with your installation. " "Please check carefully before you report this as a bug." "" ""); static gchar *dir_not_found_format_string = N_("Directory not found" "" "

" "

" "%s" "

Couldn't change to directory

" "%s" "

while trying to access

" "%s" "
" "

" "This either means that the help for this topic has not been written " "yet or that something is wrong with your installation. " "Please check carefully before you report this as a bug." "" ""); static gchar *eek_png_tag = "

Eeek!

"; static gchar *gimp_help_root = NULL; static GList *history = NULL; static Queue *queue; static gchar *current_ref; static GtkWidget *html; static GtkWidget *back_button; static GtkWidget *forward_button; static GtkWidget *combo; static GtkTargetEntry help_dnd_target_table[] = { { "_NETSCAPE_URL", 0, 0 }, }; /* GIMP plugin stuff */ static void query (void); static void run (gchar *name, gint nparams, GimpParam *param, gint *nreturn_vals, GimpParam **return_vals); GimpPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ NULL, /* quit_proc */ query, /* query_proc */ run, /* run_proc */ }; static gboolean temp_proc_installed = FALSE; /* forward declaration */ static gint load_page (const gchar *ref, gboolean add_to_queue); /* functions */ static void close_callback (GtkWidget *widget, gpointer user_data) { gtk_main_quit (); } static void update_toolbar (void) { if (back_button) gtk_widget_set_sensitive (back_button, queue_has_prev (queue)); if (forward_button) gtk_widget_set_sensitive (forward_button, queue_has_next (queue)); } static void button_callback (GtkWidget *widget, gpointer data) { const gchar *ref; switch (GPOINTER_TO_INT (data)) { case BUTTON_HOME: load_page ("contents.html", FALSE); break; case BUTTON_INDEX: load_page ("index.html", FALSE); break; case BUTTON_BACK: if (!(ref = queue_prev (queue))) return; load_page (ref, FALSE); queue_move_prev (queue); break; case BUTTON_FORWARD: if (!(ref = queue_next (queue))) return; load_page (ref, FALSE); queue_move_next (queue); break; default: return; } update_toolbar (); } static void entry_changed_callback (GtkWidget *widget, gpointer data) { GList *list; HistoryItem *item; const gchar *entry_text; gchar *compare_text; gboolean found = FALSE; entry_text = gtk_entry_get_text (GTK_ENTRY (widget)); for (list = history; list && !found; list = list->next) { item = (HistoryItem *) list->data; if (item->count) { compare_text = g_strdup_printf ("%s <%i>", item->title, item->count + 1); } else { compare_text = (gchar *) item->title; } if (strcmp (compare_text, entry_text) == 0) { load_page (item->ref, TRUE); found = TRUE; } if (item->count) { g_free (compare_text); } } } static void history_add (const gchar *ref, const gchar *title) { GList *list; GList *found = NULL; HistoryItem *item; GList *combo_list = NULL; gint title_found_count = 0; for (list = history; list && !found; list = list->next) { item = (HistoryItem *) list->data; if (strcmp (item->title, title) == 0) { if (strcmp (item->ref, ref) != 0) { title_found_count++; continue; } found = list; } } if (found) { item = (HistoryItem *) found->data; history = g_list_remove_link (history, found); } else { item = g_new (HistoryItem, 1); item->ref = g_strdup (ref); item->title = g_strdup (title); item->count = title_found_count; } history = g_list_prepend (history, item); for (list = history; list; list = list->next) { gchar* combo_title; item = (HistoryItem *) list->data; if (item->count) combo_title = g_strdup_printf ("%s <%i>", item->title, item->count + 1); else combo_title = g_strdup (item->title); combo_list = g_list_prepend (combo_list, combo_title); } combo_list = g_list_reverse (combo_list); g_signal_handlers_block_by_func (G_OBJECT (GTK_COMBO (combo)->entry), entry_changed_callback, combo); gtk_combo_set_popdown_strings (GTK_COMBO (combo), combo_list); g_signal_handlers_unblock_by_func (G_OBJECT (GTK_COMBO (combo)->entry), entry_changed_callback, combo); for (list = combo_list; list; list = list->next) g_free (list->data); g_list_free (combo_list); } static void title_changed (HtmlDocument *doc, const gchar *new_title, gpointer data) { gchar *title; if (!new_title) new_title = (_("")); title = g_strstrip (g_strdup (new_title)); history_add (current_ref, title); g_signal_handlers_block_by_func (G_OBJECT (GTK_COMBO (combo)->entry), entry_changed_callback, combo); gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (combo)->entry), title); g_signal_handlers_unblock_by_func (G_OBJECT (GTK_COMBO (combo)->entry), entry_changed_callback, combo); g_free (title); } static gboolean load_page (const gchar *ref, gboolean add_to_queue) { HtmlDocument *doc; FILE *fp = NULL; gchar buf[8192]; gchar *old_dir; gchar *new_dir; gchar *new_base; gchar *new_ref; gchar *tmp; gsize bytes_read; gboolean page_valid = FALSE; gboolean filters_dir = FALSE; g_return_val_if_fail (ref != NULL, FALSE); doc = HTML_VIEW (html)->document; old_dir = g_path_get_dirname (current_ref); new_dir = g_path_get_dirname (ref); new_base = g_path_get_basename (ref); /* return value is intentionally ignored */ chdir (old_dir); if (chdir (new_dir) == -1) { gchar *msg; if (g_path_is_absolute (ref)) new_ref = g_strdup (ref); else new_ref = g_build_filename (old_dir, ref, NULL); msg = g_strdup_printf (gettext (dir_not_found_format_string), eek_png_tag, new_dir, new_ref); html_document_clear (doc); html_document_open_stream (doc, "text/html"); html_document_write_stream (doc, msg, strlen (msg)); html_document_close_stream (doc); g_free (msg); goto FINISH; } tmp = new_dir; new_dir = g_path_get_basename (tmp); g_free (tmp); if (strcmp (new_dir, "filters") == 0) filters_dir = TRUE; g_free (new_dir); new_dir = g_get_current_dir (); new_ref = g_build_filename (new_dir, new_base, NULL); if (strcmp (current_ref, new_ref) == 0) { if (add_to_queue) queue_add (queue, new_ref); goto FINISH; } html_document_clear (doc); html_document_open_stream (doc, "text/html"); /* * handle basename like: filename.html#11111 -> filename.html */ g_strdelimit (new_base, "#", '\0'); fp = fopen (new_base, "rt"); if (fp != NULL) { while ((bytes_read = fread (buf, sizeof (gchar), sizeof (buf), fp)) > 0) html_document_write_stream (doc, buf, bytes_read); } else if (filters_dir) { gchar *undocumented_filter = g_build_filename (new_dir, "undocumented_filter.html", NULL); fp = fopen (undocumented_filter, "rt"); if (fp != NULL) { while ((bytes_read = fread (buf, sizeof (gchar), sizeof (buf), fp)) > 0) html_document_write_stream (doc, buf, bytes_read); } g_free (undocumented_filter); } if (fp) { fclose (fp); page_valid = TRUE; } else { gchar *msg = g_strdup_printf (gettext (doc_not_found_format_string), eek_png_tag, ref); chdir (old_dir); html_document_write_stream (doc, msg, strlen (msg)); g_free (msg); } html_document_close_stream (doc); FINISH: g_free (old_dir); g_free (new_dir); g_free (new_base); g_free (current_ref); current_ref = new_ref; if (add_to_queue) queue_add (queue, new_ref); update_toolbar (); return (page_valid); } static void link_clicked (HtmlDocument *doc, const gchar *url, gpointer data) { load_page (url, TRUE); #if 0 GimpParam *return_vals; gint nreturn_vals; switch (url_type) { case URL_JUMP: jump_to_anchor (ref); break; case URL_FILE_LOCAL: load_page (ref, TRUE); break; default: /* try to call netscape through the web_browser interface */ return_vals = gimp_run_procedure ("extension_web_browser", &nreturn_vals, GIMP_PDB_INT32, GIMP_RUN_NONINTERACTIVE, GIMP_PDB_STRING, ref, GIMP_PDB_INT32, FALSE, GIMP_PDB_END); gimp_destroy_params (return_vals, nreturn_vals); break; } #endif } static void drag_begin (GtkWidget *widget, GdkDragContext *context, gpointer data) { gtk_drag_set_icon_stock (context, GTK_STOCK_JUMP_TO, -8, -8); } static void drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data, guint info, guint time, gpointer data) { if (! current_ref) return; gtk_selection_data_set (selection_data, selection_data->target, 8, current_ref, strlen (current_ref)); } gboolean open_browser_dialog (gchar *help_path, gchar *locale, gchar *help_file) { GtkWidget *window; GtkWidget *vbox; GtkWidget *hbox; GtkWidget *bbox; GtkWidget *scroll; GtkWidget *button; GtkWidget *drag_source; GtkWidget *image; gchar *initial_dir; gchar *initial_ref; gchar *eek_png_path; gint success; gimp_ui_init ("helpbrowser", TRUE); if (chdir (gimp_help_root) == -1) { g_message (_("GIMP Help Browser Error.\n\n" "Couldn't find my root html directory.\n" "(%s)"), gimp_help_root); return FALSE; } eek_png_path = g_build_filename (gimp_help_root, "images", "eek.png", NULL); if (access (eek_png_path, R_OK) == 0) eek_png_tag = g_strdup_printf ("", eek_png_path); g_free (eek_png_path); if (chdir (help_path) == -1) { g_message (_("GIMP Help Browser Error.\n\n" "Couldn't find my root html directory.\n" "(%s)"), help_path); return FALSE; } initial_dir = g_get_current_dir (); /* the dialog window */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (close_callback), NULL); gtk_window_set_wmclass (GTK_WINDOW (window), "helpbrowser", "Gimp"); gtk_window_set_title (GTK_WINDOW (window), _("GIMP Help Browser")); gimp_help_connect (window, gimp_standard_help_func, "dialogs/help.html"); vbox = gtk_vbox_new (FALSE, 2); gtk_container_add (GTK_CONTAINER (window), vbox); gtk_widget_show (vbox); /* buttons */ bbox = gtk_hbutton_box_new (); gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_START); gtk_box_pack_start (GTK_BOX (vbox), bbox, FALSE, FALSE, 0); gtk_widget_show (bbox); button = gtk_button_new_from_stock (GTK_STOCK_HOME); gtk_container_add (GTK_CONTAINER (bbox), button); gtk_widget_show (button); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (button_callback), GINT_TO_POINTER (BUTTON_HOME)); button = gtk_button_new_from_stock (GTK_STOCK_INDEX); gtk_container_add (GTK_CONTAINER (bbox), button); gtk_widget_show (button); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (button_callback), GINT_TO_POINTER (BUTTON_INDEX)); back_button = button = gtk_button_new_from_stock (GTK_STOCK_GO_BACK); gtk_container_add (GTK_CONTAINER (bbox), button); gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (button_callback), GINT_TO_POINTER (BUTTON_BACK)); gtk_widget_show (button); forward_button = button = gtk_button_new_from_stock (GTK_STOCK_GO_FORWARD); gtk_container_add (GTK_CONTAINER (bbox), button); gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (button_callback), GINT_TO_POINTER (BUTTON_FORWARD)); gtk_widget_show (button); hbox = gtk_hbox_new (FALSE, 2); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox); /* the drag source */ drag_source = gtk_event_box_new (); gtk_box_pack_start (GTK_BOX (hbox), drag_source, FALSE, FALSE, 4); gtk_widget_show (drag_source); gtk_drag_source_set (GTK_WIDGET (drag_source), GDK_BUTTON1_MASK, help_dnd_target_table, G_N_ELEMENTS (help_dnd_target_table), GDK_ACTION_MOVE | GDK_ACTION_COPY); g_signal_connect (G_OBJECT (drag_source), "drag_begin", G_CALLBACK (drag_begin), NULL); g_signal_connect (G_OBJECT (drag_source), "drag_data_get", G_CALLBACK (drag_data_get), NULL); image = gtk_image_new_from_stock (GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_BUTTON); gtk_container_add (GTK_CONTAINER (drag_source), image); gtk_widget_show (image); /* the title combo */ combo = gtk_combo_new (); gtk_widget_set_size_request (GTK_WIDGET (combo), 300, -1); gtk_combo_set_use_arrows (GTK_COMBO (combo), TRUE); g_object_set (G_OBJECT (GTK_COMBO (combo)->entry), "editable", FALSE, NULL); gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0); gtk_widget_show (combo); g_signal_connect (G_OBJECT (GTK_COMBO (combo)->entry), "changed", G_CALLBACK (entry_changed_callback), combo); /* HTML view */ html = html_view_new (); queue = queue_new (); gtk_widget_set_size_request (GTK_WIDGET (html), -1, 240); scroll = gtk_scrolled_window_new (gtk_layout_get_hadjustment (GTK_LAYOUT (html)), gtk_layout_get_vadjustment (GTK_LAYOUT (html))); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_box_pack_start (GTK_BOX (vbox), scroll, TRUE, TRUE, 0); gtk_widget_show (scroll); gtk_container_add (GTK_CONTAINER (scroll), html); gtk_widget_show (html); html_view_set_document (HTML_VIEW (html), html_document_new ()); g_signal_connect (G_OBJECT (HTML_VIEW (html)->document), "title_changed", G_CALLBACK (title_changed), NULL); g_signal_connect (G_OBJECT (HTML_VIEW (html)->document), "link_clicked", G_CALLBACK (link_clicked), NULL); gtk_widget_show (window); current_ref = g_build_filename (initial_dir, locale, NULL); initial_ref = g_build_filename (initial_dir, locale, help_file, NULL); success = load_page (initial_ref, TRUE); g_free (initial_ref); g_free (initial_dir); return TRUE; } static gboolean idle_load_page (gpointer data) { gchar *path = data; load_page (path, TRUE); g_free (path); return FALSE; } static void run_temp_proc (gchar *name, gint nparams, GimpParam *param, gint *nreturn_vals, GimpParam **return_vals) { static GimpParam values[1]; GimpPDBStatusType status = GIMP_PDB_SUCCESS; gchar *help_path; gchar *locale; gchar *help_file; gchar *path; /* set default values */ help_path = g_strdup (gimp_help_root); locale = g_strdup ("C"); help_file = g_strdup ("introduction.html"); /* Make sure all the arguments are there! */ if (nparams == 3) { if (param[0].data.d_string && strlen (param[0].data.d_string)) { g_free (help_path); help_path = g_strdup (param[0].data.d_string); g_strdelimit (help_path, "/", G_DIR_SEPARATOR); } if (param[1].data.d_string && strlen (param[1].data.d_string)) { g_free (locale); locale = g_strdup (param[1].data.d_string); } if (param[2].data.d_string && strlen (param[2].data.d_string)) { g_free (help_file); help_file = g_strdup (param[2].data.d_string); g_strdelimit (help_file, "/", G_DIR_SEPARATOR); } } path = g_build_filename (help_path, locale, help_file, NULL); g_free (help_path); g_free (locale); g_free (help_file); g_idle_add (idle_load_page, path); /* frees path */ *nreturn_vals = 1; *return_vals = values; values[0].type = GIMP_PDB_STATUS; values[0].data.d_status = status; } /* from libgimp/gimp.c */ void gimp_run_temp (void); static gboolean input_callback (GIOChannel *channel, GIOCondition condition, gpointer data) { /* We have some data in the wire - read it */ /* The below will only ever run a single proc */ gimp_run_temp (); return TRUE; } static void install_temp_proc (void) { static GimpParamDef args[] = { { GIMP_PDB_STRING, "help_path", "" }, { GIMP_PDB_STRING, "locale", "Langusge to use" }, { GIMP_PDB_STRING, "help_file", "Path of a local document to open. " "Can be relative to GIMP_HELP_PATH." } }; gimp_install_temp_proc (GIMP_HELP_TEMP_EXT_NAME, "DON'T USE THIS ONE", "(Temporary procedure)", "Sven Neumann , " "Michael Natterer ", "Sven Neumann & Michael Natterer", "1999-2002", NULL, "", GIMP_TEMPORARY, G_N_ELEMENTS (args), 0, args, NULL, run_temp_proc); /* Tie into the gdk input function */ g_io_add_watch (_readchannel, G_IO_IN | G_IO_PRI, input_callback, NULL); temp_proc_installed = TRUE; } static gboolean open_url (gchar *help_path, gchar *locale, gchar *help_file) { if (! open_browser_dialog (help_path, locale, help_file)) return FALSE; install_temp_proc (); gtk_main (); return TRUE; } MAIN () static void query (void) { static GimpParamDef args[] = { { GIMP_PDB_INT32, "run_mode", "Interactive" }, { GIMP_PDB_STRING, "help_path", "" }, { GIMP_PDB_STRING, "locale", "Language to use" }, { GIMP_PDB_STRING, "help_file", "Path of a local document to open. " "Can be relative to GIMP_HELP_PATH." } }; gimp_install_procedure (GIMP_HELP_EXT_NAME, "Browse the GIMP help pages", "A small and simple HTML browser optimized for " "browsing the GIMP help pages.", "Sven Neumann , " "Michael Natterer ", "Sven Neumann & Michael Natterer", "1999-2002", NULL, "", GIMP_EXTENSION, G_N_ELEMENTS (args), 0, args, NULL); } static void run (gchar *name, gint nparams, GimpParam *param, gint *nreturn_vals, GimpParam **return_vals) { static GimpParam values[1]; GimpRunMode run_mode; GimpPDBStatusType status = GIMP_PDB_SUCCESS; const gchar *env_root_dir = NULL; gchar *help_path = NULL; gchar *locale = NULL; gchar *help_file = NULL; run_mode = param[0].data.d_int32; values[0].type = GIMP_PDB_STATUS; values[0].data.d_status = status; *nreturn_vals = 1; *return_vals = values; INIT_I18N_UI (); if (strcmp (name, GIMP_HELP_EXT_NAME) == 0) { switch (run_mode) { case GIMP_RUN_INTERACTIVE: case GIMP_RUN_NONINTERACTIVE: case GIMP_RUN_WITH_LAST_VALS: /* set default values */ env_root_dir = g_getenv ("GIMP_HELP_ROOT"); if (env_root_dir) { if (chdir (env_root_dir) == -1) { g_message (_("GIMP Help Browser Error.\n\n" "Couldn't find GIMP_HELP_ROOT html directory.\n" "(%s)"), env_root_dir); status = GIMP_PDB_EXECUTION_ERROR; break; } gimp_help_root = g_strdup (env_root_dir); } else { gimp_help_root = g_build_filename (gimp_data_directory(), GIMP_HELP_PREFIX, NULL); } help_path = g_strdup (gimp_help_root); locale = g_strdup ("C"); help_file = g_strdup ("introduction.html"); /* Make sure all the arguments are there! */ if (nparams == 4) { if (param[1].data.d_string && strlen (param[1].data.d_string)) { g_free (help_path); help_path = g_strdup (param[1].data.d_string); g_strdelimit (help_path, "/", G_DIR_SEPARATOR); } if (param[2].data.d_string && strlen (param[2].data.d_string)) { g_free (locale); locale = g_strdup (param[2].data.d_string); } if (param[3].data.d_string && strlen (param[3].data.d_string)) { g_free (help_file); help_file = g_strdup (param[3].data.d_string); g_strdelimit (help_file, "/", G_DIR_SEPARATOR); } } break; default: status = GIMP_PDB_CALLING_ERROR; break; } if (status == GIMP_PDB_SUCCESS) { if (!open_url (help_path, locale, help_file)) values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; else values[0].data.d_status = GIMP_PDB_SUCCESS; g_free (help_path); g_free (locale); g_free (help_file); } else values[0].data.d_status = status; } else values[0].data.d_status = GIMP_PDB_CALLING_ERROR; }