dialog.c 29.3 KB
Newer Older
1
2
3
4
/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * The GIMP Help Browser
5
 * Copyright (C) 1999-2005 Sven Neumann <sven@gimp.org>
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 *                         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>
28
#include <errno.h>
29
30
31
32
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

33
34
#include <glib/gstdio.h>

35
#include <gtk/gtk.h>
36

37
38
39
40
41
42
43
#include <libgtkhtml/gtkhtml.h>

#include "libgimpwidgets/gimpwidgets.h"

#include "libgimp/gimp.h"
#include "libgimp/gimpui.h"

44
45
46
47
#ifdef G_OS_WIN32
#include "libgimpbase/gimpwin32-io.h"
#endif

48
49
50
#include "gimpthrobber.h"
#include "gimpthrobberaction.h"

51
52
53
54
#include "dialog.h"
#include "queue.h"
#include "uri.h"

55
56
#include "logo-pixbuf.h"

57
58
#include "libgimp/stdplugins-intl.h"

59
60
61
62
#ifndef _O_BINARY
#define _O_BINARY 0
#endif

63

64
enum
65
{
66
  HISTORY_TITLE,
67
  HISTORY_URI
68
};
69
70
71

/*  local function prototypes  */

72
static GtkUIManager * ui_manager_new (GtkWidget        *window);
73
static void       browser_dialog_404 (HtmlDocument     *doc,
74
                                      const gchar      *uri,
Sven Neumann's avatar
Sven Neumann committed
75
                                      const gchar      *message);
76

77
78
79
80
81
82
static void       back_callback      (GtkAction        *action,
                                      gpointer          data);
static void       forward_callback   (GtkAction        *action,
                                      gpointer          data);
static void       index_callback     (GtkAction        *action,
                                      gpointer          data);
Sven Neumann's avatar
Sven Neumann committed
83
84
85
86
static void       zoom_in_callback   (GtkAction        *action,
                                      gpointer          data);
static void       zoom_out_callback  (GtkAction        *action,
                                      gpointer          data);
87
static void       close_callback     (GtkAction        *action,
88
                                      gpointer          data);
89
90
static void       online_callback    (GtkAction        *action,
                                      gpointer          data);
91

92
static void       update_toolbar     (void);
93

94
95
static void       combo_changed      (GtkWidget        *widget,
                                      gpointer          data);
96

97
98
99
100
101
102
103
104
105
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);
106
107
108
static void       view_realize       (GtkWidget        *widget);
static void       view_unrealize     (GtkWidget        *widget);
static gboolean   view_button_press  (GtkWidget        *widget,
109
110
                                      GdkEventButton   *event);

111
112
113
114
static void       title_changed      (HtmlDocument     *doc,
                                      const gchar      *new_title,
                                      gpointer          data);
static void       link_clicked       (HtmlDocument     *doc,
115
                                      const gchar      *uri,
116
117
                                      gpointer          data);
static gboolean   request_url        (HtmlDocument     *doc,
118
                                      const gchar      *uri,
119
120
121
122
123
                                      HtmlStream       *stream,
                                      GError          **error);
static gboolean   io_handler         (GIOChannel       *io,
                                      GIOCondition      condition,
                                      gpointer          data);
124
static void       load_remote_page   (const gchar      *uri);
125
126

static void       history_add        (GtkComboBox      *combo,
127
                                      const gchar      *uri,
128
129
130
131
                                      const gchar      *title);

static gboolean   has_case_prefix    (const gchar      *haystack,
                                      const gchar      *needle);
132

133
static gchar    * filename_from_uri  (const gchar      *uri);
134

135
136
137

/*  private variables  */

138
static const gchar  *eek_png_tag  = "<h1>Eeek!</h1>";
139

140
static Queue        *queue        = NULL;
141
static gchar        *current_uri  = NULL;
142

143
144
145
146
static GtkWidget    *html         = NULL;
static GtkUIManager *ui_manager   = NULL;
static GtkWidget    *button_prev  = NULL;
static GtkWidget    *button_next  = NULL;
147
static GdkCursor    *busy_cursor  = NULL;
148
149
150

