~ubuntu-branches/ubuntu/hoary/gimp/hoary

« back to all changes in this revision

Viewing changes to plug-ins/helpbrowser/dialog.c

  • Committer: Bazaar Package Importer
  • Author(s): Sebastien Bacher
  • Date: 2005-04-04 14:51:23 UTC
  • Revision ID: james.westby@ubuntu.com-20050404145123-9py049eeelfymur8
Tags: upstream-2.2.2
ImportĀ upstreamĀ versionĀ 2.2.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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-2004 Sven Neumann <sven@gimp.org>
 
6
 *                         Michael Natterer <mitch@gimp.org>
 
7
 *
 
8
 * dialog.c
 
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
 
 
25
#include "config.h"
 
26
 
 
27
#include <string.h>
 
28
#include <errno.h>
 
29
#include <sys/types.h>
 
30
#include <sys/stat.h>
 
31
#include <fcntl.h>
 
32
 
 
33
#include <gtk/gtk.h>
 
34
#include <libgtkhtml/gtkhtml.h>
 
35
 
 
36
#include "libgimpwidgets/gimpwidgets.h"
 
37
 
 
38
#include "libgimp/gimp.h"
 
39
#include "libgimp/gimpui.h"
 
40
 
 
41
#ifdef G_OS_WIN32
 
42
#include "libgimpbase/gimpwin32-io.h"
 
43
#endif
 
44
 
 
45
#include "dialog.h"
 
46
#include "queue.h"
 
47
#include "uri.h"
 
48
 
 
49
#include "libgimp/stdplugins-intl.h"
 
50
 
 
51
 
 
52
enum
 
53
{
 
54
  BUTTON_INDEX,
 
55
  BUTTON_BACK,
 
56
  BUTTON_FORWARD
 
57
};
 
58
 
 
59
enum
 
60
{
 
61
  HISTORY_TITLE,
 
62
  HISTORY_REF
 
63
};
 
64
 
 
65
/*  local function prototypes  */
 
66
 
 
67
static void       browser_dialog_404 (HtmlDocument     *doc,
 
68
                                      const gchar      *url,
 
69
                                      const gchar      *message);
 
70
 
 
71
static void       button_callback    (GtkWidget        *widget,
 
72
                                      gpointer          data);
 
73
static void       update_toolbar     (void);
 
74
static void       combo_changed      (GtkWidget        *widget,
 
75
                                      gpointer          data);
 
76
static void       drag_begin         (GtkWidget        *widget,
 
77
                                      GdkDragContext   *context,
 
78
                                      gpointer          data);
 
79
static void       drag_data_get      (GtkWidget        *widget,
 
80
                                      GdkDragContext   *context,
 
81
                                      GtkSelectionData *selection_data,
 
82
                                      guint             info,
 
83
                                      guint             time,
 
84
                                      gpointer          data);
 
85
 
 
86
static void       title_changed      (HtmlDocument     *doc,
 
87
                                      const gchar      *new_title,
 
88
                                      gpointer          data);
 
89
static void       link_clicked       (HtmlDocument     *doc,
 
90
                                      const gchar      *url,
 
91
                                      gpointer          data);
 
92
static gboolean   request_url        (HtmlDocument     *doc,
 
93
                                      const gchar      *url,
 
94
                                      HtmlStream       *stream,
 
95
                                      GError          **error);
 
96
static gboolean   io_handler         (GIOChannel       *io,
 
97
                                      GIOCondition      condition,
 
98
                                      gpointer          data);
 
99
static void       load_remote_page   (const gchar      *ref);
 
100
 
 
101
static void       history_add        (GtkComboBox      *combo,
 
102
                                      const gchar      *ref,
 
103
                                      const gchar      *title);
 
104
 
 
105
static gboolean   has_case_prefix    (const gchar      *haystack,
 
106
                                      const gchar      *needle);
 
107
 
 
108
 
 
109
/*  private variables  */
 
110
 
 
111
static const gchar  *eek_png_tag    = "<h1>Eeek!</h1>";
 
112
 
 
113
static Queue        *queue          = NULL;
 
114
static gchar        *current_ref    = NULL;
 
115
 
 
116
static GtkWidget    *back_button    = NULL;
 
117
static GtkWidget    *forward_button = NULL;
 
118
static GtkWidget    *html           = NULL;
 
119
 
 
120
static GtkTargetEntry help_dnd_target_table[] =
 
121
{
 
122
  { "_NETSCAPE_URL", 0, 0 },
 
123
};
 
