mirror of https://github.com/GNOME/gimp.git
1088 lines
30 KiB
C
1088 lines
30 KiB
C
/* The GIMP -- an image manipulation program
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* The GIMP Help Browser
|
|
* Copyright (C) 1999-2005 Sven Neumann <sven@gimp.org>
|
|
* Michael Natterer <mitch@gimp.org>
|
|
*
|
|
* dialog.c
|
|
*
|
|
* 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 <string.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
|
|
#include <glib/gstdio.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
#include <libgtkhtml/gtkhtml.h>
|
|
|
|
#include "libgimpwidgets/gimpwidgets.h"
|
|
|
|
#include "libgimp/gimp.h"
|
|
#include "libgimp/gimpui.h"
|
|
|
|
#ifdef G_OS_WIN32
|
|
#include "libgimpbase/gimpwin32-io.h"
|
|
#endif
|
|
|
|
#include "gimpthrobber.h"
|
|
#include "gimpthrobberaction.h"
|
|
|
|
#include "dialog.h"
|
|
#include "queue.h"
|
|
#include "uri.h"
|
|
|
|
#include "logo-pixbuf.h"
|
|
|
|
#include "libgimp/stdplugins-intl.h"
|
|
|
|
#ifndef _O_BINARY
|
|
#define _O_BINARY 0
|
|
#endif
|
|
|
|
|
|
enum
|
|
{
|
|
HISTORY_TITLE,
|
|
HISTORY_URI
|
|
};
|
|
|
|
/* local function prototypes */
|
|
|
|
static GtkUIManager * ui_manager_new (GtkWidget *window);
|
|
static void browser_dialog_404 (HtmlDocument *doc,
|
|
const gchar *uri,
|
|
const gchar *message);
|
|
|
|
static void back_callback (GtkAction *action,
|
|
gpointer data);
|
|
static void forward_callback (GtkAction *action,
|
|
gpointer data);
|
|
static void index_callback (GtkAction *action,
|
|
gpointer data);
|
|
static void zoom_in_callback (GtkAction *action,
|
|
gpointer data);
|
|
static void zoom_out_callback (GtkAction *action,
|
|
gpointer data);
|
|
static void close_callback (GtkAction *action,
|
|
gpointer data);
|
|
static void online_callback (GtkAction *action,
|
|
gpointer data);
|
|
|
|
static void update_toolbar (void);
|
|
|
|
static void combo_changed (GtkWidget *widget,
|
|
gpointer data);
|
|
|
|
static void drag_begin (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
gpointer data);
|
|
static void drag_data_get (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint time,
|
|
gpointer data);
|
|
static void view_realize (GtkWidget *widget);
|
|
static void view_unrealize (GtkWidget *widget);
|
|
static gboolean view_popup_menu (GtkWidget *widget,
|
|
GdkEventButton *event);
|
|
static gboolean view_button_press (GtkWidget *widget,
|
|
GdkEventButton *event);
|
|
|
|
static void title_changed (HtmlDocument *doc,
|
|
const gchar *new_title,
|
|
gpointer data);
|
|
static void link_clicked (HtmlDocument *doc,
|
|
const gchar *uri,
|
|
gpointer data);
|
|
static gboolean request_url (HtmlDocument *doc,
|
|
const gchar *uri,
|
|
HtmlStream *stream,
|
|
GError **error);
|
|
static gboolean io_handler (GIOChannel *io,
|
|
GIOCondition condition,
|
|
gpointer data);
|
|
static void load_remote_page (const gchar *uri);
|
|
|
|
static void history_add (GtkComboBox *combo,
|
|
const gchar *uri,
|
|
const gchar *title);
|
|
|
|
static gboolean has_case_prefix (const gchar *haystack,
|
|
const gchar *needle);
|
|
|
|
static gchar * filename_from_uri (const gchar *uri);
|
|
|
|
|
|
/* private variables */
|
|
|
|
static const gchar *eek_png_tag = "<h1>Eeek!</h1>";
|
|
|
|
static Queue *queue = NULL;
|
|
static gchar *current_uri = NULL;
|
|
|
|
static GtkWidget *html = NULL;
|
|
static GtkUIManager *ui_manager = NULL;
|
|
static GtkWidget *button_prev = NULL;
|
|
static GtkWidget *button_next = NULL;
|
|
static GdkCursor *busy_cursor = NULL;
|
|
|
|
static GtkTargetEntry help_dnd_target_table[] =
|
|
{
|
|
{ "text/uri-list", 0, 0 },
|
|
{ "_NETSCAPE_URL", 0, 0 }
|
|
};
|
|
|
|
|
|
/* public functions */
|
|
|
|
void
|
|
browser_dialog_open (void)
|
|
{
|
|
GtkWidget *window;
|
|
GtkWidget *vbox;
|
|
GtkWidget *toolbar;
|
|
GtkWidget *hbox;
|
|
GtkWidget *scroll;
|
|
GtkWidget *drag_source;
|
|
GtkWidget *image;
|
|
GtkWidget *combo;
|
|
GtkWidget *button;
|
|
GtkToolItem *item;
|
|
GtkAction *action;
|
|
GtkListStore *history;
|
|
GtkCellRenderer *cell;
|
|
GdkPixbuf *pixbuf;
|
|
gchar *eek_png_path;
|
|
|
|
gimp_ui_init ("helpbrowser", TRUE);
|
|
|
|
eek_png_path = g_build_filename (gimp_data_directory (),
|
|
"themes", "Default", "images",
|
|
"stock-wilber-eek-64.png", NULL);
|
|
|
|
if (g_file_test (eek_png_path, G_FILE_TEST_EXISTS))
|
|
eek_png_tag = g_strdup_printf ("<img src=\"%s\">", eek_png_path);
|
|
|
|
g_free (eek_png_path);
|
|
|
|
/* the dialog window */
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_title (GTK_WINDOW (window), _("GIMP Help Browser"));
|
|
gtk_window_set_role (GTK_WINDOW (window), "helpbrowser");
|
|
|
|
gtk_window_set_default_size (GTK_WINDOW (window), 420, 500);
|
|
|
|
g_signal_connect (window, "destroy",
|
|
G_CALLBACK (gtk_main_quit),
|
|
NULL);
|
|
|
|
pixbuf = gdk_pixbuf_new_from_inline (-1, logo_data, FALSE, NULL);
|
|
gtk_window_set_icon (GTK_WINDOW (window), pixbuf);
|
|
g_object_unref (pixbuf);
|
|
|
|
vbox = gtk_vbox_new (FALSE, 2);
|
|
gtk_container_add (GTK_CONTAINER (window), vbox);
|
|
gtk_widget_show (vbox);
|
|
|
|
ui_manager = ui_manager_new (window);
|
|
|
|
toolbar = gtk_ui_manager_get_widget (ui_manager, "/help-browser-toolbar");
|
|
gtk_box_pack_start (GTK_BOX (vbox), toolbar, FALSE, FALSE, 0);
|
|
gtk_widget_show (toolbar);
|
|
|
|
item = g_object_new (GTK_TYPE_MENU_TOOL_BUTTON, NULL);
|
|
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, 0);
|
|
gtk_widget_show (GTK_WIDGET (item));
|
|
|
|
action = gtk_ui_manager_get_action (ui_manager,
|
|
"/ui/help-browser-popup/forward");
|
|
gtk_action_connect_proxy (action, GTK_WIDGET (item));
|
|
g_object_notify (G_OBJECT (action), "tooltip");
|
|
button_next = GTK_WIDGET (item);
|
|
|
|
item = g_object_new (GTK_TYPE_MENU_TOOL_BUTTON, NULL);
|
|
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, 0);
|
|
gtk_widget_show (GTK_WIDGET (item));
|
|
|
|
action = gtk_ui_manager_get_action (ui_manager,
|
|
"/ui/help-browser-popup/back");
|
|
gtk_action_connect_proxy (action, GTK_WIDGET (item));
|
|
g_object_notify (G_OBJECT (action), "tooltip");
|
|
button_prev = GTK_WIDGET (item);
|
|
|
|
item =
|
|
GTK_TOOL_ITEM (gtk_ui_manager_get_widget (ui_manager,
|
|
"/help-browser-toolbar/space"));
|
|
gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (item), FALSE);
|
|
gtk_tool_item_set_expand (item, TRUE);
|
|
|
|
button = gtk_ui_manager_get_widget (ui_manager,
|
|
"/help-browser-toolbar/online");
|
|
gimp_throbber_set_image (GIMP_THROBBER (button),
|
|
gtk_image_new_from_pixbuf (pixbuf));
|
|
|
|
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);
|
|
|
|
gimp_help_set_help_data (drag_source,
|
|
_("Drag and drop this icon to a web browser"),
|
|
NULL);
|
|
|
|
gtk_drag_source_set (GTK_WIDGET (drag_source),
|
|
GDK_BUTTON1_MASK,
|
|
help_dnd_target_table,
|
|
G_N_ELEMENTS (help_dnd_target_table),
|
|
GDK_ACTION_COPY);
|
|
|
|
g_signal_connect (drag_source, "drag-begin",
|
|
G_CALLBACK (drag_begin),
|
|
NULL);
|
|
g_signal_connect (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 */
|
|
history = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
|
|
combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (history));
|
|
g_object_unref (history);
|
|
|
|
cell = gtk_cell_renderer_text_new ();
|
|
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
|
|
gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell,
|
|
"text", HISTORY_TITLE,
|
|
NULL);
|
|
|
|
gtk_widget_set_size_request (GTK_WIDGET (combo), 320, -1);
|
|
gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0);
|
|
gtk_widget_show (combo);
|
|
|
|
g_signal_connect (combo, "changed",
|
|
G_CALLBACK (combo_changed),
|
|
NULL);
|
|
|
|
/* HTML view */
|
|
html = html_view_new ();
|
|
queue = queue_new ();
|
|
|
|
gtk_widget_set_size_request (html, -1, 200);
|
|
|
|
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);
|
|
|
|
g_signal_connect (html, "realize",
|
|
G_CALLBACK (view_realize),
|
|
NULL);
|
|
g_signal_connect (html, "unrealize",
|
|
G_CALLBACK (view_unrealize),
|
|
NULL);
|
|
|
|
g_signal_connect (html, "popup-menu",
|
|
G_CALLBACK (view_popup_menu),
|
|
NULL);
|
|
g_signal_connect (html, "button-press-event",
|
|
G_CALLBACK (view_button_press),
|
|
NULL);
|
|
|
|
html_view_set_document (HTML_VIEW (html), html_document_new ());
|
|
|
|
g_signal_connect (HTML_VIEW (html)->document, "title-changed",
|
|
G_CALLBACK (title_changed),
|
|
combo);
|
|
g_signal_connect (HTML_VIEW (html)->document, "link-clicked",
|
|
G_CALLBACK (link_clicked),
|
|
NULL);
|
|
g_signal_connect (HTML_VIEW (html)->document, "request-url",
|
|
G_CALLBACK (request_url),
|
|
NULL);
|
|
|
|
gtk_widget_grab_focus (html);
|
|
|
|
gtk_widget_show (window);
|
|
}
|
|
|
|
static gboolean
|
|
idle_jump_to_anchor (const gchar *anchor)
|
|
{
|
|
if (html && anchor)
|
|
html_view_jump_to_anchor (HTML_VIEW (html), anchor);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
idle_scroll_to_pos (gpointer data)
|
|
{
|
|
gint offset = GPOINTER_TO_INT (data);
|
|
|
|
if (html)
|
|
{
|
|
GtkAdjustment *adj = gtk_layout_get_vadjustment (GTK_LAYOUT (html));
|
|
|
|
gtk_adjustment_set_value (adj,
|
|
(gdouble) offset / (1 << 16) * adj->upper);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
browser_dialog_load (const gchar *uri,
|
|
gboolean add_to_queue)
|
|
{
|
|
HtmlDocument *doc = HTML_VIEW (html)->document;
|
|
GtkAdjustment *adj = gtk_layout_get_vadjustment (GTK_LAYOUT (html));
|
|
gchar *abs;
|
|
gchar *new_uri;
|
|
gchar *anchor;
|
|
gchar *tmp;
|
|
|
|
g_return_if_fail (uri != NULL);
|
|
|
|
if (! current_uri)
|
|
{
|
|
gchar *slash;
|
|
|
|
current_uri = g_strdup (uri);
|
|
|
|
slash = strrchr (current_uri, '/');
|
|
|
|
if (slash)
|
|
*slash = '\0';
|
|
}
|
|
|
|
abs = uri_to_abs (uri, current_uri);
|
|
if (! abs)
|
|
return;
|
|
|
|
anchor = strchr (uri, '#');
|
|
if (anchor && anchor[0] && anchor[1])
|
|
{
|
|
new_uri = g_strconcat (abs, anchor, NULL);
|
|
anchor += 1;
|
|
}
|
|
else
|
|
{
|
|
new_uri = g_strdup (abs);
|
|
anchor = NULL;
|
|
}
|
|
|
|
if (! has_case_prefix (abs, "file:/"))
|
|
{
|
|
load_remote_page (uri);
|
|
return;
|
|
}
|
|
|
|
tmp = uri_to_abs (current_uri, current_uri);
|
|
if (!tmp || strcmp (tmp, abs))
|
|
{
|
|
GError *error = NULL;
|
|
|
|
html_document_clear (doc);
|
|
html_document_open_stream (doc, "text/html");
|
|
gtk_adjustment_set_value (adj, 0.0);
|
|
|
|
if (anchor)
|
|
g_idle_add_full (G_PRIORITY_LOW,
|
|
(GSourceFunc) idle_jump_to_anchor,
|
|
g_strdup (anchor), (GDestroyNotify) g_free);
|
|
|
|
if (request_url (doc, abs, doc->current_stream, &error))
|
|
{
|
|
if (html->window)
|
|
gdk_window_set_cursor (html->window, busy_cursor);
|
|
}
|
|
else
|
|
{
|
|
browser_dialog_404 (doc, abs, error->message);
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (anchor)
|
|
html_view_jump_to_anchor (HTML_VIEW (html), anchor);
|
|
else
|
|
gtk_adjustment_set_value (adj, 0.0);
|
|
}
|
|
|
|
g_free (tmp);
|
|
|
|
g_free (current_uri);
|
|
current_uri = new_uri;
|
|
|
|
if (add_to_queue)
|
|
queue_add (queue, new_uri);
|
|
|
|
update_toolbar ();
|
|
|
|
gtk_window_present (GTK_WINDOW (gtk_widget_get_toplevel (html)));
|
|
}
|
|
|
|
|
|
/* private functions */
|
|
|
|
static GtkUIManager *
|
|
ui_manager_new (GtkWidget *window)
|
|
{
|
|
static const GtkActionEntry actions[] =
|
|
{
|
|
{
|
|
"back", GTK_STOCK_GO_BACK,
|
|
NULL, "<alt>Left", N_("Go back one page"),
|
|
G_CALLBACK (back_callback)
|
|
},
|
|
{
|
|
"forward", GTK_STOCK_GO_FORWARD,
|
|
NULL, "<alt>Right", N_("Go forward one page"),
|
|
G_CALLBACK (forward_callback)
|
|
},
|
|
{
|
|
"index", GTK_STOCK_INDEX,
|
|
NULL, "<alt>Home", N_("Go to the index page"),
|
|
G_CALLBACK (index_callback)
|
|
},
|
|
{
|
|
"zoom-in", GTK_STOCK_ZOOM_IN,
|
|
NULL, "<control>plus", NULL,
|
|
G_CALLBACK (zoom_in_callback)
|
|
},
|
|
{
|
|
"zoom-out", GTK_STOCK_ZOOM_OUT,
|
|
NULL, "<control>minus", NULL,
|
|
G_CALLBACK (zoom_out_callback)
|
|
},
|
|
{
|
|
"close", GTK_STOCK_CLOSE,
|
|
NULL, "<control>W", NULL,
|
|
G_CALLBACK (close_callback)
|
|
},
|
|
{
|
|
"quit", GTK_STOCK_QUIT,
|
|
NULL, "<control>Q", NULL,
|
|
G_CALLBACK (close_callback)
|
|
}
|
|
};
|
|
|
|
GtkUIManager *ui_manager = gtk_ui_manager_new ();
|
|
GtkActionGroup *group = gtk_action_group_new ("Actions");
|
|
GtkAction *action;
|
|
GError *error = NULL;
|
|
|
|
gtk_action_group_set_translation_domain (group, NULL);
|
|
gtk_action_group_add_actions (group, actions, G_N_ELEMENTS (actions), NULL);
|
|
|
|
action = gimp_throbber_action_new ("online",
|
|
"docs.gimp.org",
|
|
_("Visit the GIMP documentation website"),
|
|
GIMP_STOCK_WILBER);
|
|
g_signal_connect_closure (action, "activate",
|
|
g_cclosure_new (G_CALLBACK (online_callback),
|
|
NULL, NULL),
|
|
FALSE);
|
|
gtk_action_group_add_action (group, action);
|
|
g_object_unref (action);
|
|
|
|
gtk_window_add_accel_group (GTK_WINDOW (window),
|
|
gtk_ui_manager_get_accel_group (ui_manager));
|
|
gtk_accel_group_lock (gtk_ui_manager_get_accel_group (ui_manager));
|
|
|
|
gtk_ui_manager_insert_action_group (ui_manager, group, -1);
|
|
g_object_unref (group);
|
|
|
|
gtk_ui_manager_add_ui_from_string (ui_manager,
|
|
"<ui>"
|
|
" <toolbar name=\"help-browser-toolbar\">"
|
|
" <toolitem action=\"index\" />"
|
|
" <separator name=\"space\" />"
|
|
" <toolitem action=\"online\" />"
|
|
" </toolbar>"
|
|
" <accelerator action=\"close\" />"
|
|
" <accelerator action=\"quit\" />"
|
|
"</ui>",
|
|
-1, &error);
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("error parsing ui: %s", error->message);
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
gtk_ui_manager_add_ui_from_string (ui_manager,
|
|
"<ui>"
|
|
" <popup name=\"help-browser-popup\">"
|
|
" <menuitem action=\"back\" />"
|
|
" <menuitem action=\"forward\" />"
|
|
" <menuitem action=\"index\" />"
|
|
" <separator />"
|
|
" <menuitem action=\"zoom-in\" />"
|
|
" <menuitem action=\"zoom-out\" />"
|
|
" <separator />"
|
|
" <menuitem action=\"close\" />"
|
|
" </popup>"
|
|
"</ui>",
|
|
-1, &error);
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("error parsing ui: %s", error->message);
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
return ui_manager;
|
|
}
|
|
|
|
static void
|
|
browser_dialog_404 (HtmlDocument *doc,
|
|
const gchar *uri,
|
|
const gchar *message)
|
|
{
|
|
gchar *msg = g_strdup_printf
|
|
("<html>"
|
|
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />"
|
|
"<head><title>%s</title></head>"
|
|
"<body bgcolor=\"white\">"
|
|
"<div align=\"center\">"
|
|
"<div>%s</div>"
|
|
"<h3>%s</h3>"
|
|
"<tt>%s</tt>"
|
|
"<h3>%s</h3>"
|
|
"</div>"
|
|
"</body>"
|
|
"</html>",
|
|
_("Document not found"),
|
|
eek_png_tag,
|
|
_("The requested URL could not be loaded:"),
|
|
uri,
|
|
message);
|
|
|
|
html_document_write_stream (doc, msg, strlen (msg));
|
|
|
|
g_free (msg);
|
|
}
|
|
|
|
static void
|
|
back_callback (GtkAction *action,
|
|
gpointer data)
|
|
{
|
|
gdouble pos;
|
|
const gchar *uri = queue_prev (queue, GPOINTER_TO_INT (data), &pos);
|
|
|
|
if (uri)
|
|
{
|
|
browser_dialog_load (uri, FALSE);
|
|
|
|
g_idle_add ((GSourceFunc) idle_scroll_to_pos,
|
|
GINT_TO_POINTER ((gint) (pos * (1 << 16))));
|
|
|
|
queue_move_prev (queue, GPOINTER_TO_INT (data));
|
|
}
|
|
|
|
update_toolbar ();
|
|
}
|
|
|
|
static void
|
|
forward_callback (GtkAction *action,
|
|
gpointer data)
|
|
{
|
|
gdouble pos;
|
|
const gchar *uri = queue_next (queue, GPOINTER_TO_INT (data), &pos);
|
|
|
|
if (uri)
|
|
{
|
|
browser_dialog_load (uri, FALSE);
|
|
|
|
g_idle_add ((GSourceFunc) idle_scroll_to_pos,
|
|
GINT_TO_POINTER ((gint) (pos * (1 << 16))));
|
|
|
|
queue_move_next (queue, GPOINTER_TO_INT (data));
|
|
}
|
|
|
|
update_toolbar ();
|
|
}
|
|
|
|
static void
|
|
index_callback (GtkAction *action,
|
|
gpointer data)
|
|
{
|
|
browser_dialog_load ("index.html", TRUE);
|
|
}
|
|
|
|
static void
|
|
zoom_in_callback (GtkAction *action,
|
|
gpointer data)
|
|
{
|
|
html_view_zoom_in (HTML_VIEW (html));
|
|
}
|
|
|
|
static void
|
|
zoom_out_callback (GtkAction *action,
|
|
gpointer data)
|
|
{
|
|
html_view_zoom_out (HTML_VIEW (html));
|
|
}
|
|
|
|
static void
|
|
online_callback (GtkAction *action,
|
|
gpointer data)
|
|
{
|
|
load_remote_page ("http://docs.gimp.org/");
|
|
}
|
|
|
|
static void
|
|
close_callback (GtkAction *action,
|
|
gpointer data)
|
|
{
|
|
gtk_widget_destroy (gtk_widget_get_toplevel (html));
|
|
}
|
|
|
|
static GtkWidget *
|
|
build_menu (GList *list,
|
|
GCallback callback)
|
|
{
|
|
GtkWidget *menu;
|
|
gint i;
|
|
|
|
if (! list)
|
|
return NULL;
|
|
|
|
menu = gtk_menu_new ();
|
|
|
|
for (i = 0; list && i < 15; list = g_list_next (list), i++)
|
|
{
|
|
GtkWidget *menu_item = gtk_menu_item_new_with_label (list->data);
|
|
|
|
gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
|
|
gtk_widget_show (menu_item);
|
|
|
|
g_signal_connect (menu_item, "activate",
|
|
G_CALLBACK (callback),
|
|
GINT_TO_POINTER (i));
|
|
}
|
|
|
|
g_list_free (list);
|
|
|
|
return menu;
|
|
}
|
|
|
|
static void
|
|
update_toolbar (void)
|
|
{
|
|
GtkAction *action;
|
|
|
|
/* update the back button and its menu */
|
|
|
|
action = gtk_ui_manager_get_action (ui_manager,
|
|
"/ui/help-browser-popup/back");
|
|
gtk_action_set_sensitive (action, queue_has_prev (queue));
|
|
|
|
gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (button_prev),
|
|
build_menu (queue_list_prev (queue),
|
|
G_CALLBACK (back_callback)));
|
|
|
|
/* update the forward button and its menu */
|
|
|
|
action = gtk_ui_manager_get_action (ui_manager,
|
|
"/ui/help-browser-popup/forward");
|
|
gtk_action_set_sensitive (action, queue_has_next (queue));
|
|
|
|
gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (button_next),
|
|
build_menu (queue_list_next (queue),
|
|
G_CALLBACK (forward_callback)));
|
|
}
|
|
|
|
static void
|
|
combo_changed (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
GtkComboBox *combo = GTK_COMBO_BOX (widget);
|
|
GtkTreeIter iter;
|
|
|
|
if (gtk_combo_box_get_active_iter (combo, &iter))
|
|
{
|
|
GValue value = { 0, };
|
|
|
|
gtk_tree_model_get_value (gtk_combo_box_get_model (combo),
|
|
&iter, HISTORY_URI, &value);
|
|
|
|
browser_dialog_load (g_value_get_string (&value), TRUE);
|
|
|
|
g_value_unset (&value);
|
|
}
|
|
}
|
|
|
|
static void
|
|
drag_begin (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
gpointer data)
|
|
{
|
|
gtk_drag_set_icon_stock (context, GIMP_STOCK_WEB, -8, -8);
|
|
}
|
|
|
|
static void
|
|
drag_data_get (GtkWidget *widget,
|
|
GdkDragContext *context,
|
|
GtkSelectionData *selection_data,
|
|
guint info,
|
|
guint time,
|
|
gpointer data)
|
|
{
|
|
if (! current_uri)
|
|
return;
|
|
|
|
if (selection_data->target == gdk_atom_intern ("text/uri-list", FALSE))
|
|
{
|
|
gchar *uris[2];
|
|
|
|
uris[0] = current_uri;
|
|
uris[1] = NULL;
|
|
|
|
gtk_selection_data_set_uris (selection_data, uris);
|
|
}
|
|
else if (selection_data->target == gdk_atom_intern ("_NETSCAPE_URL", FALSE))
|
|
{
|
|
gtk_selection_data_set (selection_data,
|
|
selection_data->target,
|
|
8, (guchar *) current_uri, strlen (current_uri));
|
|
}
|
|
}
|
|
|
|
static void
|
|
view_realize (GtkWidget *widget)
|
|
{
|
|
g_return_if_fail (busy_cursor == NULL);
|
|
|
|
busy_cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget),
|
|
GDK_WATCH);
|
|
}
|
|
|
|
static void
|
|
view_unrealize (GtkWidget *widget)
|
|
{
|
|
if (busy_cursor)
|
|
{
|
|
gdk_cursor_unref (busy_cursor);
|
|
busy_cursor = NULL;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
view_popup_menu (GtkWidget *widget,
|
|
GdkEventButton *event)
|
|
{
|
|
GtkWidget *menu = gtk_ui_manager_get_widget (ui_manager,
|
|
"/help-browser-popup");
|
|
|
|
gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));
|
|
gtk_menu_popup (GTK_MENU (menu),
|
|
NULL, NULL, NULL, NULL,
|
|
event ? event->button : 0,
|
|
event ? event->time : gtk_get_current_event_time ());
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
view_button_press (GtkWidget *widget,
|
|
GdkEventButton *event)
|
|
{
|
|
if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
|
|
return view_popup_menu (widget, event);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
title_changed (HtmlDocument *doc,
|
|
const gchar *new_title,
|
|
gpointer data)
|
|
{
|
|
gchar *title = NULL;
|
|
|
|
if (new_title)
|
|
{
|
|
gchar *c;
|
|
|
|
title = g_strdup (new_title);
|
|
|
|
for (c = title; *c; c++)
|
|
{
|
|
if (*c == '\n' || *c == '\r')
|
|
*c = ' ';
|
|
}
|
|
|
|
title = g_strstrip (title);
|
|
if (! strlen (title))
|
|
{
|
|
g_free (title);
|
|
title = NULL;
|
|
}
|
|
}
|
|
|
|
history_add (GTK_COMBO_BOX (data),
|
|
current_uri, title ? title : _("Untitled"));
|
|
|
|
if (title)
|
|
queue_set_title (queue, title);
|
|
|
|
g_free (title);
|
|
}
|
|
|
|
static void
|
|
link_clicked (HtmlDocument *doc,
|
|
const gchar *uri,
|
|
gpointer data)
|
|
{
|
|
GtkAdjustment *adj = gtk_layout_get_vadjustment (GTK_LAYOUT (html));
|
|
|
|
if (adj->upper > 0.0)
|
|
queue_set_scroll_offset (queue, adj->value / adj->upper);
|
|
|
|
browser_dialog_load (uri, TRUE);
|
|
}
|
|
|
|
static gboolean
|
|
request_url (HtmlDocument *doc,
|
|
const gchar *uri,
|
|
HtmlStream *stream,
|
|
GError **error)
|
|
{
|
|
gchar *abs;
|
|
gchar *filename;
|
|
|
|
g_return_val_if_fail (uri != NULL, TRUE);
|
|
g_return_val_if_fail (stream != NULL, TRUE);
|
|
|
|
abs = uri_to_abs (uri, current_uri);
|
|
if (! abs)
|
|
return TRUE;
|
|
|
|
filename = filename_from_uri (abs);
|
|
g_free (abs);
|
|
|
|
if (filename)
|
|
{
|
|
gint fd = g_open (filename, O_RDONLY | _O_BINARY, 0);
|
|
|
|
if (fd != -1)
|
|
{
|
|
GIOChannel *io = g_io_channel_unix_new (fd);
|
|
|
|
g_io_channel_set_close_on_unref (io, TRUE);
|
|
g_io_channel_set_encoding (io, NULL, NULL);
|
|
|
|
g_io_add_watch (io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
|
|
io_handler, stream);
|
|
}
|
|
|
|
g_free (filename);
|
|
|
|
if (fd == -1)
|
|
{
|
|
g_set_error (error,
|
|
G_FILE_ERROR, g_file_error_from_errno (errno),
|
|
g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
io_handler (GIOChannel *io,
|
|
GIOCondition condition,
|
|
gpointer data)
|
|
{
|
|
HtmlStream *stream = data;
|
|
gchar buffer[8192];
|
|
gsize bytes;
|
|
|
|
if (condition & G_IO_IN)
|
|
{
|
|
if (g_io_channel_read_chars (io, buffer, sizeof (buffer),
|
|
&bytes, NULL) != G_IO_STATUS_ERROR
|
|
&& bytes > 0)
|
|
{
|
|
html_stream_write (stream, buffer, bytes);
|
|
}
|
|
else
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
if (condition & G_IO_HUP)
|
|
{
|
|
while (g_io_channel_read_chars (io, buffer, sizeof (buffer),
|
|
&bytes, NULL) != G_IO_STATUS_ERROR
|
|
&& bytes > 0)
|
|
{
|
|
html_stream_write (stream, buffer, bytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
|
|
{
|
|
error:
|
|
html_stream_close (stream);
|
|
g_io_channel_unref (io);
|
|
|
|
if (html->window)
|
|
gdk_window_set_cursor (html->window, NULL);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
load_remote_page (const gchar *uri)
|
|
{
|
|
GimpParam *return_vals;
|
|
gint nreturn_vals;
|
|
|
|
/* try to call the user specified web browser */
|
|
return_vals = gimp_run_procedure ("plug-in-web-browser",
|
|
&nreturn_vals,
|
|
GIMP_PDB_STRING, uri,
|
|
GIMP_PDB_END);
|
|
gimp_destroy_params (return_vals, nreturn_vals);
|
|
}
|
|
|
|
static void
|
|
history_add (GtkComboBox *combo,
|
|
const gchar *uri,
|
|
const gchar *title)
|
|
{
|
|
GtkTreeModel *model = gtk_combo_box_get_model (combo);
|
|
GtkTreeIter iter;
|
|
gboolean iter_valid;
|
|
GValue value = { 0, };
|
|
|
|
for (iter_valid = gtk_tree_model_get_iter_first (model, &iter);
|
|
iter_valid;
|
|
iter_valid = gtk_tree_model_iter_next (model, &iter))
|
|
{
|
|
gtk_tree_model_get_value (model, &iter, HISTORY_URI, &value);
|
|
|
|
if (strcmp (g_value_get_string (&value), uri) == 0)
|
|
{
|
|
gtk_list_store_move_after (GTK_LIST_STORE (model), &iter, NULL);
|
|
g_value_unset (&value);
|
|
break;
|
|
}
|
|
|
|
g_value_unset (&value);
|
|
}
|
|
|
|
if (! iter_valid)
|
|
{
|
|
gtk_list_store_prepend (GTK_LIST_STORE (model), &iter);
|
|
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
|
|
HISTORY_TITLE, title,
|
|
HISTORY_URI, uri,
|
|
-1);
|
|
}
|
|
|
|
g_signal_handlers_block_by_func (combo, combo_changed, NULL);
|
|
gtk_combo_box_set_active_iter (combo, &iter);
|
|
g_signal_handlers_unblock_by_func (combo, combo_changed, NULL);
|
|
}
|
|
|
|
/* Taken from glib/gconvert.c:
|
|
* Test of haystack has the needle prefix, comparing case
|
|
* insensitive. haystack may be UTF-8, but needle must
|
|
* contain only ascii.
|
|
*/
|
|
static gboolean
|
|
has_case_prefix (const gchar *haystack, const gchar *needle)
|
|
{
|
|
const gchar *h = haystack;
|
|
const gchar *n = needle;
|
|
|
|
while (*n && *h && g_ascii_tolower (*n) == g_ascii_tolower (*h))
|
|
{
|
|
n++;
|
|
h++;
|
|
}
|
|
|
|
return (*n == '\0');
|
|
}
|
|
|
|
static gchar *
|
|
filename_from_uri (const gchar *uri)
|
|
{
|
|
gchar *filename;
|
|
gchar *hostname;
|
|
|
|
g_return_val_if_fail (uri != NULL, NULL);
|
|
|
|
filename = g_filename_from_uri (uri, &hostname, NULL);
|
|
|
|
if (!filename)
|
|
return NULL;
|
|
|
|
if (hostname)
|
|
{
|
|
/* we have a file: URI with a hostname */
|
|
#ifdef G_OS_WIN32
|
|
/* on Win32, create a valid UNC path and use it as the filename */
|
|
|
|
gchar *tmp = g_build_filename ("//", hostname, filename, NULL);
|
|
|
|
g_free (filename);
|
|
filename = tmp;
|
|
#else
|
|
/* otherwise return NULL, caller should use URI then */
|
|
g_free (filename);
|
|
filename = NULL;
|
|
#endif
|
|
|
|
g_free (hostname);
|
|
}
|
|
|
|
return filename;
|
|
}
|