static GtkTargetEntry help_dnd_target_table[] =
{
151
152
  { "text/uri-list", 0, 0 },
  { "_NETSCAPE_URL", 0, 0 }
153
154
155
156
157
158
159
160
};


/*  public functions  */

void
browser_dialog_open (void)
{
161
162
  GtkWidget       *window;
  GtkWidget       *vbox;
163
  GtkWidget       *toolbar;
164
165
166
167
168
  GtkWidget       *hbox;
  GtkWidget       *scroll;
  GtkWidget       *drag_source;
  GtkWidget       *image;
  GtkWidget       *combo;
169
  GtkWidget       *button;
170
171
  GtkToolItem     *item;
  GtkAction       *action;
172
173
  GtkListStore    *history;
  GtkCellRenderer *cell;
174
  GdkPixbuf       *pixbuf;
175
  gchar           *eek_png_path;
176
177
178
179
180
181
182
183
184
185
186
187
188
189

  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);
190
  gtk_window_set_title (GTK_WINDOW (window), _("GIMP Help browser"));
191
  gtk_window_set_role (GTK_WINDOW (window), "helpbrowser");
192

193
194
  gtk_window_set_default_size (GTK_WINDOW (window), 420, 500);

195
  g_signal_connect (window, "destroy",
196
                    G_CALLBACK (gtk_main_quit),
197
198
                    NULL);

199
200
201
202
  pixbuf = gdk_pixbuf_new_from_inline (-1, logo_data, FALSE, NULL);
  gtk_window_set_icon (GTK_WINDOW (window), pixbuf);
  g_object_unref (pixbuf);

203
204
205
206
  vbox = gtk_vbox_new (FALSE, 2);
  gtk_container_add (GTK_CONTAINER (window), vbox);
  gtk_widget_show (vbox);

207
  ui_manager = ui_manager_new (window);
208

209
210
211
  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);
212

213
214
215
216
217
218
219
  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));
220
  g_object_notify (G_OBJECT (action), "tooltip");
221
222
223
224
225
226
227
228
229
  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));
230
  g_object_notify (G_OBJECT (action), "tooltip");
231
232
  button_prev = GTK_WIDGET (item);

233
234
235
236
237
238
  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);

239
240
241
242
243
  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));

244
245
246
247
248
249
250
251
252
  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);

253
254
255
  gimp_help_set_help_data (drag_source,
                           _("Drag and drop this icon to a web browser"), NULL);

256
257
258
259
  gtk_drag_source_set (GTK_WIDGET (drag_source),
                       GDK_BUTTON1_MASK,
                       help_dnd_target_table,
                       G_N_ELEMENTS (help_dnd_target_table),
260
                       GDK_ACTION_COPY);
261

262
263
264
265
266
267
268
269
270
271
272
273
  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  */
274
275
276
277
278
279
280
281
282
283
284
  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);
285
286
287
  gtk_box_pack_start (GTK_BOX (hbox), combo, TRUE, TRUE, 0);
  gtk_widget_show (combo);

288
289
290
  g_signal_connect (combo, "changed",
                    G_CALLBACK (combo_changed),
                    NULL);
291
292
293
294
295

  /*  HTML view  */
  html  = html_view_new ();
  queue = queue_new ();

296
  gtk_widget_set_size_request (html, -1, 200);
297
298
299
300
301
302
303
304
305
306
307
308
309

  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);

310
311
312
313
314
315
316
317
318
319
320
  g_signal_connect (html, "realize",
                    G_CALLBACK (view_realize),
                    NULL);
  g_signal_connect (html, "unrealize",
                    G_CALLBACK (view_unrealize),
                    NULL);

  g_signal_connect (html, "button_press_event",
                    G_CALLBACK (view_button_press),
                    NULL);

321
322
323
324
  html_view_set_document (HTML_VIEW (html), html_document_new ());

  g_signal_connect (HTML_VIEW (html)->document, "title_changed",
                    G_CALLBACK (title_changed),
325
                    combo);
326
327
328
329
330
331
332
  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);

333
334
  gtk_widget_grab_focus (html);

