1 /*
2  * Copyright 2019 Vincent Sanders <vince@netsurf-browser.org>
3  *
4  * This file is part of NetSurf, http://www.netsurf-browser.org/
5  *
6  * NetSurf is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * NetSurf is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 /**
20  * \file
21  * implementation of toolbar to control browsing context
22  */
23 
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <errno.h>
28 #include <gtk/gtk.h>
29 
30 #include "utils/log.h"
31 #include "utils/messages.h"
32 #include "utils/nsoption.h"
33 #include "utils/file.h"
34 #include "utils/nsurl.h"
35 #include "utils/corestrings.h"
36 #include "desktop/browser_history.h"
37 #include "desktop/searchweb.h"
38 #include "desktop/search.h"
39 #include "desktop/save_complete.h"
40 #include "desktop/save_text.h"
41 #include "desktop/print.h"
42 #include "desktop/hotlist.h"
43 #include "netsurf/content.h"
44 #include "netsurf/browser_window.h"
45 #include "netsurf/keypress.h"
46 
47 #include "gtk/toolbar_items.h"
48 #include "gtk/completion.h"
49 #include "gtk/gui.h"
50 #include "gtk/warn.h"
51 #include "gtk/search.h"
52 #include "gtk/throbber.h"
53 #include "gtk/scaffolding.h"
54 #include "gtk/window.h"
55 #include "gtk/compat.h"
56 #include "gtk/resources.h"
57 #include "gtk/schedule.h"
58 #include "gtk/local_history.h"
59 #include "gtk/global_history.h"
60 #include "gtk/viewsource.h"
61 #include "gtk/download.h"
62 #include "gtk/viewdata.h"
63 #include "gtk/tabs.h"
64 #include "gtk/print.h"
65 #include "gtk/layout_pango.h"
66 #include "gtk/preferences.h"
67 #include "gtk/hotlist.h"
68 #include "gtk/cookies.h"
69 #include "gtk/about.h"
70 #include "gtk/gdk.h"
71 #include "gtk/bitmap.h"
72 #include "gtk/page_info.h"
73 #include "gtk/toolbar.h"
74 
75 /**
76  * button location indicating button is not to be shown
77  */
78 #define INACTIVE_LOCATION (-1)
79 
80 /**
81  * time (in ms) between throbber animation frame updates
82  */
83 #define THROBBER_FRAME_TIME (100)
84 
85 /**
86  * the minimum number of columns in the tool store
87  */
88 #define NSGTK_MIN_STORE_COLUMNS 4
89 
90 /**
91  * the 'standard' width of a button that makes sufficient of its label visible
92  */
93 #define NSGTK_BUTTON_WIDTH 120
94 
95 /**
96  * the 'standard' height of a button that fits as many toolbars as
97  *   possible into the store
98  */
99 #define NSGTK_BUTTON_HEIGHT 70
100 
101 /**
102  * the 'normal' width of the websearch bar
103  */
104 #define NSGTK_WEBSEARCH_WIDTH 150
105 
106 /**
107  * toolbar item context
108  */
109 struct nsgtk_toolbar_item {
110 
111 	/**
112 	 * GTK widget in the toolbar
113 	 */
114 	GtkToolItem *button;
115 
116 	/**
117 	 * location index in toolbar
118 	 */
119 	int location;
120 
121 	/**
122 	 * if the item is currently sensitive in the toolbar
123 	 */
124 	bool sensitivity;
125 
126 	/**
127 	 * textural name used in serialising items
128 	 */
129 	const char *name;
130 
131 	/**
132 	 * button clicked on toolbar handler
133 	 */
134 	gboolean (*clicked)(GtkWidget *widget, gpointer data);
135 
136 	/**
137 	 * handler when dragging from customisation toolbox to toolbar
138 	 */
139 	void *dataplus;
140 
141 	/**
142 	 * handler when dragging from toolbar to customisation toolbox
143 	 */
144 	void *dataminus;
145 };
146 
147 /**
148  * Location focus state machine
149  *
150  * 1. If we don't care, we're in LFS_IDLE
151  * 2. When we create a new toolbar, we can put it into
152  *    LFS_WANT which means that we want the url bar to focus
153  * 3. When we start throbbing if we're in LFS_WANT we move to LFS_THROB
154  * 4. When we stop throbbing, if we're in LFS_THROB we move to LFS_LAST
155  *
156  * While not in LFS_IDLE, if the url bar is updated and we previously had it
157  * fully selected then we reselect it all.  If we're in LFS_LAST we move to
158  * LFS_IDLE at that point.
159  */
160 typedef enum {
161 	LFS_IDLE, /**< Nothing to do */
162 	LFS_WANT, /**< Want focus, will apply */
163 	LFS_THROB, /**< Want focus, we have started throbbing */
164 	LFS_LAST, /**< Last chance for a focus update */
165 } nsgtk_toolbar_location_focus_state;
166 
167 /**
168  * control toolbar context
169  */
170 struct nsgtk_toolbar {
171 	/** gtk toolbar widget */
172 	GtkToolbar *widget;
173 
174 	/* toolbar size allocation context */
175 	int offset;
176 	int toolbarmem;
177 	int toolbarbase;
178 	int historybase;
179 
180 	/**
181 	 * Toolbar item contexts
182 	 */
183 	struct nsgtk_toolbar_item items[PLACEHOLDER_BUTTON];
184 
185 	/**
186 	 * Current frame of throbber animation
187 	 */
188 	int throb_frame;
189 
190 	/**
191 	 * Web search widget
192 	 */
193 	GtkWidget *webSearchEntry;
194 
195 	/**
196 	 * callback to obtain a browser window for navigation
197 	 */
198 	struct browser_window *(*get_bw)(void *ctx);
199 
200 	/**
201 	 * context passed to get_bw function
202 	 */
203 	void *get_ctx;
204 
205 	/**
206 	 * Location focus state machine, current state
207 	 */
208 	nsgtk_toolbar_location_focus_state loc_focus;
209 };
210 
211 
212 /**
213  * toolbar cusomisation context
214  */
215 struct nsgtk_toolbar_customisation {
216 	/**
217 	 * first entry is a toolbar widget so a customisation widget
218 	 *   can be cast to toolbar and back.
219 	 */
220 	struct nsgtk_toolbar toolbar;
221 
222 	/**
223 	 * The top level container (tabBox)
224 	 */
225 	GtkWidget *container;
226 
227 	/**
228 	 * The vertical box into which the available tools are shown
229 	 */
230 	GtkBox *toolbox;
231 
232 	/**
233 	 * widget handles for items in the customisation toolbox area
234 	 */
235 	GtkToolItem *items[PLACEHOLDER_BUTTON];
236 
237 	/**
238 	 * which item is being dragged
239 	 */
240 	int dragitem; /* currentbutton */
241 	/**
242 	 * true if item being dragged onto toolbar, false if from toolbar
243 	 */
244 	bool dragfrom; /*fromstore */
245 
246 };
247 
248 
249 /* forward declaration */
250 static nserror toolbar_item_create(nsgtk_toolbar_button id,
251 				   struct nsgtk_toolbar_item *item_out);
252 
253 
254 /**
255  * returns a string without its underscores
256  *
257  * \param s The string to change.
258  * \param replacespace true to insert a space where there was an underscore
259  * \return The altered string
260  */
remove_underscores(const char * s,bool replacespace)261 static char *remove_underscores(const char *s, bool replacespace)
262 {
263 	size_t i, ii, len;
264 	char *ret;
265 	len = strlen(s);
266 	ret = malloc(len + 1);
267 	if (ret == NULL) {
268 		return NULL;
269 	}
270 	for (i = 0, ii = 0; i < len; i++) {
271 		if (s[i] != '_') {
272 			ret[ii++] = s[i];
273 		} else if (replacespace) {
274 			ret[ii++] = ' ';
275 		}
276 	}
277 	ret[ii] = '\0';
278 	return ret;
279 }
280 
281 
282 /**
283  * create throbber toolbar item widget
284  *
285  * create a gtk entry widget with a completion attached
286  */
287 static GtkToolItem *
make_toolbar_item_throbber(bool sensitivity,bool edit)288 make_toolbar_item_throbber(bool sensitivity, bool edit)
289 {
290 	nserror res;
291 	GtkToolItem *item;
292 	GdkPixbuf *pixbuf;
293 	GtkWidget *image;
294 
295 	res = nsgtk_throbber_get_frame(0, &pixbuf);
296 	if (res != NSERROR_OK) {
297 		return NULL;
298 	}
299 
300 	if (edit) {
301 		const char *msg;
302 		msg = messages_get("ToolThrob");
303 		item = gtk_tool_button_new(
304 				GTK_WIDGET(gtk_image_new_from_pixbuf(pixbuf)),
305 				msg);
306 	} else {
307 		item = gtk_tool_item_new();
308 
309 		image = gtk_image_new_from_pixbuf(pixbuf);
310 		if (image != NULL) {
311 			nsgtk_widget_set_alignment(image,
312 						   GTK_ALIGN_CENTER,
313 						   GTK_ALIGN_CENTER);
314 			nsgtk_widget_set_margins(image, 3, 0);
315 
316 			gtk_container_add(GTK_CONTAINER(item), image);
317 		}
318 	}
319 	gtk_widget_set_sensitive(GTK_WIDGET(item), sensitivity);
320 
321 	return item;
322 }
323 
324 
325 /**
326  * create url bar toolbar item widget
327  *
328  * create a gtk entry widget with a completion attached
329  *
330  * \param sensitivity if the entry should be created sensitive to input
331  * \param edit if the entry should be editable
332  */
333 static GtkToolItem *
make_toolbar_item_url_bar(bool sensitivity,bool edit)334 make_toolbar_item_url_bar(bool sensitivity, bool edit)
335 {
336 	GtkToolItem *item;
337 	GtkWidget *entry;
338 	GtkEntryCompletion *completion;
339 
340 	entry = nsgtk_entry_new();
341 
342 	if (entry == NULL) {
343 		return NULL;
344 	}
345 	nsgtk_entry_set_icon_from_icon_name(entry,
346 					    GTK_ENTRY_ICON_PRIMARY,
347 					    "page-info-internal");
348 
349 	if (edit) {
350 		gtk_entry_set_width_chars(GTK_ENTRY(entry), 9);
351 
352 		item = gtk_tool_button_new(NULL, "URL");
353 		gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(item), entry);
354 	} else {
355 		completion = gtk_entry_completion_new();
356 		if (completion != NULL) {
357 			gtk_entry_set_completion(GTK_ENTRY(entry), completion);
358 		}
359 
360 		item = gtk_tool_item_new();
361 		if (item == NULL) {
362 			return NULL;
363 		}
364 
365 		gtk_container_add(GTK_CONTAINER(item), entry);
366 		gtk_tool_item_set_expand(item, TRUE);
367 
368 	}
369 	gtk_widget_set_sensitive(GTK_WIDGET(item), TRUE);
370 	gtk_widget_set_sensitive(GTK_WIDGET(entry), sensitivity);
371 
372 	return item;
373 }
374 
375 
376 /**
377  * create web search toolbar item widget
378  */
379 static GtkToolItem *
make_toolbar_item_websearch(bool sensitivity,bool edit)380 make_toolbar_item_websearch(bool sensitivity, bool edit)
381 {
382 	GtkToolItem *item;
383 	nserror res;
384 	GtkWidget *entry;
385 	struct bitmap *bitmap;
386 	GdkPixbuf *pixbuf = NULL;
387 
388 	res = search_web_get_provider_bitmap(&bitmap);
389 	if ((res == NSERROR_OK) && (bitmap != NULL)) {
390 		pixbuf = nsgdk_pixbuf_get_from_surface(bitmap->surface, 32, 32);
391 	}
392 
393 	entry = nsgtk_entry_new();
394 
395 	if (entry == NULL) {
396 		return NULL;
397 	}
398 
399 	if (pixbuf != NULL) {
400 		nsgtk_entry_set_icon_from_pixbuf(entry,
401 						 GTK_ENTRY_ICON_PRIMARY,
402 						 pixbuf);
403 		g_object_unref(pixbuf);
404 	} else {
405 		nsgtk_entry_set_icon_from_icon_name(entry,
406 						    GTK_ENTRY_ICON_PRIMARY,
407 						    NSGTK_STOCK_INFO);
408 	}
409 
410 	if (edit) {
411 		gtk_entry_set_width_chars(GTK_ENTRY(entry), 9);
412 
413 		item = gtk_tool_button_new(NULL, "Web Search");
414 		gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(item),
415 						entry);
416 	} else {
417 		gtk_widget_set_size_request(entry, NSGTK_WEBSEARCH_WIDTH, -1);
418 
419 		item = gtk_tool_item_new();
420 		if (item == NULL) {
421 			return NULL;
422 		}
423 
424 		gtk_container_add(GTK_CONTAINER(item), entry);
425 	}
426 	gtk_widget_set_sensitive(GTK_WIDGET(item), TRUE);
427 	gtk_widget_set_sensitive(GTK_WIDGET(entry), sensitivity);
428 
429 	return item;
430 }
431 
432 
433 /**
434  * create local history toolbar item widget
435  */
436 static GtkToolItem *
make_toolbar_item_history(bool sensitivity,bool edit)437 make_toolbar_item_history(bool sensitivity, bool edit)
438 {
439 	GtkToolItem *item;
440 	const char *msg = "H";
441 	char *label = NULL;
442 
443 	if (edit) {
444 		msg = messages_get("gtkLocalHistory");
445 	}
446 	label = remove_underscores(msg, false);
447 	item = gtk_tool_button_new(NULL, label);
448 	if (label != NULL) {
449 		free(label);
450 	}
451 	gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), "local-history");
452 
453 	/* set history widget minimum width */
454 	gtk_widget_set_size_request(GTK_WIDGET(item), 20, -1);
455 	gtk_widget_set_sensitive(GTK_WIDGET(item), sensitivity);
456 
457 	return item;
458 }
459 
460 
461 /**
462  * create generic button toolbar item widget
463  */
464 static GtkToolItem *
make_toolbar_item_button(const char * labelmsg,const char * iconname,bool sensitivity,bool edit)465 make_toolbar_item_button(const char *labelmsg,
466 			 const char *iconname,
467 			 bool sensitivity,
468 			 bool edit)
469 {
470 	GtkToolItem *item;
471 	char *label = NULL;
472 
473 	label = remove_underscores(messages_get(labelmsg), false);
474 
475 	item = gtk_tool_button_new(NULL, label);
476 	if (label != NULL) {
477 		free(label);
478 	}
479 
480 	if (item != NULL) {
481 		gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), iconname);
482 
483 		gtk_widget_set_sensitive(GTK_WIDGET(item), sensitivity);
484 		if (edit) {
485 			nsgtk_widget_set_margins(GTK_WIDGET(item), 0, 0);
486 		}
487 	}
488 
489 	return item;
490 }
491 
492 
493 /**
494  * widget factory for creation of toolbar item widgets
495  *
496  * \param i the id of the widget
497  * \param theme the theme to make the widgets from
498  * \return gtk widget
499  */
500 static GtkToolItem *
make_toolbar_item(nsgtk_toolbar_button itemid,bool sensitivity)501 make_toolbar_item(nsgtk_toolbar_button itemid, bool sensitivity)
502 {
503 	GtkToolItem *toolitem = NULL;
504 
505 	switch(itemid) {
506 #define TOOLBAR_ITEM_y(identifier, label, iconame)
507 #define TOOLBAR_ITEM_n(identifier, label, iconame)
508 #define TOOLBAR_ITEM_t(identifier, label, iconame)		\
509 	case identifier:					\
510 		toolitem = make_toolbar_item_button(#label, iconame, sensitivity, false); \
511 		break;
512 #define TOOLBAR_ITEM_b(identifier, label, iconame)		\
513 	case identifier:					\
514 		toolitem = make_toolbar_item_button(#label, iconame, sensitivity, false); \
515 		break;
516 #define TOOLBAR_ITEM(identifier, name, snstvty, clicked, activate, label, iconame) \
517 		TOOLBAR_ITEM_ ## clicked(identifier, label, iconame)
518 
519 #include "gtk/toolbar_items.h"
520 
521 #undef TOOLBAR_ITEM_t
522 #undef TOOLBAR_ITEM_b
523 #undef TOOLBAR_ITEM_n
524 #undef TOOLBAR_ITEM_y
525 #undef TOOLBAR_ITEM
526 
527 	case HISTORY_BUTTON:
528 		toolitem = make_toolbar_item_history(sensitivity, false);
529 		break;
530 
531 	case URL_BAR_ITEM:
532 		toolitem = make_toolbar_item_url_bar(sensitivity, false);
533 		break;
534 
535 	case THROBBER_ITEM:
536 		toolitem = make_toolbar_item_throbber(sensitivity, false);
537 		break;
538 
539 	case WEBSEARCH_ITEM:
540 		toolitem = make_toolbar_item_websearch(sensitivity, false);
541 		break;
542 
543 	default:
544 		break;
545 
546 	}
547 	return toolitem;
548 }
549 
550 
551 /**
552  * widget factory for creation of toolbar item widgets for the toolbox
553  *
554  * \param itemid the id of the widget
555  * \return gtk tool item widget
556  */
557 static GtkToolItem *
make_toolbox_item(nsgtk_toolbar_button itemid,bool bar)558 make_toolbox_item(nsgtk_toolbar_button itemid, bool bar)
559 {
560 	GtkToolItem *toolitem = NULL;
561 
562 	switch(itemid) {
563 #define TOOLBAR_ITEM_y(identifier, label, iconame)
564 #define TOOLBAR_ITEM_n(identifier, label, iconame)
565 #define TOOLBAR_ITEM_t(identifier, label, iconame)		\
566 	case identifier:					\
567 		if (bar) {						\
568 			toolitem = make_toolbar_item_button(#label, iconame, true, true); \
569 		}							\
570 		break;
571 #define TOOLBAR_ITEM_b(identifier, label, iconame)		\
572 	case identifier:					\
573 		toolitem = make_toolbar_item_button(#label, iconame, true, true); \
574 		break;
575 #define TOOLBAR_ITEM(identifier, name, snstvty, clicked, activate, label, iconame) \
576 		TOOLBAR_ITEM_ ## clicked(identifier, label, iconame)
577 
578 #include "gtk/toolbar_items.h"
579 
580 #undef TOOLBAR_ITEM_t
581 #undef TOOLBAR_ITEM_b
582 #undef TOOLBAR_ITEM_n
583 #undef TOOLBAR_ITEM_y
584 #undef TOOLBAR_ITEM
585 
586 	case HISTORY_BUTTON:
587 		toolitem = make_toolbar_item_history(true, true);
588 		break;
589 
590 	case URL_BAR_ITEM:
591 		toolitem = make_toolbar_item_url_bar(false, true);
592 		break;
593 
594 	case THROBBER_ITEM:
595 		toolitem = make_toolbar_item_throbber(true, true);
596 		break;
597 
598 	case WEBSEARCH_ITEM:
599 		toolitem = make_toolbar_item_websearch(false, true);
600 		break;
601 
602 	default:
603 		break;
604 
605 	}
606 	return toolitem;
607 }
608 
609 
610 /**
611  * target entry for drag source
612  */
613 static GtkTargetEntry target_entry = {
614 	 (char *)"nsgtk_button_data",
615 	 GTK_TARGET_SAME_APP,
616 	 0
617 };
618 
619 
620 /**
621  * find the toolbar item with a given location.
622  *
623  * \param tb the toolbar instance
624  * \param locaction the location to search for
625  * \return the item id for a location
626  */
627 static nsgtk_toolbar_button
itemid_from_location(struct nsgtk_toolbar * tb,int location)628 itemid_from_location(struct nsgtk_toolbar *tb, int location)
629 {
630 	int iidx;
631 	for (iidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) {
632 		if (tb->items[iidx].location == location) {
633 			break;
634 		}
635 	}
636 	return iidx;
637 }
638 
639 
640 /**
641  * save toolbar settings to file
642  */
643 static nserror
nsgtk_toolbar_customisation_save(struct nsgtk_toolbar * tb)644 nsgtk_toolbar_customisation_save(struct nsgtk_toolbar *tb)
645 {
646 	int iidx; /* item index */
647 	char *order; /* item ordering */
648 	char *start; /* start of next item name to be output */
649 	int orderlen = 0; /* length of item ordering */
650 	nsgtk_toolbar_button itemid;
651 	int location;
652 	char *choices = NULL;
653 
654 	for (iidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) {
655 		if (tb->items[iidx].location != INACTIVE_LOCATION) {
656 			orderlen += strlen(tb->items[iidx].name);
657 			orderlen++; /* allow for separator */
658 		}
659 	}
660 
661 	/* ensure there are some items to store */
662 	if (orderlen == 0) {
663 		return NSERROR_INVALID;
664 	}
665 
666 	order = malloc(orderlen);
667 	if (order == NULL) {
668 		return NSERROR_NOMEM;
669 	}
670 
671 	start = order;
672 
673 	for (location = BACK_BUTTON;
674 	     location < PLACEHOLDER_BUTTON;
675 	     location++) {
676 		itemid = itemid_from_location(tb, location);
677 		if (itemid == PLACEHOLDER_BUTTON) {
678 			/* no more filled locations */
679 			break;
680 		}
681 		start += snprintf(start,
682 				orderlen - (start - order),
683 				"%s/",
684 				tb->items[itemid].name);
685 
686 		if ((start - order) >= orderlen) {
687 			break;
688 		}
689 	}
690 
691 	order[orderlen - 1] = 0;
692 
693 	nsoption_set_charp(toolbar_items, order);
694 
695 	/* ensure choices are saved */
696 	netsurf_mkpath(&choices, NULL, 2, nsgtk_config_home, "Choices");
697 	if (choices != NULL) {
698 		nsoption_write(choices, NULL, NULL);
699 		free(choices);
700 	}
701 
702 	return NSERROR_OK;
703 }
704 
705 
706 /**
707  * connect signals to a toolbar item in a customisation toolbar
708  *
709  * \param tb The toolbar
710  * \param itemid The item id within to toolbar to connect
711  * \param NSERROR_OK on success
712  */
713 static nserror
toolbar_item_connect_signals(struct nsgtk_toolbar * tb,int itemid)714 toolbar_item_connect_signals(struct nsgtk_toolbar *tb, int itemid)
715 {
716 	/* set toolbar items to be a drag source */
717 	gtk_tool_item_set_use_drag_window(tb->items[itemid].button, TRUE);
718 	gtk_drag_source_set(GTK_WIDGET(tb->items[itemid].button),
719 			    GDK_BUTTON1_MASK,
720 			    &target_entry,
721 			    1,
722 			    GDK_ACTION_COPY);
723 	g_signal_connect(tb->items[itemid].button,
724 			 "drag-data-get",
725 			 G_CALLBACK(tb->items[itemid].dataminus),
726 			 tb);
727 	return NSERROR_OK;
728 }
729 
730 
731 /**
732  * customisation container handler for drag drop signal
733  *
734  * called when a widget is dropped onto the store window
735  */
736 static gboolean
customisation_container_drag_drop_cb(GtkWidget * widget,GdkDragContext * gdc,gint x,gint y,guint time,gpointer data)737 customisation_container_drag_drop_cb(GtkWidget *widget,
738 				     GdkDragContext *gdc,
739 				     gint x, gint y,
740 				     guint time,
741 				     gpointer data)
742 {
743 	struct nsgtk_toolbar_customisation *tbc;
744 	tbc = (struct nsgtk_toolbar_customisation *)data;
745 	int location;
746 	int itemid;
747 
748 	if ((tbc->dragfrom) || (tbc->dragitem == -1)) {
749 		tbc->dragitem = -1;
750 		return FALSE;
751 	}
752 
753 	if (tbc->toolbar.items[tbc->dragitem].location == INACTIVE_LOCATION) {
754 		tbc->dragitem = -1;
755 		gtk_drag_finish(gdc, TRUE, TRUE, time);
756 		return FALSE;
757 
758 	}
759 
760 	/* update the locations for all the subsequent toolbar items */
761 	for (location = tbc->toolbar.items[tbc->dragitem].location;
762 	     location < PLACEHOLDER_BUTTON;
763 	     location++) {
764 		itemid = itemid_from_location(&tbc->toolbar, location);
765 		if (itemid == PLACEHOLDER_BUTTON) {
766 			break;
767 		}
768 		tbc->toolbar.items[itemid].location--;
769 	}
770 
771 	/* remove existing item */
772 	tbc->toolbar.items[tbc->dragitem].location = -1;
773 	gtk_container_remove(GTK_CONTAINER(tbc->toolbar.widget),
774 			     GTK_WIDGET(tbc->toolbar.items[tbc->dragitem].button));
775 
776 	tbc->dragitem = -1;
777 	gtk_drag_finish(gdc, TRUE, TRUE, time);
778 	return FALSE;
779 }
780 
781 
782 /**
783  * customisation container handler for drag motion signal
784  *
785  * called when hovering above the store
786  */
787 static gboolean
customisation_container_drag_motion_cb(GtkWidget * widget,GdkDragContext * gdc,gint x,gint y,guint time,gpointer data)788 customisation_container_drag_motion_cb(GtkWidget *widget,
789 				       GdkDragContext *gdc,
790 				       gint x, gint y,
791 				       guint time,
792 				       gpointer data)
793 {
794 	return FALSE;
795 }
796 
797 
798 /**
799  * customisation toolbar handler for drag drop signal
800  *
801  * called when a widget is dropped onto the toolbar
802  */
803 static gboolean
customisation_toolbar_drag_drop_cb(GtkWidget * widget,GdkDragContext * gdc,gint x,gint y,guint time,gpointer data)804 customisation_toolbar_drag_drop_cb(GtkWidget *widget,
805 				   GdkDragContext *gdc,
806 				   gint x,
807 				   gint y,
808 				   guint time,
809 				   gpointer data)
810 {
811 	struct nsgtk_toolbar_customisation *tbc;
812 	tbc = (struct nsgtk_toolbar_customisation *)data;
813 	gint position; /* drop position in toolbar */
814 	int location;
815 	int itemid;
816 	struct nsgtk_toolbar_item *dragitem; /* toolbar item being dragged */
817 
818 	position = gtk_toolbar_get_drop_index(tbc->toolbar.widget, x, y);
819 	if (tbc->dragitem == -1) {
820 		return TRUE;
821 	}
822 
823 	/* pure conveiance variable */
824 	dragitem = &tbc->toolbar.items[tbc->dragitem];
825 
826 	/* deal with replacing existing item in toolbar */
827 	if (dragitem->location != INACTIVE_LOCATION) {
828 		if (dragitem->location < position) {
829 			position--;
830 		}
831 
832 		/* update the locations for all the subsequent toolbar items */
833 		for (location = dragitem->location;
834 		     location < PLACEHOLDER_BUTTON;
835 		     location++) {
836 			itemid = itemid_from_location(&tbc->toolbar, location);
837 			if (itemid == PLACEHOLDER_BUTTON) {
838 				break;
839 			}
840 			tbc->toolbar.items[itemid].location--;
841 		}
842 
843 		/* remove existing item */
844 		dragitem->location = INACTIVE_LOCATION;
845 		gtk_container_remove(GTK_CONTAINER(tbc->toolbar.widget),
846 				     GTK_WIDGET(dragitem->button));
847 	}
848 
849 
850 	dragitem->button = make_toolbox_item(tbc->dragitem, true);
851 
852 	if (dragitem->button == NULL) {
853 		nsgtk_warning("NoMemory", 0);
854 		return TRUE;
855 	}
856 
857 	/* update locations */
858 	for (location = PLACEHOLDER_BUTTON; location >= position; location--) {
859 		itemid = itemid_from_location(&tbc->toolbar, location);
860 		if (itemid != PLACEHOLDER_BUTTON) {
861 			tbc->toolbar.items[itemid].location++;
862 		}
863 	}
864 	dragitem->location = position;
865 
866 	gtk_toolbar_insert(tbc->toolbar.widget,
867 			   dragitem->button,
868 			   dragitem->location);
869 
870 	toolbar_item_connect_signals(&tbc->toolbar, tbc->dragitem);
871 	gtk_widget_show_all(GTK_WIDGET(dragitem->button));
872 	tbc->dragitem = -1;
873 	return TRUE;
874 }
875 
876 
877 /**
878  * customisation toolbar handler for drag data received signal
879  *
880  * connected to toolbutton drop; perhaps one day it'll work properly
881  * so it may replace the global current_button
882  */
883 static gboolean
customisation_toolbar_drag_data_received_cb(GtkWidget * widget,GdkDragContext * gdc,gint x,gint y,GtkSelectionData * selection,guint info,guint time,gpointer data)884 customisation_toolbar_drag_data_received_cb(GtkWidget *widget,
885 			    GdkDragContext *gdc,
886 			    gint x,
887 			    gint y,
888 			    GtkSelectionData *selection,
889 			    guint info,
890 			    guint time,
891 			    gpointer data)
892 {
893 	return FALSE;
894 }
895 
896 
897 /**
898  * customisation toolbar handler for drag motion signal
899  *
900  * called when hovering an item above the toolbar
901  */
902 static gboolean
customisation_toolbar_drag_motion_cb(GtkWidget * widget,GdkDragContext * gdc,gint x,gint y,guint time,gpointer data)903 customisation_toolbar_drag_motion_cb(GtkWidget *widget,
904 				     GdkDragContext *gdc,
905 				     gint x,
906 				     gint y,
907 				     guint time,
908 				     gpointer data)
909 {
910 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
911 	GtkToolItem *item;
912 	gint position; /* position in toolbar */
913 
914 	item = gtk_tool_button_new(NULL, NULL);
915 	position = gtk_toolbar_get_drop_index(tb->widget, x, y);
916 
917 	gtk_toolbar_set_drop_highlight_item(tb->widget, item, position);
918 
919 	return FALSE; /* drag not in drop zone */
920 }
921 
922 
923 /**
924  * customisation toolbar handler for drag leave signal
925  *
926  * called when hovering stops
927  */
928 static void
customisation_toolbar_drag_leave_cb(GtkWidget * widget,GdkDragContext * gdc,guint time,gpointer data)929 customisation_toolbar_drag_leave_cb(GtkWidget *widget,
930 				    GdkDragContext *gdc,
931 				    guint time,
932 				    gpointer data)
933 {
934 	gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(widget), NULL, 0);
935 }
936 
937 
938 /**
939  * create a new browser window
940  *
941  * creates a browser window with default url depending on user choices.
942  *
943  * \param bw The browser window to pass for existing window/
944  * \param intab true if the new window should be in a tab else false
945  *                for new window.
946  * \return NSERROR_OK on success else error code.
947  */
948 static nserror
nsgtk_browser_window_create(struct browser_window * bw,bool intab)949 nsgtk_browser_window_create(struct browser_window *bw, bool intab)
950 {
951 	nserror res = NSERROR_OK;
952 	nsurl *url = NULL;
953 	int flags = BW_CREATE_HISTORY | BW_CREATE_FOREGROUND | BW_CREATE_FOCUS_LOCATION;
954 
955 	if (intab) {
956 		flags |= BW_CREATE_TAB;
957 	}
958 
959 	if (!nsoption_bool(new_blank)) {
960 		const char *addr;
961 		if (nsoption_charp(homepage_url) != NULL) {
962 			addr = nsoption_charp(homepage_url);
963 		} else {
964 			addr = NETSURF_HOMEPAGE;
965 		}
966 		res = nsurl_create(addr, &url);
967 	}
968 
969 	if (res == NSERROR_OK) {
970 		res = browser_window_create(flags, url, NULL, bw, NULL);
971 	}
972 
973 	if (url != NULL) {
974 		nsurl_unref(url);
975 	}
976 
977 	return res;
978 }
979 
980 
981 /**
982  * Apply the user toolbar button settings from configuration
983  *
984  * GTK specific user option string is a set of fields arranged as
985  * [itemreference];[itemlocation]|[itemreference];[itemlocation]| etc
986  *
987  * \param tb The toolbar to apply customisation to
988  * \param NSERROR_OK on success else error code.
989  */
990 static nserror
apply_user_button_customisation(struct nsgtk_toolbar * tb)991 apply_user_button_customisation(struct nsgtk_toolbar *tb)
992 {
993 	const char *tbitems; /* item order user config */
994 	const char *start;
995 	const char *end;
996 	int iidx; /* item index */
997 	int location = 0; /* location index */
998 
999 	/* set all button locations to inactive */
1000 	for (iidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) {
1001 		tb->items[iidx].location = INACTIVE_LOCATION;
1002 	}
1003 
1004 	tbitems = nsoption_charp(toolbar_items);
1005 	if (tbitems == NULL) {
1006 		tbitems = "";
1007 	}
1008 
1009 	end = tbitems;
1010 	while (*end != 0) {
1011 		start = end;
1012 		while ((*end != 0) && (*end !='/')) {
1013 			end++;
1014 		}
1015 
1016 		for (iidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) {
1017 			if (((ssize_t)strlen(tb->items[iidx].name) == (end - start)) &&
1018 			    (strncmp(tb->items[iidx].name, start, end - start) == 0)) {
1019 				tb->items[iidx].location = location++;
1020 				break;
1021 			}
1022 		}
1023 
1024 		if (*end == '/') {
1025 			end++;
1026 		}
1027 	}
1028 
1029 	if (location == 0) {
1030 		/* completely failed to create any buttons so use defaults */
1031 		tb->items[BACK_BUTTON].location = location++;
1032 		tb->items[HISTORY_BUTTON].location = location++;
1033 		tb->items[FORWARD_BUTTON].location = location++;
1034 		tb->items[RELOADSTOP_BUTTON].location = location++;
1035 		tb->items[URL_BAR_ITEM].location = location++;
1036 		tb->items[WEBSEARCH_ITEM].location = location++;
1037 		tb->items[OPENMENU_BUTTON].location = location++;
1038 		tb->items[THROBBER_ITEM].location = location++;
1039 	}
1040 
1041 
1042 	return NSERROR_OK;
1043 }
1044 
1045 
1046 /**
1047  * callback function to remove a widget from a container
1048  */
container_remove_widget(GtkWidget * widget,gpointer data)1049 static void container_remove_widget(GtkWidget *widget, gpointer data)
1050 {
1051 	GtkContainer *container = GTK_CONTAINER(data);
1052 	gtk_container_remove(container, widget);
1053 }
1054 
1055 
1056 /**
1057  * populates a toolbar with widgets in correct order
1058  *
1059  * \param tb toolbar
1060  * \return NSERROR_OK on success else error code.
1061  */
populate_gtk_toolbar_widget(struct nsgtk_toolbar * tb)1062 static nserror populate_gtk_toolbar_widget(struct nsgtk_toolbar *tb)
1063 {
1064 	int location; /* location index */
1065 	int itemid;
1066 
1067 	/* clear the toolbar container of all widgets */
1068 	gtk_container_foreach(GTK_CONTAINER(tb->widget),
1069 			      container_remove_widget,
1070 			      tb->widget);
1071 
1072 	/* add widgets to toolbar */
1073 	for (location = 0; location < PLACEHOLDER_BUTTON; location++) {
1074 		itemid = itemid_from_location(tb, location);
1075 		if (itemid == PLACEHOLDER_BUTTON) {
1076 			break;
1077 		}
1078 		tb->items[itemid].button =
1079 			make_toolbar_item(itemid,
1080 					  tb->items[itemid].sensitivity);
1081 
1082 		gtk_toolbar_insert(tb->widget,
1083 				   tb->items[itemid].button,
1084 				   location);
1085 	}
1086 
1087 	gtk_widget_show_all(GTK_WIDGET(tb->widget));
1088 
1089 	return NSERROR_OK;
1090 }
1091 
1092 
1093 /**
1094  * populates the customization toolbar with widgets in correct order
1095  *
1096  * \param tb toolbar
1097  * \return NSERROR_OK on success else error code.
1098  */
customisation_toolbar_populate(struct nsgtk_toolbar * tb)1099 static nserror customisation_toolbar_populate(struct nsgtk_toolbar *tb)
1100 {
1101 	int location; /* location index */
1102 	int itemid;
1103 
1104 	/* clear the toolbar container of all widgets */
1105 	gtk_container_foreach(GTK_CONTAINER(tb->widget),
1106 			      container_remove_widget,
1107 			      tb->widget);
1108 
1109 	/* add widgets to toolbar */
1110 	for (location = 0; location < PLACEHOLDER_BUTTON; location++) {
1111 		itemid = itemid_from_location(tb, location);
1112 		if (itemid == PLACEHOLDER_BUTTON) {
1113 			break;
1114 		}
1115 		tb->items[itemid].button = make_toolbox_item(itemid, true);
1116 
1117 		gtk_toolbar_insert(tb->widget,
1118 				   tb->items[itemid].button,
1119 				   location);
1120 	}
1121 
1122 	gtk_widget_show_all(GTK_WIDGET(tb->widget));
1123 
1124 	return NSERROR_OK;
1125 }
1126 
1127 
1128 /**
1129  * find the toolbar item with a given gtk widget.
1130  *
1131  * \param tb the toolbar instance
1132  * \param toolitem the tool item widget to search for
1133  * \return the item id matching the widget
1134  */
1135 static nsgtk_toolbar_button
itemid_from_gtktoolitem(struct nsgtk_toolbar * tb,GtkToolItem * toolitem)1136 itemid_from_gtktoolitem(struct nsgtk_toolbar *tb, GtkToolItem *toolitem)
1137 {
1138 	int iidx;
1139 	for (iidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) {
1140 		if ((tb->items[iidx].location != INACTIVE_LOCATION) &&
1141 		    (tb->items[iidx].button == toolitem)) {
1142 			break;
1143 		}
1144 	}
1145 	return iidx;
1146 }
1147 
1148 
1149 /**
1150  * set a toolbar items sensitivity
1151  *
1152  * note this does not set menu items sensitivity
1153  */
1154 static nserror
set_item_sensitivity(struct nsgtk_toolbar_item * item,bool sensitivity)1155 set_item_sensitivity(struct nsgtk_toolbar_item *item, bool sensitivity)
1156 {
1157 	if (item->sensitivity != sensitivity) {
1158 		/* item requires sensitivity changing */
1159 		item->sensitivity = sensitivity;
1160 
1161 		if ((item->location != -1) && (item->button != NULL)) {
1162 			gtk_widget_set_sensitive(GTK_WIDGET(item->button),
1163 						 item->sensitivity);
1164 		}
1165 	}
1166 
1167 	return NSERROR_OK;
1168 }
1169 
1170 
1171 /**
1172  * set an item to its alternative action
1173  *
1174  * this is currently only used for the stop/reload button where we
1175  *   also reuse the item sensitivity for the state indicator.
1176  *
1177  * \param tb the toolbar instance
1178  */
set_item_action(struct nsgtk_toolbar * tb,int itemid,bool alt)1179 static nserror set_item_action(struct nsgtk_toolbar *tb, int itemid, bool alt)
1180 {
1181 	const char *iconname;
1182 	char *label = NULL;
1183 
1184 	if (itemid != RELOADSTOP_BUTTON) {
1185 		return NSERROR_INVALID;
1186 	}
1187 	if (tb->items[itemid].location == -1) {
1188 		return NSERROR_OK;
1189 	}
1190 	tb->items[itemid].sensitivity = alt;
1191 
1192 	if (tb->items[itemid].button == NULL) {
1193 		return NSERROR_INVALID;
1194 	}
1195 
1196 	if (tb->items[itemid].sensitivity) {
1197 		iconname = NSGTK_STOCK_REFRESH;
1198 		label = remove_underscores(messages_get("Reload"), false);
1199 
1200 	} else {
1201 		iconname = NSGTK_STOCK_STOP;
1202 		label = remove_underscores(messages_get("gtkStop"), false);
1203 
1204 	}
1205 	gtk_tool_button_set_label(GTK_TOOL_BUTTON(tb->items[itemid].button),
1206 				  label);
1207 
1208 	gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(tb->items[itemid].button),
1209 				      iconname);
1210 
1211 	gtk_widget_set_sensitive(GTK_WIDGET(tb->items[itemid].button), TRUE);
1212 
1213 	if (label != NULL) {
1214 		free(label);
1215 	}
1216 
1217 	return NSERROR_OK;
1218 }
1219 
1220 
1221 /**
1222  * cause the toolbar browsing context to navigate to a new url.
1223  *
1224  * \param tb the toolbar context.
1225  * \param urltxt The url string.
1226  * \return NSERROR_OK on success else appropriate error code.
1227  */
1228 static nserror
toolbar_navigate_to_url(struct nsgtk_toolbar * tb,const char * urltxt)1229 toolbar_navigate_to_url(struct nsgtk_toolbar *tb, const char *urltxt)
1230 {
1231 	struct browser_window *bw;
1232 	nsurl *url;
1233 	nserror res;
1234 
1235 	res = nsurl_create(urltxt, &url);
1236 	if (res != NSERROR_OK) {
1237 		return res;
1238 	}
1239 
1240 	bw = tb->get_bw(tb->get_ctx);
1241 
1242 	res = browser_window_navigate(bw,
1243 				      url,
1244 				      NULL,
1245 				      BW_NAVIGATE_HISTORY,
1246 				      NULL,
1247 				      NULL,
1248 				      NULL);
1249 	nsurl_unref(url);
1250 
1251 	return res;
1252 }
1253 
1254 
1255 /**
1256  * run a gtk file chooser as a save dialog to obtain a path
1257  */
1258 static nserror
nsgtk_saveas_dialog(struct browser_window * bw,const char * title,GtkWindow * parent,bool folder,gchar ** path_out)1259 nsgtk_saveas_dialog(struct browser_window *bw,
1260 		    const char *title,
1261 		    GtkWindow *parent,
1262 		    bool folder,
1263 		    gchar **path_out)
1264 {
1265 	nserror res;
1266 	GtkWidget *fc; /* file chooser widget */
1267 	GtkFileChooserAction action;
1268 	char *path; /* proposed path */
1269 
1270 	if (!browser_window_has_content(bw)) {
1271 		/* cannot save a page with no content */
1272 		return NSERROR_INVALID;
1273 	}
1274 
1275 	if (folder) {
1276 		action = GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER;
1277 	} else {
1278 		action = GTK_FILE_CHOOSER_ACTION_SAVE;
1279 	}
1280 
1281 	fc = gtk_file_chooser_dialog_new(title,
1282 					 parent,
1283 					 action,
1284 					 NSGTK_STOCK_CANCEL,
1285 					 GTK_RESPONSE_CANCEL,
1286 					 NSGTK_STOCK_SAVE,
1287 					 GTK_RESPONSE_ACCEPT,
1288 					 NULL);
1289 
1290 	/* set a default file name */
1291 	res = nsurl_nice(browser_window_access_url(bw), &path, false);
1292 	if (res != NSERROR_OK) {
1293 		path = strdup(messages_get("SaveText"));
1294 		if (path == NULL) {
1295 			gtk_widget_destroy(fc);
1296 			return NSERROR_NOMEM;
1297 		}
1298 	}
1299 
1300 	if ((!folder) || (access(path, F_OK) != 0)) {
1301 		gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(fc), path);
1302 	}
1303 	free(path);
1304 
1305 	/* confirm overwriting */
1306 	gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(fc), TRUE);
1307 
1308 	/* run the dialog to let user select path */
1309 	if (gtk_dialog_run(GTK_DIALOG(fc)) != GTK_RESPONSE_ACCEPT) {
1310 		gtk_widget_destroy(fc);
1311 		return NSERROR_NOT_FOUND;
1312 	}
1313 
1314 	*path_out = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fc));
1315 
1316 	gtk_widget_destroy(fc);
1317 
1318 	return NSERROR_OK;
1319 }
1320 
1321 
1322 /**
1323  * connect all signals to widgets in a customisation
1324  */
1325 static nserror
toolbar_customisation_connect_signals(struct nsgtk_toolbar * tb)1326 toolbar_customisation_connect_signals(struct nsgtk_toolbar *tb)
1327 {
1328 	int iidx;
1329 
1330 	for (iidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) {
1331 		/* skip inactive items in toolbar */
1332 		if (tb->items[iidx].location != INACTIVE_LOCATION) {
1333 			toolbar_item_connect_signals(tb, iidx);
1334 		}
1335 	}
1336 
1337 	/* add move button listeners */
1338 	g_signal_connect(tb->widget,
1339 			"drag-drop",
1340 			 G_CALLBACK(customisation_toolbar_drag_drop_cb),
1341 			 tb);
1342 	g_signal_connect(tb->widget,
1343 			"drag-data-received",
1344 			 G_CALLBACK(customisation_toolbar_drag_data_received_cb),
1345 			 tb);
1346 	g_signal_connect(tb->widget,
1347 			"drag-motion",
1348 			 G_CALLBACK(customisation_toolbar_drag_motion_cb),
1349 			 tb);
1350 	g_signal_connect(tb->widget,
1351 			"drag-leave",
1352 			 G_CALLBACK(customisation_toolbar_drag_leave_cb),
1353 			 tb);
1354 
1355 	/* set data types */
1356 	gtk_drag_dest_set(GTK_WIDGET(tb->widget),
1357 			  GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
1358 			  &target_entry,
1359 			  1,
1360 			  GDK_ACTION_COPY);
1361 
1362 	return NSERROR_OK;
1363 }
1364 
1365 
1366 static void
item_size_allocate_cb(GtkWidget * widget,GdkRectangle * alloc,gpointer user_data)1367 item_size_allocate_cb(GtkWidget *widget,
1368 		      GdkRectangle *alloc,
1369 		      gpointer user_data)
1370 {
1371 	if (alloc->width > NSGTK_BUTTON_WIDTH) {
1372 		alloc->width = NSGTK_BUTTON_WIDTH;
1373 	}
1374 	if (alloc->height > NSGTK_BUTTON_HEIGHT) {
1375 		alloc->height = NSGTK_BUTTON_HEIGHT;
1376 	}
1377 	gtk_widget_set_allocation(widget, alloc);
1378 }
1379 
1380 
1381 /**
1382  * add a row to a toolbar customisation toolbox
1383  *
1384  * \param tbc The toolbar customisation context
1385  * \param startitem The item index of the beginning of the row
1386  * \param enditem The item index of the beginning of the next row
1387  * \return NSERROR_OK on successs else error
1388  */
1389 static nserror
add_toolbox_row(struct nsgtk_toolbar_customisation * tbc,int startitem,int enditem)1390 add_toolbox_row(struct nsgtk_toolbar_customisation *tbc,
1391 		int startitem,
1392 		int enditem)
1393 {
1394 	GtkToolbar *rowbar;
1395 	int iidx;
1396 
1397 	rowbar = GTK_TOOLBAR(gtk_toolbar_new());
1398 	if (rowbar == NULL) {
1399 		return NSERROR_NOMEM;
1400 	}
1401 
1402 	gtk_toolbar_set_style(rowbar, GTK_TOOLBAR_BOTH);
1403 	gtk_toolbar_set_icon_size(rowbar, GTK_ICON_SIZE_LARGE_TOOLBAR);
1404 	gtk_box_pack_start(tbc->toolbox, GTK_WIDGET(rowbar), FALSE, FALSE, 0);
1405 
1406 	for (iidx = startitem; iidx < enditem; iidx++) {
1407 		if (tbc->items[iidx] == NULL) {
1408 			/* skip any widgets that failed to initialise */
1409 			continue;
1410 		}
1411 		gtk_widget_set_size_request(GTK_WIDGET(tbc->items[iidx]),
1412 					    NSGTK_BUTTON_WIDTH,
1413 					    NSGTK_BUTTON_HEIGHT);
1414 		gtk_tool_item_set_use_drag_window(tbc->items[iidx], TRUE);
1415 		gtk_drag_source_set(GTK_WIDGET(tbc->items[iidx]),
1416 				    GDK_BUTTON1_MASK,
1417 				    &target_entry,
1418 				    1,
1419 				    GDK_ACTION_COPY);
1420 		g_signal_connect(tbc->items[iidx],
1421 				 "drag-data-get",
1422 				 G_CALLBACK(tbc->toolbar.items[iidx].dataplus),
1423 				 &tbc->toolbar);
1424 		g_signal_connect(tbc->items[iidx],
1425 				 "size-allocate",
1426 				 G_CALLBACK(item_size_allocate_cb),
1427 				 NULL);
1428 		gtk_toolbar_insert(rowbar, tbc->items[iidx], -1);
1429 	}
1430 	return NSERROR_OK;
1431 }
1432 
1433 
1434 /**
1435  * creates widgets in customisation toolbox
1436  *
1437  * \param tbc The toolbar customisation context
1438  * \param width The width to layout the toolbox to
1439  * \return NSERROR_OK on success else error code.
1440  */
1441 static nserror
toolbar_customisation_create_toolbox(struct nsgtk_toolbar_customisation * tbc,int width)1442 toolbar_customisation_create_toolbox(struct nsgtk_toolbar_customisation *tbc,
1443 				     int width)
1444 {
1445 	int columns; /* number of items in a single row */
1446 	int curcol; /* current column in creation */
1447 	int iidx; /* item index */
1448 	int startidx; /* index of item at start of row */
1449 
1450 	/* ensure there are a minimum number of items per row */
1451 	columns = width / NSGTK_BUTTON_WIDTH;
1452 	if (columns < NSGTK_MIN_STORE_COLUMNS) {
1453 		columns = NSGTK_MIN_STORE_COLUMNS;
1454 	}
1455 
1456 	curcol = 0;
1457 	for (iidx = startidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) {
1458 		if (curcol >= columns) {
1459 			add_toolbox_row(tbc, startidx, iidx);
1460 			curcol = 0;
1461 			startidx = iidx;
1462 		}
1463 		tbc->items[iidx] = make_toolbox_item(iidx, false);
1464 		if (tbc->items[iidx] != NULL) {
1465 			curcol++;
1466 		}
1467 	}
1468 	if (curcol > 0) {
1469 		add_toolbox_row(tbc, startidx, iidx);
1470 	}
1471 
1472 	return NSERROR_OK;
1473 }
1474 
1475 
1476 /**
1477  * update toolbar in customisation to user settings
1478  */
1479 static nserror
customisation_toolbar_update(struct nsgtk_toolbar_customisation * tbc)1480 customisation_toolbar_update(struct nsgtk_toolbar_customisation *tbc)
1481 {
1482 	nserror res;
1483 
1484 	res = apply_user_button_customisation(&tbc->toolbar);
1485 	if (res != NSERROR_OK) {
1486 		return res;
1487 	}
1488 
1489 	/* populate toolbar widget */
1490 	res = customisation_toolbar_populate(&tbc->toolbar);
1491 	if (res != NSERROR_OK) {
1492 		return res;
1493 	}
1494 
1495 	/* ensure icon sizes and text labels on toolbar are set */
1496 	res = nsgtk_toolbar_restyle(&tbc->toolbar);
1497 	if (res != NSERROR_OK) {
1498 		return res;
1499 	}
1500 
1501 	/* attach handlers to toolbar widgets */
1502 	res = toolbar_customisation_connect_signals(&tbc->toolbar);
1503 	if (res != NSERROR_OK) {
1504 		return res;
1505 	}
1506 
1507 	return NSERROR_OK;
1508 }
1509 
1510 
1511 /**
1512  * customisation apply handler for clicked signal
1513  *
1514  * when 'save settings' button is clicked
1515  */
1516 static gboolean
customisation_apply_clicked_cb(GtkWidget * widget,gpointer data)1517 customisation_apply_clicked_cb(GtkWidget *widget, gpointer data)
1518 {
1519 	struct nsgtk_toolbar_customisation *tbc;
1520 	tbc = (struct nsgtk_toolbar_customisation *)data;
1521 
1522 	/* save state to file, update toolbars for all windows */
1523 	nsgtk_toolbar_customisation_save(&tbc->toolbar);
1524 	nsgtk_window_toolbar_update();
1525 	gtk_widget_destroy(tbc->container);
1526 
1527 	return TRUE;
1528 }
1529 
1530 
1531 /**
1532  * customisation reset handler for clicked signal
1533  *
1534  * when 'reload defaults' button is clicked
1535  */
1536 static gboolean
customisation_reset_clicked_cb(GtkWidget * widget,gpointer data)1537 customisation_reset_clicked_cb(GtkWidget *widget, gpointer data)
1538 {
1539 	struct nsgtk_toolbar_customisation *tbc;
1540 	tbc = (struct nsgtk_toolbar_customisation *)data;
1541 
1542 	customisation_toolbar_update(tbc);
1543 
1544 	return TRUE;
1545 }
1546 
1547 
1548 /**
1549  * customisation container destroy handler
1550  */
customisation_container_destroy_cb(GtkWidget * widget,gpointer data)1551 static void customisation_container_destroy_cb(GtkWidget *widget, gpointer data)
1552 {
1553 	struct nsgtk_toolbar_customisation *tbc;
1554 	tbc = (struct nsgtk_toolbar_customisation *)data;
1555 
1556 	free(tbc);
1557 }
1558 
1559 /*
1560  * Toolbar button clicked handlers
1561  */
1562 
1563 /**
1564  * create a toolbar customisation tab
1565  *
1566  * this is completely different approach to previous implementation. it
1567  *  is not modal and the toolbar configuration is performed completely
1568  *  within the tab. once the user is happy they can apply the change or
1569  *  cancel as they see fit while continuing to use the browser as usual.
1570  */
cutomize_button_clicked_cb(GtkWidget * widget,gpointer data)1571 static gboolean cutomize_button_clicked_cb(GtkWidget *widget, gpointer data)
1572 {
1573 	struct nsgtk_toolbar_customisation *tbc;
1574 	nserror res;
1575 	GtkBuilder *builder;
1576 	GtkNotebook *notebook; /* notebook containing widget */
1577 	GtkAllocation notebook_alloc; /* notebook size allocation */
1578 	int iidx; /* item index */
1579 
1580 	/* obtain the notebook being added to */
1581 	notebook = GTK_NOTEBOOK(gtk_widget_get_ancestor(widget,
1582 							GTK_TYPE_NOTEBOOK));
1583 	if (notebook == NULL) {
1584 		return TRUE;
1585 	}
1586 
1587 	/* create builder */
1588 	res = nsgtk_builder_new_from_resname("toolbar", &builder);
1589 	if (res != NSERROR_OK) {
1590 		NSLOG(netsurf, INFO, "Toolbar UI builder init failed");
1591 		return TRUE;
1592 	}
1593 	gtk_builder_connect_signals(builder, NULL);
1594 
1595 	/* create nsgtk_toolbar_customisation which has nsgtk_toolbar
1596 	 * at the front so we can reuse functions that take
1597 	 * nsgtk_toolbar
1598 	 */
1599 	tbc = calloc(1, sizeof(struct nsgtk_toolbar_customisation));
1600 	if (tbc == NULL) {
1601 		g_object_unref(builder);
1602 		return TRUE;
1603 	}
1604 
1605 	/* get container box widget which forms a page of the tabs */
1606 	tbc->container = GTK_WIDGET(gtk_builder_get_object(builder, "customisation"));
1607 	if (tbc->container == NULL) {
1608 		goto cutomize_button_clicked_cb_error;
1609 	}
1610 
1611 	/* vertical box for the toolbox to drag items into and out of */
1612 	tbc->toolbox = GTK_BOX(gtk_builder_get_object(builder, "toolbox"));
1613 	if (tbc->toolbox == NULL) {
1614 		goto cutomize_button_clicked_cb_error;
1615 	}
1616 
1617 	/* customisation toolbar container */
1618 	tbc->toolbar.widget = GTK_TOOLBAR(gtk_builder_get_object(builder, "toolbar"));
1619 	if (tbc->toolbar.widget == NULL) {
1620 		goto cutomize_button_clicked_cb_error;
1621 	}
1622 
1623 	/* build customisation toolbar */
1624 	gtk_toolbar_set_show_arrow(tbc->toolbar.widget, TRUE);
1625 
1626 	for (iidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) {
1627 		res = toolbar_item_create(iidx, &tbc->toolbar.items[iidx]);
1628 		if (res != NSERROR_OK) {
1629 			goto cutomize_button_clicked_cb_error;
1630 		}
1631 	}
1632 
1633 	res = customisation_toolbar_update(tbc);
1634 	if (res != NSERROR_OK) {
1635 		goto cutomize_button_clicked_cb_error;
1636 	}
1637 
1638 	/* use toolbox for widgets to drag to/from */
1639 	gtk_widget_get_allocation(GTK_WIDGET(notebook), &notebook_alloc);
1640 
1641 	res = toolbar_customisation_create_toolbox(tbc, notebook_alloc.width);
1642 	if (res != NSERROR_OK) {
1643 		goto cutomize_button_clicked_cb_error;
1644 	}
1645 
1646 	/* configure the container */
1647 	gtk_drag_dest_set(GTK_WIDGET(tbc->container),
1648 			  GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
1649 			  &target_entry,
1650 			  1,
1651 			  GDK_ACTION_COPY);
1652 
1653 	/* discard button calls destroy */
1654 	g_signal_connect_swapped(GTK_WIDGET(gtk_builder_get_object(builder,
1655 								   "discard")),
1656 				 "clicked",
1657 				 G_CALLBACK(gtk_widget_destroy),
1658 				 tbc->container);
1659 
1660 	/* save and update on apply button  */
1661 	g_signal_connect(GTK_WIDGET(gtk_builder_get_object(builder, "apply")),
1662 			 "clicked",
1663 			 G_CALLBACK(customisation_apply_clicked_cb),
1664 			 tbc);
1665 
1666 	g_signal_connect(GTK_WIDGET(gtk_builder_get_object(builder, "reset")),
1667 			 "clicked",
1668 			 G_CALLBACK(customisation_reset_clicked_cb),
1669 			 tbc);
1670 
1671 	/* close and cleanup on delete signal */
1672 	g_signal_connect(tbc->container,
1673 			 "destroy",
1674 			 G_CALLBACK(customisation_container_destroy_cb),
1675 			 tbc);
1676 
1677 
1678 	g_signal_connect(tbc->container,
1679 			 "drag-drop",
1680 			 G_CALLBACK(customisation_container_drag_drop_cb),
1681 			 tbc);
1682 
1683 	g_signal_connect(tbc->container,
1684 			 "drag-motion",
1685 			 G_CALLBACK(customisation_container_drag_motion_cb),
1686 			 tbc);
1687 
1688 
1689 	nsgtk_tab_add_page(notebook,
1690 			   tbc->container,
1691 			   false,
1692 			   messages_get("gtkCustomizeToolbarTitle"),
1693 			   favicon_pixbuf);
1694 
1695 
1696 	/* safe to drop the reference to the builder as the container is
1697 	 * referenced by the notebook now.
1698 	 */
1699 	g_object_unref(builder);
1700 
1701 	return TRUE;
1702 
1703  cutomize_button_clicked_cb_error:
1704 	free(tbc);
1705 	g_object_unref(builder);
1706 	return TRUE;
1707 
1708 }
1709 
1710 
1711 /**
1712  * callback for all toolbar items widget size allocation
1713  *
1714  * handler connected to all toolbar items for the size-allocate signal
1715  *
1716  * \param widget The widget the signal is being delivered to.
1717  * \param alloc The size allocation being set.
1718  * \param data The toolbar context passed when the signal was connected
1719  */
1720 static void
toolbar_item_size_allocate_cb(GtkWidget * widget,GtkAllocation * alloc,gpointer data)1721 toolbar_item_size_allocate_cb(GtkWidget *widget,
1722 			      GtkAllocation *alloc,
1723 			      gpointer data)
1724 {
1725 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
1726 	nsgtk_toolbar_button itemid;
1727 
1728 	itemid = itemid_from_gtktoolitem(tb, GTK_TOOL_ITEM(widget));
1729 
1730 	if ((tb->toolbarmem == alloc->x) ||
1731 	    (tb->items[itemid].location < tb->items[HISTORY_BUTTON].location)) {
1732 		/*
1733 		 * no reallocation after first adjustment,
1734 		 * no reallocation for buttons left of history button
1735 		 */
1736 		return;
1737 	}
1738 
1739 	if (itemid == HISTORY_BUTTON) {
1740 		if (alloc->width == 20) {
1741 			return;
1742 		}
1743 
1744 		tb->toolbarbase = alloc->y + alloc->height;
1745 		tb->historybase = alloc->x + 20;
1746 		if (tb->offset == 0) {
1747 			tb->offset = alloc->width - 20;
1748 		}
1749 		alloc->width = 20;
1750 	} else if (tb->items[itemid].location <= tb->items[URL_BAR_ITEM].location) {
1751 		alloc->x -= tb->offset;
1752 		if (itemid == URL_BAR_ITEM) {
1753 			alloc->width += tb->offset;
1754 		}
1755 	}
1756 	tb->toolbarmem = alloc->x;
1757 
1758 	gtk_widget_size_allocate(widget, alloc);
1759 }
1760 
1761 
1762 /**
1763  * handler for back tool bar item clicked signal
1764  *
1765  * \param widget The widget the signal is being delivered to.
1766  * \param data The toolbar context passed when the signal was connected
1767  * \return TRUE
1768  */
1769 static gboolean
back_button_clicked_cb(GtkWidget * widget,gpointer data)1770 back_button_clicked_cb(GtkWidget *widget, gpointer data)
1771 {
1772 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
1773 	struct browser_window *bw;
1774 
1775 	bw = tb->get_bw(tb->get_ctx);
1776 
1777 	if ((bw != NULL) && browser_window_history_back_available(bw)) {
1778 		/* clear potential search effects */
1779 		browser_window_search_clear(bw);
1780 
1781 		browser_window_history_back(bw, false);
1782 
1783 		set_item_sensitivity(&tb->items[BACK_BUTTON],
1784 				browser_window_history_back_available(bw));
1785 		set_item_sensitivity(&tb->items[FORWARD_BUTTON],
1786 				browser_window_history_forward_available(bw));
1787 
1788 		nsgtk_local_history_hide();
1789 	}
1790 	return TRUE;
1791 }
1792 
1793 
1794 /**
1795  * handler for forward tool bar item clicked signal
1796  *
1797  * \param widget The widget the signal is being delivered to.
1798  * \param data The toolbar context passed when the signal was connected
1799  * \return TRUE
1800  */
1801 static gboolean
forward_button_clicked_cb(GtkWidget * widget,gpointer data)1802 forward_button_clicked_cb(GtkWidget *widget, gpointer data)
1803 {
1804 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
1805 	struct browser_window *bw;
1806 
1807 	bw = tb->get_bw(tb->get_ctx);
1808 
1809 	if ((bw != NULL) && browser_window_history_forward_available(bw)) {
1810 		/* clear potential search effects */
1811 		browser_window_search_clear(bw);
1812 
1813 		browser_window_history_forward(bw, false);
1814 
1815 		set_item_sensitivity(&tb->items[BACK_BUTTON],
1816 				browser_window_history_back_available(bw));
1817 		set_item_sensitivity(&tb->items[FORWARD_BUTTON],
1818 				browser_window_history_forward_available(bw));
1819 		nsgtk_local_history_hide();
1820 	}
1821 	return TRUE;
1822 }
1823 
1824 
1825 /**
1826  * handler for stop tool bar item clicked signal
1827  *
1828  * \param widget The widget the signal is being delivered to.
1829  * \param data The toolbar context passed when the signal was connected
1830  * \return TRUE
1831  */
1832 static gboolean
stop_button_clicked_cb(GtkWidget * widget,gpointer data)1833 stop_button_clicked_cb(GtkWidget *widget, gpointer data)
1834 {
1835 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
1836 
1837 	browser_window_stop(tb->get_bw(tb->get_ctx));
1838 
1839 	return TRUE;
1840 }
1841 
1842 
1843 /**
1844  * handler for reload tool bar item clicked signal
1845  *
1846  * \param widget The widget the signal is being delivered to.
1847  * \param data The toolbar context passed when the signal was connected
1848  * \return TRUE
1849  */
1850 static gboolean
reload_button_clicked_cb(GtkWidget * widget,gpointer data)1851 reload_button_clicked_cb(GtkWidget *widget, gpointer data)
1852 {
1853 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
1854 	struct browser_window *bw;
1855 
1856 	bw = tb->get_bw(tb->get_ctx);
1857 
1858 	/* clear potential search effects */
1859 	browser_window_search_clear(bw);
1860 
1861 	browser_window_reload(bw, true);
1862 
1863 	return TRUE;
1864 }
1865 
1866 
1867 /**
1868  * handler for reload/stop tool bar item clicked signal
1869  *
1870  * \param widget The widget the signal is being delivered to.
1871  * \param data The toolbar context passed when the signal was connected
1872  * \return TRUE
1873  */
1874 static gboolean
reloadstop_button_clicked_cb(GtkWidget * widget,gpointer data)1875 reloadstop_button_clicked_cb(GtkWidget *widget, gpointer data)
1876 {
1877 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
1878 	struct browser_window *bw;
1879 
1880 	bw = tb->get_bw(tb->get_ctx);
1881 
1882 	/* clear potential search effects */
1883 	browser_window_search_clear(bw);
1884 
1885 	if (tb->items[RELOADSTOP_BUTTON].sensitivity) {
1886 		browser_window_reload(bw, true);
1887 	} else {
1888 		browser_window_stop(tb->get_bw(tb->get_ctx));
1889 	}
1890 
1891 	return TRUE;
1892 }
1893 
1894 
1895 /**
1896  * handler for home tool bar item clicked signal
1897  *
1898  * \param widget The widget the signal is being delivered to.
1899  * \param data The toolbar context passed when the signal was connected
1900  * \return TRUE
1901  */
1902 static gboolean
home_button_clicked_cb(GtkWidget * widget,gpointer data)1903 home_button_clicked_cb(GtkWidget *widget, gpointer data)
1904 {
1905 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
1906 	nserror res;
1907 	const char *addr;
1908 
1909 	if (nsoption_charp(homepage_url) != NULL) {
1910 		addr = nsoption_charp(homepage_url);
1911 	} else {
1912 		addr = NETSURF_HOMEPAGE;
1913 	}
1914 
1915 	res = toolbar_navigate_to_url(tb, addr);
1916 	if (res != NSERROR_OK) {
1917 		nsgtk_warning(messages_get_errorcode(res), 0);
1918 	}
1919 
1920 	return TRUE;
1921 }
1922 
1923 
1924 /**
1925  * callback for url entry widget activation
1926  *
1927  * handler connected to url entry widget for the activate signal
1928  *
1929  * \param widget The widget the signal is being delivered to.
1930  * \param data The toolbar context passed when the signal was connected
1931  * \return TRUE to allow activation.
1932  */
url_entry_activate_cb(GtkWidget * widget,gpointer data)1933 static gboolean url_entry_activate_cb(GtkWidget *widget, gpointer data)
1934 {
1935 	nserror res;
1936 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
1937 	struct browser_window *bw;
1938 	nsurl *url;
1939 
1940 	res = search_web_omni(gtk_entry_get_text(GTK_ENTRY(widget)),
1941 			      SEARCH_WEB_OMNI_NONE,
1942 			      &url);
1943 	if (res == NSERROR_OK) {
1944 		bw = tb->get_bw(tb->get_ctx);
1945 		res = browser_window_navigate(
1946 			bw, url, NULL, BW_NAVIGATE_HISTORY, NULL, NULL, NULL);
1947 		nsurl_unref(url);
1948 	}
1949 	if (res != NSERROR_OK) {
1950 		nsgtk_warning(messages_get_errorcode(res), 0);
1951 	}
1952 
1953 	return TRUE;
1954 }
1955 
1956 
1957 /**
1958  * callback for url entry widget changing
1959  *
1960  * handler connected to url entry widget for the change signal
1961  *
1962  * \param widget The widget the signal is being delivered to.
1963  * \param event The key change event that changed the entry.
1964  * \param data The toolbar context passed when the signal was connected
1965  * \return TRUE to allow activation.
1966  */
1967 static gboolean
url_entry_changed_cb(GtkWidget * widget,GdkEventKey * event,gpointer data)1968 url_entry_changed_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
1969 {
1970 	return nsgtk_completion_update(GTK_ENTRY(widget));
1971 }
1972 
1973 
1974 /**
1975  * callback for url entry widget icon button release
1976  *
1977  * handler connected to url entry widget for the icon release signal
1978  *
1979  * \param widget The widget the signal is being delivered to.
1980  * \param event The key change event that changed the entry.
1981  * \param data The toolbar context passed when the signal was connected
1982  * \return TRUE to allow activation.
1983  */
1984 static void
url_entry_icon_release_cb(GtkEntry * entry,GtkEntryIconPosition icon_pos,GdkEvent * event,gpointer data)1985 url_entry_icon_release_cb(GtkEntry *entry,
1986 			   GtkEntryIconPosition icon_pos,
1987 			   GdkEvent *event,
1988 			   gpointer data)
1989 {
1990 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
1991 	struct browser_window *bw;
1992 
1993 	bw = tb->get_bw(tb->get_ctx);
1994 
1995 	nsgtk_page_info(bw);
1996 }
1997 
1998 
1999 /**
2000  * handler for web search tool bar entry item activate signal
2001  *
2002  * handler connected to web search entry widget for the activate signal
2003  *
2004  * \todo make this user selectable to switch between opening in new
2005  *   and navigating current window. Possibly improve core search_web interfaces
2006  *
2007  * \param widget The widget the signal is being delivered to.
2008  * \param data The toolbar context passed when the signal was connected
2009  * \return TRUE
2010  */
websearch_entry_activate_cb(GtkWidget * widget,gpointer data)2011 static gboolean websearch_entry_activate_cb(GtkWidget *widget, gpointer data)
2012 {
2013 	nserror res;
2014 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2015 	struct browser_window *bw;
2016 	nsurl *url;
2017 
2018 	res = search_web_omni(gtk_entry_get_text(GTK_ENTRY(widget)),
2019 			      SEARCH_WEB_OMNI_SEARCHONLY,
2020 			      &url);
2021 	if (res == NSERROR_OK) {
2022 		bw = tb->get_bw(tb->get_ctx);
2023 
2024 		res = browser_window_create(
2025 			BW_CREATE_HISTORY | BW_CREATE_TAB | BW_CREATE_FOREGROUND,
2026 			url,
2027 			NULL,
2028 			bw,
2029 			NULL);
2030 		nsurl_unref(url);
2031 	}
2032 	if (res != NSERROR_OK) {
2033 		nsgtk_warning(messages_get_errorcode(res), 0);
2034 	}
2035 
2036 	return TRUE;
2037 }
2038 
2039 /**
2040  * handler for web search tool bar item button press signal
2041  *
2042  * allows a click in the websearch entry field to clear the name of the
2043  * provider.
2044  *
2045  * \todo this does not work well, different behaviour wanted perhaps?
2046  *
2047  * \param widget The widget the signal is being delivered to.
2048  * \param data The toolbar context passed when the signal was connected
2049  * \return TRUE
2050  */
2051 static gboolean
websearch_entry_button_press_cb(GtkWidget * widget,GdkEventFocus * f,gpointer data)2052 websearch_entry_button_press_cb(GtkWidget *widget,
2053 			     GdkEventFocus *f,
2054 			     gpointer data)
2055 {
2056 	gtk_editable_select_region(GTK_EDITABLE(widget), 0, -1);
2057 	gtk_widget_grab_focus(GTK_WIDGET(widget));
2058 
2059 	return TRUE;
2060 }
2061 
2062 
2063 /**
2064  * handler for new window tool bar item clicked signal
2065  *
2066  * \param widget The widget the signal is being delivered to.
2067  * \param data The toolbar context passed when the signal was connected
2068  * \return TRUE
2069  */
2070 static gboolean
newwindow_button_clicked_cb(GtkWidget * widget,gpointer data)2071 newwindow_button_clicked_cb(GtkWidget *widget, gpointer data)
2072 {
2073 	nserror res;
2074 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2075 
2076 	res = nsgtk_browser_window_create(tb->get_bw(tb->get_ctx), false);
2077 	if (res != NSERROR_OK) {
2078 		nsgtk_warning(messages_get_errorcode(res), 0);
2079 	}
2080 
2081 	return TRUE;
2082 }
2083 
2084 
2085 /**
2086  * handler for new tab tool bar item clicked signal
2087  *
2088  * \param widget The widget the signal is being delivered to.
2089  * \param data The toolbar context passed when the signal was connected
2090  * \return TRUE
2091  */
2092 static gboolean
newtab_button_clicked_cb(GtkWidget * widget,gpointer data)2093 newtab_button_clicked_cb(GtkWidget *widget, gpointer data)
2094 {
2095 	nserror res;
2096 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2097 
2098 	res = nsgtk_browser_window_create(tb->get_bw(tb->get_ctx), true);
2099 	if (res != NSERROR_OK) {
2100 		nsgtk_warning(messages_get_errorcode(res), 0);
2101 	}
2102 	return TRUE;
2103 }
2104 
2105 
2106 /**
2107  * handler for open file tool bar item clicked signal
2108  *
2109  * \param widget The widget the signal is being delivered to.
2110  * \param data The toolbar context passed when the signal was connected
2111  * \return TRUE
2112  */
2113 static gboolean
openfile_button_clicked_cb(GtkWidget * widget,gpointer data)2114 openfile_button_clicked_cb(GtkWidget *widget, gpointer data)
2115 {
2116 	GtkWidget *dlgOpen;
2117 	gint response;
2118 	GtkWidget *toplevel;
2119 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2120 	struct browser_window *bw;
2121 
2122 	toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
2123 
2124 	dlgOpen = gtk_file_chooser_dialog_new("Open File",
2125 					      GTK_WINDOW(toplevel),
2126 			GTK_FILE_CHOOSER_ACTION_OPEN,
2127 			NSGTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2128 			NSGTK_STOCK_OPEN, GTK_RESPONSE_OK,
2129 			NULL, NULL);
2130 
2131 	response = gtk_dialog_run(GTK_DIALOG(dlgOpen));
2132 	if (response == GTK_RESPONSE_OK) {
2133 		char *urltxt;
2134 		gchar *filename;
2135 		nserror res;
2136 		nsurl *url;
2137 
2138 		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlgOpen));
2139 
2140 		urltxt = malloc(strlen(filename) + FILE_SCHEME_PREFIX_LEN + 1);
2141 		if (urltxt != NULL) {
2142 			sprintf(urltxt, FILE_SCHEME_PREFIX"%s", filename);
2143 
2144 			res = nsurl_create(urltxt, &url);
2145 			if (res == NSERROR_OK) {
2146 				bw = tb->get_bw(tb->get_ctx);
2147 				res = browser_window_navigate(bw,
2148 							url,
2149 							NULL,
2150 							BW_NAVIGATE_HISTORY,
2151 							NULL,
2152 							NULL,
2153 							NULL);
2154 				nsurl_unref(url);
2155 			}
2156 			if (res != NSERROR_OK) {
2157 				nsgtk_warning(messages_get_errorcode(res), 0);
2158 			}
2159 			free(urltxt);
2160 		}
2161 
2162 
2163 		g_free(filename);
2164 	}
2165 
2166 	gtk_widget_destroy(dlgOpen);
2167 
2168 	return TRUE;
2169 }
2170 
2171 
2172 /**
2173  * handler for close window tool bar item clicked signal
2174  *
2175  * \param widget The widget the signal is being delivered to.
2176  * \param data The toolbar context passed when the signal was connected
2177  * \return TRUE
2178  */
2179 static gboolean
closewindow_button_clicked_cb(GtkWidget * widget,gpointer data)2180 closewindow_button_clicked_cb(GtkWidget *widget, gpointer data)
2181 {
2182 	GtkWidget *toplevel;
2183 	toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
2184 	gtk_widget_destroy(toplevel);
2185 	return TRUE;
2186 }
2187 
2188 
2189 /**
2190  * handler for full save export tool bar item clicked signal
2191  *
2192  * \param widget The widget the signal is being delivered to.
2193  * \param data The toolbar context passed when the signal was connected
2194  * \return TRUE
2195  */
2196 static gboolean
savepage_button_clicked_cb(GtkWidget * widget,gpointer data)2197 savepage_button_clicked_cb(GtkWidget *widget, gpointer data)
2198 {
2199 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2200 	struct browser_window *bw;
2201 	DIR *d;
2202 	gchar *path;
2203 	nserror res;
2204 	GtkWidget *toplevel;
2205 
2206 	bw = tb->get_bw(tb->get_ctx);
2207 	toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
2208 
2209 	res = nsgtk_saveas_dialog(bw,
2210 				  messages_get("gtkcompleteSave"),
2211 				  GTK_WINDOW(toplevel),
2212 				  true,
2213 				  &path);
2214 	if (res != NSERROR_OK) {
2215 		return FALSE;
2216 	}
2217 
2218 	d = opendir(path);
2219 	if (d == NULL) {
2220 		NSLOG(netsurf, INFO,
2221 		      "Unable to open directory %s for complete save: %s",
2222 		      path,
2223 		      strerror(errno));
2224 		if (errno == ENOTDIR) {
2225 			nsgtk_warning("NoDirError", path);
2226 		} else {
2227 			nsgtk_warning("gtkFileError", path);
2228 		}
2229 		g_free(path);
2230 		return TRUE;
2231 	}
2232 	closedir(d);
2233 
2234 	save_complete(browser_window_get_content(bw), path, NULL);
2235 	g_free(path);
2236 
2237 	return TRUE;
2238 }
2239 
2240 
2241 /**
2242  * handler for pdf export tool bar item clicked signal
2243  *
2244  * \param widget The widget the signal is being delivered to.
2245  * \param data The toolbar context passed when the signal was connected
2246  * \return TRUE
2247  */
2248 static gboolean
pdf_button_clicked_cb(GtkWidget * widget,gpointer data)2249 pdf_button_clicked_cb(GtkWidget *widget, gpointer data)
2250 {
2251 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2252 	struct browser_window *bw;
2253 	GtkWidget *toplevel;
2254 	gchar *filename;
2255 	nserror res;
2256 
2257 	bw = tb->get_bw(tb->get_ctx);
2258 
2259 	toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
2260 
2261 	res = nsgtk_saveas_dialog(bw,
2262 				  "Export to PDF",
2263 				  GTK_WINDOW(toplevel),
2264 				  false,
2265 				  &filename);
2266 	if (res != NSERROR_OK) {
2267 		return FALSE;
2268 	}
2269 
2270 #ifdef WITH_PDF_EXPORT
2271 	struct print_settings *settings;
2272 
2273 	/* this way the scale used by PDF functions is synchronised with that
2274 	 * used by the all-purpose print interface
2275 	 */
2276 	haru_nsfont_set_scale((float)option_export_scale / 100);
2277 
2278 	settings = print_make_settings(PRINT_OPTIONS,
2279 				       (const char *) filename,
2280 				       &haru_nsfont);
2281 	g_free(filename);
2282 	if (settings == NULL) {
2283 		return TRUE;
2284 	}
2285 	/* This will clean up the print_settings object for us */
2286 	print_basic_run(browser_window_get_content(bw),	&pdf_printer, settings);
2287 #endif
2288 	return TRUE;
2289 
2290 }
2291 
2292 
2293 /**
2294  * handler for plain text export tool bar item clicked signal
2295  *
2296  * \param widget The widget the signal is being delivered to.
2297  * \param data The toolbar context passed when the signal was connected
2298  * \return TRUE
2299  */
2300 static gboolean
plaintext_button_clicked_cb(GtkWidget * widget,gpointer data)2301 plaintext_button_clicked_cb(GtkWidget *widget, gpointer data)
2302 {
2303 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2304 	struct browser_window *bw;
2305 	GtkWidget *toplevel;
2306 	gchar *filename;
2307 	nserror res;
2308 
2309 	bw = tb->get_bw(tb->get_ctx);
2310 
2311 	toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
2312 
2313 	res = nsgtk_saveas_dialog(bw,
2314 				  messages_get("gtkplainSave"),
2315 				  GTK_WINDOW(toplevel),
2316 				  false,
2317 				  &filename);
2318 	if (res != NSERROR_OK) {
2319 		return FALSE;
2320 	}
2321 
2322 
2323 	save_as_text(browser_window_get_content(bw), filename);
2324 	g_free(filename);
2325 
2326 	return TRUE;
2327 }
2328 
2329 
2330 /**
2331  * handler for print tool bar item clicked signal
2332  *
2333  * \param widget The widget the signal is being delivered to.
2334  * \param data The toolbar context passed when the signal was connected
2335  * \return TRUE
2336  */
2337 static gboolean
print_button_clicked_cb(GtkWidget * widget,gpointer data)2338 print_button_clicked_cb(GtkWidget *widget, gpointer data)
2339 {
2340 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2341 	struct browser_window *bw;
2342 	GtkPrintOperation *print_op;
2343 	GtkPageSetup *page_setup;
2344 	GtkPrintSettings *print_settings;
2345 	GtkPrintOperationResult res = GTK_PRINT_OPERATION_RESULT_ERROR;
2346 	struct print_settings *nssettings;
2347 	char *settings_fname = NULL;
2348 	GtkWidget *toplevel;
2349 
2350 	bw = tb->get_bw(tb->get_ctx);
2351 
2352 	toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
2353 
2354 	print_op = gtk_print_operation_new();
2355 	if (print_op == NULL) {
2356 		nsgtk_warning(messages_get("NoMemory"), 0);
2357 		return TRUE;
2358 	}
2359 
2360 	/* use previously saved settings if any */
2361 	netsurf_mkpath(&settings_fname, NULL, 2, nsgtk_config_home, "Print");
2362 	if (settings_fname != NULL) {
2363 		print_settings = gtk_print_settings_new_from_file(settings_fname, NULL);
2364 		if (print_settings != NULL) {
2365 			gtk_print_operation_set_print_settings(print_op,
2366 							       print_settings);
2367 
2368 			/* We're not interested in the settings any more */
2369 			g_object_unref(print_settings);
2370 		}
2371 	}
2372 
2373 	content_to_print = browser_window_get_content(bw);
2374 
2375 	page_setup = gtk_print_run_page_setup_dialog(GTK_WINDOW(toplevel),
2376 						     NULL,
2377 						     NULL);
2378 	if (page_setup == NULL) {
2379 		nsgtk_warning(messages_get("NoMemory"), 0);
2380 		free(settings_fname);
2381 		g_object_unref(print_op);
2382 		return TRUE;
2383 	}
2384 	gtk_print_operation_set_default_page_setup(print_op, page_setup);
2385 
2386 	nssettings = print_make_settings(PRINT_DEFAULT,
2387 					 NULL,
2388 					 nsgtk_layout_table);
2389 
2390 	g_signal_connect(print_op,
2391 			 "begin_print",
2392 			 G_CALLBACK(gtk_print_signal_begin_print),
2393 			 nssettings);
2394 	g_signal_connect(print_op,
2395 			 "draw_page",
2396 			 G_CALLBACK(gtk_print_signal_draw_page),
2397 			 NULL);
2398 	g_signal_connect(print_op,
2399 			 "end_print",
2400 			 G_CALLBACK(gtk_print_signal_end_print),
2401 			 nssettings);
2402 
2403 	if (content_get_type(browser_window_get_content(bw)) != CONTENT_TEXTPLAIN) {
2404 		res = gtk_print_operation_run(print_op,
2405 					      GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
2406 					      GTK_WINDOW(toplevel),
2407 					      NULL);
2408 	}
2409 
2410 	/* if the settings were used save them for future use */
2411 	if (settings_fname != NULL) {
2412 		if (res == GTK_PRINT_OPERATION_RESULT_APPLY) {
2413 			/* Do not increment the settings reference */
2414 			print_settings = gtk_print_operation_get_print_settings(print_op);
2415 
2416 			gtk_print_settings_to_file(print_settings,
2417 						   settings_fname,
2418 						   NULL);
2419 		}
2420 		free(settings_fname);
2421 	}
2422 
2423 	/* Our print_settings object is destroyed by the end print handler */
2424 	g_object_unref(page_setup);
2425 	g_object_unref(print_op);
2426 
2427 	return TRUE;
2428 }
2429 
2430 /**
2431  * handler for quit tool bar item clicked signal
2432  *
2433  * \param widget The widget the signal is being delivered to.
2434  * \param data The toolbar context passed when the signal was connected
2435  * \return TRUE
2436  */
2437 static gboolean
quit_button_clicked_cb(GtkWidget * widget,gpointer data)2438 quit_button_clicked_cb(GtkWidget *widget, gpointer data)
2439 {
2440 	nsgtk_scaffolding_destroy_all();
2441 	return TRUE;
2442 }
2443 
2444 
2445 /**
2446  * handler for cut tool bar item clicked signal
2447  *
2448  * \param widget The widget the signal is being delivered to.
2449  * \param data The toolbar context passed when the signal was connected
2450  * \return TRUE
2451  */
2452 static gboolean
cut_button_clicked_cb(GtkWidget * widget,gpointer data)2453 cut_button_clicked_cb(GtkWidget *widget, gpointer data)
2454 {
2455 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2456 	struct browser_window *bw;
2457 	GtkWidget *focused;
2458 	GtkWidget *toplevel;
2459 
2460 	toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
2461 
2462 	focused = gtk_window_get_focus(GTK_WINDOW(toplevel));
2463 
2464 	/* let gtk handle it if focused widget is an editable */
2465 	if (GTK_IS_EDITABLE(focused)) {
2466 		gtk_editable_cut_clipboard(GTK_EDITABLE(focused));
2467 	} else {
2468 		bw = tb->get_bw(tb->get_ctx);
2469 		browser_window_key_press(bw, NS_KEY_CUT_SELECTION);
2470 	}
2471 
2472 	return TRUE;
2473 }
2474 
2475 
2476 /**
2477  * handler for copy tool bar item clicked signal
2478  *
2479  * \param widget The widget the signal is being delivered to.
2480  * \param data The toolbar context passed when the signal was connected
2481  * \return TRUE
2482  */
2483 static gboolean
copy_button_clicked_cb(GtkWidget * widget,gpointer data)2484 copy_button_clicked_cb(GtkWidget *widget, gpointer data)
2485 {
2486 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2487 	struct browser_window *bw;
2488 	GtkWidget *focused;
2489 	GtkWidget *toplevel;
2490 
2491 	toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
2492 
2493 	focused = gtk_window_get_focus(GTK_WINDOW(toplevel));
2494 
2495 	/* let gtk handle it if focused widget is an editable */
2496 	if (GTK_IS_EDITABLE(focused)) {
2497 		gtk_editable_copy_clipboard(GTK_EDITABLE(focused));
2498 	} else {
2499 		bw = tb->get_bw(tb->get_ctx);
2500 		browser_window_key_press(bw, NS_KEY_COPY_SELECTION);
2501 	}
2502 
2503 	return TRUE;
2504 }
2505 
2506 
2507 /**
2508  * handler for paste tool bar item clicked signal
2509  *
2510  * \param widget The widget the signal is being delivered to.
2511  * \param data The toolbar context passed when the signal was connected
2512  * \return TRUE
2513  */
2514 static gboolean
paste_button_clicked_cb(GtkWidget * widget,gpointer data)2515 paste_button_clicked_cb(GtkWidget *widget, gpointer data)
2516 {
2517 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2518 	struct browser_window *bw;
2519 	GtkWidget *focused;
2520 	GtkWidget *toplevel;
2521 
2522 	toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
2523 
2524 	focused = gtk_window_get_focus(GTK_WINDOW(toplevel));
2525 
2526 	/* let gtk handle it if focused widget is an editable */
2527 	if (GTK_IS_EDITABLE(focused)) {
2528 		gtk_editable_paste_clipboard(GTK_EDITABLE(focused));
2529 	} else {
2530 		bw = tb->get_bw(tb->get_ctx);
2531 		browser_window_key_press(bw, NS_KEY_PASTE);
2532 	}
2533 
2534 	return TRUE;
2535 }
2536 
2537 
2538 /**
2539  * handler for delete tool bar item clicked signal
2540  *
2541  * \param widget The widget the signal is being delivered to.
2542  * \param data The toolbar context passed when the signal was connected
2543  * \return TRUE
2544  */
2545 static gboolean
delete_button_clicked_cb(GtkWidget * widget,gpointer data)2546 delete_button_clicked_cb(GtkWidget *widget, gpointer data)
2547 {
2548 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2549 	struct browser_window *bw;
2550 	GtkWidget *focused;
2551 	GtkWidget *toplevel;
2552 
2553 	toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
2554 
2555 	focused = gtk_window_get_focus(GTK_WINDOW(toplevel));
2556 
2557 	/* let gtk handle it if focused widget is an editable */
2558 	if (GTK_IS_EDITABLE(focused)) {
2559 		gtk_editable_delete_selection(GTK_EDITABLE(focused));
2560 	} else {
2561 		bw = tb->get_bw(tb->get_ctx);
2562 		browser_window_key_press(bw, NS_KEY_CLEAR_SELECTION);
2563 	}
2564 
2565 	return TRUE;
2566 }
2567 
2568 
2569 /**
2570  * handler for select all tool bar item clicked signal
2571  *
2572  * \param widget The widget the signal is being delivered to.
2573  * \param data The toolbar context passed when the signal was connected
2574  * \return TRUE
2575  */
2576 static gboolean
selectall_button_clicked_cb(GtkWidget * widget,gpointer data)2577 selectall_button_clicked_cb(GtkWidget *widget, gpointer data)
2578 {
2579 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2580 	struct browser_window *bw;
2581 	GtkWidget *focused;
2582 	GtkWidget *toplevel;
2583 
2584 	toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
2585 
2586 	focused = gtk_window_get_focus(GTK_WINDOW(toplevel));
2587 
2588 	/* let gtk handle it if focused widget is an editable */
2589 	if (GTK_IS_EDITABLE(focused)) {
2590 		gtk_editable_select_region(GTK_EDITABLE(focused), 0, -1);
2591 	} else {
2592 		bw = tb->get_bw(tb->get_ctx);
2593 		browser_window_key_press(bw, NS_KEY_SELECT_ALL);
2594 	}
2595 
2596 	return TRUE;
2597 }
2598 
2599 
2600 /**
2601  * handler for preferences tool bar item clicked signal
2602  *
2603  * \param widget The widget the signal is being delivered to.
2604  * \param data The toolbar context passed when the signal was connected
2605  * \return TRUE
2606  */
2607 static gboolean
preferences_button_clicked_cb(GtkWidget * widget,gpointer data)2608 preferences_button_clicked_cb(GtkWidget *widget, gpointer data)
2609 {
2610 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2611 	struct browser_window *bw;
2612 	GtkWidget *toplevel;
2613 	GtkWidget *wndpreferences;
2614 
2615 	bw = tb->get_bw(tb->get_ctx);
2616 
2617 	toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
2618 
2619 	wndpreferences = nsgtk_preferences(bw, GTK_WINDOW(toplevel));
2620 	if (wndpreferences != NULL) {
2621 		gtk_widget_show(wndpreferences);
2622 	}
2623 
2624 	return TRUE;
2625 }
2626 
2627 
2628 /**
2629  * handler for zoom plus tool bar item clicked signal
2630  *
2631  * \param widget The widget the signal is being delivered to.
2632  * \param data The toolbar context passed when the signal was connected
2633  * \return TRUE
2634  */
2635 static gboolean
zoomplus_button_clicked_cb(GtkWidget * widget,gpointer data)2636 zoomplus_button_clicked_cb(GtkWidget *widget, gpointer data)
2637 {
2638 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2639 	struct browser_window *bw;
2640 
2641 	bw = tb->get_bw(tb->get_ctx);
2642 
2643 	browser_window_set_scale(bw, 0.05, false);
2644 
2645 	return TRUE;
2646 }
2647 
2648 
2649 /**
2650  * handler for zoom minus tool bar item clicked signal
2651  *
2652  * \param widget The widget the signal is being delivered to.
2653  * \param data The toolbar context passed when the signal was connected
2654  * \return TRUE
2655  */
2656 static gboolean
zoomminus_button_clicked_cb(GtkWidget * widget,gpointer data)2657 zoomminus_button_clicked_cb(GtkWidget *widget, gpointer data)
2658 {
2659 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2660 	struct browser_window *bw;
2661 
2662 	bw = tb->get_bw(tb->get_ctx);
2663 
2664 	browser_window_set_scale(bw, -0.05, false);
2665 
2666 	return TRUE;
2667 
2668 }
2669 
2670 
2671 /**
2672  * handler for zoom normal tool bar item clicked signal
2673  *
2674  * \param widget The widget the signal is being delivered to.
2675  * \param data The toolbar context passed when the signal was connected
2676  * \return TRUE
2677  */
2678 static gboolean
zoomnormal_button_clicked_cb(GtkWidget * widget,gpointer data)2679 zoomnormal_button_clicked_cb(GtkWidget *widget, gpointer data)
2680 {
2681 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2682 	struct browser_window *bw;
2683 
2684 	bw = tb->get_bw(tb->get_ctx);
2685 
2686 	browser_window_set_scale(bw, 1.0, true);
2687 
2688 	return TRUE;
2689 }
2690 
2691 
2692 /**
2693  * handler for full screen tool bar item clicked signal
2694  *
2695  * \param widget The widget the signal is being delivered to.
2696  * \param data The toolbar context passed when the signal was connected
2697  * \return TRUE
2698  */
2699 static gboolean
fullscreen_button_clicked_cb(GtkWidget * widget,gpointer data)2700 fullscreen_button_clicked_cb(GtkWidget *widget, gpointer data)
2701 {
2702 	GtkWindow *gtkwindow; /* gtk window widget is in */
2703 	GdkWindow *gdkwindow;
2704 	GdkWindowState state;
2705 
2706 	gtkwindow = GTK_WINDOW(gtk_widget_get_ancestor(widget,GTK_TYPE_WINDOW));
2707 	gdkwindow = gtk_widget_get_window(GTK_WIDGET(gtkwindow));
2708 	state = gdk_window_get_state(gdkwindow);
2709 
2710 	if (state & GDK_WINDOW_STATE_FULLSCREEN) {
2711 		gtk_window_unfullscreen(gtkwindow);
2712 	} else {
2713 		gtk_window_fullscreen(gtkwindow);
2714 	}
2715 	return TRUE;
2716 }
2717 
2718 
2719 /**
2720  * handler for view source tool bar item clicked signal
2721  *
2722  * \param widget The widget the signal is being delivered to.
2723  * \param data The toolbar context passed when the signal was connected
2724  * \return TRUE
2725  */
2726 static gboolean
viewsource_button_clicked_cb(GtkWidget * widget,gpointer data)2727 viewsource_button_clicked_cb(GtkWidget *widget, gpointer data)
2728 {
2729 	nserror res;
2730 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2731 	struct browser_window *bw;
2732 	GtkWindow *gtkwindow; /* gtk window widget is in */
2733 
2734 	bw = tb->get_bw(tb->get_ctx);
2735 
2736 	gtkwindow = GTK_WINDOW(gtk_widget_get_ancestor(widget,GTK_TYPE_WINDOW));
2737 
2738 	res = nsgtk_viewsource(gtkwindow, bw);
2739 	if (res != NSERROR_OK) {
2740 		nsgtk_warning(messages_get_errorcode(res), 0);
2741 	}
2742 
2743 	return TRUE;
2744 }
2745 
2746 
2747 /**
2748  * handler for show downloads tool bar item clicked signal
2749  *
2750  * \param widget The widget the signal is being delivered to.
2751  * \param data The toolbar context passed when the signal was connected
2752  * \return TRUE
2753  */
2754 static gboolean
downloads_button_clicked_cb(GtkWidget * widget,gpointer data)2755 downloads_button_clicked_cb(GtkWidget *widget, gpointer data)
2756 {
2757 	GtkWindow *gtkwindow; /* gtk window widget is in */
2758 	gtkwindow = GTK_WINDOW(gtk_widget_get_ancestor(widget,GTK_TYPE_WINDOW));
2759 	nsgtk_download_show(gtkwindow);
2760 	return TRUE;
2761 }
2762 
2763 
2764 /**
2765  * handler for show downloads tool bar item clicked signal
2766  *
2767  * \param widget The widget the signal is being delivered to.
2768  * \param data The toolbar context passed when the signal was connected
2769  * \return TRUE
2770  */
2771 static gboolean
savewindowsize_button_clicked_cb(GtkWidget * widget,gpointer data)2772 savewindowsize_button_clicked_cb(GtkWidget *widget, gpointer data)
2773 {
2774 	GtkWindow *gtkwindow; /* gtk window widget is in */
2775 	int x,y,w,h;
2776 	char *choices = NULL;
2777 
2778 	gtkwindow = GTK_WINDOW(gtk_widget_get_ancestor(widget,GTK_TYPE_WINDOW));
2779 
2780 	gtk_window_get_position(gtkwindow, &x, &y);
2781 	gtk_window_get_size(gtkwindow, &w, &h);
2782 
2783 	nsoption_set_int(window_width, w);
2784 	nsoption_set_int(window_height, h);
2785 	nsoption_set_int(window_x, x);
2786 	nsoption_set_int(window_y, y);
2787 
2788 	netsurf_mkpath(&choices, NULL, 2, nsgtk_config_home, "Choices");
2789 	if (choices != NULL) {
2790 		nsoption_write(choices, NULL, NULL);
2791 		free(choices);
2792 	}
2793 
2794 	return TRUE;
2795 }
2796 
2797 
2798 /**
2799  * handler for show downloads tool bar item clicked signal
2800  *
2801  * \param widget The widget the signal is being delivered to.
2802  * \param data The toolbar context passed when the signal was connected
2803  * \return TRUE
2804  */
2805 static gboolean
toggledebugging_button_clicked_cb(GtkWidget * widget,gpointer data)2806 toggledebugging_button_clicked_cb(GtkWidget *widget, gpointer data)
2807 {
2808 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2809 	struct browser_window *bw;
2810 
2811 	bw = tb->get_bw(tb->get_ctx);
2812 
2813 	browser_window_debug(bw, CONTENT_DEBUG_REDRAW);
2814 
2815 	nsgtk_window_update_all();
2816 
2817 	return TRUE;
2818 }
2819 
2820 
2821 /**
2822  * handler for debug box tree tool bar item clicked signal
2823  *
2824  * \param widget The widget the signal is being delivered to.
2825  * \param data The toolbar context passed when the signal was connected
2826  * \return TRUE
2827  */
2828 static gboolean
debugboxtree_button_clicked_cb(GtkWidget * widget,gpointer data)2829 debugboxtree_button_clicked_cb(GtkWidget *widget, gpointer data)
2830 {
2831 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2832 	struct browser_window *bw;
2833 	gchar *fname;
2834 	gint handle;
2835 	FILE *f;
2836 
2837 	handle = g_file_open_tmp("nsgtkboxtreeXXXXXX", &fname, NULL);
2838 	if ((handle == -1) || (fname == NULL)) {
2839 		return TRUE;
2840 	}
2841 	close(handle); /* in case it was binary mode */
2842 
2843 	/* save data to temporary file */
2844 	f = fopen(fname, "w");
2845 	if (f == NULL) {
2846 		nsgtk_warning("Error saving box tree dump.",
2847 			      "Unable to open file for writing.");
2848 		unlink(fname);
2849 		return TRUE;
2850 	}
2851 
2852 	bw = tb->get_bw(tb->get_ctx);
2853 
2854 	browser_window_debug_dump(bw, f, CONTENT_DEBUG_RENDER);
2855 
2856 	fclose(f);
2857 
2858 	nsgtk_viewfile("Box Tree Debug", "boxtree", fname);
2859 
2860 	g_free(fname);
2861 
2862 	return TRUE;
2863 }
2864 
2865 
2866 /**
2867  * handler for debug dom tree tool bar item clicked signal
2868  *
2869  * \param widget The widget the signal is being delivered to.
2870  * \param data The toolbar context passed when the signal was connected
2871  * \return TRUE
2872  */
2873 static gboolean
debugdomtree_button_clicked_cb(GtkWidget * widget,gpointer data)2874 debugdomtree_button_clicked_cb(GtkWidget *widget, gpointer data)
2875 {
2876 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2877 	struct browser_window *bw;
2878 	gchar *fname;
2879 	gint handle;
2880 	FILE *f;
2881 
2882 	handle = g_file_open_tmp("nsgtkdomtreeXXXXXX", &fname, NULL);
2883 	if ((handle == -1) || (fname == NULL)) {
2884 		return TRUE;
2885 	}
2886 	close(handle); /* in case it was binary mode */
2887 
2888 	/* save data to temporary file */
2889 	f = fopen(fname, "w");
2890 	if (f == NULL) {
2891 		nsgtk_warning("Error saving box tree dump.",
2892 			      "Unable to open file for writing.");
2893 		unlink(fname);
2894 		return TRUE;
2895 	}
2896 
2897 	bw = tb->get_bw(tb->get_ctx);
2898 
2899 	browser_window_debug_dump(bw, f, CONTENT_DEBUG_DOM);
2900 
2901 	fclose(f);
2902 
2903 	nsgtk_viewfile("DOM Tree Debug", "domtree", fname);
2904 
2905 	g_free(fname);
2906 
2907 	return TRUE;
2908 
2909 }
2910 
2911 
2912 /**
2913  * handler for local history tool bar item clicked signal
2914  *
2915  * \param widget The widget the signal is being delivered to.
2916  * \param data The toolbar context passed when the signal was connected
2917  * \return TRUE
2918  */
2919 static gboolean
localhistory_button_clicked_cb(GtkWidget * widget,gpointer data)2920 localhistory_button_clicked_cb(GtkWidget *widget, gpointer data)
2921 {
2922 	nserror res;
2923 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2924 	struct browser_window *bw;
2925 	GtkWidget *toplevel;
2926 
2927 	toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW);
2928 	if (toplevel != NULL) {
2929 		bw = tb->get_bw(tb->get_ctx);
2930 
2931 		res = nsgtk_local_history_present(GTK_WINDOW(toplevel), bw);
2932 		if (res != NSERROR_OK) {
2933 			NSLOG(netsurf, INFO,
2934 			      "Unable to present local history window.");
2935 		}
2936 	}
2937 	return TRUE;
2938 }
2939 
2940 /**
2941  * handler for history tool bar item clicked signal
2942  *
2943  * \param widget The widget the signal is being delivered to.
2944  * \param data The toolbar context passed when the signal was connected
2945  * \return TRUE
2946  */
2947 static gboolean
history_button_clicked_cb(GtkWidget * widget,gpointer data)2948 history_button_clicked_cb(GtkWidget *widget, gpointer data)
2949 {
2950 	return localhistory_button_clicked_cb(widget, data);
2951 }
2952 
2953 
2954 /**
2955  * handler for global history tool bar item clicked signal
2956  *
2957  * \param widget The widget the signal is being delivered to.
2958  * \param data The toolbar context passed when the signal was connected
2959  * \return TRUE
2960  */
2961 static gboolean
globalhistory_button_clicked_cb(GtkWidget * widget,gpointer data)2962 globalhistory_button_clicked_cb(GtkWidget *widget, gpointer data)
2963 {
2964 	nserror res;
2965 	res = nsgtk_global_history_present();
2966 	if (res != NSERROR_OK) {
2967 		NSLOG(netsurf, INFO,
2968 		      "Unable to initialise global history window.");
2969 	}
2970 	return TRUE;
2971 }
2972 
2973 
2974 /**
2975  * handler for add bookmark tool bar item clicked signal
2976  *
2977  * \param widget The widget the signal is being delivered to.
2978  * \param data The toolbar context passed when the signal was connected
2979  * \return TRUE
2980  */
2981 static gboolean
addbookmarks_button_clicked_cb(GtkWidget * widget,gpointer data)2982 addbookmarks_button_clicked_cb(GtkWidget *widget, gpointer data)
2983 {
2984 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
2985 	struct browser_window *bw;
2986 
2987 	bw = tb->get_bw(tb->get_ctx);
2988 	if (browser_window_has_content(bw)) {
2989 		hotlist_add_url(browser_window_access_url(bw));
2990 	}
2991 	return TRUE;
2992 }
2993 
2994 
2995 /**
2996  * handler for show bookmark tool bar item clicked signal
2997  *
2998  * \param widget The widget the signal is being delivered to.
2999  * \param data The toolbar context passed when the signal was connected
3000  * \return TRUE
3001  */
3002 static gboolean
showbookmarks_button_clicked_cb(GtkWidget * widget,gpointer data)3003 showbookmarks_button_clicked_cb(GtkWidget *widget, gpointer data)
3004 {
3005 	nserror res;
3006 	res = nsgtk_hotlist_present();
3007 	if (res != NSERROR_OK) {
3008 		NSLOG(netsurf, INFO, "Unable to initialise bookmark window.");
3009 	}
3010 	return TRUE;
3011 }
3012 
3013 
3014 /**
3015  * handler for show cookies tool bar item clicked signal
3016  *
3017  * \param widget The widget the signal is being delivered to.
3018  * \param data The toolbar context passed when the signal was connected
3019  * \return TRUE
3020  */
3021 static gboolean
showcookies_button_clicked_cb(GtkWidget * widget,gpointer data)3022 showcookies_button_clicked_cb(GtkWidget *widget, gpointer data)
3023 {
3024 	nserror res;
3025 	res = nsgtk_cookies_present(NULL);
3026 	if (res != NSERROR_OK) {
3027 		NSLOG(netsurf, INFO, "Unable to initialise cookies window.");
3028 	}
3029 	return TRUE;
3030 }
3031 
3032 
3033 /**
3034  * handler for open location tool bar item clicked signal
3035  *
3036  * \param widget The widget the signal is being delivered to.
3037  * \param data The toolbar context passed when the signal was connected
3038  * \return TRUE
3039  */
3040 static gboolean
openlocation_button_clicked_cb(GtkWidget * widget,gpointer data)3041 openlocation_button_clicked_cb(GtkWidget *widget, gpointer data)
3042 {
3043 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
3044 	GtkToolItem *urltitem;
3045 
3046 	urltitem = tb->items[URL_BAR_ITEM].button;
3047 	if (urltitem != NULL) {
3048 		GtkEntry *entry;
3049 		entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(urltitem)));
3050 		gtk_widget_grab_focus(GTK_WIDGET(entry));
3051 	}
3052 	return TRUE;
3053 }
3054 
3055 
3056 /**
3057  * handler for contents tool bar item clicked signal
3058  *
3059  * \param widget The widget the signal is being delivered to.
3060  * \param data The toolbar context passed when the signal was connected
3061  * \return TRUE
3062  */
3063 static gboolean
contents_button_clicked_cb(GtkWidget * widget,gpointer data)3064 contents_button_clicked_cb(GtkWidget *widget, gpointer data)
3065 {
3066 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
3067 	nserror res;
3068 
3069 	res = toolbar_navigate_to_url(tb, "http://www.netsurf-browser.org/documentation/");
3070 	if (res != NSERROR_OK) {
3071 		nsgtk_warning(messages_get_errorcode(res), 0);
3072 	}
3073 
3074 	return TRUE;
3075 }
3076 
3077 /**
3078  * handler for contents tool bar item clicked signal
3079  *
3080  * \param widget The widget the signal is being delivered to.
3081  * \param data The toolbar context passed when the signal was connected
3082  * \return TRUE
3083  */
3084 static gboolean
guide_button_clicked_cb(GtkWidget * widget,gpointer data)3085 guide_button_clicked_cb(GtkWidget *widget, gpointer data)
3086 {
3087 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
3088 	nserror res;
3089 
3090 	res = toolbar_navigate_to_url(tb, "http://www.netsurf-browser.org/documentation/guide");
3091 	if (res != NSERROR_OK) {
3092 		nsgtk_warning(messages_get_errorcode(res), 0);
3093 	}
3094 
3095 	return TRUE;
3096 }
3097 
3098 
3099 /**
3100  * handler for contents tool bar item clicked signal
3101  *
3102  * \param widget The widget the signal is being delivered to.
3103  * \param data The toolbar context passed when the signal was connected
3104  * \return TRUE
3105  */
3106 static gboolean
info_button_clicked_cb(GtkWidget * widget,gpointer data)3107 info_button_clicked_cb(GtkWidget *widget, gpointer data)
3108 {
3109 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
3110 	nserror res;
3111 
3112 	res = toolbar_navigate_to_url(tb, "http://www.netsurf-browser.org/documentation/info");
3113 	if (res != NSERROR_OK) {
3114 		nsgtk_warning(messages_get_errorcode(res), 0);
3115 	}
3116 
3117 	return TRUE;
3118 }
3119 
3120 
3121 /**
3122  * handler for contents tool bar item clicked signal
3123  *
3124  * \param widget The widget the signal is being delivered to.
3125  * \param data The toolbar context passed when the signal was connected
3126  * \return TRUE
3127  */
about_button_clicked_cb(GtkWidget * widget,gpointer data)3128 static gboolean about_button_clicked_cb(GtkWidget *widget, gpointer data)
3129 {
3130 	GtkWindow *parent; /* gtk window widget is in */
3131 
3132 	parent = GTK_WINDOW(gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW));
3133 
3134 	nsgtk_about_dialog_init(parent);
3135 	return TRUE;
3136 }
3137 
3138 /**
3139  * handler for openmenu tool bar item clicked signal
3140  *
3141  * \param widget The widget the signal is being delivered to.
3142  * \param data The toolbar context passed when the signal was connected
3143  * \return TRUE to indicate signal handled.
3144  */
openmenu_button_clicked_cb(GtkWidget * widget,gpointer data)3145 static gboolean openmenu_button_clicked_cb(GtkWidget *widget, gpointer data)
3146 {
3147 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
3148 	struct gui_window *gw;
3149 	struct nsgtk_scaffolding *gs;
3150 
3151 	gw = tb->get_ctx; /** \todo stop assuming the context is a gui window */
3152 
3153 	gs = nsgtk_get_scaffold(gw);
3154 
3155 	nsgtk_scaffolding_burger_menu(gs);
3156 
3157 	return TRUE;
3158 }
3159 
3160 
3161 /* define data plus and data minus handlers */
3162 #define TOOLBAR_ITEM(identifier, name, snstvty, clicked, activate, label, iconame) \
3163 static gboolean								\
3164 nsgtk_toolbar_##name##_data_plus(GtkWidget *widget,			\
3165 				 GdkDragContext *cont,			\
3166 				 GtkSelectionData *selection,		\
3167 				 guint info,				\
3168 				 guint time,				\
3169 				 gpointer data)				\
3170 {									\
3171 	struct nsgtk_toolbar_customisation *tbc;			\
3172 	tbc = (struct nsgtk_toolbar_customisation *)data;		\
3173 	tbc->dragitem = identifier;					\
3174 	tbc->dragfrom = true;						\
3175 	return TRUE;							\
3176 }									\
3177 static gboolean								\
3178 nsgtk_toolbar_##name##_data_minus(GtkWidget *widget,			\
3179 				  GdkDragContext *cont,			\
3180 				  GtkSelectionData *selection,		\
3181 				  guint info,				\
3182 				  guint time,				\
3183 				  gpointer data)			\
3184 {									\
3185 	struct nsgtk_toolbar_customisation *tbc;			\
3186 	tbc = (struct nsgtk_toolbar_customisation *)data;		\
3187 	tbc->dragitem = identifier;					\
3188 	tbc->dragfrom = false;						\
3189 	return TRUE;							\
3190 }
3191 
3192 #include "gtk/toolbar_items.h"
3193 
3194 #undef TOOLBAR_ITEM
3195 
3196 
3197 /**
3198  * create a toolbar item
3199  *
3200  * create a toolbar item and set up its default handlers
3201  */
3202 static nserror
toolbar_item_create(nsgtk_toolbar_button id,struct nsgtk_toolbar_item * item)3203 toolbar_item_create(nsgtk_toolbar_button id, struct nsgtk_toolbar_item *item)
3204 {
3205 	item->location = INACTIVE_LOCATION;
3206 
3207 	/* set item defaults from macro */
3208 	switch (id) {
3209 #define TOOLBAR_ITEM_t(name)						\
3210 		item->clicked = name##_button_clicked_cb;
3211 #define TOOLBAR_ITEM_b(name)						\
3212 		item->clicked = name##_button_clicked_cb;
3213 #define TOOLBAR_ITEM_y(name)						\
3214 		item->clicked = name##_button_clicked_cb;
3215 #define TOOLBAR_ITEM_n(name)						\
3216 		item->clicked = NULL;
3217 #define TOOLBAR_ITEM(identifier, iname, snstvty, clicked, activate, label, iconame) \
3218 	case identifier:						\
3219 		item->name = #iname;					\
3220 		item->sensitivity = snstvty;				\
3221 		item->dataplus = nsgtk_toolbar_##iname##_data_plus;	\
3222 		item->dataminus = nsgtk_toolbar_##iname##_data_minus;	\
3223 		TOOLBAR_ITEM_ ## clicked(iname)				\
3224 		break;
3225 
3226 #include "gtk/toolbar_items.h"
3227 
3228 #undef TOOLBAR_ITEM_t
3229 #undef TOOLBAR_ITEM_y
3230 #undef TOOLBAR_ITEM_n
3231 #undef TOOLBAR_ITEM
3232 
3233 	case PLACEHOLDER_BUTTON:
3234 		return NSERROR_INVALID;
3235 	}
3236 
3237 	return NSERROR_OK;
3238 }
3239 
3240 
3241 /**
3242  * set a toolbar item to a throbber frame number
3243  *
3244  * \param toolbar_item The toolbar item to update
3245  * \param frame The animation frame number to update to
3246  * \return NSERROR_OK on success,
3247  *         NSERROR_INVALID if the toolbar item does not contain an image,
3248  *         NSERROR_BAD_SIZE if the frame is out of range.
3249  */
set_throbber_frame(GtkToolItem * toolbar_item,int frame)3250 static nserror set_throbber_frame(GtkToolItem *toolbar_item, int frame)
3251 {
3252 	nserror res;
3253 	GdkPixbuf *pixbuf;
3254 	GtkImage *throbber;
3255 
3256 	if (toolbar_item == NULL) {
3257 		/* no toolbar item */
3258 		return NSERROR_INVALID;
3259 	}
3260 
3261 	res = nsgtk_throbber_get_frame(frame, &pixbuf);
3262 	if (res != NSERROR_OK) {
3263 		return res;
3264 	}
3265 
3266 	throbber = GTK_IMAGE(gtk_bin_get_child(GTK_BIN(toolbar_item)));
3267 
3268 	gtk_image_set_from_pixbuf(throbber, pixbuf);
3269 
3270 	return NSERROR_OK;
3271 }
3272 
3273 
3274 /**
3275  * Make the throbber run.
3276  *
3277  * scheduled callback to update the throbber
3278  *
3279  * \param p The context passed when scheduled.
3280  */
next_throbber_frame(void * p)3281 static void next_throbber_frame(void *p)
3282 {
3283 	struct nsgtk_toolbar *tb = p;
3284 	nserror res;
3285 
3286 	tb->throb_frame++; /* advance to next frame */
3287 
3288 	res = set_throbber_frame(tb->items[THROBBER_ITEM].button,
3289 				 tb->throb_frame);
3290 	if (res == NSERROR_BAD_SIZE) {
3291 		tb->throb_frame = 1;
3292 		res = set_throbber_frame(tb->items[THROBBER_ITEM].button,
3293 					 tb->throb_frame);
3294 	}
3295 
3296 	/* only schedule next frame if there are no errors */
3297 	if (res == NSERROR_OK) {
3298 		nsgtk_schedule(THROBBER_FRAME_TIME, next_throbber_frame, p);
3299 	}
3300 }
3301 
3302 
3303 /**
3304  * connect signal handlers to a gtk toolbar item
3305  */
3306 static nserror
toolbar_connect_signal(struct nsgtk_toolbar * tb,nsgtk_toolbar_button itemid)3307 toolbar_connect_signal(struct nsgtk_toolbar *tb, nsgtk_toolbar_button itemid)
3308 {
3309 	struct nsgtk_toolbar_item *item;
3310 	GtkEntry *entry;
3311 
3312 	item = &tb->items[itemid];
3313 
3314 	if (item->button != NULL) {
3315 		g_signal_connect(item->button,
3316 				 "size-allocate",
3317 				 G_CALLBACK(toolbar_item_size_allocate_cb),
3318 				 tb);
3319 	}
3320 
3321 	switch (itemid) {
3322 	case URL_BAR_ITEM:
3323 		entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(item->button)));
3324 
3325 		g_signal_connect(GTK_WIDGET(entry),
3326 				 "activate",
3327 				 G_CALLBACK(url_entry_activate_cb),
3328 				 tb);
3329 		g_signal_connect(GTK_WIDGET(entry),
3330 				 "changed",
3331 				 G_CALLBACK(url_entry_changed_cb),
3332 				 tb);
3333 		g_signal_connect(GTK_WIDGET(entry),
3334 				 "icon-release",
3335 				 G_CALLBACK(url_entry_icon_release_cb),
3336 				 tb);
3337 
3338 		nsgtk_completion_connect_signals(entry,
3339 						 tb->get_bw,
3340 						 tb->get_ctx);
3341 		break;
3342 
3343 
3344 	case WEBSEARCH_ITEM:
3345 		entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(item->button)));
3346 
3347 		g_signal_connect(GTK_WIDGET(entry),
3348 				 "activate",
3349 				 G_CALLBACK(websearch_entry_activate_cb),
3350 				 tb);
3351 		g_signal_connect(GTK_WIDGET(entry),
3352 				 "button-press-event",
3353 				 G_CALLBACK(websearch_entry_button_press_cb),
3354 				 tb);
3355 		break;
3356 
3357 	default:
3358 		if ((item->clicked != NULL) && (item->button != NULL)) {
3359 			g_signal_connect(item->button,
3360 					 "clicked",
3361 					 G_CALLBACK(item->clicked),
3362 					 tb);
3363 		}
3364 		break;
3365 
3366 	}
3367 
3368 	return NSERROR_OK;
3369 }
3370 
3371 /**
3372  * connect all signals to widgets in a toolbar
3373  */
toolbar_connect_signals(struct nsgtk_toolbar * tb)3374 static nserror toolbar_connect_signals(struct nsgtk_toolbar *tb)
3375 {
3376 	int location; /* location index */
3377 	nsgtk_toolbar_button itemid; /* item id */
3378 
3379 	for (location = BACK_BUTTON; location < PLACEHOLDER_BUTTON; location++) {
3380 		itemid = itemid_from_location(tb, location);
3381 		if (itemid == PLACEHOLDER_BUTTON) {
3382 			/* no more filled locations */
3383 			break;
3384 		}
3385 		toolbar_connect_signal(tb, itemid);
3386 	}
3387 
3388 	return NSERROR_OK;
3389 }
3390 
3391 
3392 /**
3393  * signal handler for toolbar context menu
3394  *
3395  * \param toolbar The toolbar event is being delivered to
3396  * \param x The x coordinate where the click happened
3397  * \param y The x coordinate where the click happened
3398  * \param button the buttons being pressed
3399  * \param data The context pointer passed when the connection was made.
3400  * \return TRUE to indicate signal handled.
3401  */
3402 static gboolean
toolbar_popup_context_menu_cb(GtkToolbar * toolbar,gint x,gint y,gint button,gpointer data)3403 toolbar_popup_context_menu_cb(GtkToolbar *toolbar,
3404 			      gint x,
3405 			      gint y,
3406 			      gint button,
3407 			      gpointer data)
3408 {
3409 	struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data;
3410 	struct gui_window *gw;
3411 	struct nsgtk_scaffolding *gs;
3412 
3413 	gw = tb->get_ctx; /** \todo stop assuming the context is a gui window */
3414 
3415 	gs = nsgtk_get_scaffold(gw);
3416 
3417 	nsgtk_scaffolding_toolbar_context_menu(gs);
3418 
3419 	return TRUE;
3420 }
3421 
3422 
3423 /**
3424  * toolbar delete signal handler
3425  */
toolbar_destroy_cb(GtkWidget * widget,gpointer data)3426 static void toolbar_destroy_cb(GtkWidget *widget, gpointer data)
3427 {
3428 	struct nsgtk_toolbar *tb;
3429 	tb = (struct nsgtk_toolbar *)data;
3430 
3431 	/* ensure any throbber scheduled is stopped */
3432 	nsgtk_schedule(-1, next_throbber_frame, tb);
3433 
3434 	free(tb);
3435 }
3436 
3437 
3438 /* exported interface documented in toolbar.h */
3439 nserror
nsgtk_toolbar_create(GtkBuilder * builder,struct browser_window * (* get_bw)(void * ctx),void * get_ctx,bool want_location_focus,struct nsgtk_toolbar ** tb_out)3440 nsgtk_toolbar_create(GtkBuilder *builder,
3441 		     struct browser_window *(*get_bw)(void *ctx),
3442 		     void *get_ctx,
3443 		     bool want_location_focus,
3444 		     struct nsgtk_toolbar **tb_out)
3445 {
3446 	nserror res;
3447 	struct nsgtk_toolbar *tb;
3448 	int bidx; /* button index */
3449 
3450 	tb = calloc(1, sizeof(struct nsgtk_toolbar));
3451 	if (tb == NULL) {
3452 		return NSERROR_NOMEM;
3453 	}
3454 
3455 	tb->get_bw = get_bw;
3456 	tb->get_ctx = get_ctx;
3457 	/* set the throbber start frame. */
3458 	tb->throb_frame = 0;
3459 	if (want_location_focus) {
3460 		tb->loc_focus = LFS_WANT;
3461 	} else {
3462 		tb->loc_focus = LFS_IDLE;
3463 	}
3464 
3465 	tb->widget = GTK_TOOLBAR(gtk_builder_get_object(builder, "toolbar"));
3466 	gtk_toolbar_set_show_arrow(tb->widget, TRUE);
3467 
3468 	g_signal_connect(tb->widget,
3469 			 "popup-context-menu",
3470 			 G_CALLBACK(toolbar_popup_context_menu_cb),
3471 			 tb);
3472 
3473 	/* close and cleanup on delete signal */
3474 	g_signal_connect(tb->widget,
3475 			 "destroy",
3476 			 G_CALLBACK(toolbar_destroy_cb),
3477 			 tb);
3478 
3479 	/* allocate button contexts */
3480 	for (bidx = BACK_BUTTON; bidx < PLACEHOLDER_BUTTON; bidx++) {
3481 		res = toolbar_item_create(bidx, &tb->items[bidx]);
3482 		if (res != NSERROR_OK) {
3483 			return res;
3484 		}
3485 	}
3486 
3487 	res = nsgtk_toolbar_update(tb);
3488 	if (res != NSERROR_OK) {
3489 		return res;
3490 	}
3491 
3492 	*tb_out = tb;
3493 	return NSERROR_OK;
3494 }
3495 
3496 
3497 /* exported interface documented in toolbar.h */
nsgtk_toolbar_restyle(struct nsgtk_toolbar * tb)3498 nserror nsgtk_toolbar_restyle(struct nsgtk_toolbar *tb)
3499 {
3500 	/*
3501 	 * reset toolbar size allocation so icon size change affects
3502 	 *  allocated widths.
3503 	 */
3504 	tb->offset = 0;
3505 
3506 	switch (nsoption_int(button_type)) {
3507 
3508 	case 1: /* Small icons */
3509 		gtk_toolbar_set_style(GTK_TOOLBAR(tb->widget),
3510 				      GTK_TOOLBAR_ICONS);
3511 		gtk_toolbar_set_icon_size(GTK_TOOLBAR(tb->widget),
3512 					  GTK_ICON_SIZE_SMALL_TOOLBAR);
3513 		break;
3514 
3515 	case 2: /* Large icons */
3516 		gtk_toolbar_set_style(GTK_TOOLBAR(tb->widget),
3517 				      GTK_TOOLBAR_ICONS);
3518 		gtk_toolbar_set_icon_size(GTK_TOOLBAR(tb->widget),
3519 					  GTK_ICON_SIZE_LARGE_TOOLBAR);
3520 		break;
3521 
3522 	case 3: /* Large icons with text */
3523 		gtk_toolbar_set_style(GTK_TOOLBAR(tb->widget),
3524 				      GTK_TOOLBAR_BOTH);
3525 		gtk_toolbar_set_icon_size(GTK_TOOLBAR(tb->widget),
3526 					  GTK_ICON_SIZE_LARGE_TOOLBAR);
3527 		break;
3528 
3529 	case 4: /* Text icons only */
3530 		gtk_toolbar_set_style(GTK_TOOLBAR(tb->widget),
3531 				      GTK_TOOLBAR_TEXT);
3532 		break;
3533 
3534 	default:
3535 		break;
3536 	}
3537 
3538 	return NSERROR_OK;
3539 }
3540 
3541 
3542 /* exported interface documented in toolbar.h */
nsgtk_toolbar_throbber(struct nsgtk_toolbar * tb,bool active)3543 nserror nsgtk_toolbar_throbber(struct nsgtk_toolbar *tb, bool active)
3544 {
3545 	nserror res;
3546 	struct browser_window *bw;
3547 
3548 	/* Manage the location focus state */
3549 	switch (tb->loc_focus) {
3550 	case LFS_IDLE:
3551 		break;
3552 	case LFS_WANT:
3553 		if (active) {
3554 			tb->loc_focus = LFS_THROB;
3555 		}
3556 		break;
3557 	case LFS_THROB:
3558 		if (!active) {
3559 			tb->loc_focus = LFS_LAST;
3560 		}
3561 		break;
3562 	case LFS_LAST:
3563 		break;
3564 	}
3565 
3566 	/* when activating the throbber simply schedule the next frame update */
3567 	if (active) {
3568 		nsgtk_schedule(THROBBER_FRAME_TIME, next_throbber_frame, tb);
3569 
3570 		set_item_sensitivity(&tb->items[STOP_BUTTON], true);
3571 		set_item_sensitivity(&tb->items[RELOAD_BUTTON], false);
3572 		set_item_action(tb, RELOADSTOP_BUTTON, false);
3573 
3574 		return NSERROR_OK;
3575 	}
3576 
3577 	/* stopping the throbber */
3578 	nsgtk_schedule(-1, next_throbber_frame, tb);
3579 	tb->throb_frame = 0;
3580 	res =  set_throbber_frame(tb->items[THROBBER_ITEM].button,
3581 				  tb->throb_frame);
3582 
3583 	bw = tb->get_bw(tb->get_ctx);
3584 
3585 	/* adjust sensitivity of other items */
3586 	set_item_sensitivity(&tb->items[STOP_BUTTON], false);
3587 	set_item_sensitivity(&tb->items[RELOAD_BUTTON], true);
3588 	set_item_action(tb, RELOADSTOP_BUTTON, true);
3589 	set_item_sensitivity(&tb->items[BACK_BUTTON],
3590 			     browser_window_history_back_available(bw));
3591 	set_item_sensitivity(&tb->items[FORWARD_BUTTON],
3592 			     browser_window_history_forward_available(bw));
3593 	nsgtk_local_history_hide();
3594 
3595 	return res;
3596 }
3597 
3598 
3599 /* exported interface documented in toolbar.h */
nsgtk_toolbar_page_info_change(struct nsgtk_toolbar * tb)3600 nserror nsgtk_toolbar_page_info_change(struct nsgtk_toolbar *tb)
3601 {
3602 	GtkEntry *url_entry;
3603 	browser_window_page_info_state pistate;
3604 	struct browser_window *bw;
3605 	const char *icon_name;
3606 
3607 	if (tb->items[URL_BAR_ITEM].button == NULL) {
3608 		/* no toolbar item */
3609 		return NSERROR_INVALID;
3610 	}
3611 	url_entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(tb->items[URL_BAR_ITEM].button)));
3612 
3613 	bw = tb->get_bw(tb->get_ctx);
3614 
3615 	pistate = browser_window_get_page_info_state(bw);
3616 
3617 	switch (pistate) {
3618 	case PAGE_STATE_INTERNAL:
3619 		icon_name = "page-info-internal";
3620 		break;
3621 
3622 	case PAGE_STATE_LOCAL:
3623 		icon_name = "page-info-local";
3624 		break;
3625 
3626 	case PAGE_STATE_INSECURE:
3627 		icon_name = "page-info-insecure";
3628 		break;
3629 
3630 	case PAGE_STATE_SECURE_OVERRIDE:
3631 		icon_name = "page-info-warning";
3632 		break;
3633 
3634 	case PAGE_STATE_SECURE_ISSUES:
3635 		icon_name = "page-info-warning";
3636 		break;
3637 
3638 	case PAGE_STATE_SECURE:
3639 		icon_name = "page-info-secure";
3640 		break;
3641 
3642 	default:
3643 		icon_name = "page-info-internal";
3644 		break;
3645 	}
3646 
3647 	nsgtk_entry_set_icon_from_icon_name(GTK_WIDGET(url_entry),
3648 					    GTK_ENTRY_ICON_PRIMARY,
3649 					    icon_name);
3650 	return NSERROR_OK;
3651 }
3652 
3653 
3654 /* exported interface documented in toolbar.h */
nsgtk_toolbar_set_url(struct nsgtk_toolbar * tb,nsurl * url)3655 nserror nsgtk_toolbar_set_url(struct nsgtk_toolbar *tb, nsurl *url)
3656 {
3657 	size_t idn_url_l;
3658 	char *idn_url_s = NULL;
3659 	const char *url_text = NULL;
3660 	GtkEntry *url_entry;
3661 
3662 	if (tb->items[URL_BAR_ITEM].button == NULL) {
3663 		/* no toolbar item */
3664 		return NSERROR_INVALID;
3665 	}
3666 	url_entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(tb->items[URL_BAR_ITEM].button)));
3667 
3668 	if (nsoption_bool(display_decoded_idn) == true) {
3669 		if (nsurl_get_utf8(url, &idn_url_s, &idn_url_l) != NSERROR_OK) {
3670 			idn_url_s = NULL;
3671 		}
3672 		url_text = idn_url_s;
3673 	}
3674 	if (url_text == NULL) {
3675 		url_text = nsurl_access(url);
3676 	}
3677 
3678 	if (strcmp(url_text, gtk_entry_get_text(url_entry)) != 0) {
3679 		/* The URL bar content has changed, we need to update it */
3680 		gint startpos, endpos;
3681 		bool was_selected;
3682 		gtk_editable_get_selection_bounds(GTK_EDITABLE(url_entry),
3683 						  &startpos, &endpos);
3684 		was_selected = gtk_widget_is_focus(GTK_WIDGET(url_entry)) &&
3685 			startpos == 0 &&
3686 			endpos == gtk_entry_get_text_length(url_entry);
3687 		gtk_entry_set_text(url_entry, url_text);
3688 		if (was_selected && tb->loc_focus != LFS_IDLE) {
3689 			gtk_widget_grab_focus(GTK_WIDGET(url_entry));
3690 			if (tb->loc_focus == LFS_LAST) {
3691 				tb->loc_focus = LFS_IDLE;
3692 			}
3693 		}
3694 	}
3695 
3696 	if (idn_url_s != NULL) {
3697 		free(idn_url_s);
3698 	}
3699 
3700 	return NSERROR_OK;
3701 }
3702 
3703 
3704 /* exported interface documented in toolbar.h */
3705 nserror
nsgtk_toolbar_set_websearch_image(struct nsgtk_toolbar * tb,GdkPixbuf * pixbuf)3706 nsgtk_toolbar_set_websearch_image(struct nsgtk_toolbar *tb, GdkPixbuf *pixbuf)
3707 {
3708 	GtkWidget *entry;
3709 
3710 	if (tb->items[WEBSEARCH_ITEM].button == NULL) {
3711 		/* no toolbar item */
3712 		return NSERROR_INVALID;
3713 	}
3714 
3715 	entry = gtk_bin_get_child(GTK_BIN(tb->items[WEBSEARCH_ITEM].button));
3716 
3717 	if (pixbuf != NULL) {
3718 		nsgtk_entry_set_icon_from_pixbuf(entry,
3719 						 GTK_ENTRY_ICON_PRIMARY,
3720 						 pixbuf);
3721 	} else {
3722 		nsgtk_entry_set_icon_from_icon_name(entry,
3723 						    GTK_ENTRY_ICON_PRIMARY,
3724 						    NSGTK_STOCK_INFO);
3725 	}
3726 
3727 	return NSERROR_OK;
3728 }
3729 
3730 
3731 /* exported interface documented in toolbar.h */
3732 nserror
nsgtk_toolbar_item_activate(struct nsgtk_toolbar * tb,nsgtk_toolbar_button itemid)3733 nsgtk_toolbar_item_activate(struct nsgtk_toolbar *tb,
3734 			    nsgtk_toolbar_button itemid)
3735 {
3736 	GtkWidget *widget;
3737 
3738 	/* ensure item id in range */
3739 	if ((itemid < BACK_BUTTON) || (itemid >= PLACEHOLDER_BUTTON)) {
3740 		return NSERROR_BAD_PARAMETER;
3741 	}
3742 
3743 	if (tb->items[itemid].clicked == NULL) {
3744 		return NSERROR_INVALID;
3745 	}
3746 
3747 	/*
3748 	 * if item has a widget in the current toolbar use that as the
3749 	 *   signal source otherwise use the toolbar widget itself.
3750 	 */
3751 	if (tb->items[itemid].button != NULL) {
3752 		widget = GTK_WIDGET(tb->items[itemid].button);
3753 	} else {
3754 		widget = GTK_WIDGET(tb->widget);
3755 	}
3756 
3757 	tb->items[itemid].clicked(widget, tb);
3758 
3759 	return NSERROR_OK;
3760 }
3761 
3762 
3763 /* exported interface documented in toolbar.h */
nsgtk_toolbar_show(struct nsgtk_toolbar * tb,bool show)3764 nserror nsgtk_toolbar_show(struct nsgtk_toolbar *tb, bool show)
3765 {
3766 	if (show) {
3767 		gtk_widget_show(GTK_WIDGET(tb->widget));
3768 	} else {
3769 		gtk_widget_hide(GTK_WIDGET(tb->widget));
3770 	}
3771 	return NSERROR_OK;
3772 }
3773 
3774 
3775 /* exported interface documented in toolbar.h */
nsgtk_toolbar_update(struct nsgtk_toolbar * tb)3776 nserror nsgtk_toolbar_update(struct nsgtk_toolbar *tb)
3777 {
3778 	nserror res;
3779 
3780 	/* setup item locations based on user config */
3781 	res = apply_user_button_customisation(tb);
3782 	if (res != NSERROR_OK) {
3783 		return res;
3784 	}
3785 
3786 	/* populate toolbar widget */
3787 	res = populate_gtk_toolbar_widget(tb);
3788 	if (res != NSERROR_OK) {
3789 		return res;
3790 	}
3791 
3792 	/* ensure icon sizes and text labels on toolbar are set */
3793 	res = nsgtk_toolbar_restyle(tb);
3794 	if (res != NSERROR_OK) {
3795 		return res;
3796 	}
3797 
3798 	res = toolbar_connect_signals(tb);
3799 
3800 	return res;
3801 }
3802 
3803 /**
3804  * Find the correct location for popping up a window for the chosen item.
3805  *
3806  * \param tb The toolbar to select from
3807  * \param item_idx The toolbar item to select from
3808  * \param out_x Filled with an appropriate X coordinate
3809  * \param out_y Filled with an appropriate Y coordinate
3810  */
3811 static nserror
nsgtk_toolbar_get_icon_window_position(struct nsgtk_toolbar * tb,int item_idx,int * out_x,int * out_y)3812 nsgtk_toolbar_get_icon_window_position(struct nsgtk_toolbar *tb,
3813 				       int item_idx,
3814 				       int *out_x,
3815 				       int *out_y)
3816 {
3817 	struct nsgtk_toolbar_item *item = &tb->items[item_idx];
3818 	GtkWidget *widget = GTK_WIDGET(item->button);
3819 	GtkAllocation alloc;
3820 	gint rootx, rooty, x, y;
3821 
3822 	switch (item_idx) {
3823 	case URL_BAR_ITEM:
3824 		widget = GTK_WIDGET(gtk_bin_get_child(GTK_BIN(item->button)));
3825 		break;
3826 	default:
3827 		/* Nothing to do here */
3828 		break;
3829 	}
3830 
3831 	nsgtk_widget_get_allocation(widget, &alloc);
3832 
3833 	if (gtk_widget_translate_coordinates(widget,
3834 					     gtk_widget_get_toplevel(widget),
3835 					     0,
3836 					     alloc.height - 1,
3837 					     &x, &y) != TRUE) {
3838 		return NSERROR_UNKNOWN;
3839 	}
3840 
3841 	gtk_window_get_position(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
3842 				&rootx, &rooty);
3843 
3844 	*out_x = rootx + x + 4;
3845 	*out_y = rooty + y + 4;
3846 
3847 	return NSERROR_OK;
3848 }
3849 
nsgtk_toolbar_position_page_info(struct nsgtk_toolbar * tb,struct nsgtk_pi_window * win)3850 nserror nsgtk_toolbar_position_page_info(struct nsgtk_toolbar *tb,
3851 					 struct nsgtk_pi_window *win)
3852 {
3853 	nserror res;
3854 	int x, y;
3855 
3856 	res = nsgtk_toolbar_get_icon_window_position(tb, URL_BAR_ITEM, &x, &y);
3857 	if (res != NSERROR_OK) {
3858 		return res;
3859 	}
3860 
3861 	nsgtk_page_info_set_position(win, x, y);
3862 
3863 	return NSERROR_OK;
3864 }
3865 
3866 /* exported interface documented in toolbar.h */
nsgtk_toolbar_position_local_history(struct nsgtk_toolbar * tb)3867 nserror nsgtk_toolbar_position_local_history(struct nsgtk_toolbar *tb)
3868 {
3869 	nserror res;
3870 	int x, y;
3871 
3872 	res = nsgtk_toolbar_get_icon_window_position(tb, HISTORY_BUTTON, &x, &y);
3873 	if (res != NSERROR_OK) {
3874 		return res;
3875 	}
3876 
3877 	nsgtk_local_history_set_position(x, y);
3878 
3879 	return NSERROR_OK;
3880 }
3881