124
 
 
125
 
 
126
/*  public functions  */
 
127
 
 
128
void
 
129
browser_dialog_open (void)
 
130
{
 
131
  GtkWidget       *window;
 
132
  GtkWidget       *vbox;
 
133
  GtkWidget       *hbox;
 
134
  GtkWidget       *bbox;
 
135
  GtkWidget       *scroll;
 
136
  GtkWidget       *button;
 
137
  GtkWidget       *drag_source;
 
138
  GtkWidget       *image;
 
139
  GtkWidget       *combo;
 
140
  GtkListStore    *history;
 
141
  GtkCellRenderer *cell;
 
142
  gchar           *eek_png_path;
 
143
 
 
144
  gimp_ui_init ("helpbrowser", TRUE);
 
145
 
 
146
  eek_png_path = g_build_filename (gimp_data_directory (),
 
147
                                   "themes", "Default", "images",
 
148
                                   "stock-wilber-eek-64.png", NULL);
 
149
 
 
150
  if (g_file_test (eek_png_path, G_FILE_TEST_EXISTS))
 
151
    eek_png_tag = g_strdup_printf ("<img src=\"%s\">", eek_png_path);
 
152
 
 
153
  g_free (eek_png_path);
 
154
 
 
155
  /*  the dialog window  */
 
156
  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
 
157
  gtk_window_set_title (GTK_WINDOW (window), _("GIMP Help browser"));
 
158
  gtk_window_set_role (GTK_WINDOW (window), "helpbrowser");
 
159
 
 
160
  gtk_window_set_default_size (GTK_WINDOW (window), 420, 500);
 
161
 
 
162
  g_signal_connect (window, "destroy",
 
163
                    G_CALLBACK (gtk_main_quit),
 
164
                    NULL);
 
165
 
 
166
  vbox = gtk_vbox_new (FALSE, 2);
 
167
  gtk_container_add (GTK_CONTAINER (window), vbox);
 
168
  gtk_widget_show (vbox);
 
169
 
 
170
  /*  buttons  */
 
171
  bbox = gtk_hbutton_box_new ();
 
172
  gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_START);
 
173
  gtk_box_pack_start (GTK_BOX (vbox), bbox, FALSE, FALSE, 0);
 
174
  gtk_widget_show (bbox);
 
175
 
 
176
  button = gtk_button_new_from_stock (GTK_STOCK_INDEX);
 
177
  gtk_container_add (GTK_CONTAINER (bbox), button);
 
178
  gtk_widget_show (button);
 
179
 
 
180
  g_signal_connect (button, "clicked",
 
181
                    G_CALLBACK (button_callback),
 
182
                    GINT_TO_POINTER (BUTTON_INDEX));
 
183
 
 
184
  back_button = button = gtk_button_new_from_stock (GTK_STOCK_GO_BACK);
 
185
  gtk_container_add (GTK_CONTAINER (bbox), button);
 
186
  gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE);
 
187
  gtk_widget_show (button);
 
188
 
 
189
  g_signal_connect (button, "clicked",
 
190
                    G_CALLBACK (button_callback),
 
191
                    GINT_TO_POINTER (BUTTON_BACK));
 
192
 
 
193
  forward_button = button = gtk_button_new_from_stock (GTK_STOCK_GO_FORWARD);
 
194
  gtk_container_add (GTK_CONTAINER (bbox), button);
 
195
  gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE);
 
196
  gtk_widget_show (button);
 
197
 
 
198
  g_signal_connect (button, "clicked",
 
199
                    G_CALLBACK (button_callback),
 
200
                    GINT_TO_POINTER (BUTTON_FORWARD));
 
201
 
 
202
  button = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
 
203
  gtk_container_add (GTK_CONTAINER (bbox), button);
 
204
  gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (bbox), button, TRUE);
 
205
  gtk_widget_show (button);
 
206
 
 
207
  g_signal_connect_swapped (button, "clicked",
 
208
                            G_CALLBACK (gtk_widget_destroy),
 
209
                            window);
 
210
 
 
211
  hbox = gtk_hbox_new (FALSE, 2);
 
212
  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
 
213
  gtk_widget_show (hbox);
 
214
 
 
215
  /*  the drag source  */
 
216
  drag_source = gtk_event_box_new ();
 
217
  gtk_box_pack_start (GTK_BOX (hbox), drag_source, FALSE, FALSE, 4);
 
218
  gtk_widget_show (drag_source);
 