335
336
337
  gtk_widget_show (window);
}

338
339
340
341
342
343
344
345
346
static gboolean
idle_jump_to_anchor (const gchar *anchor)
{
  if (html && anchor)
    html_view_jump_to_anchor (HTML_VIEW (html), anchor);

  return FALSE;
}

347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
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;
}

363
void
364
browser_dialog_load (const gchar *uri,
365
366
                     gboolean     add_to_queue)
{
367
368
369
  HtmlDocument  *doc = HTML_VIEW (html)->document;
  GtkAdjustment *adj = gtk_layout_get_vadjustment (GTK_LAYOUT (html));
  gchar         *abs;
370
  gchar         *new_uri;
371
372
  gchar         *anchor;
  gchar         *tmp;
373

374
  g_return_if_fail (uri != NULL);
375

376
  if (! current_uri)
377
378
379
    {
      gchar *slash;

380
      current_uri = g_strdup (uri);
381

382
      slash = strrchr (current_uri, '/');
383
384
385
386
387

      if (slash)
        *slash = '\0';
    }

388
  abs = uri_to_abs (uri, current_uri);
389
390
  if (! abs)
    return;
391

392
  anchor = strchr (uri, '#');
393
394
  if (anchor && anchor[0] && anchor[1])
    {
395
      new_uri = g_strconcat (abs, anchor, NULL);
396
397
398
399
      anchor += 1;
    }
  else
    {
400
      new_uri = g_strdup (abs);
401
402
403
      anchor = NULL;
    }

404
  if (! has_case_prefix (abs, "file:/"))
405
    {
406
      load_remote_page (uri);
407
408
      return;
    }
409

410
  tmp = uri_to_abs (current_uri, current_uri);
411
412
  if (!tmp || strcmp (tmp, abs))
    {
413
414
      GError *error = NULL;

415
416
      html_document_clear (doc);
      html_document_open_stream (doc, "text/html");
417
418
419
420
421
422
      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);
423

424
425
426
427
428
429
      if (request_url (doc, abs, doc->current_stream, &error))
        {
          if (html->window)
            gdk_window_set_cursor (html->window, busy_cursor);
        }
      else
430
        {
Sven Neumann's avatar
Sven Neumann committed
431
          browser_dialog_404 (doc, abs, error->message);
432
433
          g_error_free (error);
        }
434
    }
435
436
437
438
439
440
441
  else
    {
      if (anchor)
        html_view_jump_to_anchor (HTML_VIEW (html), anchor);
      else
        gtk_adjustment_set_value (adj, 0.0);
     }
442

443
  g_free (tmp);
444

445
446
  g_free (current_uri);
  current_uri = new_uri;
447
448

  if (add_to_queue)
449
    queue_add (queue, new_uri);
450
451

  update_toolbar ();
452
453

  gtk_window_present (GTK_WINDOW (gtk_widget_get_toplevel (html)));
454
455
456
457
458
}


/*  private functions  */

