home *** CD-ROM | disk | FTP | other *** search
/ PC Pro 2002 April / pcpro0402.iso / essentials / graphics / Gimp / gimp-src-20001226.exe / src / gimp / plug-ins / helpbrowser / helpbrowser.c < prev    next >
Encoding:
C/C++ Source or Header  |  2000-12-16  |  32.6 KB  |  1,278 lines

  1. /* The GIMP -- an image manipulation program
  2.  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
  3.  *
  4.  * The GIMP Help Browser
  5.  * Copyright (C) 1999 Sven Neumann <sven@gimp.org>
  6.  *                    Michael Natterer <mitschel@cs.tu-berlin.de>
  7.  *
  8.  * Some code & ideas stolen from the GNOME help browser.
  9.  *
  10.  * This program is free software; you can redistribute it and/or modify
  11.  * it under the terms of the GNU General Public License as published by
  12.  * the Free Software Foundation; either version 2 of the License, or
  13.  * (at your option) any later version.
  14.  *
  15.  * This program is distributed in the hope that it will be useful,
  16.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18.  * GNU General Public License for more details.
  19.  *
  20.  * You should have received a copy of the GNU General Public License
  21.  * along with this program; if not, write to the Free Software
  22.  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  23.  */
  24. #include "config.h"
  25.  
  26. #include <string.h> 
  27. #include <stdio.h>
  28. #include <stdlib.h>
  29. #include <unistd.h>
  30.  
  31. #include <gtk/gtk.h>
  32. #include <gdk/gdkkeysyms.h>
  33. #include <gtk-xmhtml/gtk-xmhtml.h>
  34.  
  35. #include <libgimp/gimp.h>
  36. #include <libgimp/gimpui.h>
  37.  
  38. #include "queue.h"
  39.  
  40. #include "libgimp/stdplugins-intl.h"
  41.  
  42. #include "forward.xpm"
  43. #include "back.xpm"
  44.  
  45.  
  46. /*  defines  */
  47.  
  48. #ifdef __EMX__
  49. #define chdir _chdir2
  50. #endif
  51.  
  52. #define GIMP_HELP_EXT_NAME      "extension_gimp_help_browser"
  53. #define GIMP_HELP_TEMP_EXT_NAME "extension_gimp_help_browser_temp"
  54.  
  55. #define GIMP_HELP_PREFIX        "help"
  56.  
  57. enum {
  58.   CONTENTS,
  59.   INDEX,
  60.   HELP
  61. };
  62.  
  63. enum {
  64.   URL_UNKNOWN,
  65.   URL_NAMED, /* ??? */
  66.   URL_JUMP,
  67.   URL_FILE_LOCAL,
  68.  
  69.   /* aliases */
  70.   URL_LAST = URL_FILE_LOCAL
  71. };
  72.  
  73. /*  structures  */
  74.  
  75. typedef struct 
  76. {
  77.   gint       index;
  78.   gchar     *label;
  79.   Queue     *queue;
  80.   gchar     *current_ref;
  81.   GtkWidget *html;
  82.   gchar     *home;
  83. } HelpPage;
  84.  
  85. typedef struct
  86. {
  87.   gchar *title;
  88.   gchar *ref;
  89.   gint   count;
  90. } HistoryItem;
  91.  
  92. /*  constant strings  */
  93.  
  94. static gchar *doc_not_found_format_string =
  95. N_("<html><head><title>Document not found</title></head>"
  96.    "<body bgcolor=\"#ffffff\">"
  97.    "<center>"
  98.    "<p>"
  99.    "%s"
  100.    "<h3>Couldn't find document</h3>"
  101.    "<tt>%s</tt>"
  102.    "</center>"
  103.    "<p>"
  104.    "<small>This either means that the help for this topic has not been written "
  105.    "yet or that something is wrong with your installation. "
  106.    "Please check carefully before you report this as a bug.</small>" 
  107.    "</body>"
  108.    "</html>");
  109.  
  110. static gchar *dir_not_found_format_string =
  111. N_("<html><head><title>Directory not found</title></head>"
  112.    "<body bgcolor=\"#ffffff\">"
  113.    "<center>"
  114.    "<p>"
  115.    "%s"
  116.    "<h3>Couldn't change to directory</h3>"
  117.    "<tt>%s</tt>"
  118.    "<h3>while trying to access</h3>"
  119.    "<tt>%s</tt>"
  120.    "</center>"
  121.    "<p>"
  122.    "<small>This either means that the help for this topic has not been written "
  123.    "yet or that something is wrong with your installation. "
  124.    "Please check carefully before you report this as a bug.</small>" 
  125.    "</body>"
  126.    "</html>");
  127.  
  128. static gchar *eek_png_tag = "<h1>Eeek!</h1>";
  129.  
  130.  
  131. /*  the three help notebook pages  */
  132.  
  133. static HelpPage pages[] =
  134. {
  135.   {
  136.     CONTENTS,
  137.     N_("Contents"),
  138.     NULL,
  139.     NULL,
  140.     NULL,
  141.     "contents.html"
  142.   },
  143.  
  144.   {
  145.     INDEX,
  146.     N_("Index"),
  147.     NULL,
  148.     NULL,
  149.     NULL,
  150.     "index.html"
  151.   },
  152.  
  153.   {
  154.     HELP,
  155.     NULL,
  156.     NULL,
  157.     NULL,
  158.     NULL,
  159.     "introduction.html"
  160.   }
  161. };
  162.  
  163. static gchar     *gimp_help_root = NULL;
  164.  
  165. static HelpPage  *current_page = &pages[HELP];
  166. static GList     *history = NULL;
  167.  
  168. static GtkWidget *back_button;
  169. static GtkWidget *forward_button;
  170. static GtkWidget *notebook;
  171. static GtkWidget *combo;
  172.  
  173. static GtkTargetEntry help_dnd_target_table[] =
  174. {
  175.   { "_NETSCAPE_URL", 0, 0 },
  176. };
  177. static guint n_help_dnd_targets = (sizeof (help_dnd_target_table) /
  178.                    sizeof (help_dnd_target_table[0]));
  179.  
  180. /*  GIMP plugin stuff  */
  181.  
  182. static void query (void);
  183. static void run   (gchar      *name,
  184.            gint        nparams,
  185.            GimpParam  *param,
  186.            gint       *nreturn_vals,
  187.            GimpParam **return_vals);
  188.  
  189. GimpPlugInInfo PLUG_IN_INFO =
  190. {
  191.   NULL,  /* init_proc  */
  192.   NULL,  /* quit_proc  */
  193.   query, /* query_proc */
  194.   run,   /* run_proc   */
  195. };
  196.  
  197. static gboolean temp_proc_installed = FALSE;
  198.  
  199. /*  forward declaration  */
  200.  
  201. static gint load_page (HelpPage *source_page,
  202.                HelpPage *dest_page,
  203.                gchar    *ref,
  204.                gint      pos, 
  205.                gboolean  add_to_queue,
  206.                gboolean  add_to_history);
  207.  
  208. /*  functions  */
  209.  
  210. static void
  211. close_callback (GtkWidget *widget,
  212.         gpointer   user_data)
  213. {
  214.   gtk_main_quit ();
  215. }
  216.  
  217. static void
  218. update_toolbar (HelpPage *page)
  219. {
  220.   if (back_button)
  221.     gtk_widget_set_sensitive (back_button, queue_isprev (page->queue));
  222.   if (forward_button)
  223.     gtk_widget_set_sensitive (forward_button, queue_isnext (page->queue));
  224. }
  225.  
  226. static void
  227. jump_to_anchor (HelpPage *page, 
  228.         gchar    *anchor)
  229. {
  230.   gint pos;
  231.  
  232.   g_return_if_fail (page != NULL && anchor != NULL);
  233.  
  234.   if (*anchor != '#') 
  235.     {
  236.       gchar *a = g_strconcat ("#", anchor, NULL);
  237.       XmHTMLAnchorScrollToName (page->html, a);
  238.       g_free (a);
  239.     }
  240.   else
  241.     XmHTMLAnchorScrollToName (page->html, anchor);
  242.  
  243.   pos = gtk_xmhtml_get_topline (GTK_XMHTML (page->html));
  244.   queue_add (page->queue, page->current_ref, pos);
  245.  
  246.   update_toolbar (page);
  247. }
  248.  
  249. static void
  250. forward_callback (GtkWidget *widget,
  251.           gpointer   data)
  252. {
  253.   gchar *ref;
  254.   gint pos;
  255.  
  256.   if (!(ref = queue_next (current_page->queue, &pos)))
  257.     return;
  258.  
  259.   load_page (current_page, current_page, ref, pos, FALSE, FALSE);
  260.   queue_move_next (current_page->queue);
  261.  
  262.   update_toolbar (current_page);
  263. }
  264.  
  265. static void
  266. back_callback (GtkWidget *widget,
  267.            gpointer   data)
  268. {
  269.   gchar *ref;
  270.   gint pos;
  271.  
  272.   if (!(ref = queue_prev (current_page->queue, &pos)))
  273.     return;
  274.  
  275.   load_page (current_page, current_page, ref, pos, FALSE, FALSE);
  276.   queue_move_prev (current_page->queue);
  277.  
  278.   update_toolbar (current_page);
  279. }
  280.  
  281. static void 
  282. entry_changed_callback (GtkWidget *widget,
  283.             gpointer   data)
  284. {
  285.   GList       *list;
  286.   HistoryItem *item;
  287.   gchar       *entry_text;
  288.   gchar       *compare_text;
  289.   gboolean     found = FALSE;
  290.  
  291.   entry_text = gtk_entry_get_text (GTK_ENTRY (widget));
  292.  
  293.   for (list = history; list && !found; list = list->next)
  294.     {
  295.       item = (HistoryItem *) list->data;
  296.  
  297.       if (item->count)
  298.     compare_text = g_strdup_printf ("%s <%i>",
  299.                     item->title,
  300.                     item->count + 1);
  301.       else
  302.     compare_text = item->title;
  303.  
  304.       if (strcmp (compare_text, entry_text) == 0)
  305.     {
  306.       load_page (&pages[HELP], &pages[HELP], item->ref, 0, TRUE, FALSE);
  307.       found = TRUE;
  308.     }
  309.  
  310.       if (item->count)
  311.     g_free (compare_text);
  312.     }
  313. }
  314.  
  315. static gint
  316. entry_button_press_callback (GtkWidget      *widget,
  317.                  GdkEventButton *bevent,
  318.                  gpointer        data)
  319. {
  320.   if (current_page != &pages[HELP])
  321.     gtk_notebook_set_page (GTK_NOTEBOOK (notebook), HELP);
  322.  
  323.   return FALSE;
  324. }
  325.  
  326. static void
  327. history_add (gchar *ref,
  328.          gchar *title)
  329. {
  330.   GList       *list;
  331.   GList       *found = NULL;
  332.   HistoryItem *item;
  333.   GList       *combo_list = NULL;
  334.   gint         title_found_count = 0;
  335.  
  336.   for (list = history; list && !found; list = list->next)
  337.     {
  338.       item = (HistoryItem *) list->data;
  339.  
  340.       if (strcmp (item->title, title) == 0)
  341.     {
  342.       if (strcmp (item->ref, ref) != 0)
  343.         {
  344.           title_found_count++;
  345.           continue;
  346.         }
  347.  
  348.       found = list;    }
  349.     }
  350.  
  351.   if (found)
  352.     {
  353.       item = (HistoryItem *) found->data;
  354.       history = g_list_remove_link (history, found);
  355.     }
  356.   else
  357.     {
  358.       item = g_new (HistoryItem, 1);
  359.       item->ref = g_strdup (ref);
  360.       item->title = g_strdup (title);
  361.       item->count = title_found_count;
  362.     }
  363.  
  364.   history = g_list_prepend (history, item);
  365.  
  366.   for (list = history; list; list = list->next)
  367.     {
  368.       gchar* combo_title;
  369.  
  370.       item = (HistoryItem *) list->data;
  371.  
  372.       if (item->count)
  373.     combo_title = g_strdup_printf ("%s <%i>",
  374.                        item->title,
  375.                        item->count + 1);
  376.       else
  377.     combo_title = g_strdup (item->title);
  378.  
  379.       combo_list = g_list_prepend (combo_list, combo_title);
  380.     }
  381.  
  382.   combo_list = g_list_reverse (combo_list);
  383.  
  384.   gtk_signal_handler_block_by_data (GTK_OBJECT (GTK_COMBO (combo)->entry), combo);
  385.   gtk_combo_set_popdown_strings (GTK_COMBO (combo), combo_list);
  386. /*    gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (combo)->entry), item->title); */
  387.   gtk_signal_handler_unblock_by_data (GTK_OBJECT (GTK_COMBO (combo)->entry), combo);
  388.  
  389.   for (list = combo_list; list; list = list->next)
  390.     g_free (list->data);
  391.  
  392.   g_list_free (combo_list);
  393. }
  394.  
  395. static void
  396. html_source (HelpPage *page,
  397.          gchar    *ref,
  398.          gint      pos,
  399.          gchar    *source, 
  400.          gboolean  add_to_queue,
  401.          gboolean  add_to_history)
  402. {
  403.   gchar *title = NULL;
  404.   
  405.   g_return_if_fail (page != NULL && ref != NULL && source != NULL);
  406.   
  407.   /* Load it up */
  408.   gtk_xmhtml_source (GTK_XMHTML (page->html), source);
  409.   
  410.   gtk_xmhtml_set_topline (GTK_XMHTML(page->html), pos);
  411.   
  412.   if (add_to_queue) 
  413.     queue_add (page->queue, ref, pos);
  414.  
  415.   if (page->index == HELP)
  416.     {
  417.       title = XmHTMLGetTitle (page->html);
  418.       if (!title)
  419.     title = (_("<Untitled>"));
  420.       
  421.       if (add_to_history)
  422.     history_add (ref, title);
  423.  
  424.       gtk_signal_handler_block_by_data (GTK_OBJECT (GTK_COMBO (combo)->entry),
  425.                     combo);
  426.       gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (combo)->entry), title);
  427.       gtk_signal_handler_unblock_by_data (GTK_OBJECT (GTK_COMBO (combo)->entry),
  428.                       combo);
  429.     }
  430.  
  431.   update_toolbar (page);
  432. }
  433.  
  434. static gint
  435. load_page (HelpPage *source_page,
  436.        HelpPage *dest_page,
  437.        gchar    *ref,      
  438.        gint      pos,
  439.        gboolean  add_to_queue,
  440.        gboolean  add_to_history)
  441. {
  442.   GString  *file_contents;
  443.   FILE     *afile = NULL;
  444.   char      aline[1024];
  445.   gchar    *old_dir;
  446.   gchar    *new_dir, *new_base;
  447.   gchar    *new_ref;
  448.   gboolean  page_valid  = FALSE;
  449.   gboolean  filters_dir = FALSE;
  450.  
  451.   g_return_val_if_fail (ref != NULL && source_page != NULL && dest_page != NULL, FALSE);
  452.  
  453.   old_dir  = g_dirname (source_page->current_ref);
  454.   new_dir  = g_dirname (ref);
  455.   new_base = g_basename (ref);
  456.  
  457.   /* return value is intentionally ignored */
  458.   chdir (old_dir);
  459.  
  460.   file_contents = g_string_new (NULL);
  461.  
  462.   if (chdir (new_dir) == -1)
  463.     {
  464.       if (g_path_is_absolute (ref))
  465.     new_ref = g_strdup (ref);
  466.       else
  467.     new_ref = g_strconcat (old_dir, G_DIR_SEPARATOR_S, ref, NULL);
  468.  
  469.       g_string_sprintf (file_contents, gettext (dir_not_found_format_string),
  470.             eek_png_tag, new_dir, new_ref);
  471.       html_source (dest_page, new_ref, 0, file_contents->str, add_to_queue, FALSE);
  472.  
  473.       goto FINISH;
  474.     }
  475.  
  476.   if (strcmp (g_basename (new_dir), "filters") == 0)
  477.     filters_dir = TRUE;
  478.  
  479.   g_free (new_dir);
  480.   new_dir = g_get_current_dir ();
  481.  
  482.   new_ref = g_strconcat (new_dir, G_DIR_SEPARATOR_S, new_base, NULL);
  483.  
  484.   if (strcmp (dest_page->current_ref, new_ref) == 0)
  485.     {
  486.       gtk_xmhtml_set_topline (GTK_XMHTML (dest_page->html), pos);
  487.  
  488.       if (add_to_queue)
  489.     queue_add (dest_page->queue, new_ref, pos);
  490.  
  491.       goto FINISH;
  492.     }
  493.  
  494.   /*
  495.    *  handle basename like: filename.html#11111 -> filename.html
  496.    */ 
  497.   g_strdelimit (new_base,"#",'\0');
  498.  
  499.   afile = fopen (new_base, "rt");
  500.  
  501.   if (afile != NULL)
  502.     {
  503.       while (fgets (aline, sizeof (aline), afile))
  504.     file_contents = g_string_append (file_contents, aline);
  505.       fclose (afile);
  506.     }
  507.   else if (filters_dir)
  508.     {
  509.       gchar *undocumented_filter;
  510.  
  511.       undocumented_filter = g_strconcat (new_dir, G_DIR_SEPARATOR_S,
  512.                      "undocumented_filter.html", NULL);
  513.  
  514.  
  515.       afile = fopen (undocumented_filter, "rt");
  516.  
  517.       if (afile != NULL)
  518.     {
  519.       while (fgets (aline, sizeof (aline), afile))
  520.         file_contents = g_string_append (file_contents, aline);
  521.       fclose (afile);
  522.     }
  523.  
  524.       g_free (undocumented_filter);
  525.     }
  526.  
  527.   if (strlen (file_contents->str) <= 0)
  528.     {
  529.       chdir (old_dir);
  530.       g_string_sprintf (file_contents, gettext (doc_not_found_format_string),
  531.             eek_png_tag, ref);
  532.     }
  533.   else
  534.     page_valid = TRUE;
  535.  
  536.   html_source (dest_page, new_ref, 0, file_contents->str, 
  537.            add_to_queue, add_to_history && page_valid);
  538.  
  539.  FINISH:
  540.  
  541.   g_free (dest_page->current_ref);
  542.   dest_page->current_ref = new_ref;
  543.  
  544.   g_string_free (file_contents, TRUE);
  545.   g_free (old_dir);
  546.   g_free (new_dir);
  547.  
  548.   gtk_notebook_set_page (GTK_NOTEBOOK (notebook), dest_page->index);
  549.  
  550.   return (page_valid);
  551. }
  552.  
  553. static void
  554. xmhtml_activate (GtkWidget *html,
  555.          gpointer   data)
  556. {
  557.   XmHTMLAnchorCallbackStruct *cbs = (XmHTMLAnchorCallbackStruct *) data;
  558.   GimpParam *return_vals;
  559.   gint       nreturn_vals;
  560.  
  561.   switch (cbs->url_type)
  562.     {
  563.     case URL_JUMP:
  564.       jump_to_anchor (current_page, cbs->href);
  565.       break;
  566.  
  567.     case URL_FILE_LOCAL:
  568.       load_page (current_page, &pages[HELP], cbs->href, 0, TRUE, TRUE);
  569.       break;
  570.  
  571.     default:
  572.       /*  try to call netscape through the web_browser interface */
  573.       return_vals = gimp_run_procedure ("extension_web_browser",
  574.                     &nreturn_vals,
  575.                     GIMP_PDB_INT32,  GIMP_RUN_NONINTERACTIVE,
  576.                     GIMP_PDB_STRING, cbs->href,
  577.                     GIMP_PDB_INT32,  FALSE,
  578.                     GIMP_PDB_END);
  579.        gimp_destroy_params (return_vals, nreturn_vals);
  580.       break;
  581.     }
  582. }
  583.  
  584. static void 
  585. notebook_switch_callback (GtkNotebook     *notebook,
  586.               GtkNotebookPage *page,
  587.               gint             page_num,
  588.               gpointer         user_data)
  589. {
  590.   GtkXmHTML *html;
  591.   gint       i;
  592.   GList     *children;
  593.  
  594.   g_return_if_fail (page_num >= 0 && page_num < 3);
  595.  
  596.   html = GTK_XMHTML (current_page->html);
  597.  
  598.   /*  The html widget fails to do the following by itself  */
  599.  
  600.   GTK_WIDGET_UNSET_FLAGS (html->html.work_area, GTK_MAPPED);
  601.   GTK_WIDGET_UNSET_FLAGS (html->html.vsb, GTK_MAPPED);
  602.   GTK_WIDGET_UNSET_FLAGS (html->html.hsb, GTK_MAPPED);
  603.  
  604.   /*  Frames  */
  605.   for (i = 0; i < html->html.nframes; i++)
  606.     GTK_WIDGET_UNSET_FLAGS (html->html.frames[i]->frame, GTK_MAPPED);
  607.  
  608.   /*  Form widgets  */
  609.   for (children = html->children; children; children = children->next)
  610.     GTK_WIDGET_UNSET_FLAGS (children->data, GTK_MAPPED);
  611.  
  612.   /*  Set the new page  */
  613.   current_page = &pages[page_num];
  614. }
  615.  
  616. static void 
  617. notebook_switch_after_callback (GtkNotebook     *notebook,
  618.                 GtkNotebookPage *page,
  619.                 gint             page_num,
  620.                 gpointer         user_data)
  621. {
  622.   GtkAccelGroup *accel_group = gtk_accel_group_get_default ();
  623.  
  624.   gtk_widget_add_accelerator (GTK_XMHTML (current_page->html)->html.vsb,
  625.                   "page_up", accel_group,
  626.                   'b', 0, 0);
  627.   gtk_widget_add_accelerator (GTK_XMHTML (current_page->html)->html.vsb,
  628.                   "page_down", accel_group,
  629.                   ' ', 0, 0);
  630.  
  631.   gtk_widget_add_accelerator (GTK_XMHTML (current_page->html)->html.vsb,
  632.                   "page_up", accel_group,
  633.                   GDK_Page_Up, 0, 0);
  634.   gtk_widget_add_accelerator (GTK_XMHTML (current_page->html)->html.vsb,
  635.                   "page_down", accel_group,
  636.                   GDK_Page_Down, 0, 0);
  637.  
  638.   update_toolbar (current_page);
  639. }
  640.  
  641.  
  642. static gint
  643. notebook_label_button_press_callback (GtkWidget *widget,
  644.                       GdkEvent  *event,
  645.                       gpointer   data)
  646. {
  647.   guint i = GPOINTER_TO_UINT (data);
  648.  
  649.   if (current_page != &pages[i])
  650.     gtk_notebook_set_page (GTK_NOTEBOOK (notebook), i);
  651.   
  652.   return TRUE;
  653. }
  654.  
  655. static void
  656. combo_drag_begin (GtkWidget *widget,
  657.           gpointer   data)
  658. }
  659.  
  660. static void
  661. combo_drag_handle (GtkWidget        *widget, 
  662.            GdkDragContext   *context,
  663.            GtkSelectionData *selection_data,
  664.            guint             info,
  665.            guint             time,
  666.            gpointer          data)
  667. {
  668.   HelpPage *page = (HelpPage*)data;
  669.  
  670.   if (page->current_ref != NULL)
  671.     {
  672.       gtk_selection_data_set (selection_data,
  673.                   selection_data->target,
  674.                   8, 
  675.                   page->current_ref, 
  676.                   strlen (page->current_ref));
  677.     }
  678. }
  679.  
  680. static void
  681. page_up_callback (GtkWidget *widget,
  682.           GtkWidget *html)
  683. {
  684.   GtkAdjustment *adj;
  685.  
  686.   adj = GTK_ADJUSTMENT (GTK_XMHTML (html)->vsba);
  687.   gtk_adjustment_set_value (adj, adj->value - (adj->page_size));
  688. }
  689.  
  690. static void
  691. page_down_callback (GtkWidget *widget,
  692.             GtkWidget *html)
  693. {
  694.   GtkAdjustment *adj;
  695.  
  696.   adj = GTK_ADJUSTMENT (GTK_XMHTML (html)->vsba);
  697.   gtk_adjustment_set_value (adj, adj->value + (adj->page_size));
  698. }
  699.  
  700. static gint
  701. wheel_callback (GtkWidget      *widget,
  702.         GdkEventButton *bevent,
  703.         GtkWidget      *html)
  704. {
  705.   GtkAdjustment *adj;
  706.   gfloat new_value;
  707.  
  708.   if (! GTK_XMHTML (html)->html.needs_vsb)
  709.     return FALSE;
  710.  
  711.   adj = GTK_ADJUSTMENT (GTK_XMHTML (html)->vsba);
  712.  
  713.   switch (bevent->button)
  714.     {
  715.     case 4:
  716.       new_value = adj->value - adj->page_increment / 2;
  717.       break;
  718.  
  719.     case 5:
  720.       new_value = adj->value + adj->page_increment / 2;
  721.       break;
  722.  
  723.     default:
  724.       return FALSE;
  725.     }
  726.  
  727.   new_value = CLAMP (new_value, adj->lower, adj->upper - adj->page_size);
  728.   gtk_adjustment_set_value (adj, new_value);
  729.  
  730.   return TRUE;
  731. }
  732.  
  733. static gint
  734. set_initial_history (gpointer data)
  735. {
  736.   gint   add_to_history = GPOINTER_TO_INT (data);
  737.   gchar *title;
  738.  
  739.   title = XmHTMLGetTitle (pages[HELP].html);
  740.   if (add_to_history)
  741.     history_add (pages[HELP].current_ref, title);
  742.  
  743.   gtk_signal_handler_block_by_data (GTK_OBJECT (GTK_COMBO (combo)->entry), combo);
  744.   gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (combo)->entry), title);
  745.   gtk_signal_handler_unblock_by_data (GTK_OBJECT (GTK_COMBO (combo)->entry), combo);
  746.  
  747.   return FALSE;
  748. }
  749.  
  750. gboolean
  751. open_browser_dialog (gchar *help_path,
  752.              gchar *locale,
  753.              gchar *help_file)
  754. {
  755.   GtkWidget *window;
  756.   GtkWidget *vbox, *hbox, *bbox, *html_box;
  757.   GtkWidget *button;
  758.   GtkWidget *title;
  759.   GtkWidget *drag_source;
  760.   GtkWidget *label;
  761.  
  762.   gchar   *initial_dir;
  763.   gchar   *initial_ref;
  764.   gchar   *root_dir;
  765.   gchar   *eek_png_path;
  766.   gint     success;
  767.   guint    i;
  768.  
  769.   gimp_ui_init ("helpbrowser", TRUE);
  770.  
  771.   root_dir = g_strdup (gimp_help_root);
  772.  
  773.   if (chdir (root_dir) == -1)
  774.     {
  775.       g_message (_("GIMP Help Browser Error.\n\n"
  776.            "Couldn't find my root html directory.\n"
  777.            "(%s)"), root_dir);
  778.       return FALSE;
  779.     }
  780.  
  781.   eek_png_path = g_strconcat (root_dir, G_DIR_SEPARATOR_S,
  782.                   "images", G_DIR_SEPARATOR_S,
  783.                   "eek.png", NULL);
  784.   if (access (eek_png_path, R_OK) == 0)
  785.     eek_png_tag = g_strdup_printf ("<img src=\"%s\">", eek_png_path);
  786.  
  787.   g_free (eek_png_path);
  788.  
  789.   if (chdir (help_path) == -1)
  790.     {
  791.       g_message (_("GIMP Help Browser Error.\n\n"
  792.            "Couldn't find my root html directory.\n"
  793.            "(%s)"), help_path);
  794.       return FALSE;
  795.     }
  796.  
  797.   initial_dir = g_get_current_dir ();
  798.  
  799.   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  800.   gtk_signal_connect (GTK_OBJECT (window), "delete_event",
  801.               GTK_SIGNAL_FUNC (close_callback),
  802.               NULL);
  803.   gtk_signal_connect (GTK_OBJECT (window), "destroy",
  804.               GTK_SIGNAL_FUNC (close_callback),
  805.               NULL);
  806.   gtk_window_set_wmclass (GTK_WINDOW (window), "helpbrowser", "Gimp");
  807.   gtk_window_set_title (GTK_WINDOW (window), _("GIMP Help Browser"));
  808.  
  809.   gimp_help_connect_help_accel (window, gimp_standard_help_func,
  810.                 "dialogs/help.html");
  811.  
  812.   vbox = gtk_vbox_new (FALSE, 0);
  813.   gtk_container_add (GTK_CONTAINER (window), vbox);
  814.  
  815.   hbox = gtk_hbox_new (FALSE, 0);
  816.   gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
  817.  
  818.   bbox = gtk_hbutton_box_new ();
  819.   gtk_button_box_set_spacing (GTK_BUTTON_BOX (bbox), 0);
  820.   gtk_box_pack_start (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
  821.  
  822.   back_button = gimp_pixmap_button_new (back_xpm, _("Back"));
  823.   gtk_button_set_relief (GTK_BUTTON (back_button), GTK_RELIEF_NONE);
  824.   gtk_container_add (GTK_CONTAINER (bbox), back_button);
  825.   gtk_widget_set_sensitive (GTK_WIDGET (back_button), FALSE);
  826.   gtk_signal_connect (GTK_OBJECT (back_button), "clicked",
  827.               GTK_SIGNAL_FUNC (back_callback),
  828.               NULL);
  829.   gtk_widget_show (back_button);
  830.  
  831.   forward_button = gimp_pixmap_button_new (forward_xpm, _("Forward"));
  832.   gtk_button_set_relief (GTK_BUTTON (forward_button), GTK_RELIEF_NONE);
  833.   gtk_container_add (GTK_CONTAINER (bbox), forward_button);
  834.   gtk_widget_set_sensitive (GTK_WIDGET (forward_button), FALSE);
  835.   gtk_signal_connect (GTK_OBJECT (forward_button), "clicked",
  836.               GTK_SIGNAL_FUNC (forward_callback),
  837.               NULL);
  838.   gtk_widget_show (forward_button);
  839.  
  840.   gtk_widget_show (bbox);
  841.  
  842.   bbox = gtk_hbutton_box_new ();
  843.   gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
  844.  
  845.   button = gtk_button_new_with_label (_("Close"));
  846.   gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
  847.   gtk_container_add (GTK_CONTAINER (bbox), button);
  848.   gtk_signal_connect (GTK_OBJECT (button), "clicked",
  849.                GTK_SIGNAL_FUNC (close_callback),
  850.               NULL);
  851.   gtk_widget_show (button);
  852.  
  853.   gtk_widget_show (bbox);
  854.   gtk_widget_show (hbox);
  855.  
  856.   notebook = gtk_notebook_new ();
  857.   gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP);
  858.   gtk_notebook_set_tab_vborder (GTK_NOTEBOOK (notebook), 0);
  859.   gtk_box_pack_start (GTK_BOX (vbox), notebook, TRUE, TRUE, 0);
  860.  
  861.   for (i = 0; i < 3; i++)
  862.     {
  863.       static guint page_up_signal = 0;
  864.       static guint page_down_signal = 0;
  865.  
  866.       pages[i].index       = i;
  867.       pages[i].html        = gtk_xmhtml_new ();
  868.       pages[i].queue       = queue_new ();
  869.  
  870.       gtk_xmhtml_set_anchor_underline_type (GTK_XMHTML (pages[i].html),
  871.                         GTK_ANCHOR_SINGLE_LINE);
  872.       gtk_xmhtml_set_anchor_buttons (GTK_XMHTML (pages[i].html), FALSE);
  873.       gtk_widget_set_usize (GTK_WIDGET (pages[i].html), -1, 300);
  874.  
  875.       switch (i)
  876.     {
  877.     case CONTENTS:
  878.     case INDEX:
  879.       pages[i].current_ref = g_strconcat (root_dir, G_DIR_SEPARATOR_S,
  880.                           locale, G_DIR_SEPARATOR_S,
  881.                           ".", NULL);
  882.  
  883.       title = drag_source = gtk_event_box_new ();
  884.       label = gtk_label_new (gettext (pages[i].label));
  885.       gtk_container_add (GTK_CONTAINER (title), label);
  886.       gtk_widget_show (label);
  887.       break;
  888.     case HELP:
  889.       pages[i].current_ref = g_strconcat (initial_dir, G_DIR_SEPARATOR_S,
  890.                           locale, G_DIR_SEPARATOR_S,
  891.                           ".", NULL);
  892.  
  893.       title = combo = gtk_combo_new ();
  894.       drag_source = GTK_COMBO (combo)->entry;
  895.       gtk_widget_set_usize (GTK_WIDGET (combo), 300, -1);
  896.       gtk_entry_set_editable (GTK_ENTRY (GTK_COMBO (combo)->entry), FALSE); 
  897.       gtk_combo_set_use_arrows (GTK_COMBO (combo), TRUE);
  898.       gtk_signal_connect (GTK_OBJECT (GTK_COMBO (combo)->entry), 
  899.                   "changed",
  900.                   GTK_SIGNAL_FUNC (entry_changed_callback), 
  901.                   combo);
  902.       gtk_signal_connect (GTK_OBJECT (GTK_WIDGET (GTK_COMBO (combo)->entry)), 
  903.                   "button-press-event",
  904.                   GTK_SIGNAL_FUNC (entry_button_press_callback), 
  905.                   NULL);
  906.       gtk_widget_show (combo);
  907.       break;
  908.     default:
  909.       title = drag_source = NULL;     /* to please the compiler */
  910.       break;
  911.     }      
  912.  
  913.       /*  connect to the button_press signal to make notebook switching work */ 
  914.       gtk_signal_connect (GTK_OBJECT (title), "button_press_event",
  915.               GTK_SIGNAL_FUNC (notebook_label_button_press_callback), 
  916.               GUINT_TO_POINTER (i));
  917.  
  918.       /*  dnd source  */
  919.       gtk_drag_source_set (GTK_WIDGET (drag_source),
  920.                GDK_BUTTON1_MASK,
  921.                help_dnd_target_table, n_help_dnd_targets, 
  922.                GDK_ACTION_MOVE | GDK_ACTION_COPY);
  923.       gtk_signal_connect (GTK_OBJECT (drag_source), "drag_begin",
  924.               GTK_SIGNAL_FUNC (combo_drag_begin),
  925.               &pages[i]);
  926.       gtk_signal_connect (GTK_OBJECT (drag_source), "drag_data_get",
  927.               GTK_SIGNAL_FUNC (combo_drag_handle),
  928.               &pages[i]);
  929.  
  930.       html_box = gtk_vbox_new (FALSE, 0);
  931.       gtk_container_add (GTK_CONTAINER (html_box), pages[i].html);
  932.  
  933.       gtk_notebook_append_page (GTK_NOTEBOOK (notebook), html_box, title);
  934.       gtk_widget_show (title);
  935.  
  936.       if (i == HELP && help_file)
  937.     {
  938.       initial_ref = g_strconcat (initial_dir, G_DIR_SEPARATOR_S,
  939.                      locale, G_DIR_SEPARATOR_S,
  940.                      help_file, NULL);
  941.     }
  942.       else
  943.     {
  944.       initial_ref = g_strconcat (root_dir, G_DIR_SEPARATOR_S,
  945.                      locale, G_DIR_SEPARATOR_S,
  946.                      pages[i].home, NULL);
  947.     }
  948.  
  949.       success = load_page (&pages[i], &pages[i], initial_ref, 0, TRUE, FALSE);
  950.       g_free (initial_ref);
  951.  
  952.       gtk_widget_show (pages[i].html);
  953.       gtk_widget_show (html_box);
  954.       gtk_signal_connect (GTK_OBJECT (pages[i].html), "activate",
  955.               (GtkSignalFunc) xmhtml_activate,
  956.               &pages[i]);
  957.  
  958.       if (! page_up_signal)
  959.     {
  960.       page_up_signal = gtk_object_class_user_signal_new 
  961.         (GTK_OBJECT (GTK_XMHTML (pages[i].html)->html.vsb)->klass,
  962.          "page_up",
  963.          GTK_RUN_FIRST,
  964.          gtk_marshal_NONE__NONE,
  965.          GTK_TYPE_NONE, 0);
  966.       page_down_signal = gtk_object_class_user_signal_new
  967.         (GTK_OBJECT (GTK_XMHTML (pages[i].html)->html.vsb)->klass,
  968.          "page_down",
  969.          GTK_RUN_FIRST,
  970.          gtk_marshal_NONE__NONE,
  971.          GTK_TYPE_NONE, 0);
  972.     }
  973.  
  974.       gtk_signal_connect (GTK_OBJECT (GTK_XMHTML (pages[i].html)->html.vsb),
  975.               "page_up",
  976.               GTK_SIGNAL_FUNC (page_up_callback),
  977.               pages[i].html);
  978.       gtk_signal_connect (GTK_OBJECT (GTK_XMHTML (pages[i].html)->html.vsb), 
  979.               "page_down",
  980.               GTK_SIGNAL_FUNC (page_down_callback),
  981.               pages[i].html);
  982.  
  983.       gtk_signal_connect (GTK_OBJECT (GTK_XMHTML (pages[i].html)->html.work_area),
  984.               "button_press_event",
  985.               GTK_SIGNAL_FUNC (wheel_callback),
  986.               pages[i].html);
  987.     }
  988.  
  989.   g_free (root_dir);
  990.  
  991.   gtk_signal_connect (GTK_OBJECT (notebook), "switch-page",
  992.               GTK_SIGNAL_FUNC (notebook_switch_callback),
  993.               NULL);
  994.   gtk_signal_connect_after (GTK_OBJECT (notebook), "switch-page",
  995.                 GTK_SIGNAL_FUNC (notebook_switch_after_callback),
  996.                 NULL);
  997.  
  998.   gtk_notebook_set_page (GTK_NOTEBOOK (notebook), HELP);
  999.   gtk_widget_show (notebook);
  1000.  
  1001.   gtk_widget_show (vbox);
  1002.   gtk_widget_show (window);
  1003.  
  1004.   gtk_idle_add ((GtkFunction) set_initial_history, GINT_TO_POINTER (success));
  1005.  
  1006.   g_free (initial_dir);
  1007.  
  1008.   return TRUE;
  1009. }
  1010.  
  1011. static gint
  1012. idle_load_page (gpointer data)
  1013. {
  1014.   gchar *path = data;
  1015.  
  1016.   load_page (&pages[HELP], &pages[HELP], path, 0, TRUE, TRUE);
  1017.   g_free (path);
  1018.  
  1019.   return FALSE;
  1020. }
  1021.  
  1022. static void
  1023. run_temp_proc (gchar      *name,
  1024.            gint        nparams,
  1025.            GimpParam  *param,
  1026.            gint       *nreturn_vals,
  1027.            GimpParam **return_vals)
  1028. {
  1029.   static GimpParam  values[1];
  1030.   GimpPDBStatusType status = GIMP_PDB_SUCCESS;
  1031.   gchar *help_path;
  1032.   gchar *locale;
  1033.   gchar *help_file;
  1034.   gchar *path;
  1035.  
  1036.   /*  set default values  */
  1037.   help_path = g_strdup (gimp_help_root);
  1038.   locale    = g_strdup ("C");
  1039.   help_file = g_strdup ("introduction.html");
  1040.  
  1041.   /*  Make sure all the arguments are there!  */
  1042.   if (nparams == 3)
  1043.     {
  1044.       if (param[0].data.d_string && strlen (param[0].data.d_string))
  1045.     {
  1046.       g_free (help_path);
  1047.       help_path = g_strdup (param[0].data.d_string);
  1048.       g_strdelimit (help_path, "/", G_DIR_SEPARATOR);
  1049.     }
  1050.       if (param[1].data.d_string && strlen (param[1].data.d_string))
  1051.     {
  1052.       g_free (locale);
  1053.       locale    = g_strdup (param[1].data.d_string);
  1054.     }
  1055.       if (param[2].data.d_string && strlen (param[2].data.d_string))
  1056.     {
  1057.       g_free (help_file);
  1058.       help_file = g_strdup (param[2].data.d_string);
  1059.       g_strdelimit (help_file, "/", G_DIR_SEPARATOR);
  1060.     }
  1061.     }
  1062.  
  1063.   path = g_strconcat (help_path, G_DIR_SEPARATOR_S, 
  1064.               locale, G_DIR_SEPARATOR_S,
  1065.               help_file, NULL);
  1066.  
  1067.   g_free (help_path);
  1068.   g_free (locale);
  1069.   g_free (help_file);
  1070.  
  1071.   gtk_idle_add (idle_load_page, path);
  1072.  
  1073.   *nreturn_vals = 1;
  1074.   *return_vals = values;
  1075.  
  1076.   values[0].type = GIMP_PDB_STATUS;
  1077.   values[0].data.d_status = status;
  1078. }
  1079.  
  1080. /*  from libgimp/gimp.c  */
  1081. void
  1082. gimp_run_temp (void);
  1083.  
  1084. static gboolean
  1085. input_callback (GIOChannel   *channel,
  1086.                 GIOCondition  condition,
  1087.                 gpointer      data)
  1088. {
  1089.   /* We have some data in the wire - read it */
  1090.   /* The below will only ever run a single proc */
  1091.   gimp_run_temp ();
  1092.  
  1093.   return TRUE;
  1094. }
  1095.  
  1096. static void
  1097. install_temp_proc (void)
  1098. {
  1099.   static GimpParamDef args[] =
  1100.   {
  1101.     { GIMP_PDB_STRING, "help_path", "" },
  1102.     { GIMP_PDB_STRING, "locale",    "Langusge to use" },
  1103.     { GIMP_PDB_STRING, "help_file", "Path of a local document to open. "
  1104.                                     "Can be relative to GIMP_HELP_PATH." }
  1105.   };
  1106.   static gint nargs = sizeof (args) / sizeof (args[0]);
  1107.  
  1108.   gimp_install_temp_proc (GIMP_HELP_TEMP_EXT_NAME,
  1109.               "DON'T USE THIS ONE",
  1110.               "(Temporary procedure)",
  1111.               "Sven Neumann <sven@gimp.org>, "
  1112.               "Michael Natterer <mitschel@cs.tu-berlin.de>",
  1113.               "Sven Neumann & Michael Natterer",
  1114.               "1999",
  1115.               NULL,
  1116.               "",
  1117.               GIMP_TEMPORARY,
  1118.               nargs, 0,
  1119.               args, NULL,
  1120.               run_temp_proc);
  1121.  
  1122.   /* Tie into the gdk input function */
  1123.   g_io_add_watch (_readchannel, G_IO_IN | G_IO_PRI, input_callback, NULL);
  1124.  
  1125.   temp_proc_installed = TRUE;
  1126. }
  1127.  
  1128. static gboolean
  1129. open_url (gchar *help_path,
  1130.       gchar *locale,
  1131.       gchar *help_file)
  1132. {
  1133.   if (! open_browser_dialog (help_path, locale, help_file))
  1134.     return FALSE;
  1135.  
  1136.   install_temp_proc ();
  1137.   gtk_main ();
  1138.  
  1139.   return TRUE;
  1140. }
  1141.  
  1142. MAIN ()
  1143.  
  1144. static void
  1145. query (void)
  1146. {
  1147.   static GimpParamDef args[] =
  1148.   {
  1149.     { GIMP_PDB_INT32,  "run_mode",  "Interactive" },
  1150.     { GIMP_PDB_STRING, "help_path", "" },
  1151.     { GIMP_PDB_STRING, "locale",    "Language to use" },
  1152.     { GIMP_PDB_STRING, "help_file", "Path of a local document to open. "
  1153.                                     "Can be relative to GIMP_HELP_PATH." }
  1154.   };
  1155.   static gint nargs = sizeof (args) / sizeof (args[0]);
  1156.  
  1157.   gimp_install_procedure (GIMP_HELP_EXT_NAME,
  1158.                           "Browse the GIMP help pages",
  1159.                           "A small and simple HTML browser optimzed for "
  1160.               "browsing the GIMP help pages.",
  1161.                           "Sven Neumann <sven@gimp.org>, "
  1162.               "Michael Natterer <mitch@gimp.org>",
  1163.               "Sven Neumann & Michael Natterer",
  1164.                           "1999",
  1165.                           NULL,
  1166.                           "",
  1167.                           GIMP_EXTENSION,
  1168.                           nargs, 0,
  1169.                           args, NULL);
  1170. }
  1171.  
  1172. static void
  1173. run (gchar      *name,
  1174.      gint        nparams,
  1175.      GimpParam  *param,
  1176.      gint       *nreturn_vals,
  1177.      GimpParam **return_vals)
  1178. {
  1179.   static GimpParam  values[1];
  1180.   GimpRunModeType   run_mode;
  1181.   GimpPDBStatusType status = GIMP_PDB_SUCCESS;
  1182.   gchar *env_root_dir = NULL;
  1183.   gchar *help_path    = NULL;
  1184.   gchar *locale       = NULL;
  1185.   gchar *help_file    = NULL;
  1186.  
  1187.   run_mode = param[0].data.d_int32;
  1188.  
  1189.   values[0].type = GIMP_PDB_STATUS;
  1190.   values[0].data.d_status = status;
  1191.  
  1192.   *nreturn_vals = 1;
  1193.   *return_vals = values;
  1194.  
  1195.   INIT_I18N_UI ();
  1196.  
  1197.   if (strcmp (name, GIMP_HELP_EXT_NAME) == 0)
  1198.     {
  1199.       switch (run_mode)
  1200.         {
  1201.         case GIMP_RUN_INTERACTIVE:
  1202.         case GIMP_RUN_NONINTERACTIVE:
  1203.     case GIMP_RUN_WITH_LAST_VALS:
  1204.       /*  set default values  */
  1205.       env_root_dir = g_getenv ("GIMP_HELP_ROOT");
  1206.  
  1207.       if (env_root_dir)
  1208.         {
  1209.           if (chdir (env_root_dir) == -1)
  1210.         {
  1211.           g_message (_("GIMP Help Browser Error.\n\n"
  1212.                    "Couldn't find GIMP_HELP_ROOT html directory.\n"
  1213.                    "(%s)"), env_root_dir);
  1214.  
  1215.           status = GIMP_PDB_EXECUTION_ERROR;
  1216.           break;
  1217.         }
  1218.  
  1219.           gimp_help_root = g_strdup (env_root_dir);
  1220.         }
  1221.       else
  1222.         {
  1223.           gimp_help_root = g_strconcat (gimp_data_directory(),
  1224.                         G_DIR_SEPARATOR_S, 
  1225.                         GIMP_HELP_PREFIX, NULL);
  1226.         }
  1227.  
  1228.       help_path = g_strdup (gimp_help_root);
  1229.       locale    = g_strdup ("C");
  1230.       help_file = g_strdup ("introduction.html");
  1231.       
  1232.       /*  Make sure all the arguments are there!  */
  1233.       if (nparams == 4)
  1234.         {
  1235.           if (param[1].data.d_string && strlen (param[1].data.d_string))
  1236.         {
  1237.           g_free (help_path);
  1238.           help_path = g_strdup (param[1].data.d_string);
  1239.           g_strdelimit (help_path, "/", G_DIR_SEPARATOR);
  1240.         }
  1241.           if (param[2].data.d_string && strlen (param[2].data.d_string))
  1242.         {
  1243.           g_free (locale);
  1244.           locale = g_strdup (param[2].data.d_string);
  1245.         }
  1246.           if (param[3].data.d_string && strlen (param[3].data.d_string))
  1247.         {
  1248.           g_free (help_file);
  1249.           help_file = g_strdup (param[3].data.d_string);
  1250.           g_strdelimit (help_file, "/", G_DIR_SEPARATOR);
  1251.         }
  1252.         }
  1253.           break;
  1254.  
  1255.         default:
  1256.       status = GIMP_PDB_CALLING_ERROR;
  1257.           break;
  1258.         }
  1259.  
  1260.       if (status == GIMP_PDB_SUCCESS)
  1261.         {
  1262.              if (!open_url (help_path, locale, help_file))
  1263.             values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
  1264.       else
  1265.         values[0].data.d_status = GIMP_PDB_SUCCESS;
  1266.  
  1267.       g_free (help_path);
  1268.       g_free (locale);
  1269.       g_free (help_file);
  1270.         }
  1271.       else
  1272.         values[0].data.d_status = status;
  1273.     }
  1274.   else
  1275.     values[0].data.d_status = GIMP_PDB_CALLING_ERROR;
  1276. }
  1277.