219
 
 
220
  gtk_drag_source_set (GTK_WIDGET (drag_source),
 
221
                       GDK_BUTTON1_MASK,
 
222
                       help_dnd_target_table,
 
223
                       G_N_ELEMENTS (help_dnd_target_table),
 
224
                       GDK_ACTION_COPY);
 
225
 
 
226
  g_signal_connect (drag_source, "drag_begin",
 
227
                    G_CALLBACK (drag_begin),
 
228
                    NULL);
 
229
  g_signal_connect (drag_source, "drag_data_get",
 
230
                    G_CALLBACK (drag_data_get),
 
231
                    NULL);
 
232
 
 
233
  image = gtk_image_new_from_stock (GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_BUTTON);
 
234
  gtk_container_add (GTK_CONTAINER (drag_source), image);
 
235
  gtk_widget_show (image);
 
236
 
 
237
  /*  the title combo  */
 
238
  history = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
 
239
  combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (history));
 
240
  g_object_unref (history);
 
241
 
 
242
  cell = gtk_cell_renderer_text_new ();
 
243
  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE);
 
244
  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell,
 
245
                                  "text", HISTORY_TITLE,
 
246
                                  NULL);
 
247
 
 
248
  gtk_widget_set_size_request (GTK_WIDGET (combo), 320, -1);
 
249
  gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0);
 
250
  gtk_widget_show (combo);
 
251
 
 
252
  g_signal_connect (combo, "changed",
 
253
                    G_CALLBACK (combo_changed),
 
254
                    NULL);
 
255
 
 
256
  /*  HTML view  */
 
257
  html  = html_view_new ();
 
258
  queue = queue_new ();
 
259
 
 
260
  gtk_widget_set_size_request (GTK_WIDGET (html), -1, 200);
 
261
 
 
262
  scroll =
 
263
    gtk_scrolled_window_new (gtk_layout_get_hadjustment (GTK_LAYOUT (html)),
 
264
                             gtk_layout_get_vadjustment (GTK_LAYOUT (html)));
 
265
 
 
266
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll),
 
267
                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
 
268
  gtk_box_pack_start (GTK_BOX (vbox), scroll, TRUE, TRUE, 0);
 
269
  gtk_widget_show (scroll);
 
270
 
 
271
  gtk_container_add (GTK_CONTAINER (scroll), html);
 
272
  gtk_widget_show (html);
 
273
 
 
274
  html_view_set_document (HTML_VIEW (html), html_document_new ());
 
275
 
 
276
  g_signal_connect (HTML_VIEW (html)->document, "title_changed",
 
277
                    G_CALLBACK (title_changed),
 
278
                    combo);
 
279
  g_signal_connect (HTML_VIEW (html)->document, "link_clicked",
 
280
                    G_CALLBACK (link_clicked),
 
281
                    NULL);
 
282
  g_signal_connect (HTML_VIEW (html)->document, "request_url",
 
283
                    G_CALLBACK (request_url),
 
284
                    NULL);
 
285
 
 
286
  gtk_widget_grab_focus (html);
 
287
 
 
288
  gtk_widget_show (window);
 
289
}
 
290
 
 
291
static gboolean
 
292
idle_jump_to_anchor (const gchar *anchor)
 
293
{
 
294
  if (html && anchor)
 
295
    html_view_jump_to_anchor (HTML_VIEW (html), anchor);
 
296
 
 
297
  return FALSE;
 
298
}
 
299
 
 
300
void
 
301
browser_dialog_load (const gchar *ref,
 
302
                     gboolean     add_to_queue)
 