459
460
461
462
463
464
static GtkUIManager *
ui_manager_new (GtkWidget *window)
{
  static GtkActionEntry actions[] =
  {
    { "back", GTK_STOCK_GO_BACK,
465
      NULL, "<alt>Left", N_("Go back one page"),
466
467
468
      G_CALLBACK (back_callback) },

    { "forward", GTK_STOCK_GO_FORWARD,
469
      NULL, "<alt>Right", N_("Go forward one page"),
470
471
472
      G_CALLBACK (forward_callback) },

    { "index", GTK_STOCK_INDEX,
473
      NULL, "<alt>Home", N_("Go to the index page"),
474
475
      G_CALLBACK (index_callback) },

Sven Neumann's avatar
Sven Neumann committed
476
    { "zoom-in", GTK_STOCK_ZOOM_IN,
477
      NULL, "<control>plus", NULL,
Sven Neumann's avatar
Sven Neumann committed
478
479
480
      G_CALLBACK (zoom_in_callback) },

    { "zoom-out", GTK_STOCK_ZOOM_OUT,
481
      NULL, "<control>minus", NULL,
Sven Neumann's avatar
Sven Neumann committed
482
483
      G_CALLBACK (zoom_out_callback) },

484
    { "close", GTK_STOCK_CLOSE,
485
      NULL, "<control>W", NULL,
486
487
488
      G_CALLBACK (close_callback) },

    { "quit", GTK_STOCK_QUIT,
489
      NULL, "<control>Q", NULL,
490
491
492
493
494
      G_CALLBACK (close_callback) },
  };

  GtkUIManager   *ui_manager = gtk_ui_manager_new ();
  GtkActionGroup *group      = gtk_action_group_new ("Actions");
495
  GtkAction      *action;
496
  GError         *error      = NULL;
497
498

  gtk_action_group_set_translation_domain (group, NULL);
499
  gtk_action_group_add_actions (group, actions, G_N_ELEMENTS (actions), NULL);
500

501
  action = gimp_throbber_action_new ("online",
Sven Neumann's avatar
Sven Neumann committed
502
                                     "docs.gimp.org",
503
504
505
506
507
508
509
510
511
                                     _("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);

512
513
514
515
516
517
518
519
520
521
522
  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\" />"
523
524
                                     "    <separator name=\"space\" />"
                                     "    <toolitem action=\"online\" />"
525
                                     "  </toolbar>"
526
527
                                     "  <accelerator action=\"close\" />"
                                     "  <accelerator action=\"quit\" />"
528
                                     "</ui>",
529
530
531
                                     -1, &error);

  if (error)
532
533
534
535
    {
      g_warning ("error parsing ui: %s", error->message);
      g_clear_error (&error);
    }
536

537
538
539
540
541
  gtk_ui_manager_add_ui_from_string (ui_manager,
                                     "<ui>"
                                     "  <popup name=\"help-browser-popup\">"
                                     "    <menuitem action=\"back\" />"
                                     "    <menuitem action=\"forward\" />"
542
                                     "    <menuitem action=\"index\" />"
Sven Neumann's avatar
Sven Neumann committed
543
544
545
                                     "    <separator />"
                                     "    <menuitem action=\"zoom-in\" />"
                                     "    <menuitem action=\"zoom-out\" />"
546
547
                                     "  </popup>"
                                     "</ui>",
548
549
550
551
552
553
554
                                     -1, &error);

  if (error)
    {
      g_warning ("error parsing ui: %s", error->message);
      g_clear_error (&error);
    }
555

556
557
558
  return ui_manager;
}

559
560
static void
browser_dialog_404 (HtmlDocument *doc,
561
                    const gchar  *uri,
Sven Neumann's avatar
Sven Neumann committed
562
                    const gchar  *message)
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
{
  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:"),
580
     uri,
Sven Neumann's avatar
Sven Neumann committed
581
     message);
582
583
584
585
586
587

  html_document_write_stream (doc, msg, strlen (msg));

  g_free (msg);
}

588
static void
589
590
back_callback (GtkAction *action,
               gpointer   data)
591
{
592
593
  gdouble      pos;
  const gchar *uri = queue_prev (queue, GPOINTER_TO_INT (data), &pos);
594

595
  if (uri)
596
    {
597
      browser_dialog_load (uri, FALSE);
598
599
600
601

      g_idle_add ((GSourceFunc) idle_scroll_to_pos,
                  GINT_TO_POINTER ((gint) (pos * (1 << 16))));

602
      queue_move_prev (queue, GPOINTER_TO_INT (data));
603
604
605
606
607
608
609
610
611
    }

  update_toolbar ();
}

static void
forward_callback (GtkAction *action,
                  gpointer   data)
{
612
613
  gdouble      pos;
  const gchar *uri = queue_next (queue, GPOINTER_TO_INT (data), &pos);
614

615
  if (uri)
616
    {
617
      browser_dialog_load (uri, FALSE);
618
619
620
621

      g_idle_add ((GSourceFunc) idle_scroll_to_pos,
                  GINT_TO_POINTER ((gint) (pos * (1 << 16))));

622
      queue_move_next (queue, GPOINTER_TO_INT (data));
623
624
625
626
627
    }

  update_toolbar ();
}

