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 #include <gtk/gtk.h>
20 #include <stdbool.h>
21 #include <string.h>
22 
23 #include "utils/utils.h"
24 #include "utils/log.h"
25 #include "utils/messages.h"
26 #include "utils/nsurl.h"
27 #include "utils/nsoption.h"
28 #include "netsurf/browser_window.h"
29 #include "desktop/browser_history.h"
30 #include "desktop/hotlist.h"
31 
32 #include "gtk/compat.h"
33 #include "gtk/toolbar_items.h"
34 #include "gtk/menu.h"
35 #include "gtk/local_history.h"
36 #include "gtk/gui.h"
37 #include "gtk/download.h"
38 #include "gtk/window.h"
39 #include "gtk/warn.h"
40 #include "gtk/tabs.h"
41 #include "gtk/resources.h"
42 #include "gtk/scaffolding.h"
43 
44 
45 /**
46  * menu entry context
47  */
48 struct nsgtk_menu {
49 	GtkWidget *main; /* main menu entry */
50 	GtkWidget *burger; /* right click menu */
51 	GtkWidget *popup; /* popup menu entry */
52 	/**
53 	 * menu item handler
54 	 */
55 	gboolean (*mhandler)(GtkMenuItem *widget, gpointer data);
56 	const char *iconname; /* name of the icon to use */
57 	bool sensitivity; /* menu item is sensitive */
58 };
59 
60 /**
61  * Core scaffolding structure.
62  */
63 struct nsgtk_scaffolding {
64 	/** global linked list of scaffolding for gui interface adjustments */
65 	struct nsgtk_scaffolding *next, *prev;
66 
67 	/** currently active gui browsing context */
68 	struct gui_window *top_level;
69 
70 	/** Builder object scaffold was created from */
71 	GtkBuilder *builder;
72 
73 	/** scaffold container window */
74 	GtkWindow *window;
75 
76 	/** tab widget holding displayed pages */
77 	GtkNotebook *notebook;
78 
79 	/** handler id for tabs remove callback */
80 	gulong tabs_remove_handler_id;
81 
82 	/** menu bar hierarchy */
83 	struct nsgtk_bar_submenu *menu_bar;
84 
85 	/** burger menu hierarchy */
86 	struct nsgtk_burger_menu *burger_menu;
87 
88 	/** right click popup menu hierarchy */
89 	struct nsgtk_popup_menu *popup_menu;
90 
91 	/** link popup menu */
92 	struct nsgtk_link_menu *link_menu;
93 
94 	/** menu entries widgets for sensitivity adjustment */
95 	struct nsgtk_menu menus[PLACEHOLDER_BUTTON];
96 };
97 
98 /**
99  * current scaffold for model dialogue use
100  */
101 static struct nsgtk_scaffolding *scaf_current;
102 
103 /**
104  * global list for interface changes
105  */
106 static struct nsgtk_scaffolding *scaf_list = NULL;
107 
108 /**
109  * holds the context data for what's under the pointer, when the
110  *  contextual menu is opened.
111  */
112 static struct browser_window_features current_menu_features;
113 
114 
115 /**
116  * Helper to hide popup menu entries by grouping.
117  *
118  * \param menu The popup menu to modify.
119  * \param nav flag to indicate if navigation entries should be hidden.
120  * \param cnp flag to indicate if cut and paste entries should be hidden.
121  * \param custom flag to indicate if menu customisation is hidden.
122  */
123 static void
popup_menu_hide(struct nsgtk_popup_menu * menu,bool nav,bool cnp)124 popup_menu_hide(struct nsgtk_popup_menu *menu, bool nav, bool cnp)
125 {
126 	if (nav) {
127 		gtk_widget_hide(GTK_WIDGET(menu->back_menuitem));
128 		gtk_widget_hide(GTK_WIDGET(menu->forward_menuitem));
129 		gtk_widget_hide(GTK_WIDGET(menu->stop_menuitem));
130 		gtk_widget_hide(GTK_WIDGET(menu->reload_menuitem));
131 
132 		gtk_widget_hide(menu->first_separator);
133 	}
134 
135 	if (cnp) {
136 		gtk_widget_hide(GTK_WIDGET(menu->cut_menuitem));
137 		gtk_widget_hide(GTK_WIDGET(menu->copy_menuitem));
138 		gtk_widget_hide(GTK_WIDGET(menu->paste_menuitem));
139 
140 		gtk_widget_hide(menu->second_separator);
141 	}
142 
143 
144 }
145 
146 
147 /**
148  * Helper to show popup menu entries by grouping.
149  *
150  * \param menu The popup menu to modify.
151  * \param nav flag to indicate if navigation entries should be visible.
152  * \param cnp flag to indicate if cut and paste entries should be visible.
153  * \param custom flag to indicate if menu customisation is visible.
154  */
155 static void
popup_menu_show(struct nsgtk_popup_menu * menu,bool nav,bool cnp)156 popup_menu_show(struct nsgtk_popup_menu *menu, bool nav, bool cnp)
157 {
158 	if (nav) {
159 		gtk_widget_show(GTK_WIDGET(menu->back_menuitem));
160 		gtk_widget_show(GTK_WIDGET(menu->forward_menuitem));
161 		gtk_widget_show(GTK_WIDGET(menu->stop_menuitem));
162 		gtk_widget_show(GTK_WIDGET(menu->reload_menuitem));
163 
164 		gtk_widget_show(menu->first_separator);
165 	}
166 
167 	if (cnp) {
168 		gtk_widget_show(GTK_WIDGET(menu->cut_menuitem));
169 		gtk_widget_show(GTK_WIDGET(menu->copy_menuitem));
170 		gtk_widget_show(GTK_WIDGET(menu->paste_menuitem));
171 
172 		gtk_widget_show(menu->second_separator);
173 	}
174 
175 }
176 
177 
178 /**
179  * resource cleanup function for window destruction.
180  *
181  * gtk event called when window is being destroyed. Need to free any
182  * resources associated with this scaffold,
183  *
184  * \param widget the widget being destroyed
185  * \param data The context pointer passed when the connection was made.
186  */
scaffolding_window_destroy(GtkWidget * widget,gpointer data)187 static void scaffolding_window_destroy(GtkWidget *widget, gpointer data)
188 {
189 	struct nsgtk_scaffolding *gs = data;
190 
191 	NSLOG(netsurf, INFO, "scaffold:%p", gs);
192 
193 	nsgtk_local_history_hide();
194 
195 	if (gs->prev != NULL) {
196 		gs->prev->next = gs->next;
197 	} else {
198 		scaf_list = gs->next;
199 	}
200 	if (gs->next != NULL) {
201 		gs->next->prev = gs->prev;
202 	}
203 
204 	NSLOG(netsurf, INFO, "scaffold list head: %p", scaf_list);
205 
206 	/* ensure menu resources are freed */
207 	nsgtk_menu_bar_destroy(gs->menu_bar);
208 	nsgtk_burger_menu_destroy(gs->burger_menu);
209 	nsgtk_popup_menu_destroy(gs->popup_menu);
210 	nsgtk_link_menu_destroy(gs->link_menu);
211 
212 	g_signal_handler_disconnect(gs->notebook, gs->tabs_remove_handler_id);
213 
214 	free(gs);
215 
216 	if (scaf_list == NULL) {
217 		/* no more open windows - stop the browser */
218 		nsgtk_complete = true;
219 	}
220 }
221 
222 
223 /**
224  * gtk event callback on window delete event.
225  *
226  * prevent window close if download is in progress
227  *
228  * \param widget The widget receiving the delete event
229  * \param event The event
230  * \param data The context pointer passed when the connection was made.
231  * \return TRUE to indicate message handled.
232  */
233 static gboolean
scaffolding_window_delete_event(GtkWidget * widget,GdkEvent * event,gpointer data)234 scaffolding_window_delete_event(GtkWidget *widget,
235 				GdkEvent *event,
236 				gpointer data)
237 {
238 	struct nsgtk_scaffolding *g = data;
239 
240 	if (nsgtk_check_for_downloads(GTK_WINDOW(widget)) == false) {
241 		gtk_widget_destroy(GTK_WIDGET(g->window));
242 	}
243 	return TRUE;
244 }
245 
246 
247 /**
248  * Update the scaffolding controls
249  *
250  * The button sensitivity, url bar and local history visibility are updated
251  *
252  * \param g The scaffolding context to update
253  */
scaffolding_update_context(struct nsgtk_scaffolding * g)254 static void scaffolding_update_context(struct nsgtk_scaffolding *g)
255 {
256 	struct browser_window *bw = nsgtk_get_browser_window(g->top_level);
257 
258 	g->menus[BACK_BUTTON].sensitivity =
259 		browser_window_history_back_available(bw);
260 	g->menus[FORWARD_BUTTON].sensitivity =
261 		browser_window_history_forward_available(bw);
262 
263 	nsgtk_scaffolding_set_sensitivity(g);
264 
265 	nsgtk_local_history_hide();
266 }
267 
268 
269 /**
270  * edit the sensitivity of focused widget
271  *
272  * \todo this needs to update toolbar sensitivity
273  *
274  * \param g The scaffolding context.
275  */
276 static guint
nsgtk_scaffolding_update_edit_actions_sensitivity(struct nsgtk_scaffolding * g)277 nsgtk_scaffolding_update_edit_actions_sensitivity(struct nsgtk_scaffolding *g)
278 {
279 	GtkWidget *widget = gtk_window_get_focus(g->window);
280 
281 	if (GTK_IS_EDITABLE(widget)) {
282 		gboolean has_selection;
283 		has_selection = gtk_editable_get_selection_bounds(
284 					GTK_EDITABLE(widget), NULL, NULL);
285 		g->menus[COPY_BUTTON].sensitivity = has_selection;
286 		g->menus[CUT_BUTTON].sensitivity = has_selection;
287 		g->menus[PASTE_BUTTON].sensitivity = true;
288 	} else {
289 		struct browser_window *bw =
290 			nsgtk_get_browser_window(g->top_level);
291 		browser_editor_flags edit_f =
292 			browser_window_get_editor_flags(bw);
293 
294 		g->menus[COPY_BUTTON].sensitivity =
295 			edit_f & BW_EDITOR_CAN_COPY;
296 		g->menus[CUT_BUTTON].sensitivity =
297 			edit_f & BW_EDITOR_CAN_CUT;
298 		g->menus[PASTE_BUTTON].sensitivity =
299 			edit_f & BW_EDITOR_CAN_PASTE;
300 	}
301 
302 	nsgtk_scaffolding_set_sensitivity(g);
303 
304 	return ((g->menus[COPY_BUTTON].sensitivity) |
305 		(g->menus[CUT_BUTTON].sensitivity) |
306 		(g->menus[PASTE_BUTTON].sensitivity));
307 }
308 
309 
310 /**
311  * make edit actions sensitive
312  *
313  * \todo toolbar sensitivity
314  *
315  * \param g The scaffolding context.
316  */
317 static void
nsgtk_scaffolding_enable_edit_actions_sensitivity(struct nsgtk_scaffolding * g)318 nsgtk_scaffolding_enable_edit_actions_sensitivity(struct nsgtk_scaffolding *g)
319 {
320 	g->menus[PASTE_BUTTON].sensitivity = true;
321 	g->menus[COPY_BUTTON].sensitivity = true;
322 	g->menus[CUT_BUTTON].sensitivity = true;
323 	nsgtk_scaffolding_set_sensitivity(g);
324 
325 	popup_menu_show(g->popup_menu, false, true);
326 }
327 
328 /* signal handling functions for the toolbar, URL bar, and menu bar */
329 
330 /**
331  * gtk event for edit menu being show
332  *
333  * \param widget The menu widget
334  * \param g scaffolding handle
335  * \return TRUE to indicate event handled
336  */
337 static gboolean
nsgtk_window_edit_menu_shown(GtkWidget * widget,struct nsgtk_scaffolding * g)338 nsgtk_window_edit_menu_shown(GtkWidget *widget,
339 			     struct nsgtk_scaffolding *g)
340 {
341 	nsgtk_scaffolding_update_edit_actions_sensitivity(g);
342 
343 	return TRUE;
344 }
345 
346 /**
347  * gtk event handler for edit menu being hidden
348  *
349  * \param widget The menu widget
350  * \param g scaffolding handle
351  * \return TRUE to indicate event handled
352  */
353 static gboolean
nsgtk_window_edit_menu_hidden(GtkWidget * widget,struct nsgtk_scaffolding * g)354 nsgtk_window_edit_menu_hidden(GtkWidget *widget,
355 			      struct nsgtk_scaffolding *g)
356 {
357 	nsgtk_scaffolding_enable_edit_actions_sensitivity(g);
358 
359 	return TRUE;
360 }
361 
362 
363 /**
364  * gtk event handler for popup menu being hidden.
365  *
366  * \param widget The menu widget
367  * \param g scaffolding handle
368  * \return TRUE to indicate event handled
369  */
370 static gboolean
nsgtk_window_popup_menu_hidden(GtkWidget * widget,struct nsgtk_scaffolding * g)371 nsgtk_window_popup_menu_hidden(GtkWidget *widget, struct nsgtk_scaffolding *g)
372 {
373 	nsgtk_scaffolding_enable_edit_actions_sensitivity(g);
374 	return TRUE;
375 }
376 
377 
378 /**
379  * Update the menus when the number of tabs changes.
380  *
381  * \todo toolbar sensitivity
382  * \todo next/previous tab ought to only be visible if there is such a tab
383  *
384  * \param notebook The notebook all the tabs are in
385  * \param page The newly added page container widget
386  * \param page_num The index of the newly added page
387  * \param g The scaffolding context containing the notebook
388  */
389 static void
nsgtk_window_tabs_add(GtkNotebook * notebook,GtkWidget * page,guint page_num,struct nsgtk_scaffolding * g)390 nsgtk_window_tabs_add(GtkNotebook *notebook,
391 		      GtkWidget *page,
392 		      guint page_num,
393 		      struct nsgtk_scaffolding *g)
394 {
395 	gboolean visible = gtk_notebook_get_show_tabs(g->notebook);
396 	g_object_set(g->menu_bar->view_submenu->tabs_menuitem,
397 		     "visible",
398 		     visible,
399 		     NULL);
400 	g_object_set(g->burger_menu->view_submenu->tabs_menuitem,
401 		     "visible",
402 		     visible,
403 		     NULL);
404 
405 	g->menus[NEXTTAB_BUTTON].sensitivity = visible;
406 	g->menus[PREVTAB_BUTTON].sensitivity = visible;
407 	g->menus[CLOSETAB_BUTTON].sensitivity = visible;
408 
409 	nsgtk_scaffolding_set_sensitivity(g);
410 }
411 
412 
413 /**
414  * Update the menus when the number of tabs changes.
415  *
416  * \todo toolbar sensitivity
417  *
418  * \param notebook The notebook all the tabs are in
419  * \param page The page container widget being removed
420  * \param page_num The index of the removed page
421  * \param gs The scaffolding context containing the notebook
422  */
423 static void
nsgtk_window_tabs_remove(GtkNotebook * notebook,GtkWidget * page,guint page_num,struct nsgtk_scaffolding * gs)424 nsgtk_window_tabs_remove(GtkNotebook *notebook,
425 			 GtkWidget *page,
426 			 guint page_num,
427 			 struct nsgtk_scaffolding *gs)
428 {
429 	gboolean visible;
430 
431 	/* if the scaffold is being destroyed it is not useful to
432 	 * update the state, further many of the widgets may have
433 	 * already been destroyed.
434 	 */
435 	if (gtk_widget_in_destruction(GTK_WIDGET(gs->window)) == TRUE) {
436 		return;
437 	}
438 
439 	/* if this is the last tab destroy the scaffold in addition */
440 	if (gtk_notebook_get_n_pages(notebook) == 1) {
441 		gtk_widget_destroy(GTK_WIDGET(gs->window));
442 		return;
443 	}
444 
445 	visible = gtk_notebook_get_show_tabs(gs->notebook);
446 	g_object_set(gs->menu_bar->view_submenu->tabs_menuitem,
447 		     "visible", visible, NULL);
448 	g_object_set(gs->burger_menu->view_submenu->tabs_menuitem,
449 		     "visible", visible, NULL);
450 
451 	gs->menus[NEXTTAB_BUTTON].sensitivity = visible;
452 	gs->menus[PREVTAB_BUTTON].sensitivity = visible;
453 	gs->menus[CLOSETAB_BUTTON].sensitivity = visible;
454 
455 	nsgtk_scaffolding_set_sensitivity(gs);
456 }
457 
458 
459 /* signal handlers for menu entries */
460 
461 /**
462  * handle menu activate signals by calling toolbar item activation
463  */
464 #define TOOLBAR_ITEM_p(identifier, name)				\
465 	static gboolean							\
466 nsgtk_on_##name##_activate_menu(GtkMenuItem *widget, gpointer data)	\
467 {									\
468 	struct nsgtk_scaffolding *gs = (struct nsgtk_scaffolding *)data;\
469 	nsgtk_window_item_activate(gs->top_level, identifier);		\
470 	return TRUE;							\
471 }
472 #define TOOLBAR_ITEM_y(identifier, name)
473 #define TOOLBAR_ITEM_n(identifier, name)
474 #define TOOLBAR_ITEM(identifier, name, sensitivity, clicked, activate, label, iconame) \
475 	TOOLBAR_ITEM_ ## activate(identifier, name)
476 #include "gtk/toolbar_items.h"
477 #undef TOOLBAR_ITEM_y
478 #undef TOOLBAR_ITEM_n
479 #undef TOOLBAR_ITEM_p
480 #undef TOOLBAR_ITEM
481 
482 
483 static gboolean
nsgtk_on_savelink_activate_menu(GtkMenuItem * widget,gpointer data)484 nsgtk_on_savelink_activate_menu(GtkMenuItem *widget, gpointer data)
485 {
486 	struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *) data;
487 	struct gui_window *gui = g->top_level;
488 	struct browser_window *bw = nsgtk_get_browser_window(gui);
489 	nserror err;
490 
491 	if (current_menu_features.link == NULL)
492 		return FALSE;
493 
494 	err = browser_window_navigate(bw,
495 				current_menu_features.link,
496 				NULL,
497 				BW_NAVIGATE_DOWNLOAD,
498 				NULL,
499 				NULL,
500 				NULL);
501 	if (err != NSERROR_OK) {
502 		nsgtk_warning(messages_get_errorcode(err), 0);
503 	}
504 
505 	return TRUE;
506 }
507 
508 
509 /**
510  * Handler for opening new window from a link. attached to the popup menu.
511  */
512 static gboolean
nsgtk_on_link_openwin_activate_menu(GtkMenuItem * widget,gpointer data)513 nsgtk_on_link_openwin_activate_menu(GtkMenuItem *widget, gpointer data)
514 {
515 	struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *) data;
516 	struct gui_window *gui = g->top_level;
517 	struct browser_window *bw = nsgtk_get_browser_window(gui);
518 	nserror err;
519 
520 	if (current_menu_features.link == NULL)
521 		return FALSE;
522 
523 	err = browser_window_create(BW_CREATE_CLONE | BW_CREATE_HISTORY,
524 				current_menu_features.link, NULL, bw, NULL);
525 	if (err != NSERROR_OK) {
526 		nsgtk_warning(messages_get_errorcode(err), 0);
527 	}
528 
529 	return TRUE;
530 }
531 
532 
533 /**
534  * Handler for opening new tab from a link. attached to the popup menu.
535  */
536 static gboolean
nsgtk_on_link_opentab_activate_menu(GtkMenuItem * widget,gpointer data)537 nsgtk_on_link_opentab_activate_menu(GtkMenuItem *widget, gpointer data)
538 {
539 	struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *) data;
540 	struct gui_window *gui = g->top_level;
541 	struct browser_window *bw = nsgtk_get_browser_window(gui);
542 	nserror err;
543 
544 	if (current_menu_features.link == NULL)
545 		return FALSE;
546 
547 	err = browser_window_create(BW_CREATE_CLONE |
548 				    BW_CREATE_HISTORY |
549 				    BW_CREATE_TAB,
550 				    current_menu_features.link, NULL, bw, NULL);
551 	if (err != NSERROR_OK) {
552 		nsgtk_warning(messages_get_errorcode(err), 0);
553 	}
554 
555 	return TRUE;
556 }
557 
558 
559 /**
560  * Handler for bookmarking a link. attached to the popup menu.
561  */
562 static gboolean
nsgtk_on_link_bookmark_activate_menu(GtkMenuItem * widget,gpointer data)563 nsgtk_on_link_bookmark_activate_menu(GtkMenuItem *widget, gpointer data)
564 {
565 	if (current_menu_features.link == NULL)
566 		return FALSE;
567 
568 	hotlist_add_url(current_menu_features.link);
569 
570 	return TRUE;
571 }
572 
573 
574 /**
575  * Handler for copying a link. attached to the popup menu.
576  */
577 static gboolean
nsgtk_on_link_copy_activate_menu(GtkMenuItem * widget,gpointer data)578 nsgtk_on_link_copy_activate_menu(GtkMenuItem *widget, gpointer data)
579 {
580 	GtkClipboard *clipboard;
581 
582 	if (current_menu_features.link == NULL)
583 		return FALSE;
584 
585 	clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
586 	gtk_clipboard_set_text(clipboard,
587 			       nsurl_access(current_menu_features.link), -1);
588 
589 	return TRUE;
590 }
591 
592 
nsgtk_on_find_activate_menu(GtkMenuItem * widget,gpointer data)593 static gboolean nsgtk_on_find_activate_menu(GtkMenuItem *widget, gpointer data)
594 {
595 	struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *)data;
596 
597 	nsgtk_window_search_toggle(g->top_level);
598 
599 	return TRUE;
600 }
601 
get_bar_show(bool * menu,bool * tool)602 static nserror get_bar_show(bool *menu, bool *tool)
603 {
604 	const char *cur_bar_show;
605 
606 	*menu = false;
607 	*tool = false;
608 
609 	cur_bar_show = nsoption_charp(bar_show);
610 	if (cur_bar_show != NULL) {
611 		if (strcmp(cur_bar_show, "menu/tool") == 0) {
612 			*menu = true;
613 			*tool = true;
614 		} else if (strcmp(cur_bar_show, "menu") == 0) {
615 			*menu = true;
616 		} else if (strcmp(cur_bar_show, "tool") == 0) {
617 			*tool = true;
618 		}
619 	}
620 
621 	return NSERROR_OK;
622 }
623 
set_bar_show(const char * bar,bool show)624 static nserror set_bar_show(const char *bar, bool show)
625 {
626 	bool menu;
627 	bool tool;
628 	const char *new_bar_show;
629 
630 	get_bar_show(&menu, &tool);
631 
632 	if (strcmp(bar, "menu") == 0) {
633 		menu = show;
634 	} else if (strcmp(bar, "tool") == 0) {
635 		tool = show;
636 	}
637 
638 	if ((menu == true) && (tool == true)) {
639 		new_bar_show = "menu/tool";
640 	} else if (menu == true) {
641 		new_bar_show = "menu";
642 	} else if (tool == true) {
643 		new_bar_show = "tool";
644 	} else {
645 		new_bar_show = "none";
646 	}
647 	nsoption_set_charp(bar_show, strdup(new_bar_show));
648 
649 	return NSERROR_OK;
650 }
651 
652 static gboolean
nsgtk_on_menubar_activate_menu(GtkMenuItem * widget,gpointer data)653 nsgtk_on_menubar_activate_menu(GtkMenuItem *widget, gpointer data)
654 {
655 	struct nsgtk_scaffolding *gs = (struct nsgtk_scaffolding *)data;
656 	GtkCheckMenuItem *bmcmi; /* burger menu check */
657 	GtkCheckMenuItem *mbcmi; /* menu bar check */
658 	GtkCheckMenuItem *tbcmi; /* popup menu check */
659 
660 	bmcmi = GTK_CHECK_MENU_ITEM(gs->burger_menu->view_submenu->toolbars_submenu->menubar_menuitem);
661 	mbcmi = GTK_CHECK_MENU_ITEM(gs->menu_bar->view_submenu->toolbars_submenu->menubar_menuitem);
662 	tbcmi = GTK_CHECK_MENU_ITEM(gs->popup_menu->toolbars_submenu->menubar_menuitem);
663 
664 	/* ensure menubar and burger menu checkboxes are both updated */
665 	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) {
666 		if (gtk_check_menu_item_get_active(bmcmi) == FALSE) {
667 			gtk_check_menu_item_set_active(bmcmi, TRUE);
668 		}
669 
670 		if (gtk_check_menu_item_get_active(mbcmi) == FALSE) {
671 			gtk_check_menu_item_set_active(mbcmi, TRUE);
672 		}
673 
674 		if (gtk_check_menu_item_get_active(tbcmi) == FALSE) {
675 			gtk_check_menu_item_set_active(tbcmi, TRUE);
676 		}
677 
678 		gtk_widget_show(GTK_WIDGET(gs->menu_bar->bar_menu));
679 		set_bar_show("menu", true);
680 	} else {
681 		if (gtk_check_menu_item_get_active(bmcmi) == TRUE) {
682 			gtk_check_menu_item_set_active(bmcmi, FALSE);
683 		}
684 
685 		if (gtk_check_menu_item_get_active(mbcmi) == TRUE) {
686 			gtk_check_menu_item_set_active(mbcmi, FALSE);
687 		}
688 
689 		if (gtk_check_menu_item_get_active(tbcmi) == TRUE) {
690 			gtk_check_menu_item_set_active(tbcmi, FALSE);
691 		}
692 
693 		gtk_widget_hide(GTK_WIDGET(gs->menu_bar->bar_menu));
694 		set_bar_show("menu", false);
695 	}
696 	return TRUE;
697 }
698 
699 
700 static gboolean
nsgtk_on_toolbar_activate_menu(GtkMenuItem * widget,gpointer data)701 nsgtk_on_toolbar_activate_menu(GtkMenuItem *widget, gpointer data)
702 {
703 	struct nsgtk_scaffolding *gs = (struct nsgtk_scaffolding *)data;
704 	GtkCheckMenuItem *bmcmi; /* burger menu check */
705 	GtkCheckMenuItem *mbcmi; /* menu bar check */
706 	GtkCheckMenuItem *tbcmi; /* popup menu check */
707 
708 	bmcmi = GTK_CHECK_MENU_ITEM(gs->burger_menu->view_submenu->toolbars_submenu->toolbar_menuitem);
709 	mbcmi = GTK_CHECK_MENU_ITEM(gs->menu_bar->view_submenu->toolbars_submenu->toolbar_menuitem);
710 	tbcmi = GTK_CHECK_MENU_ITEM(gs->popup_menu->toolbars_submenu->toolbar_menuitem);
711 
712 	/* ensure menubar and burger menu checkboxes are both updated */
713 	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) {
714 		if (gtk_check_menu_item_get_active(bmcmi) == FALSE) {
715 			gtk_check_menu_item_set_active(bmcmi, TRUE);
716 		}
717 
718 		if (gtk_check_menu_item_get_active(mbcmi) == FALSE) {
719 			gtk_check_menu_item_set_active(mbcmi, TRUE);
720 		}
721 
722 		if (gtk_check_menu_item_get_active(tbcmi) == FALSE) {
723 			gtk_check_menu_item_set_active(tbcmi, TRUE);
724 		}
725 
726 		nsgtk_window_toolbar_show(gs, true);
727 		set_bar_show("tool", true);
728 	} else {
729 		if (gtk_check_menu_item_get_active(bmcmi) == TRUE) {
730 			gtk_check_menu_item_set_active(bmcmi, FALSE);
731 		}
732 
733 		if (gtk_check_menu_item_get_active(mbcmi) == TRUE) {
734 			gtk_check_menu_item_set_active(mbcmi, FALSE);
735 		}
736 
737 		if (gtk_check_menu_item_get_active(tbcmi) == TRUE) {
738 			gtk_check_menu_item_set_active(tbcmi, FALSE);
739 		}
740 
741 		nsgtk_window_toolbar_show(gs, false);
742 		set_bar_show("tool", false);
743 	}
744 	return TRUE;
745 }
746 
747 
748 static gboolean
nsgtk_on_nexttab_activate_menu(GtkMenuItem * widget,gpointer data)749 nsgtk_on_nexttab_activate_menu(GtkMenuItem *widget, gpointer data)
750 {
751 	struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *)data;
752 
753 	nsgtk_tab_next(g->notebook);
754 
755 	return TRUE;
756 }
757 
758 
759 static gboolean
nsgtk_on_prevtab_activate_menu(GtkMenuItem * widget,gpointer data)760 nsgtk_on_prevtab_activate_menu(GtkMenuItem *widget, gpointer data)
761 {
762 	struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *)data;
763 
764 	nsgtk_tab_prev(g->notebook);
765 
766 	return TRUE;
767 }
768 
769 
770 /**
771  * menu signal handler for activation on close tab item
772  */
773 static gboolean
nsgtk_on_closetab_activate_menu(GtkMenuItem * widget,gpointer data)774 nsgtk_on_closetab_activate_menu(GtkMenuItem *widget, gpointer data)
775 {
776 	struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *)data;
777 
778 	nsgtk_tab_close_current(g->notebook);
779 
780 	return TRUE;
781 }
782 
783 /* end of menu callback handlers */
784 
785 /**
786  * attach gtk signal handlers for menus
787  */
nsgtk_menu_connect_signals(struct nsgtk_scaffolding * g)788 static void nsgtk_menu_connect_signals(struct nsgtk_scaffolding *g)
789 {
790 	int idx; /* item index */
791 	for (idx = BACK_BUTTON; idx < PLACEHOLDER_BUTTON; idx++) {
792 		if (g->menus[idx].main != NULL) {
793 			g_signal_connect(g->menus[idx].main,
794 					 "activate",
795 					 G_CALLBACK(g->menus[idx].mhandler),
796 					 g);
797 		}
798 		if (g->menus[idx].burger != NULL) {
799 			g_signal_connect(g->menus[idx].burger,
800 					 "activate",
801 					 G_CALLBACK(g->menus[idx].mhandler),
802 					 g);
803 		}
804 		if (g->menus[idx].popup != NULL) {
805 			g_signal_connect(g->menus[idx].popup,
806 					 "activate",
807 					G_CALLBACK(g->menus[idx].mhandler),
808 					 g);
809 		}
810 	}
811 }
812 
813 
814 /**
815  * Create and connect handlers to bar menu.
816  *
817  * \param gs scaffolding to attach popup menu to.
818  * \param group The accelerator group to use for the popup.
819  * \param showmenu if the bar menu should be shown
820  * \param showtool if the toolabar should be shown
821  * \return menu structure on success or NULL on error.
822  */
823 static struct nsgtk_bar_submenu *
create_scaffolding_bar_menu(struct nsgtk_scaffolding * gs,GtkAccelGroup * group,bool showmenu,bool showtool)824 create_scaffolding_bar_menu(struct nsgtk_scaffolding *gs,
825 			    GtkAccelGroup *group,
826 			    bool showmenu,
827 			    bool showtool)
828 {
829 	GtkMenuShell *menushell;
830 	struct nsgtk_bar_submenu *nmenu;
831 
832 	menushell = GTK_MENU_SHELL(gtk_builder_get_object(gs->builder,
833 							  "menubar"));
834 
835 	nmenu = nsgtk_menu_bar_create(menushell, group);
836 	if (nmenu == NULL) {
837 		return NULL;
838 	}
839 
840 	/* set menu bar visibility */
841 	if (showmenu) {
842 		gtk_widget_show(GTK_WIDGET(nmenu->bar_menu));
843 	} else {
844 		gtk_widget_hide(GTK_WIDGET(nmenu->bar_menu));
845 	}
846 
847 	/* set checks correct way on toolbar submenu */
848 	gtk_check_menu_item_set_active(nmenu->view_submenu->toolbars_submenu->menubar_menuitem, showmenu);
849 	gtk_check_menu_item_set_active(nmenu->view_submenu->toolbars_submenu->toolbar_menuitem, showtool);
850 
851 	/* bar menu signal handlers for edit controls */
852 	g_signal_connect(nmenu->edit_submenu->edit,
853 			 "show",
854 			 G_CALLBACK(nsgtk_window_edit_menu_shown),
855 			 gs);
856 
857 	g_signal_connect(nmenu->edit_submenu->edit,
858 			 "hide",
859 			 G_CALLBACK(nsgtk_window_edit_menu_hidden),
860 			 gs);
861 
862 	/*
863 	 * attach signal handlers for menubar and toolbar visibility toggling
864 	 */
865 	g_signal_connect(nmenu->view_submenu->toolbars_submenu->menubar_menuitem,
866 			 "toggled",
867 			 G_CALLBACK(nsgtk_on_menubar_activate_menu),
868 			 gs);
869 
870 	g_signal_connect(nmenu->view_submenu->toolbars_submenu->toolbar_menuitem,
871 			 "toggled",
872 			 G_CALLBACK(nsgtk_on_toolbar_activate_menu),
873 			 gs);
874 
875 
876 	return nmenu;
877 }
878 
879 
880 /**
881  * Create and connect handlers to burger menu.
882  *
883  * \param g scaffolding to attach popup menu to.
884  * \param group The accelerator group to use for the popup.
885  * \param showbar if the bar menu should be shown
886  * \param showtool if the toolabar should be shown
887  * \return menu structure on success or NULL on error.
888  */
889 static struct nsgtk_burger_menu *
create_scaffolding_burger_menu(struct nsgtk_scaffolding * gs,GtkAccelGroup * group,bool showbar,bool showtool)890 create_scaffolding_burger_menu(struct nsgtk_scaffolding *gs,
891 			       GtkAccelGroup *group,
892 			       bool showbar,
893 			       bool showtool)
894 {
895 	struct nsgtk_burger_menu *nmenu;
896 
897 	nmenu = nsgtk_burger_menu_create(group);
898 
899 	if (nmenu == NULL) {
900 		return NULL;
901 	}
902 
903 	/* set checks correct way on toolbar submenu */
904 	gtk_check_menu_item_set_active(nmenu->view_submenu->toolbars_submenu->menubar_menuitem, showbar);
905 	gtk_check_menu_item_set_active(nmenu->view_submenu->toolbars_submenu->toolbar_menuitem, showtool);
906 
907 	g_signal_connect(nmenu->view_submenu->toolbars_submenu->menubar_menuitem,
908 			 "toggled",
909 			 G_CALLBACK(nsgtk_on_menubar_activate_menu),
910 			 gs);
911 	g_signal_connect(nmenu->view_submenu->toolbars_submenu->toolbar_menuitem,
912 			 "toggled",
913 			 G_CALLBACK(nsgtk_on_toolbar_activate_menu),
914 			 gs);
915 	return nmenu;
916 }
917 
918 
919 /**
920  * Create and connect handlers to popup menu.
921  *
922  * \param gs scaffolding to attach popup menu to.
923  * \param group The accelerator group to use for the popup.
924  * \param showbar if the bar menu should be shown
925  * \param showtool if the toolabar should be shown
926  * \return menu structure on success or NULL on error.
927  */
928 static struct nsgtk_popup_menu *
create_scaffolding_popup_menu(struct nsgtk_scaffolding * gs,GtkAccelGroup * group,bool showbar,bool showtool)929 create_scaffolding_popup_menu(struct nsgtk_scaffolding *gs,
930 			      GtkAccelGroup *group,
931 			      bool showbar,
932 			      bool showtool)
933 {
934 	struct nsgtk_popup_menu *nmenu;
935 
936 	nmenu = nsgtk_popup_menu_create(group);
937 
938 	if (nmenu == NULL) {
939 		return NULL;
940 	}
941 	/* set checks correct way on toolbar submenu */
942 	gtk_check_menu_item_set_active(nmenu->toolbars_submenu->menubar_menuitem, showbar);
943 	gtk_check_menu_item_set_active(nmenu->toolbars_submenu->toolbar_menuitem, showtool);
944 
945 	g_signal_connect(nmenu->popup_menu,
946 			 "hide",
947 			 G_CALLBACK(nsgtk_window_popup_menu_hidden),
948 			 gs);
949 
950 	g_signal_connect(nmenu->toolbars_submenu->menubar_menuitem,
951 			 "toggled",
952 			 G_CALLBACK(nsgtk_on_menubar_activate_menu),
953 			 gs);
954 	g_signal_connect(nmenu->toolbars_submenu->toolbar_menuitem,
955 			 "toggled",
956 			 G_CALLBACK(nsgtk_on_toolbar_activate_menu),
957 			 gs);
958 
959 	/* set initial popup menu visibility */
960 	popup_menu_hide(nmenu, false, false);
961 
962 	return nmenu;
963 }
964 
965 
966 /**
967  * Create and connect handlers to link popup menu.
968  *
969  * \param g scaffolding to attach popup menu to.
970  * \param group The accelerator group to use for the popup.
971  * \return true on success or false on error.
972  */
973 static struct nsgtk_link_menu *
create_scaffolding_link_menu(struct nsgtk_scaffolding * g,GtkAccelGroup * group)974 create_scaffolding_link_menu(struct nsgtk_scaffolding *g, GtkAccelGroup *group)
975 {
976 	struct nsgtk_link_menu *nmenu;
977 
978 	nmenu = nsgtk_link_menu_create(group);
979 
980 	if (nmenu == NULL) {
981 		return NULL;
982 	}
983 
984 	g_signal_connect(nmenu->save_menuitem,
985 			 "activate",
986 			 G_CALLBACK(nsgtk_on_savelink_activate_menu),
987 			 g);
988 
989 	g_signal_connect(nmenu->opentab_menuitem,
990 			 "activate",
991 			 G_CALLBACK(nsgtk_on_link_opentab_activate_menu),
992 			 g);
993 
994 	g_signal_connect(nmenu->openwin_menuitem,
995 			 "activate",
996 			 G_CALLBACK(nsgtk_on_link_openwin_activate_menu),
997 			 g);
998 
999 	g_signal_connect(nmenu->bookmark_menuitem,
1000 			 "activate",
1001 			 G_CALLBACK(nsgtk_on_link_bookmark_activate_menu),
1002 			 g);
1003 
1004 	g_signal_connect(nmenu->copy_menuitem,
1005 			 "activate",
1006 			 G_CALLBACK(nsgtk_on_link_copy_activate_menu),
1007 			 g);
1008 
1009 	return nmenu;
1010 }
1011 
1012 
1013 /**
1014  * initialiase the menu signal handlers ready for connection
1015  */
nsgtk_menu_initialise(struct nsgtk_scaffolding * g)1016 static nserror nsgtk_menu_initialise(struct nsgtk_scaffolding *g)
1017 {
1018 #define TOOLBAR_ITEM_p(identifier, name, iconame)			\
1019 	g->menus[identifier].mhandler = nsgtk_on_##name##_activate_menu; \
1020 	g->menus[identifier].iconname = iconame;
1021 #define TOOLBAR_ITEM_y(identifier, name, iconame)			\
1022 	g->menus[identifier].mhandler = nsgtk_on_##name##_activate_menu; \
1023 	g->menus[identifier].iconname = iconame;
1024 #define TOOLBAR_ITEM_n(identifier, name, iconame)			\
1025 	g->menus[identifier].mhandler = NULL;				\
1026 	g->menus[identifier].iconname = iconame;
1027 #define TOOLBAR_ITEM(identifier, name, snstvty, clicked, activate, label, iconame) \
1028 	g->menus[identifier].sensitivity = snstvty;			\
1029 	TOOLBAR_ITEM_ ## activate(identifier, name, iconame)
1030 #include "gtk/toolbar_items.h"
1031 #undef TOOLBAR_ITEM_y
1032 #undef TOOLBAR_ITEM_n
1033 #undef TOOLBAR_ITEM
1034 
1035 	/* items on menubar, burger */
1036 #define ITEM_MB(p, q, r)						\
1037 	g->menus[p##_BUTTON].main = g->menu_bar->r##_submenu->q##_menuitem; \
1038 	g->menus[p##_BUTTON].burger = g->burger_menu->r##_submenu->q##_menuitem
1039 
1040 	/* items on menubar, burger and context popup submenu */
1041 #define ITEM_MBP(p, q, r)						\
1042 	g->menus[p##_BUTTON].main = g->menu_bar->r##_submenu->q##_menuitem; \
1043 	g->menus[p##_BUTTON].burger = g->burger_menu->r##_submenu->q##_menuitem; \
1044 	g->menus[p##_BUTTON].popup = g->popup_menu->r##_submenu->q##_menuitem
1045 
1046 	/* items on menubar, burger and context popup */
1047 #define ITEM_MBp(p, q, r)						\
1048 	g->menus[p##_BUTTON].main = g->menu_bar->r##_submenu->q##_menuitem; \
1049 	g->menus[p##_BUTTON].burger = g->burger_menu->r##_submenu->q##_menuitem; \
1050 	g->menus[p##_BUTTON].popup = g->popup_menu->q##_menuitem
1051 
1052 
1053 	/* file menu */
1054 	ITEM_MB(NEWWINDOW, newwindow, file);
1055 	ITEM_MB(NEWTAB, newtab, file);
1056 	ITEM_MB(OPENFILE, openfile, file);
1057 	ITEM_MB(CLOSEWINDOW, closewindow, file);
1058 	ITEM_MB(PRINTPREVIEW, printpreview, file);
1059 	ITEM_MB(PRINT, print, file);
1060 	ITEM_MB(QUIT, quit, file);
1061 	/* file - export submenu */
1062 	ITEM_MB(SAVEPAGE, savepage, file_submenu->export);
1063 	ITEM_MB(PLAINTEXT, plaintext, file_submenu->export);
1064 	ITEM_MB(PDF, pdf, file_submenu->export);
1065 
1066 	/* edit menu */
1067 	ITEM_MBp(CUT, cut, edit);
1068 	ITEM_MBp(COPY, copy, edit);
1069 	ITEM_MBp(PASTE, paste, edit);
1070 	ITEM_MB(DELETE, delete, edit);
1071 	ITEM_MB(SELECTALL, selectall, edit);
1072 	ITEM_MB(FIND, find, edit);
1073 	ITEM_MB(PREFERENCES, preferences, edit);
1074 
1075 	/* view menu */
1076 	ITEM_MB(FULLSCREEN, fullscreen, view);
1077 	ITEM_MB(SAVEWINDOWSIZE, savewindowsize, view);
1078 	/* view - scale submenu */
1079 	ITEM_MB(ZOOMPLUS, zoomplus, view_submenu->scaleview);
1080 	ITEM_MB(ZOOMMINUS, zoomminus, view_submenu->scaleview);
1081 	ITEM_MB(ZOOMNORMAL, zoomnormal, view_submenu->scaleview);
1082 	/* view - tabs submenu */
1083 	ITEM_MB(NEXTTAB, nexttab, view_submenu->tabs);
1084 	ITEM_MB(PREVTAB, prevtab, view_submenu->tabs);
1085 	ITEM_MB(CLOSETAB, closetab, view_submenu->tabs);
1086 	/* view - toolbars submenu */
1087 	ITEM_MB(CUSTOMIZE, customize, view_submenu->toolbars);
1088 	g->menus[CUSTOMIZE_BUTTON].popup = g->popup_menu->toolbars_submenu->customize_menuitem;
1089 
1090 	/* navigation menu */
1091 	ITEM_MBp(BACK, back, nav);
1092 	ITEM_MBp(FORWARD, forward, nav);
1093 	ITEM_MBp(STOP, stop, nav);
1094 	ITEM_MBp(RELOAD, reload, nav);
1095 	ITEM_MB(HOME, home, nav);
1096 	ITEM_MB(LOCALHISTORY, localhistory, nav);
1097 	ITEM_MB(GLOBALHISTORY, globalhistory, nav);
1098 	ITEM_MB(ADDBOOKMARKS, addbookmarks, nav);
1099 	ITEM_MB(SHOWBOOKMARKS, showbookmarks, nav);
1100 	ITEM_MB(OPENLOCATION, openlocation, nav);
1101 
1102 	/* tools menu */
1103 	ITEM_MBP(DOWNLOADS, downloads, tools);
1104 	ITEM_MBP(SHOWCOOKIES, showcookies, tools);
1105 	/* tools > developer submenu */
1106 	ITEM_MBP(VIEWSOURCE, viewsource, tools_submenu->developer);
1107 	ITEM_MBP(TOGGLEDEBUGGING, toggledebugging, tools_submenu->developer);
1108 	ITEM_MBP(SAVEBOXTREE, debugboxtree, tools_submenu->developer);
1109 	ITEM_MBP(SAVEDOMTREE, debugdomtree, tools_submenu->developer);
1110 
1111 	/* help menu */
1112 	ITEM_MB(CONTENTS, contents, help);
1113 	ITEM_MB(GUIDE, guide, help);
1114 	ITEM_MB(INFO, info, help);
1115 	ITEM_MB(ABOUT, about, help);
1116 
1117 
1118 #undef ITEM_MB
1119 #undef ITEM_MBp
1120 #undef ITEM_MBP
1121 
1122 	return NSERROR_OK;
1123 }
1124 
1125 
nsgtk_menu_set_sensitivity(struct nsgtk_scaffolding * g)1126 static void nsgtk_menu_set_sensitivity(struct nsgtk_scaffolding *g)
1127 {
1128 
1129 	for (int i = BACK_BUTTON; i < PLACEHOLDER_BUTTON; i++) {
1130 		if (g->menus[i].main != NULL) {
1131 			gtk_widget_set_sensitive(GTK_WIDGET(
1132 					g->menus[i].main),
1133 					g->menus[i].sensitivity);
1134 		}
1135 		if (g->menus[i].burger != NULL) {
1136 			gtk_widget_set_sensitive(GTK_WIDGET(
1137 					g->menus[i].burger),
1138 					g->menus[i].sensitivity);
1139 		}
1140 		if (g->menus[i].popup != NULL) {
1141 			gtk_widget_set_sensitive(GTK_WIDGET(
1142 					g->menus[i].popup),
1143 					g->menus[i].sensitivity);
1144 		}
1145 	}
1146 }
1147 
1148 
1149 /* set menu items to have icons */
nsgtk_menu_set_icons(struct nsgtk_scaffolding * g)1150 static void nsgtk_menu_set_icons(struct nsgtk_scaffolding *g)
1151 {
1152 	GtkWidget *img;
1153 	for (int i = BACK_BUTTON; i < PLACEHOLDER_BUTTON; i++) {
1154 		/* ensure there is an icon name */
1155 		if (g->menus[i].iconname == NULL) {
1156 			continue;
1157 		}
1158 
1159 		if (g->menus[i].main != NULL) {
1160 		img = gtk_image_new_from_icon_name(g->menus[i].iconname,
1161 						   GTK_ICON_SIZE_MENU);
1162 			nsgtk_image_menu_item_set_image(GTK_WIDGET(g->menus[i].main), img);
1163 		}
1164 		if (g->menus[i].burger != NULL) {
1165 		img = gtk_image_new_from_icon_name(g->menus[i].iconname,
1166 						   GTK_ICON_SIZE_MENU);
1167 			nsgtk_image_menu_item_set_image(GTK_WIDGET(g->menus[i].burger), img);
1168 		}
1169 		if (g->menus[i].popup != NULL) {
1170 		img = gtk_image_new_from_icon_name(g->menus[i].iconname,
1171 						   GTK_ICON_SIZE_MENU);
1172 			nsgtk_image_menu_item_set_image(GTK_WIDGET(g->menus[i].popup), img);
1173 		}
1174 	}
1175 }
1176 
1177 
1178 /**
1179  * create and initialise menus
1180  *
1181  * There are four menus held by the scaffolding:
1182  *
1183  *  1. Main menubar menu.
1184  *     This can be hidden which causes the right click popup context menu
1185  *       to use the burger menu.
1186  *  2. Burger menu.
1187  *     This can be opened from a burger icon on the toolbar.
1188  *  3. popup context menu.
1189  *     This is opened by right mouse clicking on the toolbar or browser area
1190  *  4. link context menu
1191  *     Opened like the other popup menu when the mouse is over a link in the
1192  *        browser area
1193  *
1194  * The cut, copy, paste, delete and back, forwards, stop, reload groups of
1195  *   menu entries are context sensitive and must be updated as appropriate
1196  *   when a menu is opened which contains those groups.
1197  */
nsgtk_menus_create(struct nsgtk_scaffolding * gs)1198 static nserror nsgtk_menus_create(struct nsgtk_scaffolding *gs)
1199 {
1200 	GtkAccelGroup *group;
1201 	bool showmenu; /* show menubar */
1202 	bool showtool; /* show toolbar */
1203 
1204 	get_bar_show(&showmenu, &showtool);
1205 
1206 	group = gtk_accel_group_new();
1207 
1208 	gtk_window_add_accel_group(gs->window, group);
1209 
1210 	gs->menu_bar = create_scaffolding_bar_menu(gs, group, showmenu, showtool);
1211 	gs->burger_menu = create_scaffolding_burger_menu(gs, group, showmenu, showtool);
1212 	gs->popup_menu = create_scaffolding_popup_menu(gs, group, showmenu, showtool);
1213 	gs->link_menu = create_scaffolding_link_menu(gs, group);
1214 
1215 	/* set up the menu signal handlers */
1216 	nsgtk_menu_initialise(gs);
1217 	nsgtk_menu_set_icons(gs);
1218 	nsgtk_menu_connect_signals(gs);
1219 	nsgtk_menu_set_sensitivity(gs);
1220 
1221 	return NSERROR_OK;
1222 }
1223 
1224 
1225 /* exported function documented in gtk/scaffolding.h */
nsgtk_scaffolding_set_title(struct gui_window * gw,const char * title)1226 void nsgtk_scaffolding_set_title(struct gui_window *gw, const char *title)
1227 {
1228 	struct nsgtk_scaffolding *gs = nsgtk_get_scaffold(gw);
1229 	int title_len;
1230 	char *newtitle;
1231 
1232 	/* only set window title if top level window */
1233 	if (gs->top_level != gw) {
1234 		return;
1235 	}
1236 
1237 	if (title == NULL || title[0] == '\0') {
1238 		gtk_window_set_title(gs->window, "NetSurf");
1239 		return;
1240 	}
1241 
1242 	title_len = strlen(title) + SLEN(" - NetSurf") + 1;
1243 	newtitle = malloc(title_len);
1244 	if (newtitle == NULL) {
1245 		return;
1246 	}
1247 
1248 	snprintf(newtitle, title_len, "%s - NetSurf", title);
1249 
1250 	gtk_window_set_title(gs->window, newtitle);
1251 
1252 	free(newtitle);
1253 
1254 }
1255 
1256 
1257 /* exported interface documented in scaffolding.h */
nsgtk_scaffolding_throbber(struct gui_window * gw,bool active)1258 nserror nsgtk_scaffolding_throbber(struct gui_window* gw, bool active)
1259 {
1260 	struct nsgtk_scaffolding *gs = nsgtk_get_scaffold(gw);
1261 	if (active) {
1262 		gs->menus[STOP_BUTTON].sensitivity = true;
1263 		gs->menus[RELOAD_BUTTON].sensitivity = false;
1264 	} else {
1265 		gs->menus[STOP_BUTTON].sensitivity = false;
1266 		gs->menus[RELOAD_BUTTON].sensitivity = true;
1267 	}
1268 	scaffolding_update_context(gs);
1269 
1270 	return NSERROR_OK;
1271 }
1272 
1273 
1274 /* exported interface documented in gtk/scaffolding.h */
nsgtk_scaffolding_destroy_all(void)1275 nserror nsgtk_scaffolding_destroy_all(void)
1276 {
1277 	struct nsgtk_scaffolding *gs;
1278 
1279 	gs = scaf_list;
1280 	assert(gs != NULL);
1281 
1282 	if (nsgtk_check_for_downloads(gs->window) == true) {
1283 		return NSERROR_INVALID;
1284 	}
1285 
1286 	/* iterate all scaffolding windows and destroy them */
1287 	while (gs != NULL) {
1288 		struct nsgtk_scaffolding *next = gs->next;
1289 		gtk_widget_destroy(GTK_WIDGET(gs->window));
1290 		gs = next;
1291 	}
1292 	return NSERROR_OK;
1293 }
1294 
1295 
1296 /* exported interface documented in gtk/scaffolding.h */
nsgtk_scaffolding_window(struct nsgtk_scaffolding * g)1297 GtkWindow* nsgtk_scaffolding_window(struct nsgtk_scaffolding *g)
1298 {
1299 	return g->window;
1300 }
1301 
1302 /* exported interface documented in gtk/scaffolding.h */
nsgtk_scaffolding_notebook(struct nsgtk_scaffolding * g)1303 GtkNotebook* nsgtk_scaffolding_notebook(struct nsgtk_scaffolding *g)
1304 {
1305 	return g->notebook;
1306 }
1307 
1308 
1309 /* exported interface documented in gtk/scaffolding.h */
nsgtk_scaffolding_menu_bar(struct nsgtk_scaffolding * gs)1310 GtkMenuBar *nsgtk_scaffolding_menu_bar(struct nsgtk_scaffolding *gs)
1311 {
1312 	if (gs == NULL) {
1313 		return NULL;
1314 	}
1315 	return gs->menu_bar->bar_menu;
1316 }
1317 
1318 /* exported interface documented in gtk/scaffolding.h */
nsgtk_scaffolding_iterate(struct nsgtk_scaffolding * g)1319 struct nsgtk_scaffolding *nsgtk_scaffolding_iterate(struct nsgtk_scaffolding *g)
1320 {
1321 	if (g == NULL) {
1322 		return scaf_list;
1323 	}
1324 	return g->next;
1325 }
1326 
1327 
1328 /* exported interface documented in gtk/scaffolding.h */
nsgtk_scaffolding_top_level(struct nsgtk_scaffolding * g)1329 struct gui_window *nsgtk_scaffolding_top_level(struct nsgtk_scaffolding *g)
1330 {
1331 	return g->top_level;
1332 }
1333 
1334 
1335 /* exported interface documented in gtk/scaffolding.h */
nsgtk_scaffolding_set_top_level(struct gui_window * gw)1336 void nsgtk_scaffolding_set_top_level(struct gui_window *gw)
1337 {
1338 	struct browser_window *bw;
1339 	struct nsgtk_scaffolding *sc;
1340 
1341 	assert(gw != NULL);
1342 
1343 	bw = nsgtk_get_browser_window(gw);
1344 
1345 	assert(bw != NULL);
1346 
1347 	sc = nsgtk_get_scaffold(gw);
1348 	assert(sc != NULL);
1349 
1350 	scaf_current = sc;
1351 
1352 	sc->top_level = gw;
1353 
1354 	/* Synchronise the history */
1355 	scaffolding_update_context(sc);
1356 
1357 	/* Ensure the window's title bar is updated */
1358 	nsgtk_scaffolding_set_title(gw, browser_window_get_title(bw));
1359 }
1360 
1361 
1362 /* exported interface documented in scaffolding.h */
nsgtk_scaffolding_set_sensitivity(struct nsgtk_scaffolding * g)1363 void nsgtk_scaffolding_set_sensitivity(struct nsgtk_scaffolding *g)
1364 {
1365 	int i;
1366 #define SENSITIVITY(q)							\
1367 	i = q##_BUTTON;							\
1368 	if (g->menus[i].main != NULL)					\
1369 		gtk_widget_set_sensitive(GTK_WIDGET(g->menus[i].main),	\
1370 					 g->menus[i].sensitivity);	\
1371 	if (g->menus[i].burger != NULL)					\
1372 		gtk_widget_set_sensitive(GTK_WIDGET(g->menus[i].burger), \
1373 					 g->menus[i].sensitivity);	\
1374 	if (g->menus[i].popup != NULL)					\
1375 		gtk_widget_set_sensitive(GTK_WIDGET(g->menus[i].popup), \
1376 					 g->menus[i].sensitivity);
1377 
1378 	SENSITIVITY(STOP)
1379 	SENSITIVITY(RELOAD)
1380 	SENSITIVITY(CUT)
1381 	SENSITIVITY(COPY)
1382 	SENSITIVITY(PASTE)
1383 	SENSITIVITY(BACK)
1384 	SENSITIVITY(FORWARD)
1385 	SENSITIVITY(NEXTTAB)
1386 	SENSITIVITY(PREVTAB)
1387 	SENSITIVITY(CLOSETAB)
1388 #undef SENSITIVITY
1389 
1390 }
1391 
1392 
1393 /* exported interface documented in gtk/scaffolding.h */
nsgtk_scaffolding_toolbar_context_menu(struct nsgtk_scaffolding * gs)1394 nserror nsgtk_scaffolding_toolbar_context_menu(struct nsgtk_scaffolding *gs)
1395 {
1396 	/* set visibility for right-click popup menu */
1397 	popup_menu_hide(gs->popup_menu, false, true);
1398 
1399 	nsgtk_menu_popup_at_pointer(gs->popup_menu->popup_menu, NULL);
1400 
1401 	return NSERROR_OK;
1402 }
1403 
1404 
1405 /* exported interface documented in gtk/scaffolding.h */
nsgtk_scaffolding_burger_menu(struct nsgtk_scaffolding * gs)1406 nserror nsgtk_scaffolding_burger_menu(struct nsgtk_scaffolding *gs)
1407 {
1408 	nsgtk_menu_popup_at_pointer(gs->burger_menu->burger_menu, NULL);
1409 
1410 	return NSERROR_OK;
1411 }
1412 
1413 
1414 /* exported interface documented in gtk/scaffolding.h */
1415 void
nsgtk_scaffolding_context_menu(struct nsgtk_scaffolding * g,gdouble x,gdouble y)1416 nsgtk_scaffolding_context_menu(struct nsgtk_scaffolding *g,
1417 			       gdouble x,
1418 			       gdouble y)
1419 {
1420 	GtkMenu	*gtkmenu;
1421 	struct browser_window *bw;
1422 
1423 	bw = nsgtk_get_browser_window(g->top_level);
1424 
1425 	/* update the global context menu features */
1426 	browser_window_get_features(bw,	x, y, &current_menu_features);
1427 
1428 	if (current_menu_features.link != NULL) {
1429 		/* menu is opening over a link */
1430 		gtkmenu = g->link_menu->link_menu;
1431 	} else {
1432 		gtkmenu = g->popup_menu->popup_menu;
1433 
1434 		nsgtk_scaffolding_update_edit_actions_sensitivity(g);
1435 
1436 		if (!(g->menus[COPY_BUTTON].sensitivity)) {
1437 			gtk_widget_hide(GTK_WIDGET(g->popup_menu->copy_menuitem));
1438 		} else {
1439 			gtk_widget_show(GTK_WIDGET(g->popup_menu->copy_menuitem));
1440 		}
1441 
1442 		if (!(g->menus[CUT_BUTTON].sensitivity)) {
1443 			gtk_widget_hide(GTK_WIDGET(g->popup_menu->cut_menuitem));
1444 		} else {
1445 			gtk_widget_show(GTK_WIDGET(g->popup_menu->cut_menuitem));
1446 		}
1447 
1448 		if (!(g->menus[PASTE_BUTTON].sensitivity)) {
1449 			gtk_widget_hide(GTK_WIDGET(g->popup_menu->paste_menuitem));
1450 		} else {
1451 			gtk_widget_show(GTK_WIDGET(g->popup_menu->paste_menuitem));
1452 		}
1453 
1454 	}
1455 
1456 	nsgtk_menu_popup_at_pointer(gtkmenu, NULL);
1457 }
1458 
1459 /* exported interface documented in gtk/scaffolding.h */
nsgtk_current_scaffolding(void)1460 struct nsgtk_scaffolding *nsgtk_current_scaffolding(void)
1461 {
1462 	if (scaf_current == NULL) {
1463 		scaf_current = scaf_list;
1464 	}
1465 	return scaf_current;
1466 }
1467 
1468 /* exported interface documented in gtk/scaffolding.h */
nsgtk_scaffolding_from_notebook(GtkNotebook * notebook)1469 struct nsgtk_scaffolding *nsgtk_scaffolding_from_notebook(GtkNotebook *notebook)
1470 {
1471 	struct nsgtk_scaffolding *gs;
1472 	gs = scaf_list;
1473 	while (gs != NULL) {
1474 		if (gs->notebook == notebook) {
1475 			break;
1476 		}
1477 		gs = gs->next;
1478 	}
1479 	return gs;
1480 }
1481 
1482 /* exported interface documented in gtk/scaffolding.h */
nsgtk_new_scaffolding(struct gui_window * toplevel)1483 struct nsgtk_scaffolding *nsgtk_new_scaffolding(struct gui_window *toplevel)
1484 {
1485 	nserror res;
1486 	struct nsgtk_scaffolding *gs;
1487 
1488 	gs = calloc(1, sizeof(*gs));
1489 	if (gs == NULL) {
1490 		return NULL;
1491 	}
1492 
1493 	NSLOG(netsurf, INFO,
1494 	      "Constructing a scaffold of %p for gui_window %p", gs, toplevel);
1495 
1496 	gs->top_level = toplevel;
1497 
1498 	/* Construct UI widgets */
1499 	if (nsgtk_builder_new_from_resname("netsurf", &gs->builder) != NSERROR_OK) {
1500 		free(gs);
1501 		return NULL;
1502 	}
1503 
1504 	gtk_builder_connect_signals(gs->builder, NULL);
1505 
1506 	/* containing window setup */
1507 	gs->window = GTK_WINDOW(gtk_builder_get_object(gs->builder,
1508 						       "wndBrowser"));
1509 
1510 	/**
1511 	 * set this window's size and position to what's in the options, or
1512 	 *   some sensible default if they are not set yet.
1513 	 */
1514 	if (nsoption_int(window_width) > 0) {
1515 		gtk_window_move(gs->window,
1516 				nsoption_int(window_x),
1517 				nsoption_int(window_y));
1518 		gtk_window_resize(gs->window,
1519 				  nsoption_int(window_width),
1520 				  nsoption_int(window_height));
1521 	} else {
1522 		/* Set to 1000x700, so we're very likely to fit even on
1523 		 * 1024x768 displays, not being able to take into account
1524 		 * window furniture or panels.
1525 		 */
1526 		gtk_window_set_default_size(gs->window, 1000, 700);
1527 	}
1528 
1529 	g_signal_connect(gs->window,
1530 			 "delete-event",
1531 			 G_CALLBACK(scaffolding_window_delete_event),
1532 			 gs);
1533 
1534 	g_signal_connect(gs->window,
1535 			 "destroy",
1536 			 G_CALLBACK(scaffolding_window_destroy),
1537 			 gs);
1538 
1539 
1540 	/* notebook */
1541 	res = nsgtk_notebook_create(gs->builder, &gs->notebook);
1542 	if (res != NSERROR_OK) {
1543 		free(gs);
1544 		return NULL;
1545 	}
1546 
1547 	g_signal_connect_after(gs->notebook,
1548 			       "page-added",
1549 			       G_CALLBACK(nsgtk_window_tabs_add),
1550 			       gs);
1551 	gs->tabs_remove_handler_id = g_signal_connect_after(gs->notebook,
1552 			       "page-removed",
1553 			       G_CALLBACK(nsgtk_window_tabs_remove),
1554 			       gs);
1555 
1556 
1557 	res = nsgtk_menus_create(gs);
1558 	if (res != NSERROR_OK) {
1559 		free(gs);
1560 		return NULL;
1561 	}
1562 
1563 	/* attach to the list */
1564 	if (scaf_list) {
1565 		scaf_list->prev = gs;
1566 	}
1567 	gs->next = scaf_list;
1568 	gs->prev = NULL;
1569 	scaf_list = gs;
1570 
1571 	/* finally, show the window. */
1572 	gtk_widget_show(GTK_WIDGET(gs->window));
1573 
1574 	NSLOG(netsurf, INFO, "creation complete");
1575 
1576 	return gs;
1577 }
1578 
1579 /* exported interface documented in gtk/scaffolding.h */
nsgtk_scaffolding_position_page_info(struct nsgtk_scaffolding * gs,struct nsgtk_pi_window * win)1580 nserror nsgtk_scaffolding_position_page_info(struct nsgtk_scaffolding *gs,
1581 					     struct nsgtk_pi_window *win)
1582 {
1583 	return nsgtk_window_position_page_info(gs->top_level, win);
1584 }
1585 
1586 /* exported interface documented in gtk/scaffolding.h */
nsgtk_scaffolding_position_local_history(struct nsgtk_scaffolding * gs)1587 nserror nsgtk_scaffolding_position_local_history(struct nsgtk_scaffolding *gs)
1588 {
1589 	return nsgtk_window_position_local_history(gs->top_level);
1590 }
1591