303
{
 
304
  HtmlDocument  *doc = HTML_VIEW (html)->document;
 
305
  GtkAdjustment *adj = gtk_layout_get_vadjustment (GTK_LAYOUT (html));
 
306
  gchar         *abs;
 
307
  gchar         *new_ref;
 
308
  gchar         *anchor;
 
309
  gchar         *tmp;
 
310
 
 
311
  g_return_if_fail (ref != NULL);
 
312
 
 
313
  if (! current_ref)
 
314
    {
 
315
      gchar *slash;
 
316
 
 
317
      current_ref = g_strdup (ref);
 
318
 
 
319
      slash = strrchr (current_ref, '/');
 
320
 
 
321
      if (slash)
 
322
        *slash = '\0';
 
323
    }
 
324
 
 
325
  abs = uri_to_abs (ref, current_ref);
 
326
  if (! abs)
 
327
    return;
 
328
 
 
329
  anchor = strchr (ref, '#');
 
330
  if (anchor && anchor[0] && anchor[1])
 
331
    {
 
332
      new_ref = g_strconcat (abs, anchor, NULL);
 
333
      anchor += 1;
 
334
    }
 
335
  else
 
336
    {
 
337
      new_ref = g_strdup (abs);
 
338
      anchor = NULL;
 
339
    }
 
340
 
 
341
  if (! has_case_prefix (abs, "file:/"))
 
342
    {
 
343
      load_remote_page (ref);
 
344
      return;
 
345
    }
 
346
 
 
347
  tmp = uri_to_abs (current_ref, current_ref);
 
348
  if (!tmp || strcmp (tmp, abs))
 
349
    {
 
350
      GError *error = NULL;
 
351
 
 
352
      html_document_clear (doc);
 
353
      html_document_open_stream (doc, "text/html");
 
354
      gtk_adjustment_set_value (adj, 0.0);
 
355
 
 
356
      if (anchor)
 
357
        g_idle_add_full (G_PRIORITY_LOW,
 
358
                         (GSourceFunc) idle_jump_to_anchor,
 
359
                         g_strdup (anchor), (GDestroyNotify) g_free);
 
360
 
 
361
      if (! request_url (doc, abs, doc->current_stream, &error))
 
362
        {
 
363
          browser_dialog_404 (doc, abs, error->message);
 
364
          g_error_free (error);
 
365
        }
 
366
    }
 
367
  else
 
368
    {
 
369
      if (anchor)
 
370
        html_view_jump_to_anchor (HTML_VIEW (html), anchor);
 
371
      else
 
372
        gtk_adjustment_set_value (adj, 0.0);
 
373
     }
 
374
 
 
375
  g_free (tmp);
 
376
 
 
377
  g_free (current_ref);
 
378
  current_ref = new_ref;
 
379
 
 
380
  if (add_to_queue)
 
381
    queue_add (queue, new_ref);
 
382
 
 
383
  update_toolbar ();
 
384
 
 
385
  gtk_window_present (GTK_WINDOW (gtk_widget_get_toplevel (html)));
 
386
}
 
387
 
 
388
 
 
389
/*  private functions  */
 
390
 
 
391
static void
 
392
browser_dialog_404 (HtmlDocument *doc,
 
393
                    const gchar  *url,
 
394
                    const gchar  *message)
 
395
{
 
396
  gchar *msg = g_strdup_printf
 
397
    ("<html>"
 
398
     "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />"
 
399
     "<head><title>%s</title></head>"
 
400
     "<body bgcolor=\"white\">"
 
401
     "<div align=\"center\">"
 
402
     "<div>%s</div>"
 
403
     "<h3>%s</h3>"
 
404
     "<tt>%s</tt>"
 
405
     "<h3>%s</h3>"
 
406
     "</div>"
 
407
     "</body>"
 
408
     "</html>",
 
409
     _("Document not found"),
 
410
     eek_png_tag,
 
411
     _("The requested URL could not be loaded:"),
 
412
     url,
 
413
     message);
 
414
 
 
415
  html_document_write_stream (doc, msg, strlen (msg));
 
416
 
 
417
  g_free (msg);
 
418
}
 
419
 
 
420
static void
 
421
button_callback (GtkWidget *widget,
 
422
                 gpointer   data)
 
423
{
 
424
  const gchar *ref;
 
425
 
 
426
  switch (GPOINTER_TO_INT (data))
 
427
    {
 
428
    case BUTTON_INDEX:
 
429
      browser_dialog_load ("index.html", TRUE);
 
430
      break;
 
431
 
 
432
    case BUTTON_BACK:
 
433
      if (!(ref = queue_prev (queue)))
 
434
        return;
 
435
      browser_dialog_load (ref, FALSE);
 
436
      queue_move_prev (queue);
 
437
      break;
 
438
 
 
439
    case BUTTON_FORWARD:
 
440
      if (!(ref = queue_next (queue)))
 
441
        return;
 
442
      browser_dialog_load (ref, FALSE);
 
443
      queue_move_next (queue);
 
444
      break;
 
445
 
 
446
    default:
 
447
      return;
 
448
    }
 
449
 
 
450
  update_toolbar ();
 
451
}
 
452
 
 
453
static void
 
454
update_toolbar (void)
 