628
629
630
631
632
633
634
static void
index_callback (GtkAction *action,
                gpointer   data)
{
  browser_dialog_load ("index.html", TRUE);
}

Sven Neumann's avatar
Sven Neumann committed
635
636
637
638
639
640
641
642
643
644
645
646
647
648
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));
}

649
650
651
652
static void
online_callback (GtkAction *action,
                 gpointer   data)
{
653
  load_remote_page ("http://docs.gimp.org/");
654
655
}

656
657
658
659
static void
close_callback (GtkAction *action,
                gpointer   data)
{
660
  gtk_widget_destroy (gtk_widget_get_toplevel (html));
661
662
}

663
static GtkWidget *
664
665
build_menu (GList     *list,
            GCallback  callback)
666
{
Sven Neumann's avatar
Sven Neumann committed
667
668
  GtkWidget *menu;
  gint       i;
669
670
671
672

  if (! list)
    return NULL;

Sven Neumann's avatar
Sven Neumann committed
673
  menu = gtk_menu_new ();
674

Sven Neumann's avatar
Sven Neumann committed
675
  for (i = 0; list && i < 15; list = g_list_next (list), i++)
676
677
678
    {
      GtkWidget *menu_item = gtk_menu_item_new_with_label (list->data);

Sven Neumann's avatar
Sven Neumann committed
679
      gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
680
      gtk_widget_show (menu_item);
681
682
683
684

      g_signal_connect (menu_item, "activate",
                        G_CALLBACK (callback),
                        GINT_TO_POINTER (i));
685
686
687
688
    }

  g_list_free (list);

Sven Neumann's avatar
Sven Neumann committed
689
  return menu;
690
691
}

692
693
694
static void
update_toolbar (void)
{
695
696
  GtkAction *action;

697
698
  /*  update the back button and its menu  */

699
  action = gtk_ui_manager_get_action (ui_manager,
700
                                      "/ui/help-browser-popup/back");
701
702
  gtk_action_set_sensitive (action, queue_has_prev (queue));

703
  gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (button_prev),
704
705
                                 build_menu (queue_list_prev (queue),
                                             G_CALLBACK (back_callback)));
706
707
708

  /*  update the forward button and its menu  */

709
  action = gtk_ui_manager_get_action (ui_manager,
710
                                      "/ui/help-browser-popup/forward");
711
  gtk_action_set_sensitive (action, queue_has_next (queue));
712

713
714
715
  gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (button_next),
                                 build_menu (queue_list_next (queue),
                                             G_CALLBACK (forward_callback)));
716
717
718
}

static void
719
720
combo_changed (GtkWidget *widget,
               gpointer   data)
721
{
722
723
  GtkComboBox *combo = GTK_COMBO_BOX (widget);
  GtkTreeIter  iter;
724

725
  if (gtk_combo_box_get_active_iter (combo, &iter))
726
    {
727
      GValue  value = { 0, };
728

729
      gtk_tree_model_get_value (gtk_combo_box_get_model (combo),
730
                                &iter, HISTORY_URI, &value);
731

732
      browser_dialog_load (g_value_get_string (&value), TRUE);
733

734
      g_value_unset (&value);
735
736
737
738
739
740
741
742
    }
}

static void
drag_begin (GtkWidget      *widget,
            GdkDragContext *context,
            gpointer        data)
{
743
  gtk_drag_set_icon_stock (context, GIMP_STOCK_WEB, -8, -8);
744
745
746
747
748
749
750
751
752
753
}

static void
drag_data_get (GtkWidget        *widget,
               GdkDragContext   *context,
               GtkSelectionData *selection_data,
               guint             info,
               guint             time,
               gpointer          data)
{
754
  if (! current_uri)
755
756
    return;

757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
  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, 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;
    }
791
792
}

793
static gboolean
794
795
view_button_press (GtkWidget      *widget,
                   GdkEventButton *event)
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
{
  gtk_widget_grab_focus (widget);

  if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
    {
      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->button, event->time);
    }

  return FALSE;
}

