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