455
{
 
456
  if (back_button)
 
457
    gtk_widget_set_sensitive (back_button, queue_has_prev (queue));
 
458
  if (forward_button)
 
459
    gtk_widget_set_sensitive (forward_button, queue_has_next (queue));
 
460
}
 
461
 
 
462
static void
 
463
combo_changed (GtkWidget *widget,
 
464
               gpointer   data)
 
465
{
 
466
  GtkComboBox *combo = GTK_COMBO_BOX (widget);
 
467
  GtkTreeIter  iter;
 
468
 
 
469
  if (gtk_combo_box_get_active_iter (combo, &iter))
 
470
    {
 
471
      GValue  value = { 0, };
 
472
 
 
473
      gtk_tree_model_get_value (gtk_combo_box_get_model (combo),
 
474
                                &iter, HISTORY_REF, &value);
 
475
 
 
476
      browser_dialog_load (g_value_get_string (&value), TRUE);
 
477
 
 
478
      g_value_unset (&value);
 
479
    }
 
480
}
 
481
 
 
482
static void
 
483
drag_begin (GtkWidget      *widget,
 
484
            GdkDragContext *context,
 
485
            gpointer        data)
 
486
{
 
487
  gtk_drag_set_icon_stock (context, GTK_STOCK_JUMP_TO, -8, -8);
 
488
}
 
489
 
 
490
static void
 
491
drag_data_get (GtkWidget        *widget,
 
492
               GdkDragContext   *context,
 
493
               GtkSelectionData *selection_data,
 
494
               guint             info,
 
495
               guint             time,
 
496
               gpointer          data)
 
497
{
 
498
  if (! current_ref)
 
499
    return;
 
500
 
 
501
  gtk_selection_data_set (selection_data,
 
502
                          selection_data->target,
 
503
                          8,
 
504
                          current_ref,
 
505
                          strlen (current_ref));
 
506
}
 
507
 
 
508
static void
 
509
title_changed (HtmlDocument *doc,
 
510
               const gchar  *new_title,
 
511
               gpointer      data)
 
512
{
 
513
  if (new_title)
 
514
    {
 
515
      gchar *title = g_strstrip (g_strdup (new_title));
 
516
 
 
517
      history_add (GTK_COMBO_BOX (data), current_ref, title);
 
518
 
 
519
      g_free (title);
 
520
    }
 
521
  else
 
522
    {
 
523
      history_add (GTK_COMBO_BOX (data), current_ref, _("Untitled"));
 
524
    }
 
525
}
 
526
 
 
527
static void
 
528
link_clicked (HtmlDocument *doc,
 
529
              const gchar  *url,
 
530
              gpointer      data)
 
531
{
 
532
  browser_dialog_load (url, TRUE);
 
533
}
 
534
 
 
535
static gboolean
 
536
request_url (HtmlDocument *doc,
 
537
             const gchar  *url,
 
538
             HtmlStream   *stream,
 
539
             GError      **error)
 
540
{
 
541
  gchar *abs;
 
542
  gchar *filename;
 
543
 
 
544
  g_return_val_if_fail (url != NULL, TRUE);
 
545
  g_return_val_if_fail (stream != NULL, TRUE);
 
546
 
 
547
  abs = uri_to_abs (url, current_ref);
 
548
  if (! abs)
 
549
    return TRUE;
 
550
 
 
551
  filename = g_filename_from_uri (abs, NULL, NULL);
 
552
  g_free (abs);
 
553
 
 
554
  if (filename)
 
555
    {
 
556
      gint fd;
 
557
 
 
558
      fd = open (filename, O_RDONLY);
 
559
 
 
560
      if (fd != -1)
 
561
        {
 
562
          GIOChannel *io = g_io_channel_unix_new (fd);
 
563
 
 
564
          g_io_channel_set_close_on_unref (io, TRUE);
 
565
          g_io_channel_set_encoding (io, NULL, NULL);
 
566
 
 
567
          g_io_add_watch (io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
 
568
                          io_handler, stream);
 
569
        }
 
570
 
 
571
      g_free (filename);
 
572
 
 
573
      if (fd == -1)
 
574
        {
 
575
          g_set_error (error,
 
576
                       G_FILE_ERROR, g_file_error_from_errno (errno),
 
577
                       g_strerror (errno));
 
578
          return FALSE;
 
579
        }
 
580
    }
 
581
 
 
582
  return TRUE;
 
583
}
 
584
 
 
585
static gboolean
 
586
io_handler (GIOChannel   *io,
 
587
            GIOCondition  condition,
 
588
            gpointer      data)
 