813
814
815
816
817
static void
title_changed (HtmlDocument *doc,
               const gchar  *new_title,
               gpointer      data)
{
818
819
  gchar *title = NULL;

820
821
  if (new_title)
    {
822
823
824
825
826
827
828
      title = g_strstrip (g_strdup (new_title));
      if (! strlen (title))
        {
          g_free (title);
          title = NULL;
        }
    }
829

830
  history_add (GTK_COMBO_BOX (data), current_uri,
831
               title ? title : _("Untitled"));
832

833
834
835
  if (title)
    queue_set_title (queue, title);

836
  g_free (title);
837
838
839
840
}

static void
link_clicked (HtmlDocument *doc,
841
              const gchar  *uri,
842
843
              gpointer      data)
{
844
845
846
847
848
  GtkAdjustment *adj = gtk_layout_get_vadjustment (GTK_LAYOUT (html));

  if (adj->upper > 0.0)
    queue_set_scroll_offset (queue, adj->value / adj->upper);

849
  browser_dialog_load (uri, TRUE);
850
851
}

852
static gboolean
853
request_url (HtmlDocument *doc,
854
             const gchar  *uri,
855
             HtmlStream   *stream,
856
             GError      **error)
857
858
859
860
{
  gchar *abs;
  gchar *filename;

861
  g_return_val_if_fail (uri != NULL, TRUE);
862
  g_return_val_if_fail (stream != NULL, TRUE);
863

864
  abs = uri_to_abs (uri, current_uri);
865
  if (! abs)
866
    return TRUE;
867

868
  filename = filename_from_uri (abs);
869
  g_free (abs);
870
871
872

  if (filename)
    {
873
      gint fd = g_open (filename, O_RDONLY | _O_BINARY, 0);
874

875
      if (fd != -1)
876
877
878
879
880
881
882
883
884
885
886
        {
          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);
887
888
889
890
891
892
893
894

      if (fd == -1)
        {
          g_set_error (error,
                       G_FILE_ERROR, g_file_error_from_errno (errno),
                       g_strerror (errno));
          return FALSE;
        }
895
896
    }

897
  return TRUE;
898
899
900
901
902
903
904
}

static gboolean
io_handler (GIOChannel   *io,
            GIOCondition  condition,
            gpointer      data)
{
905
  HtmlStream *stream = data;
906
  gchar       buffer[8192];
907
  gsize       bytes;
908
909
910
911
912
913
914
915
916
917
918

  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
	{
919
          goto error;
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
	}

      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))
    {
935
    error:
936
937
938
      html_stream_close (stream);
      g_io_channel_unref (io);

939
940
941
      if (html->window)
        gdk_window_set_cursor (html->window, NULL);

942
943
944
945
946
947
948
      return FALSE;
    }

  return TRUE;
}

static void
949
load_remote_page (const gchar *uri)
950
951
952
953
{
  GimpParam *return_vals;
  gint       nreturn_vals;

954
  /*  try to call the user specified web browser */
955
956
  return_vals = gimp_run_procedure ("plug_in_web_browser",
                                    &nreturn_vals,
957
                                    GIMP_PDB_STRING, uri,
958
959
960
961
962
                                    GIMP_PDB_END);
  gimp_destroy_params (return_vals, nreturn_vals);
}

static void
963
history_add (GtkComboBox *combo,
964
             const gchar *uri,
965
966
	     const gchar *title)
{
967
968
969
970
971
972
973
974
  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))
975
    {
976
      gtk_tree_model_get_value (model, &iter, HISTORY_URI, &value);
977

978
      if (strcmp (g_value_get_string (&value), uri) == 0)
979
980
981
982
        {
          gtk_list_store_move_after (GTK_LIST_STORE (model), &iter, NULL);
          g_value_unset (&value);
          break;
983
        }
984

985
      g_value_unset (&value);
986
987
    }

988
  if (! iter_valid)
989
    {
990
991
992
      gtk_list_store_prepend (GTK_LIST_STORE (model), &iter);
      gtk_list_store_set (GTK_LIST_STORE (model), &iter,
                          HISTORY_TITLE, title,
993
                          HISTORY_URI,   uri,
994
                          -1);
995
996
    }

997
998
999
  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);
1000
}