diff options
author | Vincent Sanders <vince@kyllikki.org> | 2016-05-05 22:28:51 +0100 |
---|---|---|
committer | Vincent Sanders <vince@kyllikki.org> | 2016-05-15 13:44:34 +0100 |
commit | d21447d096a320a08b3efb2b8768fad0dcdcfd64 (patch) | |
tree | 1a83814b7c9e94b2f13c473261f23dd3a17dee64 /frontends/gtk/scaffolding.c | |
parent | 2cbb337756d9af5bda4d594964d446439f602551 (diff) | |
download | netsurf-d21447d096a320a08b3efb2b8768fad0dcdcfd64.tar.gz netsurf-d21447d096a320a08b3efb2b8768fad0dcdcfd64.tar.bz2 |
move frontends into sub directory
Diffstat (limited to 'frontends/gtk/scaffolding.c')
-rw-r--r-- | frontends/gtk/scaffolding.c | 2811 |
1 files changed, 2811 insertions, 0 deletions
diff --git a/frontends/gtk/scaffolding.c b/frontends/gtk/scaffolding.c new file mode 100644 index 000000000..777256703 --- /dev/null +++ b/frontends/gtk/scaffolding.c @@ -0,0 +1,2811 @@ +/* + * Copyright 2006 Rob Kendrick <rjek@rjek.com> + * Copyright 2009 Mark Benjamin <netsurf-browser.org.MarkBenjamin@dfgh.net> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <gtk/gtk.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +#include "utils/utils.h" +#include "utils/dirent.h" +#include "utils/messages.h" +#include "utils/corestrings.h" +#include "utils/log.h" +#include "utils/nsoption.h" +#include "utils/file.h" +#include "desktop/browser_history.h" +#include "desktop/browser.h" +#include "desktop/hotlist.h" +#include "desktop/plotters.h" +#include "desktop/print.h" +#include "desktop/save_complete.h" +#ifdef WITH_PDF_EXPORT +#include "desktop/font_haru.h" +#include "desktop/save_pdf.h" +#endif +#include "desktop/save_text.h" +#include "desktop/searchweb.h" +#include "desktop/textinput.h" +#include "content/hlcache.h" + +#include "gtk/compat.h" +#include "gtk/warn.h" +#include "gtk/cookies.h" +#include "gtk/completion.h" +#include "gtk/preferences.h" +#include "gtk/about.h" +#include "gtk/viewsource.h" +#include "gtk/bitmap.h" +#include "gtk/gui.h" +#include "gtk/history.h" +#include "gtk/hotlist.h" +#include "gtk/download.h" +#include "gtk/menu.h" +#include "gtk/plotters.h" +#include "gtk/print.h" +#include "gtk/search.h" +#include "gtk/throbber.h" +#include "gtk/toolbar.h" +#include "gtk/window.h" +#include "gtk/gdk.h" +#include "gtk/scaffolding.h" +#include "gtk/tabs.h" +#include "gtk/schedule.h" +#include "gtk/viewdata.h" +#include "gtk/resources.h" +#include "gtk/layout_pango.h" + +/** Macro to define a handler for menu, button and activate events. */ +#define MULTIHANDLER(q)\ +static gboolean nsgtk_on_##q##_activate(struct nsgtk_scaffolding *g);\ +static gboolean nsgtk_on_##q##_activate_menu(GtkMenuItem *widget, gpointer data)\ +{\ + struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *)data;\ + return nsgtk_on_##q##_activate(g);\ +}\ +static gboolean nsgtk_on_##q##_activate_button(GtkButton *widget, gpointer data)\ +{\ + struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *)data;\ + return nsgtk_on_##q##_activate(g);\ +}\ +static gboolean nsgtk_on_##q##_activate(struct nsgtk_scaffolding *g) + +/** Macro to define a handler for menu events. */ +#define MENUHANDLER(q)\ +static gboolean nsgtk_on_##q##_activate_menu(GtkMenuItem *widget, gpointer data) + +/** Macro to define a handler for button events. */ +#define BUTTONHANDLER(q)\ +static gboolean nsgtk_on_##q##_activate(GtkButton *widget, gpointer data) + +/** Core scaffolding structure. */ +struct nsgtk_scaffolding { + /** global linked list of scaffoldings for gui interface adjustments */ + struct nsgtk_scaffolding *next, *prev; + + /** currently active gui browsing context */ + struct gui_window *top_level; + + /** local history window */ + struct gtk_history_window *history_window; + + /** Builder object scaffold was created from */ + GtkBuilder *builder; + + /** scaffold container window */ + GtkWindow *window; + bool fullscreen; /**< flag for the scaffold window fullscreen status */ + + /** tab widget holding displayed pages */ + GtkNotebook *notebook; + + /** entry widget holding the url of the current displayed page */ + GtkWidget *url_bar; + GtkEntryCompletion *url_bar_completion; /**< Completions for url_bar */ + + /** Activity throbber */ + GtkImage *throbber; + int throb_frame; /**< Current frame of throbber animation */ + + struct gtk_search *search; + /** Web search widget */ + GtkWidget *webSearchEntry; + + /** controls toolbar */ + GtkToolbar *tool_bar; + struct nsgtk_button_connect *buttons[PLACEHOLDER_BUTTON]; + int offset; + int toolbarmem; + int toolbarbase; + int historybase; + + /** menu bar hierarchy */ + struct nsgtk_bar_submenu *menu_bar; + + /** right click popup menu hierarchy */ + struct nsgtk_popup_menu *menu_popup; + + /** link popup menu */ + struct nsgtk_link_menu *link_menu; + +}; + +/** current scaffold for model dialogue use */ +static struct nsgtk_scaffolding *scaf_current; + +/** global list for interface changes */ +static struct nsgtk_scaffolding *scaf_list = NULL; + +/** holds the context data for what's under the pointer, when the contextual + * menu is opened. + */ +static struct browser_window_features current_menu_features; + + +/** + * Helper to hide popup menu entries by grouping + */ +static void popup_menu_hide(struct nsgtk_popup_menu *menu, bool submenu, + bool nav, bool cnp, bool custom) +{ + if (submenu){ + gtk_widget_hide(GTK_WIDGET(menu->file_menuitem)); + gtk_widget_hide(GTK_WIDGET(menu->edit_menuitem)); + gtk_widget_hide(GTK_WIDGET(menu->view_menuitem)); + gtk_widget_hide(GTK_WIDGET(menu->nav_menuitem)); + gtk_widget_hide(GTK_WIDGET(menu->help_menuitem)); + + gtk_widget_hide(menu->first_separator); + } + + if (nav) { + gtk_widget_hide(GTK_WIDGET(menu->back_menuitem)); + gtk_widget_hide(GTK_WIDGET(menu->forward_menuitem)); + gtk_widget_hide(GTK_WIDGET(menu->stop_menuitem)); + gtk_widget_hide(GTK_WIDGET(menu->reload_menuitem)); + } + + if (cnp) { + gtk_widget_hide(GTK_WIDGET(menu->cut_menuitem)); + gtk_widget_hide(GTK_WIDGET(menu->copy_menuitem)); + gtk_widget_hide(GTK_WIDGET(menu->paste_menuitem)); + } + + if (custom) { + gtk_widget_hide(GTK_WIDGET(menu->customize_menuitem)); + } + +} + +/** + * Helper to show popup menu entries by grouping + */ +static void popup_menu_show(struct nsgtk_popup_menu *menu, bool submenu, + bool nav, bool cnp, bool custom) +{ + if (submenu){ + gtk_widget_show(GTK_WIDGET(menu->file_menuitem)); + gtk_widget_show(GTK_WIDGET(menu->edit_menuitem)); + gtk_widget_show(GTK_WIDGET(menu->view_menuitem)); + gtk_widget_show(GTK_WIDGET(menu->nav_menuitem)); + gtk_widget_show(GTK_WIDGET(menu->help_menuitem)); + + gtk_widget_show(menu->first_separator); + } + + if (nav) { + gtk_widget_show(GTK_WIDGET(menu->back_menuitem)); + gtk_widget_show(GTK_WIDGET(menu->forward_menuitem)); + gtk_widget_show(GTK_WIDGET(menu->stop_menuitem)); + gtk_widget_show(GTK_WIDGET(menu->reload_menuitem)); + } + + if (cnp) { + gtk_widget_show(GTK_WIDGET(menu->cut_menuitem)); + gtk_widget_show(GTK_WIDGET(menu->copy_menuitem)); + gtk_widget_show(GTK_WIDGET(menu->paste_menuitem)); + } + + if (custom) { + gtk_widget_show(GTK_WIDGET(menu->customize_menuitem)); + } + +} + + +/* event handlers and support functions for them */ + +/** + * resource cleanup function for window destruction. + */ +static void scaffolding_window_destroy(GtkWidget *widget, gpointer data) +{ + struct nsgtk_scaffolding *gs = data; + + LOG("scaffold:%p", gs); + + if ((gs->history_window) && (gs->history_window->window)) { + gtk_widget_destroy(GTK_WIDGET(gs->history_window->window)); + } + + if (gs->prev != NULL) { + gs->prev->next = gs->next; + } else { + scaf_list = gs->next; + } + if (gs->next != NULL) { + gs->next->prev = gs->prev; + } + + LOG("scaffold list head: %p", scaf_list); + + if (scaf_list == NULL) { + /* no more open windows - stop the browser */ + nsgtk_complete = true; + } +} + +/* signal delivered on window delete event, allowing to halt close if + * download is in progress + */ +static gboolean scaffolding_window_delete_event(GtkWidget *widget, + GdkEvent *event, gpointer data) +{ + struct nsgtk_scaffolding *g = data; + + if (nsgtk_check_for_downloads(GTK_WINDOW(widget)) == false) { + gtk_widget_destroy(GTK_WIDGET(g->window)); + } + return TRUE; +} + +/** + * Update the scaffoling button sensitivity, url bar and local history size + */ +static void scaffolding_update_context(struct nsgtk_scaffolding *g) +{ + int width, height; + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + + g->buttons[BACK_BUTTON]->sensitivity = + browser_window_history_back_available(bw); + g->buttons[FORWARD_BUTTON]->sensitivity = + browser_window_history_forward_available(bw); + + nsgtk_scaffolding_set_sensitivity(g); + + /* update the url bar, particularly necessary when tabbing */ + browser_window_refresh_url_bar(bw); + + /* update the local history window, as well as queuing a redraw + * for it. + */ + browser_window_history_size(bw, &width, &height); + gtk_widget_set_size_request(GTK_WIDGET(g->history_window->drawing_area), + width, height); + gtk_widget_queue_draw(GTK_WIDGET(g->history_window->drawing_area)); +} + +/** + * Make the throbber run. + */ +static void nsgtk_throb(void *p) +{ + struct nsgtk_scaffolding *g = p; + + if (g->throb_frame >= (nsgtk_throbber->nframes - 1)) + g->throb_frame = 1; + else + g->throb_frame++; + + gtk_image_set_from_pixbuf(g->throbber, nsgtk_throbber->framedata[ + g->throb_frame]); + + nsgtk_schedule(100, nsgtk_throb, p); +} + +static guint nsgtk_scaffolding_update_edit_actions_sensitivity( + struct nsgtk_scaffolding *g) +{ + GtkWidget *widget = gtk_window_get_focus(g->window); + gboolean has_selection; + + if (GTK_IS_EDITABLE(widget)) { + has_selection = gtk_editable_get_selection_bounds( + GTK_EDITABLE (widget), NULL, NULL); + + g->buttons[COPY_BUTTON]->sensitivity = has_selection; + g->buttons[CUT_BUTTON]->sensitivity = has_selection; + g->buttons[PASTE_BUTTON]->sensitivity = true; + } else { + struct browser_window *bw = + nsgtk_get_browser_window(g->top_level); + browser_editor_flags edit_f = + browser_window_get_editor_flags(bw); + + g->buttons[COPY_BUTTON]->sensitivity = + edit_f & BW_EDITOR_CAN_COPY; + g->buttons[CUT_BUTTON]->sensitivity = + edit_f & BW_EDITOR_CAN_CUT; + g->buttons[PASTE_BUTTON]->sensitivity = + edit_f & BW_EDITOR_CAN_PASTE; + } + + nsgtk_scaffolding_set_sensitivity(g); + return ((g->buttons[COPY_BUTTON]->sensitivity) | + (g->buttons[CUT_BUTTON]->sensitivity) | + (g->buttons[PASTE_BUTTON]->sensitivity)); +} + + +static void nsgtk_scaffolding_enable_edit_actions_sensitivity( + struct nsgtk_scaffolding *g) +{ + + g->buttons[PASTE_BUTTON]->sensitivity = true; + g->buttons[COPY_BUTTON]->sensitivity = true; + g->buttons[CUT_BUTTON]->sensitivity = true; + nsgtk_scaffolding_set_sensitivity(g); + + popup_menu_show(g->menu_popup, false, false, true, false); +} + +/* signal handling functions for the toolbar, URL bar, and menu bar */ +static gboolean nsgtk_window_edit_menu_clicked(GtkWidget *widget, + struct nsgtk_scaffolding *g) +{ + nsgtk_scaffolding_update_edit_actions_sensitivity(g); + + return TRUE; +} + +static gboolean nsgtk_window_edit_menu_hidden(GtkWidget *widget, + struct nsgtk_scaffolding *g) +{ + nsgtk_scaffolding_enable_edit_actions_sensitivity(g); + + return TRUE; +} + +static gboolean nsgtk_window_popup_menu_hidden(GtkWidget *widget, + struct nsgtk_scaffolding *g) +{ + nsgtk_scaffolding_enable_edit_actions_sensitivity(g); + return TRUE; +} + +gboolean nsgtk_window_url_activate_event(GtkWidget *widget, gpointer data) +{ + struct nsgtk_scaffolding *g = data; + nserror ret; + nsurl *url; + + ret = search_web_omni(gtk_entry_get_text(GTK_ENTRY(g->url_bar)), + SEARCH_WEB_OMNI_NONE, + &url); + if (ret == NSERROR_OK) { + ret = browser_window_navigate(nsgtk_get_browser_window(g->top_level), + url, NULL, BW_NAVIGATE_HISTORY, + NULL, NULL, NULL); + nsurl_unref(url); + } + if (ret != NSERROR_OK) { + nsgtk_warning(messages_get_errorcode(ret), 0); + } + + return TRUE; +} + +/** + * update handler for URL entry widget + */ +gboolean +nsgtk_window_url_changed(GtkWidget *widget, + GdkEventKey *event, + gpointer data) +{ + return nsgtk_completion_update(GTK_ENTRY(widget)); +} + +/** + * Event handler for popup menu on toolbar. + */ +static gboolean nsgtk_window_tool_bar_clicked(GtkToolbar *toolbar, + gint x, gint y, gint button, gpointer data) +{ + struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *)data; + + /* set visibility for right-click popup menu */ + popup_menu_hide(g->menu_popup, true, false, true, false); + popup_menu_show(g->menu_popup, false, false, false, true); + + gtk_menu_popup(g->menu_popup->popup_menu, NULL, NULL, NULL, NULL, 0, + gtk_get_current_event_time()); + + return TRUE; +} + +/** + * Update the menus when the number of tabs changes. + */ +static void nsgtk_window_tabs_add(GtkNotebook *notebook, + GtkWidget *page, guint page_num, struct nsgtk_scaffolding *g) +{ + gboolean visible = gtk_notebook_get_show_tabs(g->notebook); + g_object_set(g->menu_bar->view_submenu->tabs_menuitem, "visible", visible, NULL); + g_object_set(g->menu_popup->view_submenu->tabs_menuitem, "visible", visible, NULL); + g->buttons[NEXTTAB_BUTTON]->sensitivity = visible; + g->buttons[PREVTAB_BUTTON]->sensitivity = visible; + g->buttons[CLOSETAB_BUTTON]->sensitivity = visible; + nsgtk_scaffolding_set_sensitivity(g); +} + +/** + * Update the menus when the number of tabs changes. + */ +static void +nsgtk_window_tabs_remove(GtkNotebook *notebook, + GtkWidget *page, + guint page_num, + struct nsgtk_scaffolding *gs) +{ + /* if the scaffold is being destroyed it is not useful to + * update the state, futher many of the widgets may have + * already been destroyed. + */ + if (gtk_widget_in_destruction(GTK_WIDGET(gs->window)) == TRUE) { + return; + } + + /* if this is the last tab destroy the scaffold in addition */ + if (gtk_notebook_get_n_pages(notebook) == 1) { + gtk_widget_destroy(GTK_WIDGET(gs->window)); + return; + } + + gboolean visible = gtk_notebook_get_show_tabs(gs->notebook); + g_object_set(gs->menu_bar->view_submenu->tabs_menuitem, "visible", visible, NULL); + g_object_set(gs->menu_popup->view_submenu->tabs_menuitem, "visible", visible, NULL); + gs->buttons[NEXTTAB_BUTTON]->sensitivity = visible; + gs->buttons[PREVTAB_BUTTON]->sensitivity = visible; + gs->buttons[CLOSETAB_BUTTON]->sensitivity = visible; + nsgtk_scaffolding_set_sensitivity(gs); +} + +/** + * Handle opening a file path. + */ +static void nsgtk_openfile_open(const char *filename) +{ + struct browser_window *bw; + char *urltxt; + nsurl *url; + nserror error; + + bw = nsgtk_get_browser_window(scaf_current->top_level); + + urltxt = malloc(strlen(filename) + FILE_SCHEME_PREFIX_LEN + 1); + + if (urltxt != NULL) { + sprintf(urltxt, FILE_SCHEME_PREFIX"%s", filename); + + error = nsurl_create(urltxt, &url); + if (error != NSERROR_OK) { + nsgtk_warning(messages_get_errorcode(error), 0); + } else { + browser_window_navigate(bw, + url, + NULL, + BW_NAVIGATE_HISTORY, + NULL, + NULL, + NULL); + nsurl_unref(url); + } + free(urltxt); + } +} + +/* signal handlers for menu entries */ + +MULTIHANDLER(newwindow) +{ + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + const char *addr; + nsurl *url; + nserror error; + + if (nsoption_charp(homepage_url) != NULL) { + addr = nsoption_charp(homepage_url); + } else { + addr = NETSURF_HOMEPAGE; + } + + error = nsurl_create(addr, &url); + if (error == NSERROR_OK) { + error = browser_window_create(BW_CREATE_HISTORY, + url, + NULL, + bw, + NULL); + nsurl_unref(url); + } + if (error != NSERROR_OK) { + nsgtk_warning(messages_get_errorcode(error), 0); + } + + return TRUE; +} + +nserror nsgtk_scaffolding_new_tab(struct gui_window *gw) +{ + struct browser_window *bw = nsgtk_get_browser_window(gw); + nsurl *url = NULL; + nserror error; + + if (!nsoption_bool(new_blank)) { + const char *addr; + if (nsoption_charp(homepage_url) != NULL) { + addr = nsoption_charp(homepage_url); + } else { + addr = NETSURF_HOMEPAGE; + } + error = nsurl_create(addr, &url); + if (error != NSERROR_OK) { + nsgtk_warning(messages_get_errorcode(error), 0); + } + } + + error = browser_window_create(BW_CREATE_HISTORY | + BW_CREATE_TAB, + url, + NULL, + bw, + NULL); + if (url != NULL) { + nsurl_unref(url); + } + return error; +} + +MULTIHANDLER(newtab) +{ + nserror error; + + error = nsgtk_scaffolding_new_tab(g->top_level); + if (error != NSERROR_OK) { + nsgtk_warning(messages_get_errorcode(error), 0); + } + return TRUE; +} + +MULTIHANDLER(openfile) +{ + GtkWidget *dlgOpen; + gint response; + + scaf_current = g; + dlgOpen = gtk_file_chooser_dialog_new("Open File", + scaf_current->window, + 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) { + gchar *filename; + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlgOpen)); + + nsgtk_openfile_open((const char *)filename); + + g_free(filename); + } + + gtk_widget_destroy(dlgOpen); + return TRUE; +} + +static gboolean nsgtk_filter_directory(const GtkFileFilterInfo *info, + gpointer data) +{ + DIR *d = opendir(info->filename); + if (d == NULL) + return FALSE; + closedir(d); + return TRUE; +} + +MULTIHANDLER(savepage) +{ + if (!browser_window_has_content(nsgtk_get_browser_window(g->top_level))) + return FALSE; + + GtkWidget *fc = gtk_file_chooser_dialog_new( + messages_get("gtkcompleteSave"), g->window, + GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER, + NSGTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + NSGTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + DIR *d; + char *path; + nserror res; + GtkFileFilter *filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, "Directories"); + gtk_file_filter_add_custom(filter, GTK_FILE_FILTER_FILENAME, + nsgtk_filter_directory, NULL, NULL); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(fc), filter); + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fc), filter); + + res = nsurl_nice(browser_window_get_url( + nsgtk_get_browser_window(g->top_level)), &path, false); + if (res != NSERROR_OK) { + path = strdup(messages_get("SaveText")); + if (path == NULL) { + nsgtk_warning("NoMemory", 0); + return FALSE; + } + } + + if (access(path, F_OK) != 0) + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(fc), path); + free(path); + + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(fc), + TRUE); + + if (gtk_dialog_run(GTK_DIALOG(fc)) != GTK_RESPONSE_ACCEPT) { + gtk_widget_destroy(fc); + return TRUE; + } + + path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fc)); + d = opendir(path); + if (d == NULL) { + LOG("Unable to open directory %s for complete save: %s", path, strerror(errno)); + if (errno == ENOTDIR) + nsgtk_warning("NoDirError", path); + else + nsgtk_warning("gtkFileError", path); + gtk_widget_destroy(fc); + g_free(path); + return TRUE; + } + closedir(d); + save_complete(browser_window_get_content(nsgtk_get_browser_window( + g->top_level)), path, NULL); + g_free(path); + + gtk_widget_destroy(fc); + + return TRUE; +} + + +MULTIHANDLER(pdf) +{ +#ifdef WITH_PDF_EXPORT + + GtkWidget *save_dialog; + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + struct print_settings *settings; + char filename[PATH_MAX]; + char dirname[PATH_MAX]; + char *url_name; + nserror res; + + LOG("Print preview (generating PDF) started."); + + res = nsurl_nice(browser_window_get_url(bw), &url_name, true); + if (res != NSERROR_OK) { + nsgtk_warning(messages_get_errorcode(res), 0); + return TRUE; + } + + strncpy(filename, url_name, PATH_MAX); + strncat(filename, ".pdf", PATH_MAX - strlen(filename)); + filename[PATH_MAX - 1] = '\0'; + + free(url_name); + + strncpy(dirname, option_downloads_directory, PATH_MAX); + strncat(dirname, "/", PATH_MAX - strlen(dirname)); + dirname[PATH_MAX - 1] = '\0'; + + /* this way the scale used by PDF functions is synchronized with that + * used by the all-purpose print interface + */ + haru_nsfont_set_scale((float)option_export_scale / 100); + + save_dialog = gtk_file_chooser_dialog_new("Export to PDF", g->window, + GTK_FILE_CHOOSER_ACTION_SAVE, + NSGTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + NSGTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(save_dialog), + dirname); + + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(save_dialog), + filename); + + if (gtk_dialog_run(GTK_DIALOG(save_dialog)) == GTK_RESPONSE_ACCEPT) { + gchar *filename = gtk_file_chooser_get_filename( + GTK_FILE_CHOOSER(save_dialog)); + + settings = print_make_settings(PRINT_OPTIONS, + (const char *) filename, &haru_nsfont); + g_free(filename); + + if (settings == NULL) { + nsgtk_warning(messages_get("NoMemory"), 0); + gtk_widget_destroy(save_dialog); + return TRUE; + } + + /* This will clean up the print_settings object for us */ + print_basic_run(browser_window_get_content(bw), + &pdf_printer, settings); + } + + gtk_widget_destroy(save_dialog); + +#endif /* WITH_PDF_EXPORT */ + + return TRUE; +} + +MULTIHANDLER(plaintext) +{ + if (!browser_window_has_content(nsgtk_get_browser_window(g->top_level))) + return FALSE; + + GtkWidget *fc = gtk_file_chooser_dialog_new( + messages_get("gtkplainSave"), g->window, + GTK_FILE_CHOOSER_ACTION_SAVE, + NSGTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + NSGTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + char *filename; + nserror res; + + res = nsurl_nice(browser_window_get_url( + nsgtk_get_browser_window(g->top_level)), + &filename, false); + if (res != NSERROR_OK) { + filename = strdup(messages_get("SaveText")); + if (filename == NULL) { + nsgtk_warning("NoMemory", 0); + return FALSE; + } + } + + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(fc), filename); + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(fc), + TRUE); + + free(filename); + + if (gtk_dialog_run(GTK_DIALOG(fc)) == GTK_RESPONSE_ACCEPT) { + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fc)); + save_as_text(browser_window_get_content( + nsgtk_get_browser_window( + g->top_level)), filename); + g_free(filename); + } + + gtk_widget_destroy(fc); + return TRUE; +} + +MULTIHANDLER(drawfile) +{ + return TRUE; +} + +MULTIHANDLER(postscript) +{ + return TRUE; +} + +MULTIHANDLER(printpreview) +{ + return TRUE; +} + + +MULTIHANDLER(print) +{ + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + + GtkPrintOperation *print_op; + GtkPageSetup *page_setup; + GtkPrintSettings *print_settings; + GtkPrintOperationResult res = GTK_PRINT_OPERATION_RESULT_ERROR; + struct print_settings *nssettings; + char *settings_fname = NULL; + + 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(g->window, 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, + g->window, + 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; +} + +MULTIHANDLER(closewindow) +{ + gtk_widget_destroy(GTK_WIDGET(g->window)); + return TRUE; +} + +MULTIHANDLER(quit) +{ + struct nsgtk_scaffolding *gs; + + if (nsgtk_check_for_downloads(g->window) == false) { + gs = scaf_list; + while (gs != NULL) { + gtk_widget_destroy(GTK_WIDGET(gs->window)); + gs = gs->next; + } + } + + return TRUE; +} + +MENUHANDLER(savelink) +{ + struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *) data; + struct gui_window *gui = g->top_level; + struct browser_window *bw = nsgtk_get_browser_window(gui); + nserror err; + + if (current_menu_features.link == NULL) + return FALSE; + + err = browser_window_navigate(bw, + current_menu_features.link, + NULL, + BW_NAVIGATE_DOWNLOAD, + NULL, + NULL, + NULL); + if (err != NSERROR_OK) { + nsgtk_warning(messages_get_errorcode(err), 0); + } + + return TRUE; +} + +/** + * Handler for opening new window from a link. attached to the popup menu. + */ +MENUHANDLER(link_openwin) +{ + struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *) data; + struct gui_window *gui = g->top_level; + struct browser_window *bw = nsgtk_get_browser_window(gui); + nserror err; + + if (current_menu_features.link == NULL) + return FALSE; + + err = browser_window_create(BW_CREATE_CLONE | BW_CREATE_HISTORY, + current_menu_features.link, NULL, bw, NULL); + if (err != NSERROR_OK) { + nsgtk_warning(messages_get_errorcode(err), 0); + } + + return TRUE; +} + +/** + * Handler for opening new tab from a link. attached to the popup menu. + */ +MENUHANDLER(link_opentab) +{ + struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *) data; + struct gui_window *gui = g->top_level; + struct browser_window *bw = nsgtk_get_browser_window(gui); + nserror err; + + if (current_menu_features.link == NULL) + return FALSE; + + temp_open_background = 1; + + err = browser_window_create(BW_CREATE_CLONE | + BW_CREATE_HISTORY | + BW_CREATE_TAB, + current_menu_features.link, NULL, bw, NULL); + if (err != NSERROR_OK) { + nsgtk_warning(messages_get_errorcode(err), 0); + } + + temp_open_background = -1; + + return TRUE; +} + +/** + * Handler for bookmarking a link. attached to the popup menu. + */ +MENUHANDLER(link_bookmark) +{ + if (current_menu_features.link == NULL) + return FALSE; + + hotlist_add_url(current_menu_features.link); + + return TRUE; +} + +/** + * Handler for copying a link. attached to the popup menu. + */ +MENUHANDLER(link_copy) +{ + GtkClipboard *clipboard; + + if (current_menu_features.link == NULL) + return FALSE; + + clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text(clipboard, + nsurl_access(current_menu_features.link), -1); + + return TRUE; +} + + +MULTIHANDLER(cut) +{ + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + GtkWidget *focused = gtk_window_get_focus(g->window); + + /* If the url bar has focus, let gtk handle it */ + if (GTK_IS_EDITABLE (focused)) + gtk_editable_cut_clipboard (GTK_EDITABLE(g->url_bar)); + else + browser_window_key_press(bw, NS_KEY_CUT_SELECTION); + + return TRUE; +} + +MULTIHANDLER(copy) +{ + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + GtkWidget *focused = gtk_window_get_focus(g->window); + + /* If the url bar has focus, let gtk handle it */ + if (GTK_IS_EDITABLE (focused)) + gtk_editable_copy_clipboard(GTK_EDITABLE(g->url_bar)); + else + browser_window_key_press(bw, NS_KEY_COPY_SELECTION); + + return TRUE; +} + +MULTIHANDLER(paste) +{ + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + GtkWidget *focused = gtk_window_get_focus(g->window); + + /* If the url bar has focus, let gtk handle it */ + if (GTK_IS_EDITABLE (focused)) + gtk_editable_paste_clipboard (GTK_EDITABLE (focused)); + else + browser_window_key_press(bw, NS_KEY_PASTE); + + return TRUE; +} + +MULTIHANDLER(delete) +{ + return TRUE; +} + +MENUHANDLER(customize) +{ + struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *)data; + nsgtk_toolbar_customization_init(g); + return TRUE; +} + +MULTIHANDLER(selectall) +{ + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + + if (nsgtk_widget_has_focus(GTK_WIDGET(g->url_bar))) { + LOG("Selecting all URL bar text"); + gtk_editable_select_region(GTK_EDITABLE(g->url_bar), 0, -1); + } else { + LOG("Selecting all document text"); + browser_window_key_press(bw, NS_KEY_SELECT_ALL); + } + + return TRUE; +} + +MULTIHANDLER(find) +{ + nsgtk_scaffolding_toggle_search_bar_visibility(g); + return TRUE; +} + +MULTIHANDLER(preferences) +{ + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + GtkWidget* wndpreferences; + + wndpreferences = nsgtk_preferences(bw, g->window); + if (wndpreferences != NULL) { + gtk_widget_show(GTK_WIDGET(wndpreferences)); + } + + return TRUE; +} + +MULTIHANDLER(zoomplus) +{ + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + float old_scale = nsgtk_get_scale_for_gui(g->top_level); + + browser_window_set_scale(bw, old_scale + 0.05, true); + + return TRUE; +} + +MULTIHANDLER(zoomnormal) +{ + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + + browser_window_set_scale(bw, 1.0, true); + + return TRUE; +} + +MULTIHANDLER(zoomminus) +{ + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + float old_scale = nsgtk_get_scale_for_gui(g->top_level); + + browser_window_set_scale(bw, old_scale - 0.05, true); + + return TRUE; +} + +MULTIHANDLER(fullscreen) +{ + if (g->fullscreen) { + gtk_window_unfullscreen(g->window); + } else { + gtk_window_fullscreen(g->window); + } + + g->fullscreen = !g->fullscreen; + + return TRUE; +} + +MULTIHANDLER(viewsource) +{ + nserror ret; + + ret = nsgtk_viewsource(g->window, nsgtk_get_browser_window(g->top_level)); + if (ret != NSERROR_OK) { + nsgtk_warning(messages_get_errorcode(ret), 0); + } + + return TRUE; +} + +MENUHANDLER(menubar) +{ + GtkWidget *w; + struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *)data; + + /* if the menubar is not being shown the popup menu shows the + * menubar entries instead. + */ + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) { + /* need to synchronise menus as gtk grumbles when one menu + * is attached to both headers */ + w = GTK_WIDGET(g->menu_popup->view_submenu->toolbars_submenu->menubar_menuitem); + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)) + == FALSE) + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), + TRUE); + + w = GTK_WIDGET(g->menu_bar->view_submenu->toolbars_submenu->menubar_menuitem); + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)) + == FALSE) + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), + TRUE); + + gtk_widget_show(GTK_WIDGET(g->menu_bar->bar_menu)); + + popup_menu_show(g->menu_popup, false, true, true, true); + popup_menu_hide(g->menu_popup, true, false, false, false); + } else { + w = GTK_WIDGET(g->menu_popup->view_submenu->toolbars_submenu->menubar_menuitem); + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w))) + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), + FALSE); + + w = GTK_WIDGET(g->menu_bar->view_submenu->toolbars_submenu->menubar_menuitem); + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w))) + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), + FALSE); + + gtk_widget_hide(GTK_WIDGET(g->menu_bar->bar_menu)); + + popup_menu_show(g->menu_popup, true, true, true, true); + + } + return TRUE; +} + +MENUHANDLER(toolbar) +{ + GtkWidget *w; + struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *)data; + + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) { + w = GTK_WIDGET(g->menu_popup->view_submenu->toolbars_submenu->toolbar_menuitem); + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)) + == FALSE) + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), + TRUE); + + w = GTK_WIDGET(g->menu_bar->view_submenu->toolbars_submenu->toolbar_menuitem); + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)) + == FALSE) + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), + TRUE); + gtk_widget_show(GTK_WIDGET(g->tool_bar)); + } else { + w = GTK_WIDGET(g->menu_popup->view_submenu->toolbars_submenu->toolbar_menuitem); + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w))) + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), + FALSE); + w = GTK_WIDGET(g->menu_bar->view_submenu->toolbars_submenu->toolbar_menuitem); + if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w))) + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), + FALSE); + gtk_widget_hide(GTK_WIDGET(g->tool_bar)); + } + + return TRUE; +} + +MULTIHANDLER(downloads) +{ + nsgtk_download_show(g->window); + + return TRUE; +} + +MULTIHANDLER(savewindowsize) +{ + int x,y,w,h; + char *choices = NULL; + + gtk_window_get_position(g->window, &x, &y); + gtk_window_get_size(g->window, &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; +} + +MULTIHANDLER(toggledebugging) +{ + struct browser_window *bw; + + bw = nsgtk_get_browser_window(g->top_level); + + browser_window_debug(bw, CONTENT_DEBUG_REDRAW); + + nsgtk_reflow_all_windows(); + + return TRUE; +} + +MULTIHANDLER(debugboxtree) +{ + gchar *fname; + gint handle; + FILE *f; + struct browser_window *bw; + + 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 = nsgtk_get_browser_window(g->top_level); + + browser_window_debug_dump(bw, f, CONTENT_DEBUG_RENDER); + + fclose(f); + + nsgtk_viewfile("Box Tree Debug", "boxtree", fname); + + g_free(fname); + + return TRUE; +} + +MULTIHANDLER(debugdomtree) +{ + gchar *fname; + gint handle; + FILE *f; + struct browser_window *bw; + + 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 = nsgtk_get_browser_window(g->top_level); + + browser_window_debug_dump(bw, f, CONTENT_DEBUG_DOM); + + fclose(f); + + nsgtk_viewfile("DOM Tree Debug", "domtree", fname); + + g_free(fname); + + return TRUE; +} + + +MULTIHANDLER(stop) +{ + struct browser_window *bw = + nsgtk_get_browser_window(g->top_level); + + browser_window_stop(bw); + + return TRUE; +} + +MULTIHANDLER(reload) +{ + struct browser_window *bw = + nsgtk_get_browser_window(g->top_level); + if (bw == NULL) + return TRUE; + + /* clear potential search effects */ + browser_window_search_clear(bw); + + browser_window_reload(bw, true); + + return TRUE; +} + +MULTIHANDLER(back) +{ + struct browser_window *bw = + nsgtk_get_browser_window(g->top_level); + + if ((bw == NULL) || (!browser_window_history_back_available(bw))) + return TRUE; + + /* clear potential search effects */ + browser_window_search_clear(bw); + + browser_window_history_back(bw, false); + scaffolding_update_context(g); + + return TRUE; +} + +MULTIHANDLER(forward) +{ + struct browser_window *bw = + nsgtk_get_browser_window(g->top_level); + + if ((bw == NULL) || (!browser_window_history_forward_available(bw))) + return TRUE; + + /* clear potential search effects */ + browser_window_search_clear(bw); + + browser_window_history_forward(bw, false); + scaffolding_update_context(g); + + return TRUE; +} + +MULTIHANDLER(home) +{ + static const char *addr = NETSURF_HOMEPAGE; + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + nsurl *url; + nserror error; + + if (nsoption_charp(homepage_url) != NULL) { + addr = nsoption_charp(homepage_url); + } + + error = nsurl_create(addr, &url); + if (error != NSERROR_OK) { + nsgtk_warning(messages_get_errorcode(error), 0); + } else { + browser_window_navigate(bw, + url, + NULL, + BW_NAVIGATE_HISTORY, + NULL, + NULL, + NULL); + nsurl_unref(url); + } + + return TRUE; +} + +MULTIHANDLER(localhistory) +{ + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + + int x,y, width, height, mainwidth, mainheight, margin = 20; + /* if entries of the same url but different frag_ids have been added + * the history needs redrawing (what throbber code normally does) + */ + + scaffolding_update_context(g); + gtk_window_get_position(g->window, &x, &y); + gtk_window_get_size(g->window, &mainwidth, &mainheight); + browser_window_history_size(bw, &width, &height); + width = (width + g->historybase + margin > mainwidth) ? + mainwidth - g->historybase : width + margin; + height = (height + g->toolbarbase + margin > mainheight) ? + mainheight - g->toolbarbase : height + margin; + gtk_window_set_default_size(g->history_window->window, width, height); + gtk_widget_set_size_request(GTK_WIDGET(g->history_window->window), + -1, -1); + gtk_window_resize(g->history_window->window, width, height); + gtk_window_set_transient_for(g->history_window->window, g->window); + nsgtk_window_set_opacity(g->history_window->window, 0.9); + gtk_widget_show(GTK_WIDGET(g->history_window->window)); + gtk_window_move(g->history_window->window, x + g->historybase, y + + g->toolbarbase); + gdk_window_raise(nsgtk_widget_get_window(GTK_WIDGET(g->history_window->window))); + + return TRUE; +} + +MULTIHANDLER(globalhistory) +{ + gtk_widget_show(GTK_WIDGET(wndHistory)); + gdk_window_raise(nsgtk_widget_get_window(GTK_WIDGET(wndHistory))); + + return TRUE; +} + +MULTIHANDLER(addbookmarks) +{ + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + + if (bw == NULL || !browser_window_has_content(bw)) + return TRUE; + hotlist_add_url(browser_window_get_url(bw)); + return TRUE; +} + +MULTIHANDLER(showbookmarks) +{ + gtk_widget_show(GTK_WIDGET(wndHotlist)); + gdk_window_raise(nsgtk_widget_get_window(GTK_WIDGET(wndHotlist))); + gtk_window_set_focus(wndHotlist, NULL); + + return TRUE; +} + +MULTIHANDLER(showcookies) +{ + gtk_widget_show(GTK_WIDGET(wndCookies)); + gdk_window_raise(nsgtk_widget_get_window(GTK_WIDGET(wndCookies))); + + return TRUE; +} + +MULTIHANDLER(openlocation) +{ + gtk_widget_grab_focus(GTK_WIDGET(g->url_bar)); + return TRUE; +} + +MULTIHANDLER(nexttab) +{ + nsgtk_tab_next(g->notebook); + + return TRUE; +} + +MULTIHANDLER(prevtab) +{ + + nsgtk_tab_prev(g->notebook); + + return TRUE; +} + +MULTIHANDLER(closetab) +{ + nsgtk_tab_close_current(g->notebook); + + return TRUE; +} + +MULTIHANDLER(contents) +{ + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + nsurl *url; + nserror error; + + error = nsurl_create("http://www.netsurf-browser.org/documentation/", &url); + if (error != NSERROR_OK) { + nsgtk_warning(messages_get_errorcode(error), 0); + } else { + browser_window_navigate(bw, + url, + NULL, + BW_NAVIGATE_HISTORY, + NULL, + NULL, + NULL); + nsurl_unref(url); + } + + return TRUE; +} + +MULTIHANDLER(guide) +{ + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + nsurl *url; + + if (nsurl_create("http://www.netsurf-browser.org/documentation/guide", &url) != NSERROR_OK) { + nsgtk_warning("NoMemory", 0); + } else { + browser_window_navigate(bw, + url, + NULL, + BW_NAVIGATE_HISTORY, + NULL, + NULL, + NULL); + nsurl_unref(url); + } + + return TRUE; +} + +MULTIHANDLER(info) +{ + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + nsurl *url; + + if (nsurl_create("http://www.netsurf-browser.org/documentation/info", &url) != NSERROR_OK) { + nsgtk_warning("NoMemory", 0); + } else { + browser_window_navigate(bw, + url, + NULL, + BW_NAVIGATE_HISTORY, + NULL, + NULL, + NULL); + nsurl_unref(url); + } + + return TRUE; +} + +MULTIHANDLER(about) +{ + nsgtk_about_dialog_init(g->window); + return TRUE; +} + +BUTTONHANDLER(history) +{ + struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *)data; + return nsgtk_on_localhistory_activate(g); +} + +#undef MULTIHANDLER +#undef CHECKHANDLER +#undef BUTTONHANDLER + +#if GTK_CHECK_VERSION(3,0,0) + +static gboolean +nsgtk_history_draw_event(GtkWidget *widget, cairo_t *cr, gpointer data) +{ + struct rect clip; + struct gtk_history_window *hw = (struct gtk_history_window *)data; + struct browser_window *bw = + nsgtk_get_browser_window(hw->g->top_level); + + struct redraw_context ctx = { + .interactive = true, + .background_images = true, + .plot = &nsgtk_plotters + }; + double x1; + double y1; + double x2; + double y2; + + current_widget = widget; + current_cr = cr; + + cairo_clip_extents(cr, &x1, &y1, &x2, &y2); + + clip.x0 = x1; + clip.y0 = y1; + clip.x1 = x2; + clip.y1 = y2; + + ctx.plot->clip(&clip); + + browser_window_history_redraw(bw, &ctx); + + current_widget = NULL; + + return FALSE; +} +#else + +/* signal handler functions for the local history window */ +static gboolean +nsgtk_history_draw_event(GtkWidget *widget, GdkEventExpose *event, gpointer g) +{ + struct rect clip; + struct gtk_history_window *hw = (struct gtk_history_window *)g; + struct browser_window *bw = + nsgtk_get_browser_window(hw->g->top_level); + + struct redraw_context ctx = { + .interactive = true, + .background_images = true, + .plot = &nsgtk_plotters + }; + + current_widget = widget; + + current_cr = gdk_cairo_create(nsgtk_widget_get_window(widget)); + + clip.x0 = event->area.x; + clip.y0 = event->area.y; + clip.x1 = event->area.x + event->area.width; + clip.y1 = event->area.y + event->area.height; + ctx.plot->clip(&clip); + + browser_window_history_redraw(bw, &ctx); + + cairo_destroy(current_cr); + + current_widget = NULL; + + return FALSE; +} + +#endif /* GTK_CHECK_VERSION(3,0,0) */ + +static gboolean nsgtk_history_button_press_event(GtkWidget *widget, + GdkEventButton *event, gpointer g) +{ + struct gtk_history_window *hw = (struct gtk_history_window *)g; + struct browser_window *bw = + nsgtk_get_browser_window(hw->g->top_level); + + LOG("X=%g, Y=%g", event->x, event->y); + + browser_window_history_click(bw, event->x, event->y, false); + + return TRUE; +} + + + +static void nsgtk_attach_menu_handlers(struct nsgtk_scaffolding *g) +{ + for (int i = BACK_BUTTON; i < PLACEHOLDER_BUTTON; i++) { + if (g->buttons[i]->main != NULL) { + g_signal_connect(g->buttons[i]->main, "activate", + G_CALLBACK(g->buttons[i]->mhandler), g); + } + if (g->buttons[i]->rclick != NULL) { + g_signal_connect(g->buttons[i]->rclick, "activate", + G_CALLBACK(g->buttons[i]->mhandler), g); + } + if (g->buttons[i]->popup != NULL) { + g_signal_connect(g->buttons[i]->popup, "activate", + G_CALLBACK(g->buttons[i]->mhandler), g); + } + } +#define CONNECT_CHECK(q)\ + g_signal_connect(g->menu_bar->view_submenu->toolbars_submenu->q##_menuitem, "toggled", G_CALLBACK(nsgtk_on_##q##_activate_menu), g);\ + g_signal_connect(g->menu_popup->view_submenu->toolbars_submenu->q##_menuitem, "toggled", G_CALLBACK(nsgtk_on_##q##_activate_menu), g) + CONNECT_CHECK(menubar); + CONNECT_CHECK(toolbar); +#undef CONNECT_CHECK + +} + +/** + * Create and connect handlers to popup menu. + * + * \param g scaffolding to attach popup menu to. + * \param group The accelerator group to use for the popup. + * \return menu structure on success or NULL on error. + */ +static struct nsgtk_popup_menu * +nsgtk_new_scaffolding_popup(struct nsgtk_scaffolding *g, GtkAccelGroup *group) +{ + struct nsgtk_popup_menu *nmenu; + + nmenu = nsgtk_popup_menu_create(group); + + if (nmenu == NULL) { + return NULL; + } + + g_signal_connect(nmenu->popup_menu, "hide", + G_CALLBACK(nsgtk_window_popup_menu_hidden), g); + + g_signal_connect(nmenu->cut_menuitem, "activate", + G_CALLBACK(nsgtk_on_cut_activate_menu), g); + + g_signal_connect(nmenu->copy_menuitem, "activate", + G_CALLBACK(nsgtk_on_copy_activate_menu), g); + + g_signal_connect(nmenu->paste_menuitem, "activate", + G_CALLBACK(nsgtk_on_paste_activate_menu), g); + + g_signal_connect(nmenu->customize_menuitem, "activate", + G_CALLBACK(nsgtk_on_customize_activate_menu), g); + + /* set initial popup menu visibility */ + popup_menu_hide(nmenu, true, false, false, true); + + return nmenu; +} + +/** + * Create and connect handlers to link popup menu. + * + * \param g scaffolding to attach popup menu to. + * \param group The accelerator group to use for the popup. + * \return true on success or false on error. + */ +static struct nsgtk_link_menu * +nsgtk_new_scaffolding_link_popup(struct nsgtk_scaffolding *g, GtkAccelGroup *group) +{ + struct nsgtk_link_menu *nmenu; + + nmenu = nsgtk_link_menu_create(group); + + if (nmenu == NULL) { + return NULL; + } + + g_signal_connect(nmenu->save_menuitem, "activate", + G_CALLBACK(nsgtk_on_savelink_activate_menu), g); + + g_signal_connect(nmenu->opentab_menuitem, "activate", + G_CALLBACK(nsgtk_on_link_opentab_activate_menu), g); + + g_signal_connect(nmenu->openwin_menuitem, "activate", + G_CALLBACK(nsgtk_on_link_openwin_activate_menu), g); + + g_signal_connect(nmenu->bookmark_menuitem, "activate", + G_CALLBACK(nsgtk_on_link_bookmark_activate_menu), g); + + g_signal_connect(nmenu->copy_menuitem, "activate", + G_CALLBACK(nsgtk_on_link_copy_activate_menu), g); + + return nmenu; +} + +/* exported interface documented in gtk/scaffolding.h */ +struct nsgtk_scaffolding *nsgtk_current_scaffolding(void) +{ + if (scaf_current == NULL) { + scaf_current = scaf_list; + } + return scaf_current; +} + +/** + * init the array g->buttons[] + */ +static void nsgtk_scaffolding_toolbar_init(struct nsgtk_scaffolding *g) +{ +#define ITEM_MAIN(p, q, r)\ + g->buttons[p##_BUTTON]->main = g->menu_bar->q->r##_menuitem;\ + g->buttons[p##_BUTTON]->rclick = g->menu_popup->q->r##_menuitem;\ + g->buttons[p##_BUTTON]->mhandler = nsgtk_on_##r##_activate_menu;\ + g->buttons[p##_BUTTON]->bhandler = nsgtk_on_##r##_activate_button;\ + g->buttons[p##_BUTTON]->dataplus = nsgtk_toolbar_##r##_button_data;\ + g->buttons[p##_BUTTON]->dataminus = nsgtk_toolbar_##r##_toolbar_button_data + +#define ITEM_SUB(p, q, r, s)\ + g->buttons[p##_BUTTON]->main =\ + g->menu_bar->q->r##_submenu->s##_menuitem;\ + g->buttons[p##_BUTTON]->rclick =\ + g->menu_popup->q->r##_submenu->s##_menuitem;\ + g->buttons[p##_BUTTON]->mhandler =\ + nsgtk_on_##s##_activate_menu;\ + g->buttons[p##_BUTTON]->bhandler =\ + nsgtk_on_##s##_activate_button;\ + g->buttons[p##_BUTTON]->dataplus =\ + nsgtk_toolbar_##s##_button_data;\ + g->buttons[p##_BUTTON]->dataminus =\ + nsgtk_toolbar_##s##_toolbar_button_data + +#define ITEM_BUTTON(p, q)\ + g->buttons[p##_BUTTON]->bhandler =\ + nsgtk_on_##q##_activate;\ + g->buttons[p##_BUTTON]->dataplus =\ + nsgtk_toolbar_##q##_button_data;\ + g->buttons[p##_BUTTON]->dataminus =\ + nsgtk_toolbar_##q##_toolbar_button_data + +#define ITEM_POP(p, q) \ + g->buttons[p##_BUTTON]->popup = g->menu_popup->q##_menuitem + +#define SENSITIVITY(q) \ + g->buttons[q##_BUTTON]->sensitivity = false + +#define ITEM_ITEM(p, q)\ + g->buttons[p##_ITEM]->dataplus =\ + nsgtk_toolbar_##q##_button_data;\ + g->buttons[p##_ITEM]->dataminus =\ + nsgtk_toolbar_##q##_toolbar_button_data + + ITEM_ITEM(WEBSEARCH, websearch); + ITEM_ITEM(THROBBER, throbber); + ITEM_MAIN(NEWWINDOW, file_submenu, newwindow); + ITEM_MAIN(NEWTAB, file_submenu, newtab); + ITEM_MAIN(OPENFILE, file_submenu, openfile); + ITEM_MAIN(PRINT, file_submenu, print); + ITEM_MAIN(CLOSEWINDOW, file_submenu, closewindow); + ITEM_MAIN(SAVEPAGE, file_submenu, savepage); + ITEM_MAIN(PRINTPREVIEW, file_submenu, printpreview); + ITEM_MAIN(PRINT, file_submenu, print); + ITEM_MAIN(QUIT, file_submenu, quit); + ITEM_MAIN(CUT, edit_submenu, cut); + ITEM_MAIN(COPY, edit_submenu, copy); + ITEM_MAIN(PASTE, edit_submenu, paste); + ITEM_MAIN(DELETE, edit_submenu, delete); + ITEM_MAIN(SELECTALL, edit_submenu, selectall); + ITEM_MAIN(FIND, edit_submenu, find); + ITEM_MAIN(PREFERENCES, edit_submenu, preferences); + ITEM_MAIN(STOP, view_submenu, stop); + ITEM_POP(STOP, stop); + ITEM_MAIN(RELOAD, view_submenu, reload); + ITEM_POP(RELOAD, reload); + ITEM_MAIN(FULLSCREEN, view_submenu, fullscreen); + ITEM_MAIN(DOWNLOADS, tools_submenu, downloads); + ITEM_MAIN(SAVEWINDOWSIZE, view_submenu, savewindowsize); + ITEM_MAIN(BACK, nav_submenu, back); + ITEM_POP(BACK, back); + ITEM_MAIN(FORWARD, nav_submenu, forward); + ITEM_POP(FORWARD, forward); + ITEM_MAIN(HOME, nav_submenu, home); + ITEM_MAIN(LOCALHISTORY, nav_submenu, localhistory); + ITEM_MAIN(GLOBALHISTORY, nav_submenu, globalhistory); + ITEM_MAIN(ADDBOOKMARKS, nav_submenu, addbookmarks); + ITEM_MAIN(SHOWBOOKMARKS, nav_submenu, showbookmarks); + ITEM_MAIN(SHOWCOOKIES, tools_submenu, showcookies); + ITEM_MAIN(OPENLOCATION, nav_submenu, openlocation); + ITEM_MAIN(CONTENTS, help_submenu, contents); + ITEM_MAIN(INFO, help_submenu, info); + ITEM_MAIN(GUIDE, help_submenu, guide); + ITEM_MAIN(ABOUT, help_submenu, about); + ITEM_SUB(PLAINTEXT, file_submenu, export, plaintext); + ITEM_SUB(PDF, file_submenu, export, pdf); + ITEM_SUB(DRAWFILE, file_submenu, export, drawfile); + ITEM_SUB(POSTSCRIPT, file_submenu, export, postscript); + ITEM_SUB(ZOOMPLUS, view_submenu, scaleview, zoomplus); + ITEM_SUB(ZOOMMINUS, view_submenu, scaleview, zoomminus); + ITEM_SUB(ZOOMNORMAL, view_submenu, scaleview, zoomnormal); + ITEM_SUB(NEXTTAB, view_submenu, tabs, nexttab); + ITEM_SUB(PREVTAB, view_submenu, tabs, prevtab); + ITEM_SUB(CLOSETAB, view_submenu, tabs, closetab); + + /* development submenu */ + ITEM_SUB(VIEWSOURCE, tools_submenu, developer, viewsource); + ITEM_SUB(TOGGLEDEBUGGING, tools_submenu, developer, toggledebugging); + ITEM_SUB(SAVEBOXTREE, tools_submenu, developer, debugboxtree); + ITEM_SUB(SAVEDOMTREE, tools_submenu, developer, debugdomtree); + ITEM_BUTTON(HISTORY, history); + + /* disable items that make no sense initially, as well as + * as-yet-unimplemented items */ + SENSITIVITY(BACK); + SENSITIVITY(FORWARD); + SENSITIVITY(STOP); + SENSITIVITY(PRINTPREVIEW); + SENSITIVITY(DELETE); + SENSITIVITY(DRAWFILE); + SENSITIVITY(POSTSCRIPT); + SENSITIVITY(NEXTTAB); + SENSITIVITY(PREVTAB); + SENSITIVITY(CLOSETAB); +#ifndef WITH_PDF_EXPORT + SENSITIVITY(PDF); +#endif + +#undef ITEM_MAIN +#undef ITEM_SUB +#undef ITEM_BUTTON +#undef ITEM_POP +#undef SENSITIVITY + +} + +static void nsgtk_scaffolding_initial_sensitivity(struct nsgtk_scaffolding *g) +{ + for (int i = BACK_BUTTON; i < PLACEHOLDER_BUTTON; i++) { + if (g->buttons[i]->main != NULL) + gtk_widget_set_sensitive(GTK_WIDGET( + g->buttons[i]->main), + g->buttons[i]->sensitivity); + if (g->buttons[i]->rclick != NULL) + gtk_widget_set_sensitive(GTK_WIDGET( + g->buttons[i]->rclick), + g->buttons[i]->sensitivity); + if ((g->buttons[i]->location != -1) && + (g->buttons[i]->button != NULL)) + gtk_widget_set_sensitive(GTK_WIDGET( + g->buttons[i]->button), + g->buttons[i]->sensitivity); + if (g->buttons[i]->popup != NULL) + gtk_widget_set_sensitive(GTK_WIDGET( + g->buttons[i]->popup), + g->buttons[i]->sensitivity); + } + gtk_widget_set_sensitive(GTK_WIDGET(g->menu_bar->view_submenu->images_menuitem), FALSE); +} + + +void nsgtk_scaffolding_toolbars(struct nsgtk_scaffolding *g, int tbi) +{ + switch (tbi) { + /* case 0 is 'unset' [from fresh install / clearing options] + * see above */ + + case 1: /* Small icons */ + /* main toolbar */ + gtk_toolbar_set_style(GTK_TOOLBAR(g->tool_bar), + GTK_TOOLBAR_ICONS); + gtk_toolbar_set_icon_size(GTK_TOOLBAR(g->tool_bar), + GTK_ICON_SIZE_SMALL_TOOLBAR); + /* search toolbar */ + gtk_toolbar_set_style(GTK_TOOLBAR(g->search->bar), + GTK_TOOLBAR_ICONS); + gtk_toolbar_set_icon_size(GTK_TOOLBAR(g->search->bar), + GTK_ICON_SIZE_SMALL_TOOLBAR); + break; + + case 2: /* Large icons */ + gtk_toolbar_set_style(GTK_TOOLBAR(g->tool_bar), + GTK_TOOLBAR_ICONS); + gtk_toolbar_set_icon_size(GTK_TOOLBAR(g->tool_bar), + GTK_ICON_SIZE_LARGE_TOOLBAR); + /* search toolbar */ + gtk_toolbar_set_style(GTK_TOOLBAR(g->search->bar), + GTK_TOOLBAR_ICONS); + gtk_toolbar_set_icon_size(GTK_TOOLBAR(g->search->bar), + GTK_ICON_SIZE_LARGE_TOOLBAR); + break; + + case 3: /* Large icons with text */ + gtk_toolbar_set_style(GTK_TOOLBAR(g->tool_bar), + GTK_TOOLBAR_BOTH); + gtk_toolbar_set_icon_size(GTK_TOOLBAR(g->tool_bar), + GTK_ICON_SIZE_LARGE_TOOLBAR); + /* search toolbar */ + gtk_toolbar_set_style(GTK_TOOLBAR(g->search->bar), + GTK_TOOLBAR_BOTH); + gtk_toolbar_set_icon_size(GTK_TOOLBAR(g->search->bar), + GTK_ICON_SIZE_LARGE_TOOLBAR); + break; + + case 4: /* Text icons only */ + gtk_toolbar_set_style(GTK_TOOLBAR(g->tool_bar), + GTK_TOOLBAR_TEXT); + /* search toolbar */ + gtk_toolbar_set_style(GTK_TOOLBAR(g->search->bar), + GTK_TOOLBAR_TEXT); + default: + break; + } +} + +/* exported interface documented in gtk/scaffolding.h */ +struct nsgtk_scaffolding *nsgtk_new_scaffolding(struct gui_window *toplevel) +{ + struct nsgtk_scaffolding *gs; + int i; + GtkAccelGroup *group; + + gs = malloc(sizeof(*gs)); + if (gs == NULL) { + return NULL; + } + + LOG("Constructing a scaffold of %p for gui_window %p", gs, toplevel); + + gs->top_level = toplevel; + + /* Construct UI widgets */ + if (nsgtk_builder_new_from_resname("netsurf", &gs->builder) != NSERROR_OK) { + free(gs); + return NULL; + } + + gtk_builder_connect_signals(gs->builder, NULL); + +/** Obtain a GTK widget handle from UI builder object */ +#define GET_WIDGET(x) GTK_WIDGET (gtk_builder_get_object(gs->builder, (x))) + + gs->window = GTK_WINDOW(GET_WIDGET("wndBrowser")); + gs->notebook = GTK_NOTEBOOK(GET_WIDGET("notebook")); + gs->tool_bar = GTK_TOOLBAR(GET_WIDGET("toolbar")); + + gs->search = malloc(sizeof(struct gtk_search)); + if (gs->search == NULL) { + free(gs); + return NULL; + } + + gs->search->bar = GTK_TOOLBAR(GET_WIDGET("searchbar")); + gs->search->entry = GTK_ENTRY(GET_WIDGET("searchEntry")); + + gs->search->buttons[0] = GTK_TOOL_BUTTON(GET_WIDGET("searchBackButton")); + gs->search->buttons[1] = GTK_TOOL_BUTTON(GET_WIDGET("searchForwardButton")); + gs->search->buttons[2] = GTK_TOOL_BUTTON(GET_WIDGET("closeSearchButton")); + gs->search->checkAll = GTK_CHECK_BUTTON(GET_WIDGET("checkAllSearch")); + gs->search->caseSens = GTK_CHECK_BUTTON(GET_WIDGET("caseSensButton")); + +#undef GET_WIDGET + + /* allocate buttons */ + for (i = BACK_BUTTON; i < PLACEHOLDER_BUTTON; i++) { + gs->buttons[i] = calloc(1, sizeof(struct nsgtk_button_connect)); + if (gs->buttons[i] == NULL) { + for (i-- ; i >= BACK_BUTTON; i--) { + free(gs->buttons[i]); + } + free(gs); + return NULL; + } + gs->buttons[i]->location = -1; + gs->buttons[i]->sensitivity = true; + } + + /* here custom toolbutton adding code */ + gs->offset = 0; + gs->toolbarmem = 0; + gs->toolbarbase = 0; + gs->historybase = 0; + nsgtk_toolbar_customization_load(gs); + nsgtk_toolbar_set_physical(gs); + + group = gtk_accel_group_new(); + gtk_window_add_accel_group(gs->window, group); + + gs->menu_bar = nsgtk_menu_bar_create(GTK_MENU_SHELL(gtk_builder_get_object(gs->builder, "menubar")), group); + + + /* set this window's size and position to what's in the options, or + * or some sensible default if they're not set yet. + */ + if (nsoption_int(window_width) > 0) { + gtk_window_move(gs->window, + nsoption_int(window_x), + nsoption_int(window_y)); + gtk_window_resize(gs->window, + nsoption_int(window_width), + nsoption_int(window_height)); + } else { + /* Set to 1000x700, so we're very likely to fit even on + * 1024x768 displays, not being able to take into account + * window furniture or panels. + */ + gtk_window_set_default_size(gs->window, 1000, 700); + } + + /* Default toolbar button type uses system defaults */ + if (nsoption_int(button_type) == 0) { + GtkSettings *settings = gtk_settings_get_default(); + GtkIconSize tooliconsize; + GtkToolbarStyle toolbarstyle; + + g_object_get(settings, + "gtk-toolbar-icon-size", &tooliconsize, + "gtk-toolbar-style", &toolbarstyle, NULL); + + switch (toolbarstyle) { + case GTK_TOOLBAR_ICONS: + if (tooliconsize == GTK_ICON_SIZE_SMALL_TOOLBAR) { + nsoption_set_int(button_type, 1); + } else { + nsoption_set_int(button_type, 2); + } + break; + + case GTK_TOOLBAR_TEXT: + nsoption_set_int(button_type, 4); + break; + + case GTK_TOOLBAR_BOTH: + case GTK_TOOLBAR_BOTH_HORIZ: + /* no labels in default configuration */ + default: + /* No system default, so use large icons */ + nsoption_set_int(button_type, 2); + break; + } + } + + nsgtk_scaffolding_toolbars(gs, nsoption_int(button_type)); + + gtk_toolbar_set_show_arrow(gs->tool_bar, TRUE); + gtk_widget_show_all(GTK_WIDGET(gs->tool_bar)); + nsgtk_tab_init(gs); + + gtk_widget_set_size_request(GTK_WIDGET( + gs->buttons[HISTORY_BUTTON]->button), 20, -1); + + /* create the local history window to be associated with this scaffold */ + gs->history_window = malloc(sizeof(struct gtk_history_window)); + gs->history_window->g = gs; + gs->history_window->window = + GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); + gtk_window_set_transient_for(gs->history_window->window, gs->window); + gtk_window_set_title(gs->history_window->window, "NetSurf History"); + gtk_window_set_type_hint(gs->history_window->window, + GDK_WINDOW_TYPE_HINT_UTILITY); + gs->history_window->scrolled = + GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(0, 0)); + gtk_container_add(GTK_CONTAINER(gs->history_window->window), + GTK_WIDGET(gs->history_window->scrolled)); + + gtk_widget_show(GTK_WIDGET(gs->history_window->scrolled)); + gs->history_window->drawing_area = + GTK_DRAWING_AREA(gtk_drawing_area_new()); + + gtk_widget_set_events(GTK_WIDGET(gs->history_window->drawing_area), + GDK_EXPOSURE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK); + nsgtk_widget_override_background_color(GTK_WIDGET(gs->history_window->drawing_area), + GTK_STATE_NORMAL, + 0, 0xffff, 0xffff, 0xffff); + nsgtk_scrolled_window_add_with_viewport(gs->history_window->scrolled, + GTK_WIDGET(gs->history_window->drawing_area)); + gtk_widget_show(GTK_WIDGET(gs->history_window->drawing_area)); + + + /* set up URL bar completion */ + gs->url_bar_completion = nsgtk_url_entry_completion_new(gs); + + /* set up the throbber. */ + gs->throb_frame = 0; + + +#define CONNECT(obj, sig, callback, ptr) \ + g_signal_connect(G_OBJECT(obj), (sig), G_CALLBACK(callback), (ptr)) + + /* connect history window signals to their handlers */ + nsgtk_connect_draw_event(GTK_WIDGET(gs->history_window->drawing_area), + G_CALLBACK(nsgtk_history_draw_event), + gs->history_window); + /*CONNECT(gs->history_window->drawing_area, "motion_notify_event", + nsgtk_history_motion_notify_event, gs->history_window);*/ + CONNECT(gs->history_window->drawing_area, "button_press_event", + nsgtk_history_button_press_event, gs->history_window); + CONNECT(gs->history_window->window, "delete_event", + gtk_widget_hide_on_delete, NULL); + + g_signal_connect_after(gs->notebook, "page-added", + G_CALLBACK(nsgtk_window_tabs_add), gs); + g_signal_connect_after(gs->notebook, "page-removed", + G_CALLBACK(nsgtk_window_tabs_remove), gs); + + /* connect main window signals to their handlers. */ + CONNECT(gs->window, "delete-event", + scaffolding_window_delete_event, gs); + + CONNECT(gs->window, "destroy", scaffolding_window_destroy, gs); + + /* toolbar URL bar menu bar search bar signal handlers */ + CONNECT(gs->menu_bar->edit_submenu->edit, "show", + nsgtk_window_edit_menu_clicked, gs); + CONNECT(gs->menu_bar->edit_submenu->edit, "hide", + nsgtk_window_edit_menu_hidden, gs); + + CONNECT(gs->search->buttons[1], "clicked", + nsgtk_search_forward_button_clicked, gs); + + CONNECT(gs->search->buttons[0], "clicked", + nsgtk_search_back_button_clicked, gs); + + CONNECT(gs->search->entry, "changed", nsgtk_search_entry_changed, gs); + + CONNECT(gs->search->entry, "activate", nsgtk_search_entry_activate, gs); + + CONNECT(gs->search->entry, "key-press-event", + nsgtk_search_entry_key, gs); + + CONNECT(gs->search->buttons[2], "clicked", + nsgtk_search_close_button_clicked, gs); + + CONNECT(gs->search->caseSens, "toggled", + nsgtk_search_entry_changed, gs); + + CONNECT(gs->tool_bar, "popup-context-menu", + nsgtk_window_tool_bar_clicked, gs); + + /* create popup menu */ + gs->menu_popup = nsgtk_new_scaffolding_popup(gs, group); + + gs->link_menu = nsgtk_new_scaffolding_link_popup(gs, group); + + /* set up the menu signal handlers */ + nsgtk_scaffolding_toolbar_init(gs); + nsgtk_toolbar_connect_all(gs); + nsgtk_attach_menu_handlers(gs); + + nsgtk_scaffolding_initial_sensitivity(gs); + + gs->fullscreen = false; + + /* attach to the list */ + if (scaf_list) { + scaf_list->prev = gs; + } + gs->next = scaf_list; + gs->prev = NULL; + scaf_list = gs; + + /* set icon images */ + nsgtk_theme_implement(gs); + + /* set web search provider */ + search_web_select_provider(nsoption_int(search_provider)); + + /* finally, show the window. */ + gtk_widget_show(GTK_WIDGET(gs->window)); + + LOG("creation complete"); + + return gs; +} + +/* exported function documented in gtk/scaffolding.h */ +void nsgtk_window_set_title(struct gui_window *gw, const char *title) +{ + struct nsgtk_scaffolding *gs = nsgtk_get_scaffold(gw); + int title_len; + char *newtitle; + + if ((title == NULL) || (title[0] == '\0')) { + if (gs->top_level != gw) { + gtk_window_set_title(gs->window, "NetSurf"); + } + return; + } + + nsgtk_tab_set_title(gw, title); + + if (gs->top_level != gw) { + /* not top level window so do not set window title */ + return; + } + + title_len = strlen(title) + SLEN(" - NetSurf") + 1; + newtitle = malloc(title_len); + if (newtitle == NULL) { + return; + } + + snprintf(newtitle, title_len, "%s - NetSurf", title); + + gtk_window_set_title(gs->window, newtitle); + + free(newtitle); +} + + +nserror gui_window_set_url(struct gui_window *gw, nsurl *url) +{ + struct nsgtk_scaffolding *g; + size_t idn_url_l; + char *idn_url_s = NULL; + + g = nsgtk_get_scaffold(gw); + if (g->top_level == gw) { + if (nsoption_bool(display_decoded_idn) == true) { + if (nsurl_get_utf8(url, &idn_url_s, &idn_url_l) != NSERROR_OK) + idn_url_s = NULL; + } + + gtk_entry_set_text(GTK_ENTRY(g->url_bar), idn_url_s ? idn_url_s : nsurl_access(url)); + + if(idn_url_s) + free(idn_url_s); + + gtk_editable_set_position(GTK_EDITABLE(g->url_bar), -1); + } + return NSERROR_OK; +} + +void gui_window_start_throbber(struct gui_window* _g) +{ + struct nsgtk_scaffolding *g = nsgtk_get_scaffold(_g); + g->buttons[STOP_BUTTON]->sensitivity = true; + g->buttons[RELOAD_BUTTON]->sensitivity = false; + nsgtk_scaffolding_set_sensitivity(g); + + scaffolding_update_context(g); + + nsgtk_schedule(100, nsgtk_throb, g); +} + +void gui_window_stop_throbber(struct gui_window* _g) +{ + struct nsgtk_scaffolding *g = nsgtk_get_scaffold(_g); + if (g == NULL) + return; + scaffolding_update_context(g); + nsgtk_schedule(-1, nsgtk_throb, g); + if (g->buttons[STOP_BUTTON] != NULL) + g->buttons[STOP_BUTTON]->sensitivity = false; + if (g->buttons[RELOAD_BUTTON] != NULL) + g->buttons[RELOAD_BUTTON]->sensitivity = true; + + nsgtk_scaffolding_set_sensitivity(g); + + if ((g->throbber == NULL) || (nsgtk_throbber == NULL) || + (nsgtk_throbber->framedata == NULL) || + (nsgtk_throbber->framedata[0] == NULL)) + return; + gtk_image_set_from_pixbuf(g->throbber, nsgtk_throbber->framedata[0]); +} + + +/** + * set favicon + */ +void +nsgtk_scaffolding_set_icon(struct gui_window *gw) +{ + struct nsgtk_scaffolding *sc = nsgtk_get_scaffold(gw); + GdkPixbuf *icon_pixbuf = nsgtk_get_icon(gw); + + /* check icon needs to be shown */ + if ((icon_pixbuf == NULL) || + (sc->top_level != gw)) { + return; + } + + nsgtk_entry_set_icon_from_pixbuf(sc->url_bar, + GTK_ENTRY_ICON_PRIMARY, + icon_pixbuf); + + gtk_widget_show_all(GTK_WIDGET(sc->buttons[URL_BAR_ITEM]->button)); +} + +static void +nsgtk_scaffolding_set_websearch(struct nsgtk_scaffolding *g, const char *content) +{ + /** \todo this code appears technically correct, though + * currently has no effect at all. + */ + PangoLayout *lo = gtk_entry_get_layout(GTK_ENTRY(g->webSearchEntry)); + if (lo != NULL) { + pango_layout_set_font_description(lo, NULL); + PangoFontDescription *desc = pango_font_description_new(); + if (desc != NULL) { + pango_font_description_set_style(desc, + PANGO_STYLE_ITALIC); + pango_font_description_set_family(desc, "Arial"); + pango_font_description_set_weight(desc, + PANGO_WEIGHT_ULTRALIGHT); + pango_font_description_set_size(desc, + 10 * PANGO_SCALE); + pango_layout_set_font_description(lo, desc); + } + + PangoAttrList *list = pango_attr_list_new(); + if (list != NULL) { + PangoAttribute *italic = pango_attr_style_new( + PANGO_STYLE_ITALIC); + if (italic != NULL) { + italic->start_index = 0; + italic->end_index = strlen(content); + } + PangoAttribute *grey = pango_attr_foreground_new( + 0x7777, 0x7777, 0x7777); + if (grey != NULL) { + grey->start_index = 0; + grey->end_index = strlen(content); + } + pango_attr_list_insert(list, italic); + pango_attr_list_insert(list, grey); + pango_layout_set_attributes(lo, list); + pango_attr_list_unref(list); + } + pango_layout_set_text(lo, content, -1); + } +/* an alternative method */ +/* char *parse = malloc(strlen(content) + 1); + PangoAttrList *list = pango_layout_get_attributes(lo); + char *markup = g_strconcat("<span foreground='#777777'><i>", content, + "</i></span>", NULL); + pango_parse_markup(markup, -1, 0, &list, &parse, NULL, NULL); + gtk_widget_show_all(g->webSearchEntry); +*/ + gtk_entry_set_visibility(GTK_ENTRY(g->webSearchEntry), TRUE); + gtk_entry_set_text(GTK_ENTRY(g->webSearchEntry), content); +} + +/** + * GTK UI callback when search provider details are updated. + * + * \param provider_name The providers name. + * \param provider_bitmap The bitmap representing the provider. + * \return NSERROR_OK on success else error code. + */ +static nserror +gui_search_web_provider_update(const char *provider_name, + struct bitmap *provider_bitmap) +{ + struct nsgtk_scaffolding *current; + GdkPixbuf *srch_pixbuf = NULL; + char *searchcontent; + + LOG("name:%s bitmap %p", provider_name, provider_bitmap); + + if (provider_bitmap != NULL) { + srch_pixbuf = nsgdk_pixbuf_get_from_surface(provider_bitmap->surface, 16, 16); + + if (srch_pixbuf == NULL) { + return NSERROR_NOMEM; + } + } + + /* setup the search content name */ + searchcontent = malloc(strlen(provider_name) + SLEN("Search ") + 1); + if (searchcontent != NULL) { + sprintf(searchcontent, "Search %s", provider_name); + } + + /* set the search provider parameters up in each scaffold */ + for (current = scaf_list; current != NULL; current = current->next) { + /* add ico to each window's toolbar */ + if (srch_pixbuf != NULL) { + nsgtk_entry_set_icon_from_pixbuf(current->webSearchEntry, + GTK_ENTRY_ICON_PRIMARY, + srch_pixbuf); + } else { + nsgtk_entry_set_icon_from_stock(current->webSearchEntry, + GTK_ENTRY_ICON_PRIMARY, + NSGTK_STOCK_FIND); + } + + /* set search entry text */ + if (searchcontent != NULL) { + nsgtk_scaffolding_set_websearch(current, searchcontent); + } else { + nsgtk_scaffolding_set_websearch(current, provider_name); + } + } + + free(searchcontent); + + if (srch_pixbuf != NULL) { + g_object_unref(srch_pixbuf); + } + + return NSERROR_OK; +} + +static struct gui_search_web_table search_web_table = { + .provider_update = gui_search_web_provider_update, +}; + +struct gui_search_web_table *nsgtk_search_web_table = &search_web_table; + +/* exported interface documented in gtk/scaffolding.h */ +GtkWindow* nsgtk_scaffolding_window(struct nsgtk_scaffolding *g) +{ + return g->window; +} + +/* exported interface documented in gtk/scaffolding.h */ +GtkNotebook* nsgtk_scaffolding_notebook(struct nsgtk_scaffolding *g) +{ + return g->notebook; +} + +/* exported interface documented in gtk/scaffolding.h */ +GtkWidget *nsgtk_scaffolding_urlbar(struct nsgtk_scaffolding *g) +{ + return g->url_bar; +} + +/* exported interface documented in gtk/scaffolding.h */ +GtkWidget *nsgtk_scaffolding_websearch(struct nsgtk_scaffolding *g) +{ + return g->webSearchEntry; +} + +/* exported interface documented in gtk/scaffolding.h */ +GtkToolbar *nsgtk_scaffolding_toolbar(struct nsgtk_scaffolding *g) +{ + return g->tool_bar; +} + +/* exported interface documented in gtk/scaffolding.h */ +struct nsgtk_button_connect * +nsgtk_scaffolding_button(struct nsgtk_scaffolding *g, int i) +{ + return g->buttons[i]; +} + +/* exported interface documented in gtk/scaffolding.h */ +struct gtk_search *nsgtk_scaffolding_search(struct nsgtk_scaffolding *g) +{ + return g->search; +} + +/* exported interface documented in gtk/scaffolding.h */ +GtkMenuBar *nsgtk_scaffolding_menu_bar(struct nsgtk_scaffolding *g) +{ + return g->menu_bar->bar_menu; +} + +/* exported interface documented in gtk/scaffolding.h */ +struct gtk_history_window * +nsgtk_scaffolding_history_window(struct nsgtk_scaffolding *g) +{ + return g->history_window; +} + +/* exported interface documented in gtk/scaffolding.h */ +struct nsgtk_scaffolding *nsgtk_scaffolding_iterate(struct nsgtk_scaffolding *g) +{ + if (g == NULL) { + return scaf_list; + } + return g->next; +} + +/* exported interface documented in gtk/scaffolding.h */ +void nsgtk_scaffolding_reset_offset(struct nsgtk_scaffolding *g) +{ + g->offset = 0; +} + +/* exported interface documented in gtk/scaffolding.h */ +void nsgtk_scaffolding_update_url_bar_ref(struct nsgtk_scaffolding *g) +{ + g->url_bar = GTK_WIDGET(gtk_bin_get_child(GTK_BIN( + nsgtk_scaffolding_button(g, URL_BAR_ITEM)->button))); + + gtk_entry_set_completion(GTK_ENTRY(g->url_bar), + g->url_bar_completion); +} + +/* exported interface documented in gtk/scaffolding.h */ +void nsgtk_scaffolding_update_throbber_ref(struct nsgtk_scaffolding *g) +{ + g->throbber = GTK_IMAGE(gtk_bin_get_child( + GTK_BIN(g->buttons[THROBBER_ITEM]->button))); +} + +/* exported interface documented in gtk/scaffolding.h */ +void nsgtk_scaffolding_update_websearch_ref(struct nsgtk_scaffolding *g) +{ + g->webSearchEntry = gtk_bin_get_child(GTK_BIN( + g->buttons[WEBSEARCH_ITEM]->button)); +} + +/* exported interface documented in gtk/scaffolding.h */ +void nsgtk_scaffolding_toggle_search_bar_visibility(struct nsgtk_scaffolding *g) +{ + gboolean vis; + struct browser_window *bw = nsgtk_get_browser_window(g->top_level); + + g_object_get(G_OBJECT(g->search->bar), "visible", &vis, NULL); + if (vis) { + if (bw != NULL) { + browser_window_search_clear(bw); + } + + gtk_widget_hide(GTK_WIDGET(g->search->bar)); + } else { + gtk_widget_show(GTK_WIDGET(g->search->bar)); + gtk_widget_grab_focus(GTK_WIDGET(g->search->entry)); + } +} + +/* exported interface documented in gtk/scaffolding.h */ +struct gui_window *nsgtk_scaffolding_top_level(struct nsgtk_scaffolding *g) +{ + return g->top_level; +} + +/* exported interface documented in gtk/scaffolding.h */ +void nsgtk_scaffolding_set_top_level(struct gui_window *gw) +{ + struct browser_window *bw; + struct nsgtk_scaffolding *sc; + + assert(gw != NULL); + + bw = nsgtk_get_browser_window(gw); + + assert(bw != NULL); + + sc = nsgtk_get_scaffold(gw); + assert(sc != NULL); + + sc->top_level = gw; + + /* Synchronise the history (will also update the URL bar) */ + scaffolding_update_context(sc); + + /* clear effects of potential searches */ + browser_window_search_clear(bw); + + nsgtk_scaffolding_set_icon(gw); + + /* Ensure the window's title bar is updated */ + nsgtk_window_set_title(gw, browser_window_get_title(bw)); + +} + +/* exported interface documented in scaffolding.h */ +void nsgtk_scaffolding_set_sensitivity(struct nsgtk_scaffolding *g) +{ + int i; +#define SENSITIVITY(q)\ + i = q##_BUTTON;\ + if (g->buttons[i]->main != NULL)\ + gtk_widget_set_sensitive(GTK_WIDGET(\ + g->buttons[i]->main),\ + g->buttons[i]->sensitivity);\ + if (g->buttons[i]->rclick != NULL)\ + gtk_widget_set_sensitive(GTK_WIDGET(\ + g->buttons[i]->rclick),\ + g->buttons[i]->sensitivity);\ + if ((g->buttons[i]->location != -1) && \ + (g->buttons[i]->button != NULL))\ + gtk_widget_set_sensitive(GTK_WIDGET(\ + g->buttons[i]->button),\ + g->buttons[i]->sensitivity);\ + if (g->buttons[i]->popup != NULL)\ + gtk_widget_set_sensitive(GTK_WIDGET(\ + g->buttons[i]->popup),\ + g->buttons[i]->sensitivity); + + SENSITIVITY(STOP) + SENSITIVITY(RELOAD) + SENSITIVITY(CUT) + SENSITIVITY(COPY) + SENSITIVITY(PASTE) + SENSITIVITY(BACK) + SENSITIVITY(FORWARD) + SENSITIVITY(NEXTTAB) + SENSITIVITY(PREVTAB) + SENSITIVITY(CLOSETAB) +#undef SENSITIVITY +} + + +/* exported interface documented in gtk/scaffolding.h */ +void nsgtk_scaffolding_context_menu(struct nsgtk_scaffolding *g, + gdouble x, + gdouble y) +{ + GtkMenu *gtkmenu; + + /* update the global context menu features */ + browser_window_get_features(nsgtk_get_browser_window(g->top_level), + x, y, ¤t_menu_features); + + if (current_menu_features.link != NULL) { + /* menu is opening over a link */ + gtkmenu = g->link_menu->link_menu; + } else { + gtkmenu = g->menu_popup->popup_menu; + + nsgtk_scaffolding_update_edit_actions_sensitivity(g); + + if (!(g->buttons[COPY_BUTTON]->sensitivity)) { + gtk_widget_hide(GTK_WIDGET(g->menu_popup->copy_menuitem)); + } else { + gtk_widget_show(GTK_WIDGET(g->menu_popup->copy_menuitem)); + } + + if (!(g->buttons[CUT_BUTTON]->sensitivity)) { + gtk_widget_hide(GTK_WIDGET(g->menu_popup->cut_menuitem)); + } else { + gtk_widget_show(GTK_WIDGET(g->menu_popup->cut_menuitem)); + } + + if (!(g->buttons[PASTE_BUTTON]->sensitivity)) { + gtk_widget_hide(GTK_WIDGET(g->menu_popup->paste_menuitem)); + } else { + gtk_widget_show(GTK_WIDGET(g->menu_popup->paste_menuitem)); + } + + /* hide customize */ + popup_menu_hide(g->menu_popup, false, false, false, true); + } + + gtk_menu_popup(gtkmenu, NULL, NULL, NULL, NULL, 0, + gtk_get_current_event_time()); +} + +/** + * reallocate width for history button, reallocate buttons right of history; + * memorise base of history button / toolbar + */ +void nsgtk_scaffolding_toolbar_size_allocate(GtkWidget *widget, + GtkAllocation *alloc, gpointer data) +{ + struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *)data; + int i = nsgtk_toolbar_get_id_from_widget(widget, g); + if (i == -1) + return; + if ((g->toolbarmem == alloc->x) || + (g->buttons[i]->location < + g->buttons[HISTORY_BUTTON]->location)) + /* no reallocation after first adjustment, no reallocation for buttons + * left of history button */ + return; + if (widget == GTK_WIDGET(g->buttons[HISTORY_BUTTON]->button)) { + if (alloc->width == 20) + return; + + g->toolbarbase = alloc->y + alloc->height; + g->historybase = alloc->x + 20; + if (g->offset == 0) + g->offset = alloc->width - 20; + alloc->width = 20; + } else if (g->buttons[i]->location <= + g->buttons[URL_BAR_ITEM]->location) { + alloc->x -= g->offset; + if (i == URL_BAR_ITEM) + alloc->width += g->offset; + } + g->toolbarmem = alloc->x; + gtk_widget_size_allocate(widget, alloc); +} |