/* * Copyright 2019 Vincent Sanders * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * NetSurf is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * NetSurf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * \file * implementation of toolbar to control browsing context */ #include #include #include #include #include #include "utils/log.h" #include "utils/messages.h" #include "utils/nsoption.h" #include "utils/file.h" #include "utils/nsurl.h" #include "utils/corestrings.h" #include "desktop/browser_history.h" #include "desktop/searchweb.h" #include "desktop/search.h" #include "desktop/save_complete.h" #include "desktop/save_text.h" #include "desktop/print.h" #include "desktop/hotlist.h" #include "netsurf/content.h" #include "netsurf/browser_window.h" #include "netsurf/keypress.h" #include "gtk/toolbar_items.h" #include "gtk/completion.h" #include "gtk/gui.h" #include "gtk/warn.h" #include "gtk/search.h" #include "gtk/throbber.h" #include "gtk/scaffolding.h" #include "gtk/window.h" #include "gtk/compat.h" #include "gtk/resources.h" #include "gtk/schedule.h" #include "gtk/local_history.h" #include "gtk/global_history.h" #include "gtk/viewsource.h" #include "gtk/download.h" #include "gtk/viewdata.h" #include "gtk/tabs.h" #include "gtk/print.h" #include "gtk/layout_pango.h" #include "gtk/preferences.h" #include "gtk/hotlist.h" #include "gtk/cookies.h" #include "gtk/about.h" #include "gtk/gdk.h" #include "gtk/bitmap.h" #include "gtk/page_info.h" #include "gtk/toolbar.h" /** * button location indicating button is not to be shown */ #define INACTIVE_LOCATION (-1) /** * time (in ms) between throbber animation frame updates */ #define THROBBER_FRAME_TIME (100) /** * the minimum number of columns in the tool store */ #define NSGTK_MIN_STORE_COLUMNS 4 /** * the 'standard' width of a button that makes sufficient of its label visible */ #define NSGTK_BUTTON_WIDTH 120 /** * the 'standard' height of a button that fits as many toolbars as * possible into the store */ #define NSGTK_BUTTON_HEIGHT 70 /** * the 'normal' width of the websearch bar */ #define NSGTK_WEBSEARCH_WIDTH 150 /** * toolbar item context */ struct nsgtk_toolbar_item { /** * GTK widget in the toolbar */ GtkToolItem *button; /** * location index in toolbar */ int location; /** * if the item is currently sensitive in the toolbar */ bool sensitivity; /** * textural name used in serialising items */ const char *name; /** * button clicked on toolbar handler */ gboolean (*clicked)(GtkWidget *widget, gpointer data); /** * handler when dragging from customisation toolbox to toolbar */ void *dataplus; /** * handler when dragging from toolbar to customisation toolbox */ void *dataminus; }; /** * Location focus state machine * * 1. If we don't care, we're in LFS_IDLE * 2. When we create a new toolbar, we can put it into * LFS_WANT which means that we want the url bar to focus * 3. When we start throbbing if we're in LFS_WANT we move to LFS_THROB * 4. When we stop throbbing, if we're in LFS_THROB we move to LFS_LAST * * While not in LFS_IDLE, if the url bar is updated and we previously had it * fully selected then we reselect it all. If we're in LFS_LAST we move to * LFS_IDLE at that point. */ typedef enum { LFS_IDLE, /**< Nothing to do */ LFS_WANT, /**< Want focus, will apply */ LFS_THROB, /**< Want focus, we have started throbbing */ LFS_LAST, /**< Last chance for a focus update */ } nsgtk_toolbar_location_focus_state; /** * control toolbar context */ struct nsgtk_toolbar { /** gtk toolbar widget */ GtkToolbar *widget; /* toolbar size allocation context */ int offset; int toolbarmem; int toolbarbase; int historybase; /** * Toolbar item contexts */ struct nsgtk_toolbar_item items[PLACEHOLDER_BUTTON]; /** * Current frame of throbber animation */ int throb_frame; /** * Web search widget */ GtkWidget *webSearchEntry; /** * callback to obtain a browser window for navigation */ struct browser_window *(*get_bw)(void *ctx); /** * context passed to get_bw function */ void *get_ctx; /** * Location focus state machine, current state */ nsgtk_toolbar_location_focus_state loc_focus; }; /** * toolbar cusomisation context */ struct nsgtk_toolbar_customisation { /** * first entry is a toolbar widget so a customisation widget * can be cast to toolbar and back. */ struct nsgtk_toolbar toolbar; /** * The top level container (tabBox) */ GtkWidget *container; /** * The vertical box into which the available tools are shown */ GtkBox *toolbox; /** * widget handles for items in the customisation toolbox area */ GtkToolItem *items[PLACEHOLDER_BUTTON]; /** * which item is being dragged */ int dragitem; /* currentbutton */ /** * true if item being dragged onto toolbar, false if from toolbar */ bool dragfrom; /*fromstore */ }; /* forward declaration */ static nserror toolbar_item_create(nsgtk_toolbar_button id, struct nsgtk_toolbar_item *item_out); /** * returns a string without its underscores * * \param s The string to change. * \param replacespace true to insert a space where there was an underscore * \return The altered string */ static char *remove_underscores(const char *s, bool replacespace) { size_t i, ii, len; char *ret; len = strlen(s); ret = malloc(len + 1); if (ret == NULL) { return NULL; } for (i = 0, ii = 0; i < len; i++) { if (s[i] != '_') { ret[ii++] = s[i]; } else if (replacespace) { ret[ii++] = ' '; } } ret[ii] = '\0'; return ret; } /** * create throbber toolbar item widget * * create a gtk entry widget with a completion attached */ static GtkToolItem * make_toolbar_item_throbber(bool sensitivity, bool edit) { nserror res; GtkToolItem *item; GdkPixbuf *pixbuf; GtkWidget *image; res = nsgtk_throbber_get_frame(0, &pixbuf); if (res != NSERROR_OK) { return NULL; } if (edit) { const char *msg; msg = messages_get("ToolThrob"); item = gtk_tool_button_new( GTK_WIDGET(gtk_image_new_from_pixbuf(pixbuf)), msg); } else { item = gtk_tool_item_new(); image = gtk_image_new_from_pixbuf(pixbuf); if (image != NULL) { nsgtk_widget_set_alignment(image, GTK_ALIGN_CENTER, GTK_ALIGN_CENTER); nsgtk_widget_set_margins(image, 3, 0); gtk_container_add(GTK_CONTAINER(item), image); } } gtk_widget_set_sensitive(GTK_WIDGET(item), sensitivity); return item; } /** * create url bar toolbar item widget * * create a gtk entry widget with a completion attached * * \param sensitivity if the entry should be created sensitive to input * \param edit if the entry should be editable */ static GtkToolItem * make_toolbar_item_url_bar(bool sensitivity, bool edit) { GtkToolItem *item; GtkWidget *entry; GtkEntryCompletion *completion; entry = nsgtk_entry_new(); if (entry == NULL) { return NULL; } nsgtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, "page-info-internal"); if (edit) { gtk_entry_set_width_chars(GTK_ENTRY(entry), 9); item = gtk_tool_button_new(NULL, "URL"); gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(item), entry); } else { completion = gtk_entry_completion_new(); if (completion != NULL) { gtk_entry_set_completion(GTK_ENTRY(entry), completion); } item = gtk_tool_item_new(); if (item == NULL) { return NULL; } gtk_container_add(GTK_CONTAINER(item), entry); gtk_tool_item_set_expand(item, TRUE); } gtk_widget_set_sensitive(GTK_WIDGET(item), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(entry), sensitivity); return item; } /** * create web search toolbar item widget */ static GtkToolItem * make_toolbar_item_websearch(bool sensitivity, bool edit) { GtkToolItem *item; nserror res; GtkWidget *entry; struct bitmap *bitmap; GdkPixbuf *pixbuf = NULL; res = search_web_get_provider_bitmap(&bitmap); if ((res == NSERROR_OK) && (bitmap != NULL)) { pixbuf = nsgdk_pixbuf_get_from_surface(bitmap->surface, 32, 32); } entry = nsgtk_entry_new(); if (entry == NULL) { return NULL; } if (pixbuf != NULL) { nsgtk_entry_set_icon_from_pixbuf(entry, GTK_ENTRY_ICON_PRIMARY, pixbuf); g_object_unref(pixbuf); } else { nsgtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, NSGTK_STOCK_INFO); } if (edit) { gtk_entry_set_width_chars(GTK_ENTRY(entry), 9); item = gtk_tool_button_new(NULL, "Web Search"); gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(item), entry); } else { gtk_widget_set_size_request(entry, NSGTK_WEBSEARCH_WIDTH, -1); item = gtk_tool_item_new(); if (item == NULL) { return NULL; } gtk_container_add(GTK_CONTAINER(item), entry); } gtk_widget_set_sensitive(GTK_WIDGET(item), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(entry), sensitivity); return item; } /** * create local history toolbar item widget */ static GtkToolItem * make_toolbar_item_history(bool sensitivity, bool edit) { GtkToolItem *item; const char *msg = "H"; char *label = NULL; if (edit) { msg = messages_get("gtkLocalHistory"); } label = remove_underscores(msg, false); item = gtk_tool_button_new(NULL, label); if (label != NULL) { free(label); } gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), "local-history"); /* set history widget minimum width */ gtk_widget_set_size_request(GTK_WIDGET(item), 20, -1); gtk_widget_set_sensitive(GTK_WIDGET(item), sensitivity); return item; } /** * create generic button toolbar item widget */ static GtkToolItem * make_toolbar_item_button(const char *labelmsg, const char *iconname, bool sensitivity, bool edit) { GtkToolItem *item; char *label = NULL; label = remove_underscores(messages_get(labelmsg), false); item = gtk_tool_button_new(NULL, label); if (label != NULL) { free(label); } if (item != NULL) { gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(item), iconname); gtk_widget_set_sensitive(GTK_WIDGET(item), sensitivity); if (edit) { nsgtk_widget_set_margins(GTK_WIDGET(item), 0, 0); } } return item; } /** * widget factory for creation of toolbar item widgets * * \param i the id of the widget * \param theme the theme to make the widgets from * \return gtk widget */ static GtkToolItem * make_toolbar_item(nsgtk_toolbar_button itemid, bool sensitivity) { GtkToolItem *toolitem = NULL; switch(itemid) { #define TOOLBAR_ITEM_y(identifier, label, iconame) #define TOOLBAR_ITEM_n(identifier, label, iconame) #define TOOLBAR_ITEM_t(identifier, label, iconame) \ case identifier: \ toolitem = make_toolbar_item_button(#label, iconame, sensitivity, false); \ break; #define TOOLBAR_ITEM_b(identifier, label, iconame) \ case identifier: \ toolitem = make_toolbar_item_button(#label, iconame, sensitivity, false); \ break; #define TOOLBAR_ITEM(identifier, name, snstvty, clicked, activate, label, iconame) \ TOOLBAR_ITEM_ ## clicked(identifier, label, iconame) #include "gtk/toolbar_items.h" #undef TOOLBAR_ITEM_t #undef TOOLBAR_ITEM_b #undef TOOLBAR_ITEM_n #undef TOOLBAR_ITEM_y #undef TOOLBAR_ITEM case HISTORY_BUTTON: toolitem = make_toolbar_item_history(sensitivity, false); break; case URL_BAR_ITEM: toolitem = make_toolbar_item_url_bar(sensitivity, false); break; case THROBBER_ITEM: toolitem = make_toolbar_item_throbber(sensitivity, false); break; case WEBSEARCH_ITEM: toolitem = make_toolbar_item_websearch(sensitivity, false); break; default: break; } return toolitem; } /** * widget factory for creation of toolbar item widgets for the toolbox * * \param itemid the id of the widget * \return gtk tool item widget */ static GtkToolItem * make_toolbox_item(nsgtk_toolbar_button itemid, bool bar) { GtkToolItem *toolitem = NULL; switch(itemid) { #define TOOLBAR_ITEM_y(identifier, label, iconame) #define TOOLBAR_ITEM_n(identifier, label, iconame) #define TOOLBAR_ITEM_t(identifier, label, iconame) \ case identifier: \ if (bar) { \ toolitem = make_toolbar_item_button(#label, iconame, true, true); \ } \ break; #define TOOLBAR_ITEM_b(identifier, label, iconame) \ case identifier: \ toolitem = make_toolbar_item_button(#label, iconame, true, true); \ break; #define TOOLBAR_ITEM(identifier, name, snstvty, clicked, activate, label, iconame) \ TOOLBAR_ITEM_ ## clicked(identifier, label, iconame) #include "gtk/toolbar_items.h" #undef TOOLBAR_ITEM_t #undef TOOLBAR_ITEM_b #undef TOOLBAR_ITEM_n #undef TOOLBAR_ITEM_y #undef TOOLBAR_ITEM case HISTORY_BUTTON: toolitem = make_toolbar_item_history(true, true); break; case URL_BAR_ITEM: toolitem = make_toolbar_item_url_bar(false, true); break; case THROBBER_ITEM: toolitem = make_toolbar_item_throbber(true, true); break; case WEBSEARCH_ITEM: toolitem = make_toolbar_item_websearch(false, true); break; default: break; } return toolitem; } /** * target entry for drag source */ static GtkTargetEntry target_entry = { (char *)"nsgtk_button_data", GTK_TARGET_SAME_APP, 0 }; /** * find the toolbar item with a given location. * * \param tb the toolbar instance * \param locaction the location to search for * \return the item id for a location */ static nsgtk_toolbar_button itemid_from_location(struct nsgtk_toolbar *tb, int location) { int iidx; for (iidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) { if (tb->items[iidx].location == location) { break; } } return iidx; } /** * save toolbar settings to file */ static nserror nsgtk_toolbar_customisation_save(struct nsgtk_toolbar *tb) { int iidx; /* item index */ char *order; /* item ordering */ char *start; /* start of next item name to be output */ int orderlen = 0; /* length of item ordering */ nsgtk_toolbar_button itemid; int location; char *choices = NULL; for (iidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) { if (tb->items[iidx].location != INACTIVE_LOCATION) { orderlen += strlen(tb->items[iidx].name); orderlen++; /* allow for separator */ } } /* ensure there are some items to store */ if (orderlen == 0) { return NSERROR_INVALID; } order = malloc(orderlen); if (order == NULL) { return NSERROR_NOMEM; } start = order; for (location = BACK_BUTTON; location < PLACEHOLDER_BUTTON; location++) { itemid = itemid_from_location(tb, location); if (itemid == PLACEHOLDER_BUTTON) { /* no more filled locations */ break; } start += snprintf(start, orderlen - (start - order), "%s/", tb->items[itemid].name); if ((start - order) >= orderlen) { break; } } order[orderlen - 1] = 0; nsoption_set_charp(toolbar_items, order); /* ensure choices are saved */ netsurf_mkpath(&choices, NULL, 2, nsgtk_config_home, "Choices"); if (choices != NULL) { nsoption_write(choices, NULL, NULL); free(choices); } return NSERROR_OK; } /** * connect signals to a toolbar item in a customisation toolbar * * \param tb The toolbar * \param itemid The item id within to toolbar to connect * \param NSERROR_OK on success */ static nserror toolbar_item_connect_signals(struct nsgtk_toolbar *tb, int itemid) { /* set toolbar items to be a drag source */ gtk_tool_item_set_use_drag_window(tb->items[itemid].button, TRUE); gtk_drag_source_set(GTK_WIDGET(tb->items[itemid].button), GDK_BUTTON1_MASK, &target_entry, 1, GDK_ACTION_COPY); g_signal_connect(tb->items[itemid].button, "drag-data-get", G_CALLBACK(tb->items[itemid].dataminus), tb); return NSERROR_OK; } /** * customisation container handler for drag drop signal * * called when a widget is dropped onto the store window */ static gboolean customisation_container_drag_drop_cb(GtkWidget *widget, GdkDragContext *gdc, gint x, gint y, guint time, gpointer data) { struct nsgtk_toolbar_customisation *tbc; tbc = (struct nsgtk_toolbar_customisation *)data; int location; int itemid; if ((tbc->dragfrom) || (tbc->dragitem == -1)) { tbc->dragitem = -1; return FALSE; } if (tbc->toolbar.items[tbc->dragitem].location == INACTIVE_LOCATION) { tbc->dragitem = -1; gtk_drag_finish(gdc, TRUE, TRUE, time); return FALSE; } /* update the locations for all the subsequent toolbar items */ for (location = tbc->toolbar.items[tbc->dragitem].location; location < PLACEHOLDER_BUTTON; location++) { itemid = itemid_from_location(&tbc->toolbar, location); if (itemid == PLACEHOLDER_BUTTON) { break; } tbc->toolbar.items[itemid].location--; } /* remove existing item */ tbc->toolbar.items[tbc->dragitem].location = -1; gtk_container_remove(GTK_CONTAINER(tbc->toolbar.widget), GTK_WIDGET(tbc->toolbar.items[tbc->dragitem].button)); tbc->dragitem = -1; gtk_drag_finish(gdc, TRUE, TRUE, time); return FALSE; } /** * customisation container handler for drag motion signal * * called when hovering above the store */ static gboolean customisation_container_drag_motion_cb(GtkWidget *widget, GdkDragContext *gdc, gint x, gint y, guint time, gpointer data) { return FALSE; } /** * customisation toolbar handler for drag drop signal * * called when a widget is dropped onto the toolbar */ static gboolean customisation_toolbar_drag_drop_cb(GtkWidget *widget, GdkDragContext *gdc, gint x, gint y, guint time, gpointer data) { struct nsgtk_toolbar_customisation *tbc; tbc = (struct nsgtk_toolbar_customisation *)data; gint position; /* drop position in toolbar */ int location; int itemid; struct nsgtk_toolbar_item *dragitem; /* toolbar item being dragged */ position = gtk_toolbar_get_drop_index(tbc->toolbar.widget, x, y); if (tbc->dragitem == -1) { return TRUE; } /* pure conveiance variable */ dragitem = &tbc->toolbar.items[tbc->dragitem]; /* deal with replacing existing item in toolbar */ if (dragitem->location != INACTIVE_LOCATION) { if (dragitem->location < position) { position--; } /* update the locations for all the subsequent toolbar items */ for (location = dragitem->location; location < PLACEHOLDER_BUTTON; location++) { itemid = itemid_from_location(&tbc->toolbar, location); if (itemid == PLACEHOLDER_BUTTON) { break; } tbc->toolbar.items[itemid].location--; } /* remove existing item */ dragitem->location = INACTIVE_LOCATION; gtk_container_remove(GTK_CONTAINER(tbc->toolbar.widget), GTK_WIDGET(dragitem->button)); } dragitem->button = make_toolbox_item(tbc->dragitem, true); if (dragitem->button == NULL) { nsgtk_warning("NoMemory", 0); return TRUE; } /* update locations */ for (location = PLACEHOLDER_BUTTON; location >= position; location--) { itemid = itemid_from_location(&tbc->toolbar, location); if (itemid != PLACEHOLDER_BUTTON) { tbc->toolbar.items[itemid].location++; } } dragitem->location = position; gtk_toolbar_insert(tbc->toolbar.widget, dragitem->button, dragitem->location); toolbar_item_connect_signals(&tbc->toolbar, tbc->dragitem); gtk_widget_show_all(GTK_WIDGET(dragitem->button)); tbc->dragitem = -1; return TRUE; } /** * customisation toolbar handler for drag data received signal * * connected to toolbutton drop; perhaps one day it'll work properly * so it may replace the global current_button */ static gboolean customisation_toolbar_drag_data_received_cb(GtkWidget *widget, GdkDragContext *gdc, gint x, gint y, GtkSelectionData *selection, guint info, guint time, gpointer data) { return FALSE; } /** * customisation toolbar handler for drag motion signal * * called when hovering an item above the toolbar */ static gboolean customisation_toolbar_drag_motion_cb(GtkWidget *widget, GdkDragContext *gdc, gint x, gint y, guint time, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; GtkToolItem *item; gint position; /* position in toolbar */ item = gtk_tool_button_new(NULL, NULL); position = gtk_toolbar_get_drop_index(tb->widget, x, y); gtk_toolbar_set_drop_highlight_item(tb->widget, item, position); return FALSE; /* drag not in drop zone */ } /** * customisation toolbar handler for drag leave signal * * called when hovering stops */ static void customisation_toolbar_drag_leave_cb(GtkWidget *widget, GdkDragContext *gdc, guint time, gpointer data) { gtk_toolbar_set_drop_highlight_item(GTK_TOOLBAR(widget), NULL, 0); } /** * create a new browser window * * creates a browser window with default url depending on user choices. * * \param bw The browser window to pass for existing window/ * \param intab true if the new window should be in a tab else false * for new window. * \return NSERROR_OK on success else error code. */ static nserror nsgtk_browser_window_create(struct browser_window *bw, bool intab) { nserror res = NSERROR_OK; nsurl *url = NULL; int flags = BW_CREATE_HISTORY | BW_CREATE_FOREGROUND | BW_CREATE_FOCUS_LOCATION; if (intab) { flags |= BW_CREATE_TAB; } if (!nsoption_bool(new_blank)) { const char *addr; if (nsoption_charp(homepage_url) != NULL) { addr = nsoption_charp(homepage_url); } else { addr = NETSURF_HOMEPAGE; } res = nsurl_create(addr, &url); } if (res == NSERROR_OK) { res = browser_window_create(flags, url, NULL, bw, NULL); } if (url != NULL) { nsurl_unref(url); } return res; } /** * Apply the user toolbar button settings from configuration * * GTK specific user option string is a set of fields arranged as * [itemreference];[itemlocation]|[itemreference];[itemlocation]| etc * * \param tb The toolbar to apply customisation to * \param NSERROR_OK on success else error code. */ static nserror apply_user_button_customisation(struct nsgtk_toolbar *tb) { const char *tbitems; /* item order user config */ const char *start; const char *end; int iidx; /* item index */ int location = 0; /* location index */ /* set all button locations to inactive */ for (iidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) { tb->items[iidx].location = INACTIVE_LOCATION; } tbitems = nsoption_charp(toolbar_items); if (tbitems == NULL) { tbitems = ""; } end = tbitems; while (*end != 0) { start = end; while ((*end != 0) && (*end !='/')) { end++; } for (iidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) { if (((ssize_t)strlen(tb->items[iidx].name) == (end - start)) && (strncmp(tb->items[iidx].name, start, end - start) == 0)) { tb->items[iidx].location = location++; break; } } if (*end == '/') { end++; } } if (location == 0) { /* completely failed to create any buttons so use defaults */ tb->items[BACK_BUTTON].location = location++; tb->items[HISTORY_BUTTON].location = location++; tb->items[FORWARD_BUTTON].location = location++; tb->items[RELOADSTOP_BUTTON].location = location++; tb->items[URL_BAR_ITEM].location = location++; tb->items[WEBSEARCH_ITEM].location = location++; tb->items[OPENMENU_BUTTON].location = location++; tb->items[THROBBER_ITEM].location = location++; } return NSERROR_OK; } /** * callback function to remove a widget from a container */ static void container_remove_widget(GtkWidget *widget, gpointer data) { GtkContainer *container = GTK_CONTAINER(data); gtk_container_remove(container, widget); } /** * populates a toolbar with widgets in correct order * * \param tb toolbar * \return NSERROR_OK on success else error code. */ static nserror populate_gtk_toolbar_widget(struct nsgtk_toolbar *tb) { int location; /* location index */ int itemid; /* clear the toolbar container of all widgets */ gtk_container_foreach(GTK_CONTAINER(tb->widget), container_remove_widget, tb->widget); /* add widgets to toolbar */ for (location = 0; location < PLACEHOLDER_BUTTON; location++) { itemid = itemid_from_location(tb, location); if (itemid == PLACEHOLDER_BUTTON) { break; } tb->items[itemid].button = make_toolbar_item(itemid, tb->items[itemid].sensitivity); gtk_toolbar_insert(tb->widget, tb->items[itemid].button, location); } gtk_widget_show_all(GTK_WIDGET(tb->widget)); return NSERROR_OK; } /** * populates the customization toolbar with widgets in correct order * * \param tb toolbar * \return NSERROR_OK on success else error code. */ static nserror customisation_toolbar_populate(struct nsgtk_toolbar *tb) { int location; /* location index */ int itemid; /* clear the toolbar container of all widgets */ gtk_container_foreach(GTK_CONTAINER(tb->widget), container_remove_widget, tb->widget); /* add widgets to toolbar */ for (location = 0; location < PLACEHOLDER_BUTTON; location++) { itemid = itemid_from_location(tb, location); if (itemid == PLACEHOLDER_BUTTON) { break; } tb->items[itemid].button = make_toolbox_item(itemid, true); gtk_toolbar_insert(tb->widget, tb->items[itemid].button, location); } gtk_widget_show_all(GTK_WIDGET(tb->widget)); return NSERROR_OK; } /** * find the toolbar item with a given gtk widget. * * \param tb the toolbar instance * \param toolitem the tool item widget to search for * \return the item id matching the widget */ static nsgtk_toolbar_button itemid_from_gtktoolitem(struct nsgtk_toolbar *tb, GtkToolItem *toolitem) { int iidx; for (iidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) { if ((tb->items[iidx].location != INACTIVE_LOCATION) && (tb->items[iidx].button == toolitem)) { break; } } return iidx; } /** * set a toolbar items sensitivity * * note this does not set menu items sensitivity */ static nserror set_item_sensitivity(struct nsgtk_toolbar_item *item, bool sensitivity) { if (item->sensitivity != sensitivity) { /* item requires sensitivity changing */ item->sensitivity = sensitivity; if ((item->location != -1) && (item->button != NULL)) { gtk_widget_set_sensitive(GTK_WIDGET(item->button), item->sensitivity); } } return NSERROR_OK; } /** * set an item to its alternative action * * this is currently only used for the stop/reload button where we * also reuse the item sensitivity for the state indicator. * * \param tb the toolbar instance */ static nserror set_item_action(struct nsgtk_toolbar *tb, int itemid, bool alt) { const char *iconname; char *label = NULL; if (itemid != RELOADSTOP_BUTTON) { return NSERROR_INVALID; } if (tb->items[itemid].location == -1) { return NSERROR_OK; } tb->items[itemid].sensitivity = alt; if (tb->items[itemid].button == NULL) { return NSERROR_INVALID; } if (tb->items[itemid].sensitivity) { iconname = NSGTK_STOCK_REFRESH; label = remove_underscores(messages_get("Reload"), false); } else { iconname = NSGTK_STOCK_STOP; label = remove_underscores(messages_get("gtkStop"), false); } gtk_tool_button_set_label(GTK_TOOL_BUTTON(tb->items[itemid].button), label); gtk_tool_button_set_icon_name(GTK_TOOL_BUTTON(tb->items[itemid].button), iconname); gtk_widget_set_sensitive(GTK_WIDGET(tb->items[itemid].button), TRUE); if (label != NULL) { free(label); } return NSERROR_OK; } /** * cause the toolbar browsing context to navigate to a new url. * * \param tb the toolbar context. * \param urltxt The url string. * \return NSERROR_OK on success else appropriate error code. */ static nserror toolbar_navigate_to_url(struct nsgtk_toolbar *tb, const char *urltxt) { struct browser_window *bw; nsurl *url; nserror res; res = nsurl_create(urltxt, &url); if (res != NSERROR_OK) { return res; } bw = tb->get_bw(tb->get_ctx); res = browser_window_navigate(bw, url, NULL, BW_NAVIGATE_HISTORY, NULL, NULL, NULL); nsurl_unref(url); return res; } /** * run a gtk file chooser as a save dialog to obtain a path */ static nserror nsgtk_saveas_dialog(struct browser_window *bw, const char *title, GtkWindow *parent, bool folder, gchar **path_out) { nserror res; GtkWidget *fc; /* file chooser widget */ GtkFileChooserAction action; char *path; /* proposed path */ if (!browser_window_has_content(bw)) { /* cannot save a page with no content */ return NSERROR_INVALID; } if (folder) { action = GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER; } else { action = GTK_FILE_CHOOSER_ACTION_SAVE; } fc = gtk_file_chooser_dialog_new(title, parent, action, NSGTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NSGTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); /* set a default file name */ res = nsurl_nice(browser_window_access_url(bw), &path, false); if (res != NSERROR_OK) { path = strdup(messages_get("SaveText")); if (path == NULL) { gtk_widget_destroy(fc); return NSERROR_NOMEM; } } if ((!folder) || (access(path, F_OK) != 0)) { gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(fc), path); } free(path); /* confirm overwriting */ gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(fc), TRUE); /* run the dialog to let user select path */ if (gtk_dialog_run(GTK_DIALOG(fc)) != GTK_RESPONSE_ACCEPT) { gtk_widget_destroy(fc); return NSERROR_NOT_FOUND; } *path_out = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fc)); gtk_widget_destroy(fc); return NSERROR_OK; } /** * connect all signals to widgets in a customisation */ static nserror toolbar_customisation_connect_signals(struct nsgtk_toolbar *tb) { int iidx; for (iidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) { /* skip inactive items in toolbar */ if (tb->items[iidx].location != INACTIVE_LOCATION) { toolbar_item_connect_signals(tb, iidx); } } /* add move button listeners */ g_signal_connect(tb->widget, "drag-drop", G_CALLBACK(customisation_toolbar_drag_drop_cb), tb); g_signal_connect(tb->widget, "drag-data-received", G_CALLBACK(customisation_toolbar_drag_data_received_cb), tb); g_signal_connect(tb->widget, "drag-motion", G_CALLBACK(customisation_toolbar_drag_motion_cb), tb); g_signal_connect(tb->widget, "drag-leave", G_CALLBACK(customisation_toolbar_drag_leave_cb), tb); /* set data types */ gtk_drag_dest_set(GTK_WIDGET(tb->widget), GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP, &target_entry, 1, GDK_ACTION_COPY); return NSERROR_OK; } static void item_size_allocate_cb(GtkWidget *widget, GdkRectangle *alloc, gpointer user_data) { if (alloc->width > NSGTK_BUTTON_WIDTH) { alloc->width = NSGTK_BUTTON_WIDTH; } if (alloc->height > NSGTK_BUTTON_HEIGHT) { alloc->height = NSGTK_BUTTON_HEIGHT; } gtk_widget_set_allocation(widget, alloc); } /** * add a row to a toolbar customisation toolbox * * \param tbc The toolbar customisation context * \param startitem The item index of the beginning of the row * \param enditem The item index of the beginning of the next row * \return NSERROR_OK on successs else error */ static nserror add_toolbox_row(struct nsgtk_toolbar_customisation *tbc, int startitem, int enditem) { GtkToolbar *rowbar; int iidx; rowbar = GTK_TOOLBAR(gtk_toolbar_new()); if (rowbar == NULL) { return NSERROR_NOMEM; } gtk_toolbar_set_style(rowbar, GTK_TOOLBAR_BOTH); gtk_toolbar_set_icon_size(rowbar, GTK_ICON_SIZE_LARGE_TOOLBAR); gtk_box_pack_start(tbc->toolbox, GTK_WIDGET(rowbar), FALSE, FALSE, 0); for (iidx = startitem; iidx < enditem; iidx++) { if (tbc->items[iidx] == NULL) { /* skip any widgets that failed to initialise */ continue; } gtk_widget_set_size_request(GTK_WIDGET(tbc->items[iidx]), NSGTK_BUTTON_WIDTH, NSGTK_BUTTON_HEIGHT); gtk_tool_item_set_use_drag_window(tbc->items[iidx], TRUE); gtk_drag_source_set(GTK_WIDGET(tbc->items[iidx]), GDK_BUTTON1_MASK, &target_entry, 1, GDK_ACTION_COPY); g_signal_connect(tbc->items[iidx], "drag-data-get", G_CALLBACK(tbc->toolbar.items[iidx].dataplus), &tbc->toolbar); g_signal_connect(tbc->items[iidx], "size-allocate", G_CALLBACK(item_size_allocate_cb), NULL); gtk_toolbar_insert(rowbar, tbc->items[iidx], -1); } return NSERROR_OK; } /** * creates widgets in customisation toolbox * * \param tbc The toolbar customisation context * \param width The width to layout the toolbox to * \return NSERROR_OK on success else error code. */ static nserror toolbar_customisation_create_toolbox(struct nsgtk_toolbar_customisation *tbc, int width) { int columns; /* number of items in a single row */ int curcol; /* current column in creation */ int iidx; /* item index */ int startidx; /* index of item at start of row */ /* ensure there are a minimum number of items per row */ columns = width / NSGTK_BUTTON_WIDTH; if (columns < NSGTK_MIN_STORE_COLUMNS) { columns = NSGTK_MIN_STORE_COLUMNS; } curcol = 0; for (iidx = startidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) { if (curcol >= columns) { add_toolbox_row(tbc, startidx, iidx); curcol = 0; startidx = iidx; } tbc->items[iidx] = make_toolbox_item(iidx, false); if (tbc->items[iidx] != NULL) { curcol++; } } if (curcol > 0) { add_toolbox_row(tbc, startidx, iidx); } return NSERROR_OK; } /** * update toolbar in customisation to user settings */ static nserror customisation_toolbar_update(struct nsgtk_toolbar_customisation *tbc) { nserror res; res = apply_user_button_customisation(&tbc->toolbar); if (res != NSERROR_OK) { return res; } /* populate toolbar widget */ res = customisation_toolbar_populate(&tbc->toolbar); if (res != NSERROR_OK) { return res; } /* ensure icon sizes and text labels on toolbar are set */ res = nsgtk_toolbar_restyle(&tbc->toolbar); if (res != NSERROR_OK) { return res; } /* attach handlers to toolbar widgets */ res = toolbar_customisation_connect_signals(&tbc->toolbar); if (res != NSERROR_OK) { return res; } return NSERROR_OK; } /** * customisation apply handler for clicked signal * * when 'save settings' button is clicked */ static gboolean customisation_apply_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar_customisation *tbc; tbc = (struct nsgtk_toolbar_customisation *)data; /* save state to file, update toolbars for all windows */ nsgtk_toolbar_customisation_save(&tbc->toolbar); nsgtk_window_toolbar_update(); gtk_widget_destroy(tbc->container); return TRUE; } /** * customisation reset handler for clicked signal * * when 'reload defaults' button is clicked */ static gboolean customisation_reset_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar_customisation *tbc; tbc = (struct nsgtk_toolbar_customisation *)data; customisation_toolbar_update(tbc); return TRUE; } /** * customisation container destroy handler */ static void customisation_container_destroy_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar_customisation *tbc; tbc = (struct nsgtk_toolbar_customisation *)data; free(tbc); } /* * Toolbar button clicked handlers */ /** * create a toolbar customisation tab * * this is completely different approach to previous implementation. it * is not modal and the toolbar configuration is performed completely * within the tab. once the user is happy they can apply the change or * cancel as they see fit while continuing to use the browser as usual. */ static gboolean cutomize_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar_customisation *tbc; nserror res; GtkBuilder *builder; GtkNotebook *notebook; /* notebook containing widget */ GtkAllocation notebook_alloc; /* notebook size allocation */ int iidx; /* item index */ /* obtain the notebook being added to */ notebook = GTK_NOTEBOOK(gtk_widget_get_ancestor(widget, GTK_TYPE_NOTEBOOK)); if (notebook == NULL) { return TRUE; } /* create builder */ res = nsgtk_builder_new_from_resname("toolbar", &builder); if (res != NSERROR_OK) { NSLOG(netsurf, INFO, "Toolbar UI builder init failed"); return TRUE; } gtk_builder_connect_signals(builder, NULL); /* create nsgtk_toolbar_customisation which has nsgtk_toolbar * at the front so we can reuse functions that take * nsgtk_toolbar */ tbc = calloc(1, sizeof(struct nsgtk_toolbar_customisation)); if (tbc == NULL) { g_object_unref(builder); return TRUE; } /* get container box widget which forms a page of the tabs */ tbc->container = GTK_WIDGET(gtk_builder_get_object(builder, "customisation")); if (tbc->container == NULL) { goto cutomize_button_clicked_cb_error; } /* vertical box for the toolbox to drag items into and out of */ tbc->toolbox = GTK_BOX(gtk_builder_get_object(builder, "toolbox")); if (tbc->toolbox == NULL) { goto cutomize_button_clicked_cb_error; } /* customisation toolbar container */ tbc->toolbar.widget = GTK_TOOLBAR(gtk_builder_get_object(builder, "toolbar")); if (tbc->toolbar.widget == NULL) { goto cutomize_button_clicked_cb_error; } /* build customisation toolbar */ gtk_toolbar_set_show_arrow(tbc->toolbar.widget, TRUE); for (iidx = BACK_BUTTON; iidx < PLACEHOLDER_BUTTON; iidx++) { res = toolbar_item_create(iidx, &tbc->toolbar.items[iidx]); if (res != NSERROR_OK) { goto cutomize_button_clicked_cb_error; } } res = customisation_toolbar_update(tbc); if (res != NSERROR_OK) { goto cutomize_button_clicked_cb_error; } /* use toolbox for widgets to drag to/from */ gtk_widget_get_allocation(GTK_WIDGET(notebook), ¬ebook_alloc); res = toolbar_customisation_create_toolbox(tbc, notebook_alloc.width); if (res != NSERROR_OK) { goto cutomize_button_clicked_cb_error; } /* configure the container */ gtk_drag_dest_set(GTK_WIDGET(tbc->container), GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP, &target_entry, 1, GDK_ACTION_COPY); /* discard button calls destroy */ g_signal_connect_swapped(GTK_WIDGET(gtk_builder_get_object(builder, "discard")), "clicked", G_CALLBACK(gtk_widget_destroy), tbc->container); /* save and update on apply button */ g_signal_connect(GTK_WIDGET(gtk_builder_get_object(builder, "apply")), "clicked", G_CALLBACK(customisation_apply_clicked_cb), tbc); g_signal_connect(GTK_WIDGET(gtk_builder_get_object(builder, "reset")), "clicked", G_CALLBACK(customisation_reset_clicked_cb), tbc); /* close and cleanup on delete signal */ g_signal_connect(tbc->container, "destroy", G_CALLBACK(customisation_container_destroy_cb), tbc); g_signal_connect(tbc->container, "drag-drop", G_CALLBACK(customisation_container_drag_drop_cb), tbc); g_signal_connect(tbc->container, "drag-motion", G_CALLBACK(customisation_container_drag_motion_cb), tbc); nsgtk_tab_add_page(notebook, tbc->container, false, messages_get("gtkCustomizeToolbarTitle"), favicon_pixbuf); /* safe to drop the reference to the builder as the container is * referenced by the notebook now. */ g_object_unref(builder); return TRUE; cutomize_button_clicked_cb_error: free(tbc); g_object_unref(builder); return TRUE; } /** * callback for all toolbar items widget size allocation * * handler connected to all toolbar items for the size-allocate signal * * \param widget The widget the signal is being delivered to. * \param alloc The size allocation being set. * \param data The toolbar context passed when the signal was connected */ static void toolbar_item_size_allocate_cb(GtkWidget *widget, GtkAllocation *alloc, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; nsgtk_toolbar_button itemid; itemid = itemid_from_gtktoolitem(tb, GTK_TOOL_ITEM(widget)); if ((tb->toolbarmem == alloc->x) || (tb->items[itemid].location < tb->items[HISTORY_BUTTON].location)) { /* * no reallocation after first adjustment, * no reallocation for buttons left of history button */ return; } if (itemid == HISTORY_BUTTON) { if (alloc->width == 20) { return; } tb->toolbarbase = alloc->y + alloc->height; tb->historybase = alloc->x + 20; if (tb->offset == 0) { tb->offset = alloc->width - 20; } alloc->width = 20; } else if (tb->items[itemid].location <= tb->items[URL_BAR_ITEM].location) { alloc->x -= tb->offset; if (itemid == URL_BAR_ITEM) { alloc->width += tb->offset; } } tb->toolbarmem = alloc->x; gtk_widget_size_allocate(widget, alloc); } /** * handler for back tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean back_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; bw = tb->get_bw(tb->get_ctx); if ((bw != NULL) && browser_window_history_back_available(bw)) { /* clear potential search effects */ browser_window_search_clear(bw); browser_window_history_back(bw, false); set_item_sensitivity(&tb->items[BACK_BUTTON], browser_window_history_back_available(bw)); set_item_sensitivity(&tb->items[FORWARD_BUTTON], browser_window_history_forward_available(bw)); nsgtk_local_history_hide(); } return TRUE; } /** * handler for forward tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean forward_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; bw = tb->get_bw(tb->get_ctx); if ((bw != NULL) && browser_window_history_forward_available(bw)) { /* clear potential search effects */ browser_window_search_clear(bw); browser_window_history_forward(bw, false); set_item_sensitivity(&tb->items[BACK_BUTTON], browser_window_history_back_available(bw)); set_item_sensitivity(&tb->items[FORWARD_BUTTON], browser_window_history_forward_available(bw)); nsgtk_local_history_hide(); } return TRUE; } /** * handler for stop tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean stop_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; browser_window_stop(tb->get_bw(tb->get_ctx)); return TRUE; } /** * handler for reload tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean reload_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; bw = tb->get_bw(tb->get_ctx); /* clear potential search effects */ browser_window_search_clear(bw); browser_window_reload(bw, true); return TRUE; } /** * handler for reload/stop tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean reloadstop_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; bw = tb->get_bw(tb->get_ctx); /* clear potential search effects */ browser_window_search_clear(bw); if (tb->items[RELOADSTOP_BUTTON].sensitivity) { browser_window_reload(bw, true); } else { browser_window_stop(tb->get_bw(tb->get_ctx)); } return TRUE; } /** * handler for home tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean home_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; nserror res; const char *addr; if (nsoption_charp(homepage_url) != NULL) { addr = nsoption_charp(homepage_url); } else { addr = NETSURF_HOMEPAGE; } res = toolbar_navigate_to_url(tb, addr); if (res != NSERROR_OK) { nsgtk_warning(messages_get_errorcode(res), 0); } return TRUE; } /** * callback for url entry widget activation * * handler connected to url entry widget for the activate signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE to allow activation. */ static gboolean url_entry_activate_cb(GtkWidget *widget, gpointer data) { nserror res; struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; nsurl *url; res = search_web_omni(gtk_entry_get_text(GTK_ENTRY(widget)), SEARCH_WEB_OMNI_NONE, &url); if (res == NSERROR_OK) { bw = tb->get_bw(tb->get_ctx); res = browser_window_navigate( bw, url, NULL, BW_NAVIGATE_HISTORY, NULL, NULL, NULL); nsurl_unref(url); } if (res != NSERROR_OK) { nsgtk_warning(messages_get_errorcode(res), 0); } return TRUE; } /** * callback for url entry widget changing * * handler connected to url entry widget for the change signal * * \param widget The widget the signal is being delivered to. * \param event The key change event that changed the entry. * \param data The toolbar context passed when the signal was connected * \return TRUE to allow activation. */ static gboolean url_entry_changed_cb(GtkWidget *widget, GdkEventKey *event, gpointer data) { return nsgtk_completion_update(GTK_ENTRY(widget)); } /** * callback for url entry widget icon button release * * handler connected to url entry widget for the icon release signal * * \param widget The widget the signal is being delivered to. * \param event The key change event that changed the entry. * \param data The toolbar context passed when the signal was connected * \return TRUE to allow activation. */ static void url_entry_icon_release_cb(GtkEntry *entry, GtkEntryIconPosition icon_pos, GdkEvent *event, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; bw = tb->get_bw(tb->get_ctx); nsgtk_page_info(bw); } /** * handler for web search tool bar entry item activate signal * * handler connected to web search entry widget for the activate signal * * \todo make this user selectable to switch between opening in new * and navigating current window. Possibly improve core search_web interfaces * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean websearch_entry_activate_cb(GtkWidget *widget, gpointer data) { nserror res; struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; nsurl *url; res = search_web_omni(gtk_entry_get_text(GTK_ENTRY(widget)), SEARCH_WEB_OMNI_SEARCHONLY, &url); if (res == NSERROR_OK) { bw = tb->get_bw(tb->get_ctx); res = browser_window_create( BW_CREATE_HISTORY | BW_CREATE_TAB | BW_CREATE_FOREGROUND, url, NULL, bw, NULL); nsurl_unref(url); } if (res != NSERROR_OK) { nsgtk_warning(messages_get_errorcode(res), 0); } return TRUE; } /** * handler for web search tool bar item button press signal * * allows a click in the websearch entry field to clear the name of the * provider. * * \todo this does not work well, different behaviour wanted perhaps? * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean websearch_entry_button_press_cb(GtkWidget *widget, GdkEventFocus *f, gpointer data) { gtk_editable_select_region(GTK_EDITABLE(widget), 0, -1); gtk_widget_grab_focus(GTK_WIDGET(widget)); return TRUE; } /** * handler for new window tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean newwindow_button_clicked_cb(GtkWidget *widget, gpointer data) { nserror res; struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; res = nsgtk_browser_window_create(tb->get_bw(tb->get_ctx), false); if (res != NSERROR_OK) { nsgtk_warning(messages_get_errorcode(res), 0); } return TRUE; } /** * handler for new tab tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean newtab_button_clicked_cb(GtkWidget *widget, gpointer data) { nserror res; struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; res = nsgtk_browser_window_create(tb->get_bw(tb->get_ctx), true); if (res != NSERROR_OK) { nsgtk_warning(messages_get_errorcode(res), 0); } return TRUE; } /** * handler for open file tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean openfile_button_clicked_cb(GtkWidget *widget, gpointer data) { GtkWidget *dlgOpen; gint response; GtkWidget *toplevel; struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW); dlgOpen = gtk_file_chooser_dialog_new("Open File", GTK_WINDOW(toplevel), GTK_FILE_CHOOSER_ACTION_OPEN, NSGTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NSGTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL, NULL); response = gtk_dialog_run(GTK_DIALOG(dlgOpen)); if (response == GTK_RESPONSE_OK) { char *urltxt; gchar *filename; nserror res; nsurl *url; filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlgOpen)); urltxt = malloc(strlen(filename) + FILE_SCHEME_PREFIX_LEN + 1); if (urltxt != NULL) { sprintf(urltxt, FILE_SCHEME_PREFIX"%s", filename); res = nsurl_create(urltxt, &url); if (res == NSERROR_OK) { bw = tb->get_bw(tb->get_ctx); res = browser_window_navigate(bw, url, NULL, BW_NAVIGATE_HISTORY, NULL, NULL, NULL); nsurl_unref(url); } if (res != NSERROR_OK) { nsgtk_warning(messages_get_errorcode(res), 0); } free(urltxt); } g_free(filename); } gtk_widget_destroy(dlgOpen); return TRUE; } /** * handler for close window tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean closewindow_button_clicked_cb(GtkWidget *widget, gpointer data) { GtkWidget *toplevel; toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW); gtk_widget_destroy(toplevel); return TRUE; } /** * handler for full save export tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean savepage_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; DIR *d; gchar *path; nserror res; GtkWidget *toplevel; bw = tb->get_bw(tb->get_ctx); toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW); res = nsgtk_saveas_dialog(bw, messages_get("gtkcompleteSave"), GTK_WINDOW(toplevel), true, &path); if (res != NSERROR_OK) { return FALSE; } d = opendir(path); if (d == NULL) { NSLOG(netsurf, INFO, "Unable to open directory %s for complete save: %s", path, strerror(errno)); if (errno == ENOTDIR) { nsgtk_warning("NoDirError", path); } else { nsgtk_warning("gtkFileError", path); } g_free(path); return TRUE; } closedir(d); save_complete(browser_window_get_content(bw), path, NULL); g_free(path); return TRUE; } /** * handler for pdf export tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean pdf_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; GtkWidget *toplevel; gchar *filename; nserror res; bw = tb->get_bw(tb->get_ctx); toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW); res = nsgtk_saveas_dialog(bw, "Export to PDF", GTK_WINDOW(toplevel), false, &filename); if (res != NSERROR_OK) { return FALSE; } #ifdef WITH_PDF_EXPORT struct print_settings *settings; /* this way the scale used by PDF functions is synchronised with that * used by the all-purpose print interface */ haru_nsfont_set_scale((float)option_export_scale / 100); settings = print_make_settings(PRINT_OPTIONS, (const char *) filename, &haru_nsfont); g_free(filename); if (settings == NULL) { return TRUE; } /* This will clean up the print_settings object for us */ print_basic_run(browser_window_get_content(bw), &pdf_printer, settings); #endif return TRUE; } /** * handler for plain text export tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean plaintext_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; GtkWidget *toplevel; gchar *filename; nserror res; bw = tb->get_bw(tb->get_ctx); toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW); res = nsgtk_saveas_dialog(bw, messages_get("gtkplainSave"), GTK_WINDOW(toplevel), false, &filename); if (res != NSERROR_OK) { return FALSE; } save_as_text(browser_window_get_content(bw), filename); g_free(filename); return TRUE; } /** * handler for print tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean print_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; GtkPrintOperation *print_op; GtkPageSetup *page_setup; GtkPrintSettings *print_settings; GtkPrintOperationResult res = GTK_PRINT_OPERATION_RESULT_ERROR; struct print_settings *nssettings; char *settings_fname = NULL; GtkWidget *toplevel; bw = tb->get_bw(tb->get_ctx); toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW); print_op = gtk_print_operation_new(); if (print_op == NULL) { nsgtk_warning(messages_get("NoMemory"), 0); return TRUE; } /* use previously saved settings if any */ netsurf_mkpath(&settings_fname, NULL, 2, nsgtk_config_home, "Print"); if (settings_fname != NULL) { print_settings = gtk_print_settings_new_from_file(settings_fname, NULL); if (print_settings != NULL) { gtk_print_operation_set_print_settings(print_op, print_settings); /* We're not interested in the settings any more */ g_object_unref(print_settings); } } content_to_print = browser_window_get_content(bw); page_setup = gtk_print_run_page_setup_dialog(GTK_WINDOW(toplevel), NULL, NULL); if (page_setup == NULL) { nsgtk_warning(messages_get("NoMemory"), 0); free(settings_fname); g_object_unref(print_op); return TRUE; } gtk_print_operation_set_default_page_setup(print_op, page_setup); nssettings = print_make_settings(PRINT_DEFAULT, NULL, nsgtk_layout_table); g_signal_connect(print_op, "begin_print", G_CALLBACK(gtk_print_signal_begin_print), nssettings); g_signal_connect(print_op, "draw_page", G_CALLBACK(gtk_print_signal_draw_page), NULL); g_signal_connect(print_op, "end_print", G_CALLBACK(gtk_print_signal_end_print), nssettings); if (content_get_type(browser_window_get_content(bw)) != CONTENT_TEXTPLAIN) { res = gtk_print_operation_run(print_op, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, GTK_WINDOW(toplevel), NULL); } /* if the settings were used save them for future use */ if (settings_fname != NULL) { if (res == GTK_PRINT_OPERATION_RESULT_APPLY) { /* Do not increment the settings reference */ print_settings = gtk_print_operation_get_print_settings(print_op); gtk_print_settings_to_file(print_settings, settings_fname, NULL); } free(settings_fname); } /* Our print_settings object is destroyed by the end print handler */ g_object_unref(page_setup); g_object_unref(print_op); return TRUE; } /** * handler for quit tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean quit_button_clicked_cb(GtkWidget *widget, gpointer data) { nsgtk_scaffolding_destroy_all(); return TRUE; } /** * handler for cut tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean cut_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; GtkWidget *focused; GtkWidget *toplevel; toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW); focused = gtk_window_get_focus(GTK_WINDOW(toplevel)); /* let gtk handle it if focused widget is an editable */ if (GTK_IS_EDITABLE(focused)) { gtk_editable_cut_clipboard(GTK_EDITABLE(focused)); } else { bw = tb->get_bw(tb->get_ctx); browser_window_key_press(bw, NS_KEY_CUT_SELECTION); } return TRUE; } /** * handler for copy tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean copy_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; GtkWidget *focused; GtkWidget *toplevel; toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW); focused = gtk_window_get_focus(GTK_WINDOW(toplevel)); /* let gtk handle it if focused widget is an editable */ if (GTK_IS_EDITABLE(focused)) { gtk_editable_copy_clipboard(GTK_EDITABLE(focused)); } else { bw = tb->get_bw(tb->get_ctx); browser_window_key_press(bw, NS_KEY_COPY_SELECTION); } return TRUE; } /** * handler for paste tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean paste_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; GtkWidget *focused; GtkWidget *toplevel; toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW); focused = gtk_window_get_focus(GTK_WINDOW(toplevel)); /* let gtk handle it if focused widget is an editable */ if (GTK_IS_EDITABLE(focused)) { gtk_editable_paste_clipboard(GTK_EDITABLE(focused)); } else { bw = tb->get_bw(tb->get_ctx); browser_window_key_press(bw, NS_KEY_PASTE); } return TRUE; } /** * handler for delete tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean delete_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; GtkWidget *focused; GtkWidget *toplevel; toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW); focused = gtk_window_get_focus(GTK_WINDOW(toplevel)); /* let gtk handle it if focused widget is an editable */ if (GTK_IS_EDITABLE(focused)) { gtk_editable_delete_selection(GTK_EDITABLE(focused)); } else { bw = tb->get_bw(tb->get_ctx); browser_window_key_press(bw, NS_KEY_CLEAR_SELECTION); } return TRUE; } /** * handler for select all tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean selectall_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; GtkWidget *focused; GtkWidget *toplevel; toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW); focused = gtk_window_get_focus(GTK_WINDOW(toplevel)); /* let gtk handle it if focused widget is an editable */ if (GTK_IS_EDITABLE(focused)) { gtk_editable_select_region(GTK_EDITABLE(focused), 0, -1); } else { bw = tb->get_bw(tb->get_ctx); browser_window_key_press(bw, NS_KEY_SELECT_ALL); } return TRUE; } /** * handler for preferences tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean preferences_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; GtkWidget *toplevel; GtkWidget *wndpreferences; bw = tb->get_bw(tb->get_ctx); toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW); wndpreferences = nsgtk_preferences(bw, GTK_WINDOW(toplevel)); if (wndpreferences != NULL) { gtk_widget_show(wndpreferences); } return TRUE; } /** * handler for zoom plus tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean zoomplus_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; bw = tb->get_bw(tb->get_ctx); browser_window_set_scale(bw, 0.05, false); return TRUE; } /** * handler for zoom minus tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean zoomminus_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; bw = tb->get_bw(tb->get_ctx); browser_window_set_scale(bw, -0.05, false); return TRUE; } /** * handler for zoom normal tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean zoomnormal_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; bw = tb->get_bw(tb->get_ctx); browser_window_set_scale(bw, 1.0, true); return TRUE; } /** * handler for full screen tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean fullscreen_button_clicked_cb(GtkWidget *widget, gpointer data) { GtkWindow *gtkwindow; /* gtk window widget is in */ GdkWindow *gdkwindow; GdkWindowState state; gtkwindow = GTK_WINDOW(gtk_widget_get_ancestor(widget,GTK_TYPE_WINDOW)); gdkwindow = gtk_widget_get_window(GTK_WIDGET(gtkwindow)); state = gdk_window_get_state(gdkwindow); if (state & GDK_WINDOW_STATE_FULLSCREEN) { gtk_window_unfullscreen(gtkwindow); } else { gtk_window_fullscreen(gtkwindow); } return TRUE; } /** * handler for view source tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean viewsource_button_clicked_cb(GtkWidget *widget, gpointer data) { nserror res; struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; GtkWindow *gtkwindow; /* gtk window widget is in */ bw = tb->get_bw(tb->get_ctx); gtkwindow = GTK_WINDOW(gtk_widget_get_ancestor(widget,GTK_TYPE_WINDOW)); res = nsgtk_viewsource(gtkwindow, bw); if (res != NSERROR_OK) { nsgtk_warning(messages_get_errorcode(res), 0); } return TRUE; } /** * handler for show downloads tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean downloads_button_clicked_cb(GtkWidget *widget, gpointer data) { GtkWindow *gtkwindow; /* gtk window widget is in */ gtkwindow = GTK_WINDOW(gtk_widget_get_ancestor(widget,GTK_TYPE_WINDOW)); nsgtk_download_show(gtkwindow); return TRUE; } /** * handler for show downloads tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean savewindowsize_button_clicked_cb(GtkWidget *widget, gpointer data) { GtkWindow *gtkwindow; /* gtk window widget is in */ int x,y,w,h; char *choices = NULL; gtkwindow = GTK_WINDOW(gtk_widget_get_ancestor(widget,GTK_TYPE_WINDOW)); gtk_window_get_position(gtkwindow, &x, &y); gtk_window_get_size(gtkwindow, &w, &h); nsoption_set_int(window_width, w); nsoption_set_int(window_height, h); nsoption_set_int(window_x, x); nsoption_set_int(window_y, y); netsurf_mkpath(&choices, NULL, 2, nsgtk_config_home, "Choices"); if (choices != NULL) { nsoption_write(choices, NULL, NULL); free(choices); } return TRUE; } /** * handler for show downloads tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean toggledebugging_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; bw = tb->get_bw(tb->get_ctx); browser_window_debug(bw, CONTENT_DEBUG_REDRAW); nsgtk_window_update_all(); return TRUE; } /** * handler for debug box tree tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean debugboxtree_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; gchar *fname; gint handle; FILE *f; handle = g_file_open_tmp("nsgtkboxtreeXXXXXX", &fname, NULL); if ((handle == -1) || (fname == NULL)) { return TRUE; } close(handle); /* in case it was binary mode */ /* save data to temporary file */ f = fopen(fname, "w"); if (f == NULL) { nsgtk_warning("Error saving box tree dump.", "Unable to open file for writing."); unlink(fname); return TRUE; } bw = tb->get_bw(tb->get_ctx); browser_window_debug_dump(bw, f, CONTENT_DEBUG_RENDER); fclose(f); nsgtk_viewfile("Box Tree Debug", "boxtree", fname); g_free(fname); return TRUE; } /** * handler for debug dom tree tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean debugdomtree_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; gchar *fname; gint handle; FILE *f; handle = g_file_open_tmp("nsgtkdomtreeXXXXXX", &fname, NULL); if ((handle == -1) || (fname == NULL)) { return TRUE; } close(handle); /* in case it was binary mode */ /* save data to temporary file */ f = fopen(fname, "w"); if (f == NULL) { nsgtk_warning("Error saving box tree dump.", "Unable to open file for writing."); unlink(fname); return TRUE; } bw = tb->get_bw(tb->get_ctx); browser_window_debug_dump(bw, f, CONTENT_DEBUG_DOM); fclose(f); nsgtk_viewfile("DOM Tree Debug", "domtree", fname); g_free(fname); return TRUE; } /** * handler for local history tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean localhistory_button_clicked_cb(GtkWidget *widget, gpointer data) { nserror res; struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; GtkWidget *toplevel; toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW); if (toplevel != NULL) { bw = tb->get_bw(tb->get_ctx); res = nsgtk_local_history_present(GTK_WINDOW(toplevel), bw); if (res != NSERROR_OK) { NSLOG(netsurf, INFO, "Unable to present local history window."); } } return TRUE; } /** * handler for history tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean history_button_clicked_cb(GtkWidget *widget, gpointer data) { return localhistory_button_clicked_cb(widget, data); } /** * handler for global history tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean globalhistory_button_clicked_cb(GtkWidget *widget, gpointer data) { nserror res; res = nsgtk_global_history_present(); if (res != NSERROR_OK) { NSLOG(netsurf, INFO, "Unable to initialise global history window."); } return TRUE; } /** * handler for add bookmark tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean addbookmarks_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct browser_window *bw; bw = tb->get_bw(tb->get_ctx); if (browser_window_has_content(bw)) { hotlist_add_url(browser_window_access_url(bw)); } return TRUE; } /** * handler for show bookmark tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean showbookmarks_button_clicked_cb(GtkWidget *widget, gpointer data) { nserror res; res = nsgtk_hotlist_present(); if (res != NSERROR_OK) { NSLOG(netsurf, INFO, "Unable to initialise bookmark window."); } return TRUE; } /** * handler for show cookies tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean showcookies_button_clicked_cb(GtkWidget *widget, gpointer data) { nserror res; res = nsgtk_cookies_present(); if (res != NSERROR_OK) { NSLOG(netsurf, INFO, "Unable to initialise cookies window."); } return TRUE; } /** * handler for open location tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean openlocation_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; GtkToolItem *urltitem; urltitem = tb->items[URL_BAR_ITEM].button; if (urltitem != NULL) { GtkEntry *entry; entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(urltitem))); gtk_widget_grab_focus(GTK_WIDGET(entry)); } return TRUE; } /** * handler for contents tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean contents_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; nserror res; res = toolbar_navigate_to_url(tb, "http://www.netsurf-browser.org/documentation/"); if (res != NSERROR_OK) { nsgtk_warning(messages_get_errorcode(res), 0); } return TRUE; } /** * handler for contents tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean guide_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; nserror res; res = toolbar_navigate_to_url(tb, "http://www.netsurf-browser.org/documentation/guide"); if (res != NSERROR_OK) { nsgtk_warning(messages_get_errorcode(res), 0); } return TRUE; } /** * handler for contents tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean info_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; nserror res; res = toolbar_navigate_to_url(tb, "http://www.netsurf-browser.org/documentation/info"); if (res != NSERROR_OK) { nsgtk_warning(messages_get_errorcode(res), 0); } return TRUE; } /** * handler for contents tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE */ static gboolean about_button_clicked_cb(GtkWidget *widget, gpointer data) { GtkWindow *parent; /* gtk window widget is in */ parent = GTK_WINDOW(gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW)); nsgtk_about_dialog_init(parent); return TRUE; } /** * handler for openmenu tool bar item clicked signal * * \param widget The widget the signal is being delivered to. * \param data The toolbar context passed when the signal was connected * \return TRUE to indicate signal handled. */ static gboolean openmenu_button_clicked_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct gui_window *gw; struct nsgtk_scaffolding *gs; gw = tb->get_ctx; /** \todo stop assuming the context is a gui window */ gs = nsgtk_get_scaffold(gw); nsgtk_scaffolding_burger_menu(gs); return TRUE; } /* define data plus and data minus handlers */ #define TOOLBAR_ITEM(identifier, name, snstvty, clicked, activate, label, iconame) \ static gboolean \ nsgtk_toolbar_##name##_data_plus(GtkWidget *widget, \ GdkDragContext *cont, \ GtkSelectionData *selection, \ guint info, \ guint time, \ gpointer data) \ { \ struct nsgtk_toolbar_customisation *tbc; \ tbc = (struct nsgtk_toolbar_customisation *)data; \ tbc->dragitem = identifier; \ tbc->dragfrom = true; \ return TRUE; \ } \ static gboolean \ nsgtk_toolbar_##name##_data_minus(GtkWidget *widget, \ GdkDragContext *cont, \ GtkSelectionData *selection, \ guint info, \ guint time, \ gpointer data) \ { \ struct nsgtk_toolbar_customisation *tbc; \ tbc = (struct nsgtk_toolbar_customisation *)data; \ tbc->dragitem = identifier; \ tbc->dragfrom = false; \ return TRUE; \ } #include "gtk/toolbar_items.h" #undef TOOLBAR_ITEM /** * create a toolbar item * * create a toolbar item and set up its default handlers */ static nserror toolbar_item_create(nsgtk_toolbar_button id, struct nsgtk_toolbar_item *item) { item->location = INACTIVE_LOCATION; /* set item defaults from macro */ switch (id) { #define TOOLBAR_ITEM_t(name) \ item->clicked = name##_button_clicked_cb; #define TOOLBAR_ITEM_b(name) \ item->clicked = name##_button_clicked_cb; #define TOOLBAR_ITEM_y(name) \ item->clicked = name##_button_clicked_cb; #define TOOLBAR_ITEM_n(name) \ item->clicked = NULL; #define TOOLBAR_ITEM(identifier, iname, snstvty, clicked, activate, label, iconame) \ case identifier: \ item->name = #iname; \ item->sensitivity = snstvty; \ item->dataplus = nsgtk_toolbar_##iname##_data_plus; \ item->dataminus = nsgtk_toolbar_##iname##_data_minus; \ TOOLBAR_ITEM_ ## clicked(iname) \ break; #include "gtk/toolbar_items.h" #undef TOOLBAR_ITEM_t #undef TOOLBAR_ITEM_y #undef TOOLBAR_ITEM_n #undef TOOLBAR_ITEM case PLACEHOLDER_BUTTON: return NSERROR_INVALID; } return NSERROR_OK; } /** * set a toolbar item to a throbber frame number * * \param toolbar_item The toolbar item to update * \param frame The animation frame number to update to * \return NSERROR_OK on success, * NSERROR_INVALID if the toolbar item does not contain an image, * NSERROR_BAD_SIZE if the frame is out of range. */ static nserror set_throbber_frame(GtkToolItem *toolbar_item, int frame) { nserror res; GdkPixbuf *pixbuf; GtkImage *throbber; if (toolbar_item == NULL) { /* no toolbar item */ return NSERROR_INVALID; } res = nsgtk_throbber_get_frame(frame, &pixbuf); if (res != NSERROR_OK) { return res; } throbber = GTK_IMAGE(gtk_bin_get_child(GTK_BIN(toolbar_item))); gtk_image_set_from_pixbuf(throbber, pixbuf); return NSERROR_OK; } /** * Make the throbber run. * * scheduled callback to update the throbber * * \param p The context passed when scheduled. */ static void next_throbber_frame(void *p) { struct nsgtk_toolbar *tb = p; nserror res; tb->throb_frame++; /* advance to next frame */ res = set_throbber_frame(tb->items[THROBBER_ITEM].button, tb->throb_frame); if (res == NSERROR_BAD_SIZE) { tb->throb_frame = 1; res = set_throbber_frame(tb->items[THROBBER_ITEM].button, tb->throb_frame); } /* only schedule next frame if there are no errors */ if (res == NSERROR_OK) { nsgtk_schedule(THROBBER_FRAME_TIME, next_throbber_frame, p); } } /** * connect signal handlers to a gtk toolbar item */ static nserror toolbar_connect_signal(struct nsgtk_toolbar *tb, nsgtk_toolbar_button itemid) { struct nsgtk_toolbar_item *item; GtkEntry *entry; item = &tb->items[itemid]; if (item->button != NULL) { g_signal_connect(item->button, "size-allocate", G_CALLBACK(toolbar_item_size_allocate_cb), tb); } switch (itemid) { case URL_BAR_ITEM: entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(item->button))); g_signal_connect(GTK_WIDGET(entry), "activate", G_CALLBACK(url_entry_activate_cb), tb); g_signal_connect(GTK_WIDGET(entry), "changed", G_CALLBACK(url_entry_changed_cb), tb); g_signal_connect(GTK_WIDGET(entry), "icon-release", G_CALLBACK(url_entry_icon_release_cb), tb); nsgtk_completion_connect_signals(entry, tb->get_bw, tb->get_ctx); break; case WEBSEARCH_ITEM: entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(item->button))); g_signal_connect(GTK_WIDGET(entry), "activate", G_CALLBACK(websearch_entry_activate_cb), tb); g_signal_connect(GTK_WIDGET(entry), "button-press-event", G_CALLBACK(websearch_entry_button_press_cb), tb); break; default: if ((item->clicked != NULL) && (item->button != NULL)) { g_signal_connect(item->button, "clicked", G_CALLBACK(item->clicked), tb); } break; } return NSERROR_OK; } /** * connect all signals to widgets in a toolbar */ static nserror toolbar_connect_signals(struct nsgtk_toolbar *tb) { int location; /* location index */ nsgtk_toolbar_button itemid; /* item id */ for (location = BACK_BUTTON; location < PLACEHOLDER_BUTTON; location++) { itemid = itemid_from_location(tb, location); if (itemid == PLACEHOLDER_BUTTON) { /* no more filled locations */ break; } toolbar_connect_signal(tb, itemid); } return NSERROR_OK; } /** * signal handler for toolbar context menu * * \param toolbar The toolbar event is being delivered to * \param x The x coordinate where the click happened * \param y The x coordinate where the click happened * \param button the buttons being pressed * \param data The context pointer passed when the connection was made. * \return TRUE to indicate signal handled. */ static gboolean toolbar_popup_context_menu_cb(GtkToolbar *toolbar, gint x, gint y, gint button, gpointer data) { struct nsgtk_toolbar *tb = (struct nsgtk_toolbar *)data; struct gui_window *gw; struct nsgtk_scaffolding *gs; gw = tb->get_ctx; /** \todo stop assuming the context is a gui window */ gs = nsgtk_get_scaffold(gw); nsgtk_scaffolding_toolbar_context_menu(gs); return TRUE; } /** * toolbar delete signal handler */ static void toolbar_destroy_cb(GtkWidget *widget, gpointer data) { struct nsgtk_toolbar *tb; tb = (struct nsgtk_toolbar *)data; /* ensure any throbber scheduled is stopped */ nsgtk_schedule(-1, next_throbber_frame, tb); free(tb); } /* exported interface documented in toolbar.h */ 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) { nserror res; struct nsgtk_toolbar *tb; int bidx; /* button index */ tb = calloc(1, sizeof(struct nsgtk_toolbar)); if (tb == NULL) { return NSERROR_NOMEM; } tb->get_bw = get_bw; tb->get_ctx = get_ctx; /* set the throbber start frame. */ tb->throb_frame = 0; if (want_location_focus) { tb->loc_focus = LFS_WANT; } else { tb->loc_focus = LFS_IDLE; } tb->widget = GTK_TOOLBAR(gtk_builder_get_object(builder, "toolbar")); gtk_toolbar_set_show_arrow(tb->widget, TRUE); g_signal_connect(tb->widget, "popup-context-menu", G_CALLBACK(toolbar_popup_context_menu_cb), tb); /* close and cleanup on delete signal */ g_signal_connect(tb->widget, "destroy", G_CALLBACK(toolbar_destroy_cb), tb); /* allocate button contexts */ for (bidx = BACK_BUTTON; bidx < PLACEHOLDER_BUTTON; bidx++) { res = toolbar_item_create(bidx, &tb->items[bidx]); if (res != NSERROR_OK) { return res; } } res = nsgtk_toolbar_update(tb); if (res != NSERROR_OK) { return res; } *tb_out = tb; return NSERROR_OK; } /* exported interface documented in toolbar.h */ nserror nsgtk_toolbar_restyle(struct nsgtk_toolbar *tb) { /* * reset toolbar size allocation so icon size change affects * allocated widths. */ tb->offset = 0; switch (nsoption_int(button_type)) { case 1: /* Small icons */ gtk_toolbar_set_style(GTK_TOOLBAR(tb->widget), GTK_TOOLBAR_ICONS); gtk_toolbar_set_icon_size(GTK_TOOLBAR(tb->widget), GTK_ICON_SIZE_SMALL_TOOLBAR); break; case 2: /* Large icons */ gtk_toolbar_set_style(GTK_TOOLBAR(tb->widget), GTK_TOOLBAR_ICONS); gtk_toolbar_set_icon_size(GTK_TOOLBAR(tb->widget), GTK_ICON_SIZE_LARGE_TOOLBAR); break; case 3: /* Large icons with text */ gtk_toolbar_set_style(GTK_TOOLBAR(tb->widget), GTK_TOOLBAR_BOTH); gtk_toolbar_set_icon_size(GTK_TOOLBAR(tb->widget), GTK_ICON_SIZE_LARGE_TOOLBAR); break; case 4: /* Text icons only */ gtk_toolbar_set_style(GTK_TOOLBAR(tb->widget), GTK_TOOLBAR_TEXT); break; default: break; } return NSERROR_OK; } /* exported interface documented in toolbar.h */ nserror nsgtk_toolbar_throbber(struct nsgtk_toolbar *tb, bool active) { nserror res; struct browser_window *bw; /* Manage the location focus state */ switch (tb->loc_focus) { case LFS_IDLE: break; case LFS_WANT: if (active) { tb->loc_focus = LFS_THROB; } break; case LFS_THROB: if (!active) { tb->loc_focus = LFS_LAST; } break; case LFS_LAST: break; } /* when activating the throbber simply schedule the next frame update */ if (active) { nsgtk_schedule(THROBBER_FRAME_TIME, next_throbber_frame, tb); set_item_sensitivity(&tb->items[STOP_BUTTON], true); set_item_sensitivity(&tb->items[RELOAD_BUTTON], false); set_item_action(tb, RELOADSTOP_BUTTON, false); return NSERROR_OK; } /* stopping the throbber */ nsgtk_schedule(-1, next_throbber_frame, tb); tb->throb_frame = 0; res = set_throbber_frame(tb->items[THROBBER_ITEM].button, tb->throb_frame); bw = tb->get_bw(tb->get_ctx); /* adjust sensitivity of other items */ set_item_sensitivity(&tb->items[STOP_BUTTON], false); set_item_sensitivity(&tb->items[RELOAD_BUTTON], true); set_item_action(tb, RELOADSTOP_BUTTON, true); set_item_sensitivity(&tb->items[BACK_BUTTON], browser_window_history_back_available(bw)); set_item_sensitivity(&tb->items[FORWARD_BUTTON], browser_window_history_forward_available(bw)); nsgtk_local_history_hide(); return res; } /* exported interface documented in toolbar.h */ nserror nsgtk_toolbar_page_info_change(struct nsgtk_toolbar *tb) { GtkEntry *url_entry; browser_window_page_info_state pistate; struct browser_window *bw; const char *icon_name; if (tb->items[URL_BAR_ITEM].button == NULL) { /* no toolbar item */ return NSERROR_INVALID; } url_entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(tb->items[URL_BAR_ITEM].button))); bw = tb->get_bw(tb->get_ctx); pistate = browser_window_get_page_info_state(bw); switch (pistate) { case PAGE_STATE_INTERNAL: icon_name = "page-info-internal"; break; case PAGE_STATE_LOCAL: icon_name = "page-info-local"; break; case PAGE_STATE_INSECURE: icon_name = "page-info-insecure"; break; case PAGE_STATE_SECURE_OVERRIDE: icon_name = "page-info-warning"; break; case PAGE_STATE_SECURE_ISSUES: icon_name = "page-info-warning"; break; case PAGE_STATE_SECURE: icon_name = "page-info-secure"; break; default: icon_name = "page-info-internal"; break; } nsgtk_entry_set_icon_from_icon_name(GTK_WIDGET(url_entry), GTK_ENTRY_ICON_PRIMARY, icon_name); return NSERROR_OK; } /* exported interface documented in toolbar.h */ nserror nsgtk_toolbar_set_url(struct nsgtk_toolbar *tb, nsurl *url) { size_t idn_url_l; char *idn_url_s = NULL; const char *url_text = NULL; GtkEntry *url_entry; if (tb->items[URL_BAR_ITEM].button == NULL) { /* no toolbar item */ return NSERROR_INVALID; } url_entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(tb->items[URL_BAR_ITEM].button))); if (nsoption_bool(display_decoded_idn) == true) { if (nsurl_get_utf8(url, &idn_url_s, &idn_url_l) != NSERROR_OK) { idn_url_s = NULL; } url_text = idn_url_s; } if (url_text == NULL) { url_text = nsurl_access(url); } if (strcmp(url_text, gtk_entry_get_text(url_entry)) != 0) { /* The URL bar content has changed, we need to update it */ gint startpos, endpos; bool was_selected; gtk_editable_get_selection_bounds(GTK_EDITABLE(url_entry), &startpos, &endpos); was_selected = gtk_widget_is_focus(GTK_WIDGET(url_entry)) && startpos == 0 && endpos == gtk_entry_get_text_length(url_entry); gtk_entry_set_text(url_entry, url_text); if (was_selected && tb->loc_focus != LFS_IDLE) { gtk_widget_grab_focus(GTK_WIDGET(url_entry)); if (tb->loc_focus == LFS_LAST) { tb->loc_focus = LFS_IDLE; } } } if (idn_url_s != NULL) { free(idn_url_s); } return NSERROR_OK; } /* exported interface documented in toolbar.h */ nserror nsgtk_toolbar_set_websearch_image(struct nsgtk_toolbar *tb, GdkPixbuf *pixbuf) { GtkWidget *entry; if (tb->items[WEBSEARCH_ITEM].button == NULL) { /* no toolbar item */ return NSERROR_INVALID; } entry = gtk_bin_get_child(GTK_BIN(tb->items[WEBSEARCH_ITEM].button)); if (pixbuf != NULL) { nsgtk_entry_set_icon_from_pixbuf(entry, GTK_ENTRY_ICON_PRIMARY, pixbuf); } else { nsgtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, NSGTK_STOCK_INFO); } return NSERROR_OK; } /* exported interface documented in toolbar.h */ nserror nsgtk_toolbar_item_activate(struct nsgtk_toolbar *tb, nsgtk_toolbar_button itemid) { GtkWidget *widget; /* ensure item id in range */ if ((itemid < BACK_BUTTON) || (itemid >= PLACEHOLDER_BUTTON)) { return NSERROR_BAD_PARAMETER; } if (tb->items[itemid].clicked == NULL) { return NSERROR_INVALID; } /* * if item has a widget in the current toolbar use that as the * signal source otherwise use the toolbar widget itself. */ if (tb->items[itemid].button != NULL) { widget = GTK_WIDGET(tb->items[itemid].button); } else { widget = GTK_WIDGET(tb->widget); } tb->items[itemid].clicked(widget, tb); return NSERROR_OK; } /* exported interface documented in toolbar.h */ nserror nsgtk_toolbar_show(struct nsgtk_toolbar *tb, bool show) { if (show) { gtk_widget_show(GTK_WIDGET(tb->widget)); } else { gtk_widget_hide(GTK_WIDGET(tb->widget)); } return NSERROR_OK; } /* exported interface documented in toolbar.h */ nserror nsgtk_toolbar_update(struct nsgtk_toolbar *tb) { nserror res; /* setup item locations based on user config */ res = apply_user_button_customisation(tb); if (res != NSERROR_OK) { return res; } /* populate toolbar widget */ res = populate_gtk_toolbar_widget(tb); if (res != NSERROR_OK) { return res; } /* ensure icon sizes and text labels on toolbar are set */ res = nsgtk_toolbar_restyle(tb); if (res != NSERROR_OK) { return res; } res = toolbar_connect_signals(tb); return res; }