589
{
 
590
  HtmlStream *stream = data;
 
591
  gchar       buffer[8192];
 
592
  gsize       bytes;
 
593
 
 
594
  if (condition & G_IO_IN)
 
595
    {
 
596
      if (g_io_channel_read_chars (io, buffer, sizeof (buffer),
 
597
                                   &bytes, NULL) != G_IO_STATUS_ERROR
 
598
          && bytes > 0)
 
599
        {
 
600
          html_stream_write (stream, buffer, bytes);
 
601
        }
 
602
      else
 
603
        {
 
604
          html_stream_close (stream);
 
605
          g_io_channel_unref (io);
 
606
 
 
607
          return FALSE;
 
608
        }
 
609
 
 
610
      if (condition & G_IO_HUP)
 
611
        {
 
612
          while (g_io_channel_read_chars (io, buffer, sizeof (buffer),
 
613
                                          &bytes, NULL) != G_IO_STATUS_ERROR
 
614
                 && bytes > 0)
 
615
            {
 
616
              html_stream_write (stream, buffer, bytes);
 
617
            }
 
618
        }
 
619
    }
 
620
 
 
621
  if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
 
622
    {
 
623
      html_stream_close (stream);
 
624
      g_io_channel_unref (io);
 
625
 
 
626
      return FALSE;
 
627
    }
 
628
 
 
629
  return TRUE;
 
630
}
 
631
 
 
632
static void
 
633
load_remote_page (const gchar *ref)
 
634
{
 
635
  GimpParam *return_vals;
 
636
  gint       nreturn_vals;
 
637
 
 
638
  /*  try to call the user specified web browser */
 
639
  return_vals = gimp_run_procedure ("plug_in_web_browser",
 
640
                                    &nreturn_vals,
 
641
                                    GIMP_PDB_STRING, ref,
 
642
                                    GIMP_PDB_END);
 
643
  gimp_destroy_params (return_vals, nreturn_vals);
 
644
}
 
645
 
 
646
static void
 
647
history_add (GtkComboBox *combo,
 
648
             const gchar *ref,
 
649
             const gchar *title)
 
650
{
 
651
  GtkTreeModel *model = gtk_combo_box_get_model (combo);
 
652
  GtkTreeIter   iter;
 
653
  gboolean      iter_valid;
 
654
  GValue        value = { 0, };
 
655
 
 
656
  for (iter_valid = gtk_tree_model_get_iter_first (model, &iter);
 
657
       iter_valid;
 
658
       iter_valid = gtk_tree_model_iter_next (model, &iter))
 
659
    {
 
660
      gtk_tree_model_get_value (model, &iter, HISTORY_REF, &value);
 
661
 
 
662
      if (strcmp (g_value_get_string (&value), ref) == 0)
 
663
        {
 
664
          gtk_list_store_move_after (GTK_LIST_STORE (model), &iter, NULL);
 
665
          g_value_unset (&value);
 
666
          break;
 
667
        }
 
668
 
 
669
      g_value_unset (&value);
 
670
    }
 
671
 
 
672
  if (! iter_valid)
 
673
    {
 
674
      gtk_list_store_prepend (GTK_LIST_STORE (model), &iter);
 
675
      gtk_list_store_set (GTK_LIST_STORE (model), &iter,
 
676
                          HISTORY_TITLE, title,
 
677
                          HISTORY_REF,   ref,
 
678
                          -1);
 
679
    }
 
680
 
 
681
  g_signal_handlers_block_by_func (combo, combo_changed, NULL);
 
682
  gtk_combo_box_set_active_iter (combo, &iter);
 
683
  g_signal_handlers_unblock_by_func (combo, combo_changed, NULL);
 
684
}
 
685
 
 
686
/* Taken from glib/gconvert.c:
 
687
 * Test of haystack has the needle prefix, comparing case
 
688
 * insensitive. haystack may be UTF-8, but needle must
 
689
 * contain only ascii.
 
690
 */
 
691
static gboolean
 
692
has_case_prefix (const gchar *haystack, const gchar *needle)
 
693
{
 
694
  const gchar *h = haystack;
 
695
  const gchar *n = needle;
 
696
 
 
697
  while (*n && *h && g_ascii_tolower (*n) == g_ascii_tolower (*h))
 
698
    {
 
699
      n++;
 
700
      h++;
 
701
    }
 
702
 
 
703
  return (*n == '\0');
 
704
}