1 /*
2 * Copyright 2006 Daniel Silverstone <dsilvers@digital-scurf.org>
3 * Copyright 2006 Rob Kendrick <rjek@rjek.com>
4 *
5 * This file is part of NetSurf, http://www.netsurf-browser.org/
6 *
7 * NetSurf is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; version 2 of the License.
10 *
11 * NetSurf is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /**
21 * \file
22 * Implementation of gtk windowing.
23 */
24
25 #include <stdlib.h>
26 #include <string.h>
27 #include <limits.h>
28 #include <assert.h>
29 #include <math.h>
30 #include <gtk/gtk.h>
31 #include <gdk/gdkkeysyms.h>
32 #include <gdk-pixbuf/gdk-pixdata.h>
33
34 #include "utils/utils.h"
35 #include "utils/log.h"
36 #include "utils/utf8.h"
37 #include "utils/nsoption.h"
38 #include "utils/messages.h"
39 #include "utils/nsurl.h"
40 #include "netsurf/inttypes.h"
41 #include "netsurf/content.h"
42 #include "netsurf/browser_window.h"
43 #include "netsurf/mouse.h"
44 #include "netsurf/window.h"
45 #include "netsurf/plotters.h"
46 #include "netsurf/form.h"
47 #include "netsurf/keypress.h"
48 #include "desktop/searchweb.h"
49 #include "desktop/textinput.h"
50
51 #include "gtk/selection.h"
52 #include "gtk/warn.h"
53 #include "gtk/compat.h"
54 #include "gtk/gui.h"
55 #include "gtk/scaffolding.h"
56 #include "gtk/toolbar_items.h"
57 #include "gtk/toolbar.h"
58 #include "gtk/local_history.h"
59 #include "gtk/plotters.h"
60 #include "gtk/schedule.h"
61 #include "gtk/tabs.h"
62 #include "gtk/bitmap.h"
63 #include "gtk/gdk.h"
64 #include "gtk/resources.h"
65 #include "gtk/search.h"
66 #include "gtk/throbber.h"
67 #include "gtk/window.h"
68
69 /**
70 * time (in ms) between throbber animation frame updates
71 */
72 #define THROBBER_FRAME_TIME (100)
73
74 static GtkWidget *select_menu;
75 static struct form_control *select_menu_control;
76
77 struct gui_window {
78 /**
79 * The gtk scaffold object containing menu, buttons, url bar, [tabs],
80 * drawing area, etc that may contain one or more gui_windows.
81 */
82 struct nsgtk_scaffolding *scaffold;
83
84 /** The 'content' window that is rendered in the gui_window */
85 struct browser_window *bw;
86
87 /** mouse state and events. */
88 struct {
89 struct gui_window *gui;
90
91 gdouble pressed_x;
92 gdouble pressed_y;
93 gboolean waiting;
94 browser_mouse_state state;
95 } mouse;
96
97 /** caret dimension and location for rendering */
98 int caretx, carety, careth;
99
100 /** caret shape for rendering */
101 gui_pointer_shape current_pointer;
102
103 /** previous event location */
104 int last_x, last_y;
105
106 /** controls toolbar context */
107 struct nsgtk_toolbar *toolbar;
108
109 /** search toolbar context */
110 struct gtk_search *search;
111
112 /** The top level container (tabBox) */
113 GtkWidget *container;
114
115 /** display widget for this page or frame */
116 GtkLayout *layout;
117
118 /** The container for the layout etc */
119 GtkWidget *grid;
120
121 /** handle to the the visible tab */
122 GtkWidget *tab;
123
124 /** statusbar */
125 GtkLabel *status_bar;
126
127 /** status pane */
128 GtkPaned *paned;
129
130 /** has the status pane had its first size operation yet? */
131 bool paned_sized;
132
133 /** The icon this window should have */
134 GdkPixbuf *icon;
135
136 /** The input method to use with this window */
137 GtkIMContext *input_method;
138
139 /** current frame of throbber */
140 int throb_frame;
141
142 /** list for cleanup */
143 struct gui_window *next, *prev;
144 };
145
146 /**< first entry in window list */
147 struct gui_window *window_list = NULL;
148
149 static void
nsgtk_select_menu_clicked(GtkCheckMenuItem * checkmenuitem,gpointer user_data)150 nsgtk_select_menu_clicked(GtkCheckMenuItem *checkmenuitem,
151 gpointer user_data)
152 {
153 form_select_process_selection(select_menu_control,
154 (intptr_t)user_data);
155 }
156
157 #if GTK_CHECK_VERSION(3,0,0)
158
159 static gboolean
nsgtk_window_draw_event(GtkWidget * widget,cairo_t * cr,gpointer data)160 nsgtk_window_draw_event(GtkWidget *widget, cairo_t *cr, gpointer data)
161 {
162 struct gui_window *gw = data;
163 struct gui_window *z;
164 struct rect clip;
165 struct redraw_context ctx = {
166 .interactive = true,
167 .background_images = true,
168 .plot = &nsgtk_plotters
169 };
170
171 double x1;
172 double y1;
173 double x2;
174 double y2;
175
176 assert(gw);
177 assert(gw->bw);
178
179 for (z = window_list; z && z != gw; z = z->next)
180 continue;
181 assert(z);
182 assert(GTK_WIDGET(gw->layout) == widget);
183
184 current_cr = cr;
185
186 GtkAdjustment *vscroll = nsgtk_layout_get_vadjustment(gw->layout);
187 GtkAdjustment *hscroll = nsgtk_layout_get_hadjustment(gw->layout);
188
189 cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
190
191 clip.x0 = x1;
192 clip.y0 = y1;
193 clip.x1 = x2;
194 clip.y1 = y2;
195
196 browser_window_redraw(gw->bw,
197 -gtk_adjustment_get_value(hscroll),
198 -gtk_adjustment_get_value(vscroll),
199 &clip,
200 &ctx);
201
202 if (gw->careth != 0) {
203 nsgtk_plot_caret(gw->caretx, gw->carety, gw->careth);
204 }
205
206 return FALSE;
207 }
208
209 #else
210
211 static gboolean
nsgtk_window_draw_event(GtkWidget * widget,GdkEventExpose * event,gpointer data)212 nsgtk_window_draw_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
213 {
214 struct gui_window *gw = data;
215 struct gui_window *z;
216 struct rect clip;
217 struct redraw_context ctx = {
218 .interactive = true,
219 .background_images = true,
220 .plot = &nsgtk_plotters
221 };
222
223 assert(gw);
224 assert(gw->bw);
225
226 for (z = window_list; z && z != gw; z = z->next)
227 continue;
228 assert(z);
229 assert(GTK_WIDGET(gw->layout) == widget);
230
231 current_cr = gdk_cairo_create(nsgtk_layout_get_bin_window(gw->layout));
232
233 clip.x0 = event->area.x;
234 clip.y0 = event->area.y;
235 clip.x1 = event->area.x + event->area.width;
236 clip.y1 = event->area.y + event->area.height;
237
238 browser_window_redraw(gw->bw, 0, 0, &clip, &ctx);
239
240 if (gw->careth != 0) {
241 nsgtk_plot_caret(gw->caretx, gw->carety, gw->careth);
242 }
243
244 cairo_destroy(current_cr);
245
246 return FALSE;
247 }
248
249 #endif
250
251 static gboolean
nsgtk_window_motion_notify_event(GtkWidget * widget,GdkEventMotion * event,gpointer data)252 nsgtk_window_motion_notify_event(GtkWidget *widget,
253 GdkEventMotion *event,
254 gpointer data)
255 {
256 struct gui_window *g = data;
257 bool shift = event->state & GDK_SHIFT_MASK;
258 bool ctrl = event->state & GDK_CONTROL_MASK;
259
260 if ((fabs(event->x - g->last_x) < 5.0) &&
261 (fabs(event->y - g->last_y) < 5.0)) {
262 /* Mouse hasn't moved far enough from press coordinate
263 * for this to be considered a drag.
264 */
265 return FALSE;
266 } else {
267 /* This is a drag, ensure it's always treated as such,
268 * even if we drag back over the press location.
269 */
270 g->last_x = INT_MIN;
271 g->last_y = INT_MIN;
272 }
273
274 if (g->mouse.state & BROWSER_MOUSE_PRESS_1) {
275 /* Start button 1 drag */
276 browser_window_mouse_click(g->bw, BROWSER_MOUSE_DRAG_1,
277 g->mouse.pressed_x, g->mouse.pressed_y);
278
279 /* Replace PRESS with HOLDING and declare drag in progress */
280 g->mouse.state ^= (BROWSER_MOUSE_PRESS_1 |
281 BROWSER_MOUSE_HOLDING_1);
282 g->mouse.state |= BROWSER_MOUSE_DRAG_ON;
283 } else if (g->mouse.state & BROWSER_MOUSE_PRESS_2) {
284 /* Start button 2 drag */
285 browser_window_mouse_click(g->bw, BROWSER_MOUSE_DRAG_2,
286 g->mouse.pressed_x, g->mouse.pressed_y);
287
288 /* Replace PRESS with HOLDING and declare drag in progress */
289 g->mouse.state ^= (BROWSER_MOUSE_PRESS_2 |
290 BROWSER_MOUSE_HOLDING_2);
291 g->mouse.state |= BROWSER_MOUSE_DRAG_ON;
292 }
293
294 /* Handle modifiers being removed */
295 if (g->mouse.state & BROWSER_MOUSE_MOD_1 && !shift)
296 g->mouse.state ^= BROWSER_MOUSE_MOD_1;
297 if (g->mouse.state & BROWSER_MOUSE_MOD_2 && !ctrl)
298 g->mouse.state ^= BROWSER_MOUSE_MOD_2;
299
300 browser_window_mouse_track(g->bw, g->mouse.state, event->x, event->y);
301
302 return TRUE;
303 }
304
305 /**
306 * GTK signal handler for focus-out-event on layout
307 *
308 * when focus leaves the layout widget ensure the caret is cleared
309 */
310 static gboolean
nsgtk_window_focus_out_event(GtkWidget * widget,GdkEvent * event,gpointer data)311 nsgtk_window_focus_out_event(GtkWidget *widget,
312 GdkEvent *event,
313 gpointer data)
314 {
315 struct gui_window *g = data;
316
317 browser_window_remove_caret(g->bw, true);
318 return FALSE;
319 }
320
321 /**
322 * GTK signal handler for button-press-event on layout
323 */
324 static gboolean
nsgtk_window_button_press_event(GtkWidget * widget,GdkEventButton * event,gpointer data)325 nsgtk_window_button_press_event(GtkWidget *widget,
326 GdkEventButton *event,
327 gpointer data)
328 {
329 struct gui_window *g = data;
330
331 gtk_im_context_reset(g->input_method);
332 gtk_widget_grab_focus(GTK_WIDGET(g->layout));
333 nsgtk_local_history_hide();
334
335 g->mouse.pressed_x = event->x;
336 g->mouse.pressed_y = event->y;
337
338 switch (event->button) {
339 case 1: /* Left button, usually. Pass to core as BUTTON 1. */
340 g->mouse.state = BROWSER_MOUSE_PRESS_1;
341 break;
342
343 case 2: /* Middle button, usually. Pass to core as BUTTON 2 */
344 g->mouse.state = BROWSER_MOUSE_PRESS_2;
345 break;
346
347 case 3: /* Right button, usually. Action button, context menu. */
348 /** \todo determine if hiding the caret here is necessary */
349 browser_window_remove_caret(g->bw, true);
350 nsgtk_scaffolding_context_menu(g->scaffold,
351 g->mouse.pressed_x,
352 g->mouse.pressed_y);
353 return TRUE;
354
355 default:
356 return FALSE;
357 }
358
359 /* Modify for double & triple clicks */
360 if (event->type == GDK_3BUTTON_PRESS)
361 g->mouse.state |= BROWSER_MOUSE_TRIPLE_CLICK;
362 else if (event->type == GDK_2BUTTON_PRESS)
363 g->mouse.state |= BROWSER_MOUSE_DOUBLE_CLICK;
364
365 /* Handle the modifiers too */
366 if (event->state & GDK_SHIFT_MASK)
367 g->mouse.state |= BROWSER_MOUSE_MOD_1;
368 if (event->state & GDK_CONTROL_MASK)
369 g->mouse.state |= BROWSER_MOUSE_MOD_2;
370
371 /* Record where we pressed, for use when determining whether to start
372 * a drag in motion notify events. */
373 g->last_x = event->x;
374 g->last_y = event->y;
375
376 browser_window_mouse_click(g->bw,
377 g->mouse.state,
378 g->mouse.pressed_x,
379 g->mouse.pressed_y);
380
381 return TRUE;
382 }
383
384
385 static gboolean
nsgtk_window_button_release_event(GtkWidget * widget,GdkEventButton * event,gpointer data)386 nsgtk_window_button_release_event(GtkWidget *widget,
387 GdkEventButton *event,
388 gpointer data)
389 {
390 struct gui_window *g = data;
391 bool shift = event->state & GDK_SHIFT_MASK;
392 bool ctrl = event->state & GDK_CONTROL_MASK;
393
394 /* If the mouse state is PRESS then we are waiting for a release to emit
395 * a click event, otherwise just reset the state to nothing */
396 if (g->mouse.state & BROWSER_MOUSE_PRESS_1)
397 g->mouse.state ^= (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_CLICK_1);
398 else if (g->mouse.state & BROWSER_MOUSE_PRESS_2)
399 g->mouse.state ^= (BROWSER_MOUSE_PRESS_2 | BROWSER_MOUSE_CLICK_2);
400
401 /* Handle modifiers being removed */
402 if (g->mouse.state & BROWSER_MOUSE_MOD_1 && !shift)
403 g->mouse.state ^= BROWSER_MOUSE_MOD_1;
404 if (g->mouse.state & BROWSER_MOUSE_MOD_2 && !ctrl)
405 g->mouse.state ^= BROWSER_MOUSE_MOD_2;
406
407 if (g->mouse.state & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2)) {
408 browser_window_mouse_click(g->bw, g->mouse.state, event->x, event->y);
409 } else {
410 browser_window_mouse_track(g->bw, 0, event->x, event->y);
411 }
412
413 g->mouse.state = 0;
414 return TRUE;
415 }
416
417
418 static gboolean
nsgtk_window_scroll_event(GtkWidget * widget,GdkEventScroll * event,gpointer data)419 nsgtk_window_scroll_event(GtkWidget *widget,
420 GdkEventScroll *event,
421 gpointer data)
422 {
423 struct gui_window *g = data;
424 double value;
425 double deltax = 0;
426 double deltay = 0;
427 GtkAdjustment *vscroll = nsgtk_layout_get_vadjustment(g->layout);
428 GtkAdjustment *hscroll = nsgtk_layout_get_hadjustment(g->layout);
429 GtkAllocation alloc;
430
431 switch (event->direction) {
432 case GDK_SCROLL_LEFT:
433 deltax = -1.0;
434 break;
435
436 case GDK_SCROLL_UP:
437 deltay = -1.0;
438 break;
439
440 case GDK_SCROLL_RIGHT:
441 deltax = 1.0;
442 break;
443
444 case GDK_SCROLL_DOWN:
445 deltay = 1.0;
446 break;
447
448 #if GTK_CHECK_VERSION(3,4,0)
449 case GDK_SCROLL_SMOOTH:
450 gdk_event_get_scroll_deltas((GdkEvent *)event, &deltax, &deltay);
451 break;
452 #endif
453 default:
454 NSLOG(netsurf, INFO, "Unhandled mouse scroll direction");
455 return TRUE;
456 }
457
458 deltax *= nsgtk_adjustment_get_step_increment(hscroll);
459 deltay *= nsgtk_adjustment_get_step_increment(vscroll);
460
461 if (browser_window_scroll_at_point(g->bw,
462 event->x, event->y,
463 deltax, deltay) != true) {
464
465 /* core did not handle event so change adjustments */
466
467 /* Horizontal */
468 if (deltax != 0) {
469 value = gtk_adjustment_get_value(hscroll) + deltax;
470
471 /* @todo consider gtk_widget_get_allocated_width() */
472 nsgtk_widget_get_allocation(GTK_WIDGET(g->layout), &alloc);
473
474 if (value > nsgtk_adjustment_get_upper(hscroll) - alloc.width) {
475 value = nsgtk_adjustment_get_upper(hscroll) - alloc.width;
476 }
477 if (value < nsgtk_adjustment_get_lower(hscroll)) {
478 value = nsgtk_adjustment_get_lower(hscroll);
479 }
480
481 gtk_adjustment_set_value(hscroll, value);
482 }
483
484 /* Vertical */
485 if (deltay != 0) {
486 value = gtk_adjustment_get_value(vscroll) + deltay;
487
488 /* @todo consider gtk_widget_get_allocated_height */
489 nsgtk_widget_get_allocation(GTK_WIDGET(g->layout), &alloc);
490
491 if (value > (nsgtk_adjustment_get_upper(vscroll) - alloc.height)) {
492 value = nsgtk_adjustment_get_upper(vscroll) - alloc.height;
493 }
494 if (value < nsgtk_adjustment_get_lower(vscroll)) {
495 value = nsgtk_adjustment_get_lower(vscroll);
496 }
497
498 gtk_adjustment_set_value(vscroll, value);
499 }
500 }
501
502 return TRUE;
503 }
504
505
506 static gboolean
nsgtk_window_keypress_event(GtkWidget * widget,GdkEventKey * event,gpointer data)507 nsgtk_window_keypress_event(GtkWidget *widget,
508 GdkEventKey *event,
509 gpointer data)
510 {
511 struct gui_window *g = data;
512 uint32_t nskey;
513
514 if (gtk_im_context_filter_keypress(g->input_method, event))
515 return TRUE;
516
517 nskey = gtk_gui_gdkkey_to_nskey(event);
518
519 if (browser_window_key_press(g->bw, nskey))
520 return TRUE;
521
522 if ((event->state & 0x7) != 0)
523 return TRUE;
524
525 double value;
526 GtkAdjustment *vscroll = nsgtk_layout_get_vadjustment(g->layout);
527 GtkAdjustment *hscroll = nsgtk_layout_get_hadjustment(g->layout);
528 GtkAllocation alloc;
529
530 /* @todo consider gtk_widget_get_allocated_width() */
531 nsgtk_widget_get_allocation(GTK_WIDGET(g->layout), &alloc);
532
533 switch (event->keyval) {
534
535 case GDK_KEY(Home):
536 case GDK_KEY(KP_Home):
537 value = nsgtk_adjustment_get_lower(vscroll);
538 gtk_adjustment_set_value(vscroll, value);
539 break;
540
541 case GDK_KEY(End):
542 case GDK_KEY(KP_End):
543 value = nsgtk_adjustment_get_upper(vscroll) - alloc.height;
544
545 if (value < nsgtk_adjustment_get_lower(vscroll))
546 value = nsgtk_adjustment_get_lower(vscroll);
547
548 gtk_adjustment_set_value(vscroll, value);
549 break;
550
551 case GDK_KEY(Left):
552 case GDK_KEY(KP_Left):
553 value = gtk_adjustment_get_value(hscroll) -
554 nsgtk_adjustment_get_step_increment(hscroll);
555
556 if (value < nsgtk_adjustment_get_lower(hscroll))
557 value = nsgtk_adjustment_get_lower(hscroll);
558
559 gtk_adjustment_set_value(hscroll, value);
560 break;
561
562 case GDK_KEY(Up):
563 case GDK_KEY(KP_Up):
564 value = gtk_adjustment_get_value(vscroll) -
565 nsgtk_adjustment_get_step_increment(vscroll);
566
567 if (value < nsgtk_adjustment_get_lower(vscroll))
568 value = nsgtk_adjustment_get_lower(vscroll);
569
570 gtk_adjustment_set_value(vscroll, value);
571 break;
572
573 case GDK_KEY(Right):
574 case GDK_KEY(KP_Right):
575 value = gtk_adjustment_get_value(hscroll) +
576 nsgtk_adjustment_get_step_increment(hscroll);
577
578 if (value > nsgtk_adjustment_get_upper(hscroll) - alloc.width)
579 value = nsgtk_adjustment_get_upper(hscroll) - alloc.width;
580
581 gtk_adjustment_set_value(hscroll, value);
582 break;
583
584 case GDK_KEY(Down):
585 case GDK_KEY(KP_Down):
586 value = gtk_adjustment_get_value(vscroll) +
587 nsgtk_adjustment_get_step_increment(vscroll);
588
589 if (value > nsgtk_adjustment_get_upper(vscroll) - alloc.height)
590 value = nsgtk_adjustment_get_upper(vscroll) - alloc.height;
591
592 gtk_adjustment_set_value(vscroll, value);
593 break;
594
595 case GDK_KEY(Page_Up):
596 case GDK_KEY(KP_Page_Up):
597 value = gtk_adjustment_get_value(vscroll) -
598 nsgtk_adjustment_get_page_increment(vscroll);
599
600 if (value < nsgtk_adjustment_get_lower(vscroll))
601 value = nsgtk_adjustment_get_lower(vscroll);
602
603 gtk_adjustment_set_value(vscroll, value);
604 break;
605
606 case GDK_KEY(Page_Down):
607 case GDK_KEY(KP_Page_Down):
608 value = gtk_adjustment_get_value(vscroll) +
609 nsgtk_adjustment_get_page_increment(vscroll);
610
611 if (value > nsgtk_adjustment_get_upper(vscroll) - alloc.height)
612 value = nsgtk_adjustment_get_upper(vscroll) - alloc.height;
613
614 gtk_adjustment_set_value(vscroll, value);
615 break;
616
617 default:
618 break;
619
620 }
621
622 return TRUE;
623 }
624
625
626 static gboolean
nsgtk_window_keyrelease_event(GtkWidget * widget,GdkEventKey * event,gpointer data)627 nsgtk_window_keyrelease_event(GtkWidget *widget,
628 GdkEventKey *event,
629 gpointer data)
630 {
631 struct gui_window *g = data;
632
633 return gtk_im_context_filter_keypress(g->input_method, event);
634 }
635
636
637 static void
nsgtk_window_input_method_commit(GtkIMContext * ctx,const gchar * str,gpointer data)638 nsgtk_window_input_method_commit(GtkIMContext *ctx,
639 const gchar *str,
640 gpointer data)
641 {
642 struct gui_window *g = data;
643 size_t len = strlen(str), offset = 0;
644
645 while (offset < len) {
646 uint32_t nskey = utf8_to_ucs4(str + offset, len - offset);
647
648 browser_window_key_press(g->bw, nskey);
649
650 offset = utf8_next(str, len, offset);
651 }
652 }
653
654
655 static gboolean
nsgtk_window_size_allocate_event(GtkWidget * widget,GtkAllocation * allocation,gpointer data)656 nsgtk_window_size_allocate_event(GtkWidget *widget,
657 GtkAllocation *allocation,
658 gpointer data)
659 {
660 struct gui_window *g = data;
661
662 browser_window_schedule_reformat(g->bw);
663
664 return TRUE;
665 }
666
667
668 /**
669 * when the pane position is changed update the user option
670 *
671 * The slightly awkward implementation with the first allocation flag
672 * is necessary because the initial window creation does not cause an
673 * allocate-event signal so the position value in the pane is incorrect
674 * and we cannot know what it should be until after the allocation
675 * (which did not generate a signal) is done as the user position is a
676 * percentage of pane total width not an absolute value.
677 */
678 static void
nsgtk_paned_notify__position(GObject * gobject,GParamSpec * pspec,gpointer data)679 nsgtk_paned_notify__position(GObject *gobject, GParamSpec *pspec, gpointer data)
680 {
681 struct gui_window *g = data;
682 GtkAllocation pane_alloc;
683
684 gtk_widget_get_allocation(GTK_WIDGET(g->paned), &pane_alloc);
685
686 if (g->paned_sized == false)
687 {
688 g->paned_sized = true;
689 gtk_paned_set_position(g->paned,
690 (nsoption_int(toolbar_status_size) * pane_alloc.width) / 10000);
691 return;
692 }
693
694 nsoption_set_int(toolbar_status_size,
695 ((gtk_paned_get_position(g->paned) * 10000) / (pane_alloc.width - 1)));
696 }
697
698
699 /**
700 * Set status bar / scroll bar proportion according to user option
701 * when pane is resized.
702 */
703 static gboolean
nsgtk_paned_size_allocate_event(GtkWidget * widget,GtkAllocation * allocation,gpointer data)704 nsgtk_paned_size_allocate_event(GtkWidget *widget,
705 GtkAllocation *allocation,
706 gpointer data)
707 {
708 gtk_paned_set_position(GTK_PANED(widget),
709 (nsoption_int(toolbar_status_size) * allocation->width) / 10000);
710
711 return TRUE;
712 }
713
714
715 /**
716 * handler for gtk destroy signal on window container
717 *
718 * destroy the browsing context as there is will be nothing to display it now
719 */
window_destroy(GtkWidget * widget,gpointer data)720 static void window_destroy(GtkWidget *widget, gpointer data)
721 {
722 struct gui_window *gw = data;
723
724 browser_window_destroy(gw->bw);
725
726 g_object_unref(gw->input_method);
727
728 /* free any existing icon */
729 if (gw->icon != NULL) {
730 g_object_unref(gw->icon);
731 gw->icon = NULL;
732 }
733
734 free(gw);
735 }
736
737
bw_from_gw(void * data)738 static struct browser_window *bw_from_gw(void *data)
739 {
740 struct gui_window *gw = data;
741 return gw->bw;
742 }
743
744
get_tool_bar_show(void)745 static bool get_tool_bar_show(void)
746 {
747 const char *cur_bar_show;
748
749 cur_bar_show = nsoption_charp(bar_show);
750 if (cur_bar_show != NULL) {
751 if (strcmp(cur_bar_show, "menu/tool") == 0) {
752 return true;
753 } else if (strcmp(cur_bar_show, "tool") == 0) {
754 return true;
755 }
756 }
757 return false;
758 }
759
760
761 /**
762 * Make the throbber advance to next frame.
763 *
764 * scheduled callback to update the throbber
765 *
766 * \param p The context passed when scheduled.
767 */
next_throbber_frame(void * p)768 static void next_throbber_frame(void *p)
769 {
770 struct gui_window *gw = p;
771 nserror res;
772 GdkPixbuf *pixbuf;
773
774 gw->throb_frame++; /* advance to next frame */
775
776 res = nsgtk_throbber_get_frame(gw->throb_frame, &pixbuf);
777 if (res == NSERROR_BAD_SIZE) {
778 gw->throb_frame = 1;
779 res = nsgtk_throbber_get_frame(gw->throb_frame, &pixbuf);
780 }
781
782 if (res == NSERROR_OK) {
783 nsgtk_tab_set_icon(gw->container, pixbuf);
784 /* only schedule next frame if there are no errors */
785 nsgtk_schedule(THROBBER_FRAME_TIME, next_throbber_frame, p);
786 }
787 }
788
789
790 /**
791 * Create and open a gtk container (window or tab) for a browsing context.
792 *
793 * \param bw The browsing context to create gui_window for.
794 * \param existing An existing gui_window, may be NULL
795 * \param flags flags to control the container creation
796 * \return gui window, or NULL on error
797 *
798 * If GW_CREATE_CLONE flag is set existing is non-NULL.
799 *
800 * Front end's gui_window must include a reference to the
801 * browser window passed in the bw param.
802 */
803 static struct gui_window *
gui_window_create(struct browser_window * bw,struct gui_window * existing,gui_window_create_flags flags)804 gui_window_create(struct browser_window *bw,
805 struct gui_window *existing,
806 gui_window_create_flags flags)
807 {
808 struct gui_window *g; /* what is being created to return */
809 bool open_in_background = !(nsoption_bool(focus_new));
810 GtkBuilder* tab_builder;
811
812 /* If there is a foreground request, override user preference */
813 if (flags & GW_CREATE_FOREGROUND)
814 open_in_background = false;
815
816 nserror res;
817
818 res = nsgtk_builder_new_from_resname("tabcontents", &tab_builder);
819 if (res != NSERROR_OK) {
820 NSLOG(netsurf, INFO, "Tab contents UI builder init failed");
821 return NULL;
822 }
823
824 gtk_builder_connect_signals(tab_builder, NULL);
825
826 g = calloc(1, sizeof(*g));
827 if (!g) {
828 nsgtk_warning("NoMemory", 0);
829 g_object_unref(tab_builder);
830 return NULL;
831 }
832
833 NSLOG(netsurf, INFO, "Creating gui window %p for browser window %p",
834 g, bw);
835
836 g->bw = bw;
837 g->mouse.state = 0;
838 g->current_pointer = GUI_POINTER_DEFAULT;
839
840 /* attach scaffold */
841 if (flags & GW_CREATE_TAB) {
842 /* open in new tab, attach to existing scaffold */
843 if (existing != NULL) {
844 g->scaffold = existing->scaffold;
845 } else {
846 g->scaffold = nsgtk_current_scaffolding();
847 }
848 } else {
849 /* open in new window, create and attach to scaffold */
850 g->scaffold = nsgtk_new_scaffolding(g);
851 }
852 if (g->scaffold == NULL) {
853 nsgtk_warning("NoMemory", 0);
854 free(g);
855 g_object_unref(tab_builder);
856 return NULL;
857 }
858
859 /* Construct our primary elements */
860 g->container = GTK_WIDGET(gtk_builder_get_object(tab_builder, "tabBox"));
861 g->layout = GTK_LAYOUT(gtk_builder_get_object(tab_builder, "layout"));
862 g->grid = GTK_WIDGET(gtk_builder_get_object(tab_builder, "tabContents"));
863 g->status_bar = GTK_LABEL(gtk_builder_get_object(tab_builder, "status_bar"));
864 g->paned = GTK_PANED(gtk_builder_get_object(tab_builder, "hpaned1"));
865 g->input_method = gtk_im_multicontext_new();
866
867
868 /* create toolbar */
869 res = nsgtk_toolbar_create(tab_builder, bw_from_gw, g,
870 !!(flags & GW_CREATE_FOCUS_LOCATION),
871 &g->toolbar);
872 if (res != NSERROR_OK) {
873 free(g);
874 g_object_unref(tab_builder);
875 return NULL;
876 }
877
878 /* local page text search toolbar */
879 res = nsgtk_search_create(tab_builder, g->bw, &g->search);
880 if (res != NSERROR_OK) {
881 free(g);
882 g_object_unref(tab_builder);
883 return NULL;
884 }
885
886 /* set a default favicon */
887 g_object_ref(favicon_pixbuf);
888 g->icon = favicon_pixbuf;
889
890 /* add new gui window to global list (push_top) */
891 if (window_list) {
892 window_list->prev = g;
893 }
894 g->next = window_list;
895 g->prev = NULL;
896 window_list = g;
897
898 /* set the events we're interested in receiving from the browser's
899 * drawing area.
900 */
901 gtk_widget_add_events(GTK_WIDGET(g->layout),
902 GDK_EXPOSURE_MASK |
903 GDK_LEAVE_NOTIFY_MASK |
904 GDK_BUTTON_PRESS_MASK |
905 GDK_BUTTON_RELEASE_MASK |
906 GDK_POINTER_MOTION_MASK |
907 GDK_POINTER_MOTION_HINT_MASK |
908 GDK_KEY_PRESS_MASK |
909 GDK_KEY_RELEASE_MASK |
910 GDK_SCROLL_MASK);
911 nsgtk_widget_set_can_focus(GTK_WIDGET(g->layout), TRUE);
912
913 /* set the default background colour of the drawing area to white. */
914 nsgtk_widget_override_background_color(GTK_WIDGET(g->layout),
915 GTK_STATE_FLAG_NORMAL,
916 0, 0xffff, 0xffff, 0xffff);
917
918 nsgtk_connect_draw_event(GTK_WIDGET(g->layout),
919 G_CALLBACK(nsgtk_window_draw_event), g);
920
921 /* helper macro to conect signals to callbacks */
922 #define CONNECT(obj, sig, callback, ptr) \
923 g_signal_connect(G_OBJECT(obj), (sig), G_CALLBACK(callback), (ptr))
924
925 /* layout signals */
926 CONNECT(g->layout, "motion-notify-event",
927 nsgtk_window_motion_notify_event, g);
928 CONNECT(g->layout, "button-press-event",
929 nsgtk_window_button_press_event, g);
930 CONNECT(g->layout, "button-release-event",
931 nsgtk_window_button_release_event, g);
932 CONNECT(g->layout, "key-press-event",
933 nsgtk_window_keypress_event, g);
934 CONNECT(g->layout, "key-release-event",
935 nsgtk_window_keyrelease_event, g);
936 CONNECT(g->layout, "size-allocate",
937 nsgtk_window_size_allocate_event, g);
938 CONNECT(g->layout, "scroll-event",
939 nsgtk_window_scroll_event, g);
940 CONNECT(g->layout, "focus-out-event",
941 nsgtk_window_focus_out_event, g);
942
943 /* status pane signals */
944 CONNECT(g->paned, "size-allocate",
945 nsgtk_paned_size_allocate_event, g);
946
947 CONNECT(g->paned, "notify::position",
948 nsgtk_paned_notify__position, g);
949
950 /* gtk container destructor */
951 CONNECT(g->container, "destroy", window_destroy, g);
952
953 /* input method */
954 gtk_im_context_set_client_window(g->input_method,
955 nsgtk_layout_get_bin_window(g->layout));
956 gtk_im_context_set_use_preedit(g->input_method, FALSE);
957
958 /* input method signals */
959 CONNECT(g->input_method, "commit",
960 nsgtk_window_input_method_commit, g);
961
962 /* add the tab container to the scaffold notebook */
963 nsgtk_tab_add(g, g->container,
964 open_in_background,
965 messages_get("NewTab"), g->icon);
966
967 /* initialy should not be visible */
968 nsgtk_search_toggle_visibility(g->search);
969
970 /* set toolbar visibility from user option */
971 nsgtk_toolbar_show(g->toolbar, get_tool_bar_show());
972
973 /* safe to drop the reference to the tab_builder as the container is
974 * referenced by the notebook now.
975 */
976 g_object_unref(tab_builder);
977
978 /* Finally we need to focus the location bar if requested */
979 if (flags & GW_CREATE_FOCUS_LOCATION) {
980 if (nsgtk_window_item_activate(g, OPENLOCATION_BUTTON) != NSERROR_OK) {
981 NSLOG(netsurf, WARNING, "Unable to focus location input");
982 }
983 }
984
985 return g;
986 }
987
988
gui_window_destroy(struct gui_window * gw)989 static void gui_window_destroy(struct gui_window *gw)
990 {
991 NSLOG(netsurf, INFO, "gui_window: %p", gw);
992 assert(gw != NULL);
993 assert(gw->bw != NULL);
994 NSLOG(netsurf, INFO, "scaffolding: %p", gw->scaffold);
995
996 /* kill off any throbber that might be running */
997 nsgtk_schedule(-1, next_throbber_frame, gw);
998
999 /* remove from window list */
1000 if (gw->prev) {
1001 gw->prev->next = gw->next;
1002 } else {
1003 window_list = gw->next;
1004 }
1005
1006 if (gw->next) {
1007 gw->next->prev = gw->prev;
1008 }
1009
1010 NSLOG(netsurf, INFO, "window list head: %p", window_list);
1011 }
1012
1013
1014 /**
1015 * favicon setting for gtk gui window.
1016 *
1017 * \param gw gtk gui window to set favicon on.
1018 * \param icon A handle to the new favicon content.
1019 */
1020 static void
gui_window_set_icon(struct gui_window * gw,struct hlcache_handle * icon)1021 gui_window_set_icon(struct gui_window *gw, struct hlcache_handle *icon)
1022 {
1023 struct bitmap *icon_bitmap = NULL;
1024
1025 /* free any existing icon */
1026 if (gw->icon != NULL) {
1027 g_object_unref(gw->icon);
1028 gw->icon = NULL;
1029 }
1030
1031 if (icon != NULL) {
1032 icon_bitmap = content_get_bitmap(icon);
1033 if (icon_bitmap != NULL) {
1034 NSLOG(netsurf, INFO, "Using %p bitmap", icon_bitmap);
1035 gw->icon = nsgdk_pixbuf_get_from_surface(icon_bitmap->surface, 16, 16);
1036 }
1037 }
1038
1039 if (gw->icon == NULL) {
1040 NSLOG(netsurf, INFO, "Using default favicon");
1041 g_object_ref(favicon_pixbuf);
1042 gw->icon = favicon_pixbuf;
1043 }
1044
1045 /* only set icon if throbber not running */
1046 if (gw->throb_frame == 0) {
1047 nsgtk_tab_set_icon(gw->container, gw->icon);
1048 }
1049 }
1050
1051
gui_window_get_scroll(struct gui_window * g,int * sx,int * sy)1052 static bool gui_window_get_scroll(struct gui_window *g, int *sx, int *sy)
1053 {
1054 GtkAdjustment *vadj = nsgtk_layout_get_vadjustment(g->layout);
1055 GtkAdjustment *hadj = nsgtk_layout_get_hadjustment(g->layout);
1056
1057 assert(vadj);
1058 assert(hadj);
1059
1060 *sy = (int)(gtk_adjustment_get_value(vadj));
1061 *sx = (int)(gtk_adjustment_get_value(hadj));
1062
1063 return true;
1064 }
1065
1066
nsgtk_redraw_caret(struct gui_window * g)1067 static void nsgtk_redraw_caret(struct gui_window *g)
1068 {
1069 int sx, sy;
1070
1071 if (g->careth == 0)
1072 return;
1073
1074 gui_window_get_scroll(g, &sx, &sy);
1075
1076 gtk_widget_queue_draw_area(GTK_WIDGET(g->layout),
1077 g->caretx - sx, g->carety - sy, 1, g->careth + 1);
1078
1079 }
1080
1081
gui_window_remove_caret(struct gui_window * g)1082 static void gui_window_remove_caret(struct gui_window *g)
1083 {
1084 int sx, sy;
1085 int oh = g->careth;
1086
1087 if (oh == 0)
1088 return;
1089
1090 g->careth = 0;
1091
1092 gui_window_get_scroll(g, &sx, &sy);
1093
1094 gtk_widget_queue_draw_area(GTK_WIDGET(g->layout),
1095 g->caretx - sx, g->carety - sy, 1, oh + 1);
1096
1097 }
1098
1099
1100 /**
1101 * Invalidates an area of a GTK browser window
1102 *
1103 * \param g gui_window
1104 * \param rect area to redraw or NULL for the entire window area
1105 * \return NSERROR_OK on success or appropriate error code
1106 */
1107 static nserror
nsgtk_window_invalidate_area(struct gui_window * g,const struct rect * rect)1108 nsgtk_window_invalidate_area(struct gui_window *g, const struct rect *rect)
1109 {
1110 int sx, sy;
1111
1112 if (rect == NULL) {
1113 gtk_widget_queue_draw(GTK_WIDGET(g->layout));
1114 return NSERROR_OK;
1115 }
1116
1117 if (!browser_window_has_content(g->bw)) {
1118 return NSERROR_OK;
1119 }
1120
1121 gui_window_get_scroll(g, &sx, &sy);
1122
1123 gtk_widget_queue_draw_area(GTK_WIDGET(g->layout),
1124 rect->x0 - sx,
1125 rect->y0 - sy,
1126 rect->x1 - rect->x0,
1127 rect->y1 - rect->y0);
1128
1129 return NSERROR_OK;
1130 }
1131
1132
gui_window_set_status(struct gui_window * g,const char * text)1133 static void gui_window_set_status(struct gui_window *g, const char *text)
1134 {
1135 assert(g);
1136 assert(g->status_bar);
1137 gtk_label_set_text(g->status_bar, text);
1138 }
1139
1140
1141 /**
1142 * Set the scroll position of a gtk browser window.
1143 *
1144 * Scrolls the viewport to ensure the specified rectangle of the
1145 * content is shown. The GTK implementation scrolls the contents so
1146 * the specified point in the content is at the top of the viewport.
1147 *
1148 * \param g gui window to scroll
1149 * \param rect The rectangle to ensure is shown.
1150 * \return NSERROR_OK on success or apropriate error code.
1151 */
1152 static nserror
gui_window_set_scroll(struct gui_window * g,const struct rect * rect)1153 gui_window_set_scroll(struct gui_window *g, const struct rect *rect)
1154 {
1155 GtkAdjustment *vadj = nsgtk_layout_get_vadjustment(g->layout);
1156 GtkAdjustment *hadj = nsgtk_layout_get_hadjustment(g->layout);
1157 gdouble vlower, vpage, vupper, hlower, hpage, hupper;
1158 gdouble x = (gdouble)rect->x0;
1159 gdouble y = (gdouble)rect->y0;
1160
1161 assert(vadj);
1162 assert(hadj);
1163
1164 g_object_get(vadj, "page-size", &vpage, "lower", &vlower, "upper", &vupper, NULL);
1165 g_object_get(hadj, "page-size", &hpage, "lower", &hlower, "upper", &hupper, NULL);
1166
1167 if (x < hlower) {
1168 x = hlower;
1169 }
1170 if (x > (hupper - hpage)) {
1171 x = hupper - hpage;
1172 }
1173 if (y < vlower) {
1174 y = vlower;
1175 }
1176 if (y > (vupper - vpage)) {
1177 y = vupper - vpage;
1178 }
1179
1180 gtk_adjustment_set_value(vadj, y);
1181 gtk_adjustment_set_value(hadj, x);
1182
1183 return NSERROR_OK;
1184 }
1185
1186
gui_window_update_extent(struct gui_window * g)1187 static void gui_window_update_extent(struct gui_window *g)
1188 {
1189 int w, h;
1190
1191 if (browser_window_get_extents(g->bw, true, &w, &h) == NSERROR_OK) {
1192 gtk_layout_set_size(g->layout, w, h);
1193 gtk_widget_queue_resize(g->grid);
1194 }
1195 }
1196
1197
1198 static void
gui_window_set_pointer(struct gui_window * g,gui_pointer_shape shape)1199 gui_window_set_pointer(struct gui_window *g, gui_pointer_shape shape)
1200 {
1201 GdkCursor *cursor = NULL;
1202 GdkCursorType cursortype;
1203 bool nullcursor = false;
1204
1205 if (g->current_pointer == shape)
1206 return;
1207
1208 g->current_pointer = shape;
1209
1210 switch (shape) {
1211 case GUI_POINTER_POINT:
1212 cursortype = GDK_HAND2;
1213 break;
1214 case GUI_POINTER_CARET:
1215 cursortype = GDK_XTERM;
1216 break;
1217 case GUI_POINTER_UP:
1218 cursortype = GDK_TOP_SIDE;
1219 break;
1220 case GUI_POINTER_DOWN:
1221 cursortype = GDK_BOTTOM_SIDE;
1222 break;
1223 case GUI_POINTER_LEFT:
1224 cursortype = GDK_LEFT_SIDE;
1225 break;
1226 case GUI_POINTER_RIGHT:
1227 cursortype = GDK_RIGHT_SIDE;
1228 break;
1229 case GUI_POINTER_LD:
1230 cursortype = GDK_BOTTOM_LEFT_CORNER;
1231 break;
1232 case GUI_POINTER_RD:
1233 cursortype = GDK_BOTTOM_RIGHT_CORNER;
1234 break;
1235 case GUI_POINTER_LU:
1236 cursortype = GDK_TOP_LEFT_CORNER;
1237 break;
1238 case GUI_POINTER_RU:
1239 cursortype = GDK_TOP_RIGHT_CORNER;
1240 break;
1241 case GUI_POINTER_CROSS:
1242 cursortype = GDK_CROSS;
1243 break;
1244 case GUI_POINTER_MOVE:
1245 cursortype = GDK_FLEUR;
1246 break;
1247 case GUI_POINTER_WAIT:
1248 cursortype = GDK_WATCH;
1249 break;
1250 case GUI_POINTER_HELP:
1251 cursortype = GDK_QUESTION_ARROW;
1252 break;
1253 case GUI_POINTER_MENU:
1254 cursor = nsgtk_create_menu_cursor();
1255 nullcursor = true;
1256 break;
1257 case GUI_POINTER_PROGRESS:
1258 /* In reality, this needs to be the funky left_ptr_watch
1259 * which we can't do easily yet.
1260 */
1261 cursortype = GDK_WATCH;
1262 break;
1263 /* The following we're not sure about */
1264 case GUI_POINTER_NO_DROP:
1265 case GUI_POINTER_NOT_ALLOWED:
1266 case GUI_POINTER_DEFAULT:
1267 default:
1268 nullcursor = true;
1269 }
1270
1271 if (!nullcursor)
1272 cursor = gdk_cursor_new_for_display(
1273 gtk_widget_get_display(
1274 GTK_WIDGET(g->layout)),
1275 cursortype);
1276 gdk_window_set_cursor(nsgtk_widget_get_window(GTK_WIDGET(g->layout)),
1277 cursor);
1278
1279 if (!nullcursor)
1280 nsgdk_cursor_unref(cursor);
1281 }
1282
1283
1284 static void
gui_window_place_caret(struct gui_window * g,int x,int y,int height,const struct rect * clip)1285 gui_window_place_caret(struct gui_window *g,
1286 int x, int y, int height,
1287 const struct rect *clip)
1288 {
1289 nsgtk_redraw_caret(g);
1290
1291 y += 1;
1292 height -= 1;
1293
1294 if (y < clip->y0) {
1295 height -= clip->y0 - y;
1296 y = clip->y0;
1297 }
1298
1299 if (y + height > clip->y1) {
1300 height = clip->y1 - y + 1;
1301 }
1302
1303 g->caretx = x;
1304 g->carety = y;
1305 g->careth = height;
1306
1307 nsgtk_redraw_caret(g);
1308
1309 gtk_widget_grab_focus(GTK_WIDGET(g->layout));
1310 }
1311
1312
1313 /**
1314 * Find the current dimensions of a GTK browser window content area.
1315 *
1316 * \param gw The gui window to measure content area of.
1317 * \param width receives width of window
1318 * \param height receives height of window
1319 * \return NSERROR_OK on sucess and width and height updated
1320 * else error code.
1321 */
1322 static nserror
gui_window_get_dimensions(struct gui_window * gw,int * width,int * height)1323 gui_window_get_dimensions(struct gui_window *gw, int *width, int *height)
1324 {
1325 GtkAllocation alloc;
1326
1327 /** @todo consider gtk_widget_get_allocated_width() */
1328 nsgtk_widget_get_allocation(GTK_WIDGET(gw->layout), &alloc);
1329
1330 *width = alloc.width;
1331 *height = alloc.height;
1332
1333 return NSERROR_OK;
1334 }
1335
1336
gui_window_start_selection(struct gui_window * g)1337 static void gui_window_start_selection(struct gui_window *g)
1338 {
1339 gtk_widget_grab_focus(GTK_WIDGET(g->layout));
1340 }
1341
1342
1343 static void
gui_window_create_form_select_menu(struct gui_window * g,struct form_control * control)1344 gui_window_create_form_select_menu(struct gui_window *g,
1345 struct form_control *control)
1346 {
1347 intptr_t item;
1348 struct form_option *option;
1349
1350 GtkWidget *menu_item;
1351
1352 /* control->data.select.multiple is true if multiple selections
1353 * are allowable. We ignore this, as the core handles it for us.
1354 * Yay. \o/
1355 */
1356
1357 if (select_menu != NULL) {
1358 gtk_widget_destroy(select_menu);
1359 }
1360
1361 select_menu = gtk_menu_new();
1362 select_menu_control = control;
1363
1364 item = 0;
1365 option = form_select_get_option(control, item);
1366 while (option != NULL) {
1367 NSLOG(netsurf, INFO, "Item %"PRIdPTR" option %p text %s",
1368 item, option, option->text);
1369 menu_item = gtk_check_menu_item_new_with_label(option->text);
1370 if (option->selected) {
1371 gtk_check_menu_item_set_active(
1372 GTK_CHECK_MENU_ITEM(menu_item), TRUE);
1373 }
1374
1375 /*
1376 * This casts the item index integer into an integer
1377 * the size of a pointer. This allows the callback
1378 * parameter to be passed avoiding allocating memory
1379 * for a context with a single integer in it.
1380 */
1381 g_signal_connect(menu_item, "toggled",
1382 G_CALLBACK(nsgtk_select_menu_clicked), (gpointer)item);
1383
1384 gtk_menu_shell_append(GTK_MENU_SHELL(select_menu), menu_item);
1385
1386 item++;
1387 option = form_select_get_option(control, item);
1388 }
1389
1390 gtk_widget_show_all(select_menu);
1391
1392 nsgtk_menu_popup_at_pointer(GTK_MENU(select_menu), NULL);
1393 }
1394
1395
1396 /**
1397 * GTK window UI callback when core needs a file selection gadget
1398 *
1399 * \param g The gui window on which the gadget has been requested
1400 */
1401 static void
gui_window_file_gadget_open(struct gui_window * g,struct hlcache_handle * hl,struct form_control * gadget)1402 gui_window_file_gadget_open(struct gui_window *g,
1403 struct hlcache_handle *hl,
1404 struct form_control *gadget)
1405 {
1406 GtkWidget *dialog;
1407
1408 dialog = gtk_file_chooser_dialog_new("Select File",
1409 nsgtk_scaffolding_window(g->scaffold),
1410 GTK_FILE_CHOOSER_ACTION_OPEN,
1411 NSGTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1412 NSGTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
1413 NULL);
1414
1415 NSLOG(netsurf, INFO, "*** open dialog: %p", dialog);
1416
1417 int ret = gtk_dialog_run(GTK_DIALOG(dialog));
1418 NSLOG(netsurf, INFO, "*** return value: %d", ret);
1419 if (ret == GTK_RESPONSE_ACCEPT) {
1420 char *filename;
1421
1422 filename = gtk_file_chooser_get_filename(
1423 GTK_FILE_CHOOSER(dialog));
1424
1425 browser_window_set_gadget_filename(g->bw, gadget, filename);
1426
1427 g_free(filename);
1428 }
1429
1430 gtk_widget_destroy(dialog);
1431 }
1432
1433
1434 /**
1435 * handle throbber changing state
1436 */
throbber(struct gui_window * gw,bool active)1437 static nserror throbber(struct gui_window *gw, bool active)
1438 {
1439 nsgtk_toolbar_throbber(gw->toolbar, active);
1440 nsgtk_scaffolding_throbber(gw, active);
1441 if (active) {
1442 nsgtk_schedule(THROBBER_FRAME_TIME, next_throbber_frame, gw);
1443 } else {
1444 nsgtk_schedule(-1, next_throbber_frame, gw);
1445 gw->throb_frame = 0;
1446 /* set tab back to favicon */
1447 nsgtk_tab_set_icon(gw->container, gw->icon);
1448 }
1449 return NSERROR_OK;
1450 }
1451
1452
1453 /**
1454 * handle page info changing
1455 */
page_info_change(struct gui_window * gw)1456 static nserror page_info_change(struct gui_window *gw)
1457 {
1458 nsgtk_toolbar_page_info_change(gw->toolbar);
1459 return NSERROR_OK;
1460 }
1461
1462 /**
1463 * GTK window UI callback to process miscellaneous events
1464 *
1465 * \param gw The window receiving the event.
1466 * \param event The event code.
1467 * \return NSERROR_OK when processed ok
1468 */
1469 static nserror
gui_window_event(struct gui_window * gw,enum gui_window_event event)1470 gui_window_event(struct gui_window *gw, enum gui_window_event event)
1471 {
1472 switch (event) {
1473 case GW_EVENT_UPDATE_EXTENT:
1474 gui_window_update_extent(gw);
1475 break;
1476
1477 case GW_EVENT_REMOVE_CARET:
1478 gui_window_remove_caret(gw);
1479 break;
1480
1481 case GW_EVENT_START_SELECTION:
1482 gui_window_start_selection(gw);
1483 break;
1484
1485 case GW_EVENT_START_THROBBER:
1486 throbber(gw, true);
1487 break;
1488
1489 case GW_EVENT_STOP_THROBBER:
1490 throbber(gw, false);
1491 break;
1492
1493 case GW_EVENT_PAGE_INFO_CHANGE:
1494 page_info_change(gw);
1495 break;
1496
1497 default:
1498 break;
1499 }
1500 return NSERROR_OK;
1501 }
1502
1503
1504 /**
1505 * GTK window UI callback when core changes the current url
1506 *
1507 * \param gw The gui window on which the url has been set.
1508 * \param url The new url.
1509 */
gui_window_set_url(struct gui_window * gw,nsurl * url)1510 static nserror gui_window_set_url(struct gui_window *gw, nsurl *url)
1511 {
1512 return nsgtk_toolbar_set_url(gw->toolbar, url);
1513 }
1514
1515
1516 /**
1517 * GTK window UI callback when core changes the current title
1518 *
1519 * \param gw The gui window on which the url has been set.
1520 * \param url The new url.
1521 */
gui_window_set_title(struct gui_window * gw,const char * title)1522 static void gui_window_set_title(struct gui_window *gw, const char *title)
1523 {
1524
1525 if ((title != NULL) && (title[0] != '\0')) {
1526 nsgtk_tab_set_title(gw->container, title);
1527 }
1528 nsgtk_scaffolding_set_title(gw, title);
1529 }
1530
1531
1532 /**
1533 * GTK UI callback when search provider details are updated.
1534 *
1535 * \param name The providers name.
1536 * \param bitmap The bitmap representing the provider.
1537 * \return NSERROR_OK on success else error code.
1538 */
1539 static nserror
gui_search_web_provider_update(const char * name,struct bitmap * bitmap)1540 gui_search_web_provider_update(const char *name, struct bitmap *bitmap)
1541 {
1542 struct gui_window *gw;
1543 GdkPixbuf *pixbuf = NULL;
1544
1545 if (bitmap != NULL) {
1546 pixbuf = nsgdk_pixbuf_get_from_surface(bitmap->surface, 32, 32);
1547 }
1548
1549 for (gw = window_list; gw != NULL; gw = gw->next) {
1550 nsgtk_toolbar_set_websearch_image(gw->toolbar, pixbuf);
1551 }
1552
1553 if (pixbuf != NULL) {
1554 g_object_unref(pixbuf);
1555 }
1556
1557 return NSERROR_OK;
1558 }
1559
1560
1561 /**
1562 * GTK frontend web search operation table
1563 */
1564 static struct gui_search_web_table search_web_table = {
1565 .provider_update = gui_search_web_provider_update,
1566 };
1567
1568 struct gui_search_web_table *nsgtk_search_web_table = &search_web_table;
1569
1570
1571 /**
1572 * GTK frontend browser window operation table
1573 */
1574 static struct gui_window_table window_table = {
1575 .create = gui_window_create,
1576 .destroy = gui_window_destroy,
1577 .invalidate = nsgtk_window_invalidate_area,
1578 .get_scroll = gui_window_get_scroll,
1579 .set_scroll = gui_window_set_scroll,
1580 .get_dimensions = gui_window_get_dimensions,
1581 .event = gui_window_event,
1582
1583 .set_icon = gui_window_set_icon,
1584 .set_title = gui_window_set_title,
1585 .set_status = gui_window_set_status,
1586 .set_pointer = gui_window_set_pointer,
1587 .place_caret = gui_window_place_caret,
1588 .create_form_select_menu = gui_window_create_form_select_menu,
1589 .file_gadget_open = gui_window_file_gadget_open,
1590 .set_url = gui_window_set_url,
1591
1592
1593 };
1594
1595 struct gui_window_table *nsgtk_window_table = &window_table;
1596
1597
1598 /* exported interface documented in window.h */
nsgtk_get_scaffold(struct gui_window * g)1599 struct nsgtk_scaffolding *nsgtk_get_scaffold(struct gui_window *g)
1600 {
1601 return g->scaffold;
1602 }
1603
1604
1605 /* exported interface documented in window.h */
nsgtk_get_browser_window(struct gui_window * g)1606 struct browser_window *nsgtk_get_browser_window(struct gui_window *g)
1607 {
1608 return g->bw;
1609 }
1610
1611
1612 /* exported interface documented in window.h */
nsgtk_window_get_layout(struct gui_window * g)1613 GtkLayout *nsgtk_window_get_layout(struct gui_window *g)
1614 {
1615 return g->layout;
1616 }
1617
1618
1619 /* exported interface documented in window.h */
1620 nserror
nsgtk_window_search_toggle(struct gui_window * gw)1621 nsgtk_window_search_toggle(struct gui_window *gw)
1622 {
1623 return nsgtk_search_toggle_visibility(gw->search);
1624 }
1625
1626
1627 /* exported interface documented in window.h */
1628 nserror
nsgtk_window_item_activate(struct gui_window * gw,nsgtk_toolbar_button itemid)1629 nsgtk_window_item_activate(struct gui_window *gw, nsgtk_toolbar_button itemid)
1630 {
1631 return nsgtk_toolbar_item_activate(gw->toolbar, itemid);
1632 }
1633
1634
1635 /* exported interface documented in window.h */
nsgtk_window_destroy_browser(struct gui_window * gw)1636 void nsgtk_window_destroy_browser(struct gui_window *gw)
1637 {
1638 /* remove tab */
1639 gtk_widget_destroy(gw->container);
1640 }
1641
1642
1643 /* exported interface documented in window.h */
nsgtk_window_update_all(void)1644 nserror nsgtk_window_update_all(void)
1645 {
1646 struct gui_window *gw;
1647 for (gw = window_list; gw != NULL; gw = gw->next) {
1648 nsgtk_tab_options_changed(nsgtk_scaffolding_notebook(gw->scaffold));
1649 nsgtk_toolbar_restyle(gw->toolbar);
1650 nsgtk_search_restyle(gw->search);
1651 browser_window_schedule_reformat(gw->bw);
1652 }
1653 return NSERROR_OK;
1654 }
1655
1656
1657 /* exported interface documented in window.h */
nsgtk_window_toolbar_show(struct nsgtk_scaffolding * gs,bool show)1658 nserror nsgtk_window_toolbar_show(struct nsgtk_scaffolding *gs, bool show)
1659 {
1660 struct gui_window *gw;
1661 for (gw = window_list; gw != NULL; gw = gw->next) {
1662 if (gw->scaffold == gs) {
1663 nsgtk_toolbar_show(gw->toolbar, show);
1664 }
1665 }
1666 return NSERROR_OK;
1667 }
1668
1669
1670 /* exported interface documented in window.h */
nsgtk_window_toolbar_update(void)1671 nserror nsgtk_window_toolbar_update(void)
1672 {
1673 struct gui_window *gw;
1674 for (gw = window_list; gw != NULL; gw = gw->next) {
1675 nsgtk_toolbar_update(gw->toolbar);
1676
1677 }
1678 return NSERROR_OK;
1679 }
1680
1681 /* exported interface documented in window.h */
nsgtk_window_position_page_info(struct gui_window * gw,struct nsgtk_pi_window * win)1682 nserror nsgtk_window_position_page_info(struct gui_window *gw,
1683 struct nsgtk_pi_window *win)
1684 {
1685 return nsgtk_toolbar_position_page_info(gw->toolbar, win);
1686 }
1687
1688 /* exported interface documented in window.h */
nsgtk_window_position_local_history(struct gui_window * gw)1689 nserror nsgtk_window_position_local_history(struct gui_window *gw)
1690 {
1691 return nsgtk_toolbar_position_local_history(gw->toolbar);
1692 }
1693