summaryrefslogtreecommitdiff
path: root/frontends/gtk/window.c
diff options
context:
space:
mode:
Diffstat (limited to 'frontends/gtk/window.c')
-rw-r--r--frontends/gtk/window.c1337
1 files changed, 1337 insertions, 0 deletions
diff --git a/frontends/gtk/window.c b/frontends/gtk/window.c
new file mode 100644
index 000000000..de333d1b0
--- /dev/null
+++ b/frontends/gtk/window.c
@@ -0,0 +1,1337 @@
+/*
+ * Copyright 2006 Daniel Silverstone <dsilvers@digital-scurf.org>
+ * Copyright 2006 Rob Kendrick <rjek@rjek.com>
+ *
+ * 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/>.
+ */
+
+/**
+ * \file
+ * Implementation of gtk windowing.
+ */
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <limits.h>
+#include <assert.h>
+#include <math.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk-pixbuf/gdk-pixdata.h>
+
+#include "utils/log.h"
+#include "utils/utf8.h"
+#include "utils/utils.h"
+#include "utils/nsoption.h"
+#include "content/hlcache.h"
+#include "gtk/window.h"
+#include "gtk/selection.h"
+#include "desktop/browser.h"
+#include "desktop/mouse.h"
+#include "desktop/searchweb.h"
+#include "desktop/textinput.h"
+#include "desktop/gui_window.h"
+#include "desktop/plotters.h"
+#include "render/form.h"
+
+#include "gtk/warn.h"
+#include "gtk/compat.h"
+#include "gtk/gui.h"
+#include "gtk/scaffolding.h"
+#include "gtk/plotters.h"
+#include "gtk/schedule.h"
+#include "gtk/tabs.h"
+#include "gtk/bitmap.h"
+#include "gtk/gdk.h"
+#include "gtk/resources.h"
+
+static GtkWidget *select_menu;
+static struct form_control *select_menu_control;
+
+static void nsgtk_select_menu_clicked(GtkCheckMenuItem *checkmenuitem,
+ gpointer user_data);
+
+struct gui_window {
+ /**
+ * The gtk scaffold object containing menu, buttons, url bar, [tabs],
+ * drawing area, etc that may contain one or more gui_windows.
+ */
+ struct nsgtk_scaffolding *scaffold;
+
+ /** The 'content' window that is rendered in the gui_window */
+ struct browser_window *bw;
+
+ /** mouse state and events. */
+ struct {
+ struct gui_window *gui;
+
+ gdouble pressed_x;
+ gdouble pressed_y;
+ gboolean waiting;
+ browser_mouse_state state;
+ } mouse;
+
+ /** caret dimension and location for rendering */
+ int caretx, carety, careth;
+
+ /** caret shape for rendering */
+ gui_pointer_shape current_pointer;
+
+ /** previous event location */
+ int last_x, last_y;
+
+ /** The top level container (tabContents) */
+ GtkWidget *container;
+
+ /** display widget for this page or frame */
+ GtkLayout *layout;
+
+ /** handle to the the visible tab */
+ GtkWidget *tab;
+
+ /** statusbar */
+ GtkLabel *status_bar;
+
+ /** status pane */
+ GtkPaned *paned;
+
+ /** has the status pane had its first size operation yet? */
+ bool paned_sized;
+
+ /** to allow disactivation / resume of normal window behaviour */
+ gulong signalhandler[NSGTK_WINDOW_SIGNAL_COUNT];
+
+ /** The icon this window should have */
+ GdkPixbuf *icon;
+
+ /** The input method to use with this window */
+ GtkIMContext *input_method;
+
+ /** list for cleanup */
+ struct gui_window *next, *prev;
+};
+
+/**< first entry in window list */
+struct gui_window *window_list = NULL;
+
+/** flag controlling opening of tabs in teh background */
+int temp_open_background = -1;
+
+struct nsgtk_scaffolding *nsgtk_get_scaffold(struct gui_window *g)
+{
+ return g->scaffold;
+}
+
+GdkPixbuf *nsgtk_get_icon(struct gui_window *gw)
+{
+ return gw->icon;
+}
+
+struct browser_window *nsgtk_get_browser_window(struct gui_window *g)
+{
+ return g->bw;
+}
+
+unsigned long nsgtk_window_get_signalhandler(struct gui_window *g, int i)
+{
+ return g->signalhandler[i];
+}
+
+GtkLayout *nsgtk_window_get_layout(struct gui_window *g)
+{
+ return g->layout;
+}
+
+GtkWidget *nsgtk_window_get_tab(struct gui_window *g)
+{
+ return g->tab;
+}
+
+void nsgtk_window_set_tab(struct gui_window *g, GtkWidget *w)
+{
+ g->tab = w;
+}
+
+
+struct gui_window *nsgtk_window_iterate(struct gui_window *g)
+{
+ return g->next;
+}
+
+float nsgtk_get_scale_for_gui(struct gui_window *g)
+{
+ return browser_window_get_scale(g->bw);
+}
+
+static void nsgtk_select_menu_clicked(GtkCheckMenuItem *checkmenuitem,
+ gpointer user_data)
+{
+ form_select_process_selection(select_menu_control,
+ (intptr_t)user_data);
+}
+
+#if GTK_CHECK_VERSION(3,0,0)
+
+static gboolean
+nsgtk_window_draw_event(GtkWidget *widget, cairo_t *cr, gpointer data)
+{
+ struct gui_window *gw = data;
+ struct gui_window *z;
+ struct rect clip;
+ struct redraw_context ctx = {
+ .interactive = true,
+ .background_images = true,
+ .plot = &nsgtk_plotters
+ };
+
+ double x1;
+ double y1;
+ double x2;
+ double y2;
+
+ assert(gw);
+ assert(gw->bw);
+
+ for (z = window_list; z && z != gw; z = z->next)
+ continue;
+ assert(z);
+ assert(GTK_WIDGET(gw->layout) == widget);
+
+ current_widget = (GtkWidget *)gw->layout;
+ current_cr = cr;
+
+ GtkAdjustment *vscroll = nsgtk_layout_get_vadjustment(gw->layout);
+ GtkAdjustment *hscroll = nsgtk_layout_get_hadjustment(gw->layout);
+
+ cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
+
+ clip.x0 = x1;
+ clip.y0 = y1;
+ clip.x1 = x2;
+ clip.y1 = y2;
+
+ browser_window_redraw(gw->bw,
+ -gtk_adjustment_get_value(hscroll),
+ -gtk_adjustment_get_value(vscroll),
+ &clip,
+ &ctx);
+
+ if (gw->careth != 0) {
+ nsgtk_plot_caret(gw->caretx, gw->carety, gw->careth);
+ }
+
+ current_widget = NULL;
+
+ return FALSE;
+}
+
+#else
+
+static gboolean
+nsgtk_window_draw_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
+{
+ struct gui_window *gw = data;
+ struct gui_window *z;
+ struct rect clip;
+ struct redraw_context ctx = {
+ .interactive = true,
+ .background_images = true,
+ .plot = &nsgtk_plotters
+ };
+
+ assert(gw);
+ assert(gw->bw);
+
+ for (z = window_list; z && z != gw; z = z->next)
+ continue;
+ assert(z);
+ assert(GTK_WIDGET(gw->layout) == widget);
+
+ current_widget = (GtkWidget *)gw->layout;
+ current_cr = gdk_cairo_create(nsgtk_layout_get_bin_window(gw->layout));
+
+ 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;
+
+ browser_window_redraw(gw->bw, 0, 0, &clip, &ctx);
+
+ if (gw->careth != 0) {
+ nsgtk_plot_caret(gw->caretx, gw->carety, gw->careth);
+ }
+
+ cairo_destroy(current_cr);
+
+ current_widget = NULL;
+
+ return FALSE;
+}
+
+#endif
+
+static gboolean nsgtk_window_motion_notify_event(GtkWidget *widget,
+ GdkEventMotion *event, gpointer data)
+{
+ struct gui_window *g = data;
+ bool shift = event->state & GDK_SHIFT_MASK;
+ bool ctrl = event->state & GDK_CONTROL_MASK;
+
+ if ((fabs(event->x - g->last_x) < 5.0) &&
+ (fabs(event->y - g->last_y) < 5.0)) {
+ /* Mouse hasn't moved far enough from press coordinate
+ * for this to be considered a drag.
+ */
+ return FALSE;
+ } else {
+ /* This is a drag, ensure it's always treated as such,
+ * even if we drag back over the press location.
+ */
+ g->last_x = INT_MIN;
+ g->last_y = INT_MIN;
+ }
+
+ if (g->mouse.state & BROWSER_MOUSE_PRESS_1) {
+ /* Start button 1 drag */
+ browser_window_mouse_click(g->bw, BROWSER_MOUSE_DRAG_1,
+ g->mouse.pressed_x, g->mouse.pressed_y);
+
+ /* Replace PRESS with HOLDING and declare drag in progress */
+ g->mouse.state ^= (BROWSER_MOUSE_PRESS_1 |
+ BROWSER_MOUSE_HOLDING_1);
+ g->mouse.state |= BROWSER_MOUSE_DRAG_ON;
+ } else if (g->mouse.state & BROWSER_MOUSE_PRESS_2) {
+ /* Start button 2 drag */
+ browser_window_mouse_click(g->bw, BROWSER_MOUSE_DRAG_2,
+ g->mouse.pressed_x, g->mouse.pressed_y);
+
+ /* Replace PRESS with HOLDING and declare drag in progress */
+ g->mouse.state ^= (BROWSER_MOUSE_PRESS_2 |
+ BROWSER_MOUSE_HOLDING_2);
+ g->mouse.state |= BROWSER_MOUSE_DRAG_ON;
+ }
+
+ /* Handle modifiers being removed */
+ if (g->mouse.state & BROWSER_MOUSE_MOD_1 && !shift)
+ g->mouse.state ^= BROWSER_MOUSE_MOD_1;
+ if (g->mouse.state & BROWSER_MOUSE_MOD_2 && !ctrl)
+ g->mouse.state ^= BROWSER_MOUSE_MOD_2;
+
+ browser_window_mouse_track(g->bw, g->mouse.state,
+ event->x / browser_window_get_scale(g->bw),
+ event->y / browser_window_get_scale(g->bw));
+
+ return TRUE;
+}
+
+static gboolean nsgtk_window_button_press_event(GtkWidget *widget,
+ GdkEventButton *event, gpointer data)
+{
+ struct gui_window *g = data;
+
+ gtk_im_context_reset(g->input_method);
+ gtk_widget_grab_focus(GTK_WIDGET(g->layout));
+ gtk_widget_hide(GTK_WIDGET(nsgtk_scaffolding_history_window(
+ g->scaffold)->window));
+
+ g->mouse.pressed_x = event->x / browser_window_get_scale(g->bw);
+ g->mouse.pressed_y = event->y / browser_window_get_scale(g->bw);
+
+ switch (event->button) {
+ case 1: /* Left button, usually. Pass to core as BUTTON 1. */
+ g->mouse.state = BROWSER_MOUSE_PRESS_1;
+ break;
+
+ case 2: /* Middle button, usually. Pass to core as BUTTON 2 */
+ g->mouse.state = BROWSER_MOUSE_PRESS_2;
+ break;
+
+ case 3: /* Right button, usually. Action button, context menu. */
+ browser_window_remove_caret(g->bw, true);
+ nsgtk_scaffolding_context_menu(g->scaffold,
+ g->mouse.pressed_x,
+ g->mouse.pressed_y);
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+
+ /* Modify for double & triple clicks */
+ if (event->type == GDK_3BUTTON_PRESS)
+ g->mouse.state |= BROWSER_MOUSE_TRIPLE_CLICK;
+ else if (event->type == GDK_2BUTTON_PRESS)
+ g->mouse.state |= BROWSER_MOUSE_DOUBLE_CLICK;
+
+ /* Handle the modifiers too */
+ if (event->state & GDK_SHIFT_MASK)
+ g->mouse.state |= BROWSER_MOUSE_MOD_1;
+ if (event->state & GDK_CONTROL_MASK)
+ g->mouse.state |= BROWSER_MOUSE_MOD_2;
+
+ /* Record where we pressed, for use when determining whether to start
+ * a drag in motion notify events. */
+ g->last_x = event->x;
+ g->last_y = event->y;
+
+ browser_window_mouse_click(g->bw, g->mouse.state, g->mouse.pressed_x,
+ g->mouse.pressed_y);
+
+ return TRUE;
+}
+
+static gboolean nsgtk_window_button_release_event(GtkWidget *widget,
+ GdkEventButton *event, gpointer data)
+{
+ struct gui_window *g = data;
+ bool shift = event->state & GDK_SHIFT_MASK;
+ bool ctrl = event->state & GDK_CONTROL_MASK;
+
+ /* If the mouse state is PRESS then we are waiting for a release to emit
+ * a click event, otherwise just reset the state to nothing */
+ if (g->mouse.state & BROWSER_MOUSE_PRESS_1)
+ g->mouse.state ^= (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_CLICK_1);
+ else if (g->mouse.state & BROWSER_MOUSE_PRESS_2)
+ g->mouse.state ^= (BROWSER_MOUSE_PRESS_2 | BROWSER_MOUSE_CLICK_2);
+
+ /* Handle modifiers being removed */
+ if (g->mouse.state & BROWSER_MOUSE_MOD_1 && !shift)
+ g->mouse.state ^= BROWSER_MOUSE_MOD_1;
+ if (g->mouse.state & BROWSER_MOUSE_MOD_2 && !ctrl)
+ g->mouse.state ^= BROWSER_MOUSE_MOD_2;
+
+ if (g->mouse.state & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2)) {
+ browser_window_mouse_click(g->bw, g->mouse.state,
+ event->x / browser_window_get_scale(g->bw),
+ event->y / browser_window_get_scale(g->bw));
+ } else {
+ browser_window_mouse_track(g->bw, 0,
+ event->x / browser_window_get_scale(g->bw),
+ event->y / browser_window_get_scale(g->bw));
+ }
+
+ g->mouse.state = 0;
+ return TRUE;
+}
+
+static gboolean
+nsgtk_window_scroll_event(GtkWidget *widget,
+ GdkEventScroll *event,
+ gpointer data)
+{
+ struct gui_window *g = data;
+ double value;
+ double deltax = 0;
+ double deltay = 0;
+ GtkAdjustment *vscroll = nsgtk_layout_get_vadjustment(g->layout);
+ GtkAdjustment *hscroll = nsgtk_layout_get_hadjustment(g->layout);
+ GtkAllocation alloc;
+
+ switch (event->direction) {
+ case GDK_SCROLL_LEFT:
+ deltax = -1.0;
+ break;
+
+ case GDK_SCROLL_UP:
+ deltay = -1.0;
+ break;
+
+ case GDK_SCROLL_RIGHT:
+ deltax = 1.0;
+ break;
+
+ case GDK_SCROLL_DOWN:
+ deltay = 1.0;
+ break;
+
+#if GTK_CHECK_VERSION(3,4,0)
+ case GDK_SCROLL_SMOOTH:
+ gdk_event_get_scroll_deltas((GdkEvent *)event, &deltax, &deltay);
+ break;
+#endif
+ default:
+ LOG("Unhandled mouse scroll direction");
+ return TRUE;
+ }
+
+ deltax *= nsgtk_adjustment_get_step_increment(hscroll);
+ deltay *= nsgtk_adjustment_get_step_increment(vscroll);
+
+ if (browser_window_scroll_at_point(g->bw,
+ event->x / browser_window_get_scale(g->bw),
+ event->y / browser_window_get_scale(g->bw),
+ deltax, deltay) != true) {
+
+ /* core did not handle event so change adjustments */
+
+ /* Horizontal */
+ if (deltax != 0) {
+ value = gtk_adjustment_get_value(hscroll) + deltax;
+
+ /* @todo consider gtk_widget_get_allocated_width() */
+ nsgtk_widget_get_allocation(GTK_WIDGET(g->layout), &alloc);
+
+ if (value > nsgtk_adjustment_get_upper(hscroll) - alloc.width) {
+ value = nsgtk_adjustment_get_upper(hscroll) - alloc.width;
+ }
+ if (value < nsgtk_adjustment_get_lower(hscroll)) {
+ value = nsgtk_adjustment_get_lower(hscroll);
+ }
+
+ gtk_adjustment_set_value(hscroll, value);
+ }
+
+ /* Vertical */
+ if (deltay != 0) {
+ value = gtk_adjustment_get_value(vscroll) + deltay;
+
+ /* @todo consider gtk_widget_get_allocated_height */
+ nsgtk_widget_get_allocation(GTK_WIDGET(g->layout), &alloc);
+
+ if (value > (nsgtk_adjustment_get_upper(vscroll) - alloc.height)) {
+ value = nsgtk_adjustment_get_upper(vscroll) - alloc.height;
+ }
+ if (value < nsgtk_adjustment_get_lower(vscroll)) {
+ value = nsgtk_adjustment_get_lower(vscroll);
+ }
+
+ gtk_adjustment_set_value(vscroll, value);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean nsgtk_window_keypress_event(GtkWidget *widget,
+ GdkEventKey *event, gpointer data)
+{
+ struct gui_window *g = data;
+ uint32_t nskey;
+
+ if (gtk_im_context_filter_keypress(g->input_method, event))
+ return TRUE;
+
+ nskey = gtk_gui_gdkkey_to_nskey(event);
+
+ if (browser_window_key_press(g->bw, nskey))
+ return TRUE;
+
+ if ((event->state & 0x7) != 0)
+ return TRUE;
+
+ double value;
+ GtkAdjustment *vscroll = nsgtk_layout_get_vadjustment(g->layout);
+ GtkAdjustment *hscroll = nsgtk_layout_get_hadjustment(g->layout);
+ GtkAllocation alloc;
+
+ /* @todo consider gtk_widget_get_allocated_width() */
+ nsgtk_widget_get_allocation(GTK_WIDGET(g->layout), &alloc);
+
+ switch (event->keyval) {
+
+ case GDK_KEY(Home):
+ case GDK_KEY(KP_Home):
+ value = nsgtk_adjustment_get_lower(vscroll);
+ gtk_adjustment_set_value(vscroll, value);
+ break;
+
+ case GDK_KEY(End):
+ case GDK_KEY(KP_End):
+ value = nsgtk_adjustment_get_upper(vscroll) - alloc.height;
+
+ if (value < nsgtk_adjustment_get_lower(vscroll))
+ value = nsgtk_adjustment_get_lower(vscroll);
+
+ gtk_adjustment_set_value(vscroll, value);
+ break;
+
+ case GDK_KEY(Left):
+ case GDK_KEY(KP_Left):
+ value = gtk_adjustment_get_value(hscroll) -
+ nsgtk_adjustment_get_step_increment(hscroll);
+
+ if (value < nsgtk_adjustment_get_lower(hscroll))
+ value = nsgtk_adjustment_get_lower(hscroll);
+
+ gtk_adjustment_set_value(hscroll, value);
+ break;
+
+ case GDK_KEY(Up):
+ case GDK_KEY(KP_Up):
+ value = gtk_adjustment_get_value(vscroll) -
+ nsgtk_adjustment_get_step_increment(vscroll);
+
+ if (value < nsgtk_adjustment_get_lower(vscroll))
+ value = nsgtk_adjustment_get_lower(vscroll);
+
+ gtk_adjustment_set_value(vscroll, value);
+ break;
+
+ case GDK_KEY(Right):
+ case GDK_KEY(KP_Right):
+ value = gtk_adjustment_get_value(hscroll) +
+ nsgtk_adjustment_get_step_increment(hscroll);
+
+ if (value > nsgtk_adjustment_get_upper(hscroll) - alloc.width)
+ value = nsgtk_adjustment_get_upper(hscroll) - alloc.width;
+
+ gtk_adjustment_set_value(hscroll, value);
+ break;
+
+ case GDK_KEY(Down):
+ case GDK_KEY(KP_Down):
+ value = gtk_adjustment_get_value(vscroll) +
+ nsgtk_adjustment_get_step_increment(vscroll);
+
+ if (value > nsgtk_adjustment_get_upper(vscroll) - alloc.height)
+ value = nsgtk_adjustment_get_upper(vscroll) - alloc.height;
+
+ gtk_adjustment_set_value(vscroll, value);
+ break;
+
+ case GDK_KEY(Page_Up):
+ case GDK_KEY(KP_Page_Up):
+ value = gtk_adjustment_get_value(vscroll) -
+ nsgtk_adjustment_get_page_increment(vscroll);
+
+ if (value < nsgtk_adjustment_get_lower(vscroll))
+ value = nsgtk_adjustment_get_lower(vscroll);
+
+ gtk_adjustment_set_value(vscroll, value);
+ break;
+
+ case GDK_KEY(Page_Down):
+ case GDK_KEY(KP_Page_Down):
+ value = gtk_adjustment_get_value(vscroll) +
+ nsgtk_adjustment_get_page_increment(vscroll);
+
+ if (value > nsgtk_adjustment_get_upper(vscroll) - alloc.height)
+ value = nsgtk_adjustment_get_upper(vscroll) - alloc.height;
+
+ gtk_adjustment_set_value(vscroll, value);
+ break;
+
+ default:
+ break;
+
+ }
+
+ return TRUE;
+}
+
+static gboolean nsgtk_window_keyrelease_event(GtkWidget *widget,
+ GdkEventKey *event, gpointer data)
+{
+ struct gui_window *g = data;
+
+ return gtk_im_context_filter_keypress(g->input_method, event);
+}
+
+
+static void nsgtk_window_input_method_commit(GtkIMContext *ctx,
+ const gchar *str, gpointer data)
+{
+ struct gui_window *g = data;
+ size_t len = strlen(str), offset = 0;
+
+ while (offset < len) {
+ uint32_t nskey = utf8_to_ucs4(str + offset, len - offset);
+
+ browser_window_key_press(g->bw, nskey);
+
+ offset = utf8_next(str, len, offset);
+ }
+}
+
+
+static gboolean nsgtk_window_size_allocate_event(GtkWidget *widget,
+ GtkAllocation *allocation, gpointer data)
+{
+ struct gui_window *g = data;
+
+ browser_window_schedule_reformat(g->bw);
+
+ return TRUE;
+}
+
+
+/** when the pane position is changed update the user option
+ *
+ * The slightly awkward implementation with the first allocation flag
+ * is necessary because the initial window creation does not cause an
+ * allocate-event signal so the position value in the pane is incorrect
+ * and we cannot know what it should be until after the allocation
+ * (which did not generate a signal) is done as the user position is a
+ * percentage of pane total width not an absolute value.
+ */
+static void
+nsgtk_paned_notify__position(GObject *gobject, GParamSpec *pspec, gpointer data)
+{
+ struct gui_window *g = data;
+ GtkAllocation pane_alloc;
+
+ gtk_widget_get_allocation(GTK_WIDGET(g->paned), &pane_alloc);
+
+ if (g->paned_sized == false)
+ {
+ g->paned_sized = true;
+ gtk_paned_set_position(g->paned,
+ (nsoption_int(toolbar_status_size) * pane_alloc.width) / 10000);
+ return;
+ }
+
+ nsoption_set_int(toolbar_status_size,
+ ((gtk_paned_get_position(g->paned) * 10000) / (pane_alloc.width - 1)));
+}
+
+/** Set status bar / scroll bar proportion according to user option
+ * when pane is resized.
+ */
+static gboolean nsgtk_paned_size_allocate_event(GtkWidget *widget,
+ GtkAllocation *allocation, gpointer data)
+{
+ gtk_paned_set_position(GTK_PANED(widget),
+ (nsoption_int(toolbar_status_size) * allocation->width) / 10000);
+
+ return TRUE;
+}
+
+/* destroy the browsing context as there is nothing to display it now */
+static void window_destroy(GtkWidget *widget, gpointer data)
+{
+ struct gui_window *gw = data;
+
+ browser_window_destroy(gw->bw);
+
+ g_object_unref(gw->input_method);
+}
+
+
+/**
+ * Create and open a gtk container (window or tab) for a browsing context.
+ *
+ * \param bw The browsing context to create gui_window for.
+ * \param existing An existing gui_window, may be NULL
+ * \param flags flags to control the container creation
+ * \return gui window, or NULL on error
+ *
+ * If GW_CREATE_CLONE flag is set existing is non-NULL.
+ *
+ * Front end's gui_window must include a reference to the
+ * browser window passed in the bw param.
+ */
+static struct gui_window *
+gui_window_create(struct browser_window *bw,
+ struct gui_window *existing,
+ gui_window_create_flags flags)
+{
+ struct gui_window *g; /* what is being created to return */
+ bool tempback;
+ GtkBuilder* tab_builder;
+
+ nserror res;
+
+ res = nsgtk_builder_new_from_resname("tabcontents", &tab_builder);
+ if (res != NSERROR_OK) {
+ LOG("Tab contents UI builder init failed");
+ return NULL;
+ }
+
+ gtk_builder_connect_signals(tab_builder, NULL);
+
+ g = calloc(1, sizeof(*g));
+ if (!g) {
+ nsgtk_warning("NoMemory", 0);
+ g_object_unref(tab_builder);
+ return NULL;
+ }
+
+ LOG("Creating gui window %p for browser window %p", g, bw);
+
+ g->bw = bw;
+ g->mouse.state = 0;
+ g->current_pointer = GUI_POINTER_DEFAULT;
+
+ /* attach scaffold */
+ if (flags & GW_CREATE_TAB) {
+ /* open in new tab, attach to existing scaffold */
+ if (existing != NULL) {
+ g->scaffold = existing->scaffold;
+ } else {
+ g->scaffold = nsgtk_current_scaffolding();
+ }
+ } else {
+ /* open in new window, create and attach to scaffold */
+ g->scaffold = nsgtk_new_scaffolding(g);
+ }
+ if (g->scaffold == NULL) {
+ nsgtk_warning("NoMemory", 0);
+ free(g);
+ g_object_unref(tab_builder);
+ return NULL;
+ }
+
+ /* Construct our primary elements */
+ g->container = GTK_WIDGET(gtk_builder_get_object(tab_builder, "tabContents"));
+ g->layout = GTK_LAYOUT(gtk_builder_get_object(tab_builder, "layout"));
+ g->status_bar = GTK_LABEL(gtk_builder_get_object(tab_builder, "status_bar"));
+ g->paned = GTK_PANED(gtk_builder_get_object(tab_builder, "hpaned1"));
+ g->input_method = gtk_im_multicontext_new();
+
+ /* set a default favicon */
+ g_object_ref(favicon_pixbuf);
+ g->icon = favicon_pixbuf;
+
+ /* add new gui window to global list (push_top) */
+ if (window_list) {
+ window_list->prev = g;
+ }
+ g->next = window_list;
+ g->prev = NULL;
+ window_list = g;
+
+ /* set the events we're interested in receiving from the browser's
+ * drawing area.
+ */
+ gtk_widget_add_events(GTK_WIDGET(g->layout),
+ GDK_EXPOSURE_MASK |
+ GDK_LEAVE_NOTIFY_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_POINTER_MOTION_HINT_MASK |
+ GDK_KEY_PRESS_MASK |
+ GDK_KEY_RELEASE_MASK |
+ GDK_SCROLL_MASK);
+ nsgtk_widget_set_can_focus(GTK_WIDGET(g->layout), TRUE);
+
+ /* set the default background colour of the drawing area to white. */
+ nsgtk_widget_override_background_color(GTK_WIDGET(g->layout),
+ GTK_STATE_NORMAL,
+ 0, 0xffff, 0xffff, 0xffff);
+
+ g->signalhandler[NSGTK_WINDOW_SIGNAL_REDRAW] =
+ nsgtk_connect_draw_event(GTK_WIDGET(g->layout),
+ G_CALLBACK(nsgtk_window_draw_event), g);
+
+ /* helper macro to conect signals to callbacks */
+#define CONNECT(obj, sig, callback, ptr) \
+ g_signal_connect(G_OBJECT(obj), (sig), G_CALLBACK(callback), (ptr))
+
+ /* layout signals */
+ CONNECT(g->layout, "motion-notify-event",
+ nsgtk_window_motion_notify_event, g);
+ g->signalhandler[NSGTK_WINDOW_SIGNAL_CLICK] =
+ CONNECT(g->layout, "button-press-event",
+ nsgtk_window_button_press_event, g);
+ CONNECT(g->layout, "button-release-event",
+ nsgtk_window_button_release_event, g);
+ CONNECT(g->layout, "key-press-event",
+ nsgtk_window_keypress_event, g);
+ CONNECT(g->layout, "key-release-event",
+ nsgtk_window_keyrelease_event, g);
+ CONNECT(g->layout, "size-allocate",
+ nsgtk_window_size_allocate_event, g);
+ CONNECT(g->layout, "scroll-event",
+ nsgtk_window_scroll_event, g);
+
+ /* status pane signals */
+ CONNECT(g->paned, "size-allocate",
+ nsgtk_paned_size_allocate_event, g);
+
+ CONNECT(g->paned, "notify::position",
+ nsgtk_paned_notify__position, g);
+
+ /* gtk container destructor */
+ CONNECT(g->container, "destroy", window_destroy, g);
+
+ /* input method */
+ gtk_im_context_set_client_window(g->input_method,
+ nsgtk_layout_get_bin_window(g->layout));
+ gtk_im_context_set_use_preedit(g->input_method, FALSE);
+
+ /* input method signals */
+ CONNECT(g->input_method, "commit",
+ nsgtk_window_input_method_commit, g);
+
+ /* add the tab container to the scaffold notebook */
+ switch (temp_open_background) {
+ case -1:
+ tempback = !(nsoption_bool(focus_new));
+ break;
+ case 0:
+ tempback = false;
+ break;
+ default:
+ tempback = true;
+ break;
+ }
+ nsgtk_tab_add(g, g->container, tempback);
+
+ /* safe to drop the reference to the tab_builder as the container is
+ * referenced by the notebook now.
+ */
+ g_object_unref(tab_builder);
+
+ return g;
+}
+
+
+
+void nsgtk_reflow_all_windows(void)
+{
+ for (struct gui_window *g = window_list; g; g = g->next) {
+ nsgtk_tab_options_changed(nsgtk_scaffolding_notebook(g->scaffold));
+ browser_window_schedule_reformat(g->bw);
+ }
+}
+
+
+/**
+ * callback from core to reformat a window.
+ */
+static void nsgtk_window_reformat(struct gui_window *gw)
+{
+ GtkAllocation alloc;
+
+ if (gw != NULL) {
+ /** @todo consider gtk_widget_get_allocated_width() */
+ nsgtk_widget_get_allocation(GTK_WIDGET(gw->layout), &alloc);
+
+ browser_window_reformat(gw->bw, false, alloc.width, alloc.height);
+ }
+}
+
+void nsgtk_window_destroy_browser(struct gui_window *gw)
+{
+ /* remove tab */
+ gtk_widget_destroy(gw->container);
+}
+
+static void gui_window_destroy(struct gui_window *g)
+{
+ LOG("gui_window: %p", g);
+ assert(g != NULL);
+ assert(g->bw != NULL);
+ LOG("scaffolding: %p", g->scaffold);
+
+ if (g->prev) {
+ g->prev->next = g->next;
+ } else {
+ window_list = g->next;
+ }
+
+ if (g->next) {
+ g->next->prev = g->prev;
+ }
+
+ LOG("window list head: %p", window_list);
+}
+
+/**
+ * favicon setting for gtk gui window.
+ *
+ * \param gw gtk gui window to set favicon on.
+ * \param icon A handle to the new favicon content.
+ */
+static void gui_window_set_icon(struct gui_window *gw, hlcache_handle *icon)
+{
+ struct bitmap *icon_bitmap = NULL;
+
+ /* free any existing icon */
+ if (gw->icon != NULL) {
+ g_object_unref(gw->icon);
+ gw->icon = NULL;
+ }
+
+ if (icon != NULL) {
+ icon_bitmap = content_get_bitmap(icon);
+ if (icon_bitmap != NULL) {
+ LOG("Using %p bitmap", icon_bitmap);
+ gw->icon = nsgdk_pixbuf_get_from_surface(icon_bitmap->surface, 16, 16);
+ }
+ }
+
+ if (gw->icon == NULL) {
+ LOG("Using default favicon");
+ g_object_ref(favicon_pixbuf);
+ gw->icon = favicon_pixbuf;
+ }
+
+ nsgtk_scaffolding_set_icon(gw);
+}
+
+static bool gui_window_get_scroll(struct gui_window *g, int *sx, int *sy)
+{
+ GtkAdjustment *vadj = nsgtk_layout_get_vadjustment(g->layout);
+ GtkAdjustment *hadj = nsgtk_layout_get_hadjustment(g->layout);
+
+ assert(vadj);
+ assert(hadj);
+
+ *sy = (int)(gtk_adjustment_get_value(vadj));
+ *sx = (int)(gtk_adjustment_get_value(hadj));
+
+ return true;
+}
+
+static void nsgtk_redraw_caret(struct gui_window *g)
+{
+ int sx, sy;
+
+ if (g->careth == 0)
+ return;
+
+ gui_window_get_scroll(g, &sx, &sy);
+
+ gtk_widget_queue_draw_area(GTK_WIDGET(g->layout),
+ g->caretx - sx, g->carety - sy, 1, g->careth + 1);
+
+}
+
+static void gui_window_remove_caret(struct gui_window *g)
+{
+ int sx, sy;
+ int oh = g->careth;
+
+ if (oh == 0)
+ return;
+
+ g->careth = 0;
+
+ gui_window_get_scroll(g, &sx, &sy);
+
+ gtk_widget_queue_draw_area(GTK_WIDGET(g->layout),
+ g->caretx - sx, g->carety - sy, 1, oh + 1);
+
+}
+
+static void gui_window_redraw_window(struct gui_window *g)
+{
+ gtk_widget_queue_draw(GTK_WIDGET(g->layout));
+}
+
+static void gui_window_update_box(struct gui_window *g, const struct rect *rect)
+{
+ int sx, sy;
+ float scale;
+
+ if (!browser_window_has_content(g->bw))
+ return;
+
+ gui_window_get_scroll(g, &sx, &sy);
+ scale = browser_window_get_scale(g->bw);
+
+ gtk_widget_queue_draw_area(GTK_WIDGET(g->layout),
+ rect->x0 * scale - sx,
+ rect->y0 * scale - sy,
+ (rect->x1 - rect->x0) * scale,
+ (rect->y1 - rect->y0) * scale);
+}
+
+static void gui_window_set_status(struct gui_window *g, const char *text)
+{
+ assert(g);
+ assert(g->status_bar);
+ gtk_label_set_text(g->status_bar, text);
+}
+
+
+static void gui_window_set_scroll(struct gui_window *g, int sx, int sy)
+{
+ GtkAdjustment *vadj = nsgtk_layout_get_vadjustment(g->layout);
+ GtkAdjustment *hadj = nsgtk_layout_get_hadjustment(g->layout);
+ gdouble vlower, vpage, vupper, hlower, hpage, hupper, x = (double)sx, y = (double)sy;
+
+ assert(vadj);
+ assert(hadj);
+
+ g_object_get(vadj, "page-size", &vpage, "lower", &vlower, "upper", &vupper, NULL);
+ g_object_get(hadj, "page-size", &hpage, "lower", &hlower, "upper", &hupper, NULL);
+
+ if (x < hlower)
+ x = hlower;
+ if (x > (hupper - hpage))
+ x = hupper - hpage;
+ if (y < vlower)
+ y = vlower;
+ if (y > (vupper - vpage))
+ y = vupper - vpage;
+
+ gtk_adjustment_set_value(vadj, y);
+ gtk_adjustment_set_value(hadj, x);
+}
+
+static void gui_window_update_extent(struct gui_window *g)
+{
+ int w, h;
+
+ if (browser_window_get_extents(g->bw, true, &w, &h) == NSERROR_OK) {
+ gtk_layout_set_size(g->layout, w, h);
+ }
+}
+
+static void gui_window_set_pointer(struct gui_window *g,
+ gui_pointer_shape shape)
+{
+ GdkCursor *cursor = NULL;
+ GdkCursorType cursortype;
+ bool nullcursor = false;
+
+ if (g->current_pointer == shape)
+ return;
+
+ g->current_pointer = shape;
+
+ switch (shape) {
+ case GUI_POINTER_POINT:
+ cursortype = GDK_HAND2;
+ break;
+ case GUI_POINTER_CARET:
+ cursortype = GDK_XTERM;
+ break;
+ case GUI_POINTER_UP:
+ cursortype = GDK_TOP_SIDE;
+ break;
+ case GUI_POINTER_DOWN:
+ cursortype = GDK_BOTTOM_SIDE;
+ break;
+ case GUI_POINTER_LEFT:
+ cursortype = GDK_LEFT_SIDE;
+ break;
+ case GUI_POINTER_RIGHT:
+ cursortype = GDK_RIGHT_SIDE;
+ break;
+ case GUI_POINTER_LD:
+ cursortype = GDK_BOTTOM_LEFT_CORNER;
+ break;
+ case GUI_POINTER_RD:
+ cursortype = GDK_BOTTOM_RIGHT_CORNER;
+ break;
+ case GUI_POINTER_LU:
+ cursortype = GDK_TOP_LEFT_CORNER;
+ break;
+ case GUI_POINTER_RU:
+ cursortype = GDK_TOP_RIGHT_CORNER;
+ break;
+ case GUI_POINTER_CROSS:
+ cursortype = GDK_CROSS;
+ break;
+ case GUI_POINTER_MOVE:
+ cursortype = GDK_FLEUR;
+ break;
+ case GUI_POINTER_WAIT:
+ cursortype = GDK_WATCH;
+ break;
+ case GUI_POINTER_HELP:
+ cursortype = GDK_QUESTION_ARROW;
+ break;
+ case GUI_POINTER_MENU:
+ cursor = nsgtk_create_menu_cursor();
+ nullcursor = true;
+ break;
+ case GUI_POINTER_PROGRESS:
+ /* In reality, this needs to be the funky left_ptr_watch
+ * which we can't do easily yet.
+ */
+ cursortype = GDK_WATCH;
+ break;
+ /* The following we're not sure about */
+ case GUI_POINTER_NO_DROP:
+ case GUI_POINTER_NOT_ALLOWED:
+ case GUI_POINTER_DEFAULT:
+ default:
+ nullcursor = true;
+ }
+
+ if (!nullcursor)
+ cursor = gdk_cursor_new_for_display(
+ gtk_widget_get_display(
+ GTK_WIDGET(g->layout)),
+ cursortype);
+ gdk_window_set_cursor(nsgtk_widget_get_window(GTK_WIDGET(g->layout)),
+ cursor);
+
+ if (!nullcursor)
+ nsgdk_cursor_unref(cursor);
+}
+
+
+static void gui_window_place_caret(struct gui_window *g, int x, int y, int height,
+ const struct rect *clip)
+{
+ nsgtk_redraw_caret(g);
+
+ y += 1;
+ height -= 1;
+
+ if (y < clip->y0) {
+ height -= clip->y0 - y;
+ y = clip->y0;
+ }
+
+ if (y + height > clip->y1) {
+ height = clip->y1 - y + 1;
+ }
+
+ g->caretx = x;
+ g->carety = y;
+ g->careth = height;
+
+ nsgtk_redraw_caret(g);
+
+ gtk_widget_grab_focus(GTK_WIDGET(g->layout));
+}
+
+
+static void gui_window_get_dimensions(struct gui_window *g, int *width, int *height,
+ bool scaled)
+{
+ GtkAllocation alloc;
+
+ /* @todo consider gtk_widget_get_allocated_width() */
+ nsgtk_widget_get_allocation(GTK_WIDGET(g->layout), &alloc);
+
+ *width = alloc.width;
+ *height = alloc.height;
+
+ if (scaled) {
+ float scale = browser_window_get_scale(g->bw);
+ *width /= scale;
+ *height /= scale;
+ }
+ LOG("width: %i", *width);
+ LOG("height: %i", *height);
+}
+
+static void gui_window_start_selection(struct gui_window *g)
+{
+ gtk_widget_grab_focus(GTK_WIDGET(g->layout));
+}
+
+static void gui_window_create_form_select_menu(struct gui_window *g,
+ struct form_control *control)
+{
+ intptr_t item;
+ struct form_option *option;
+
+ GtkWidget *menu_item;
+
+ /* control->data.select.multiple is true if multiple selections
+ * are allowable. We ignore this, as the core handles it for us.
+ * Yay. \o/
+ */
+
+ if (select_menu != NULL) {
+ gtk_widget_destroy(select_menu);
+ }
+
+ select_menu = gtk_menu_new();
+ select_menu_control = control;
+
+ item = 0;
+ option = form_select_get_option(control, item);
+ while (option != NULL) {
+ LOG("Item %"PRIdPTR" option %p text %s",
+ item, option, option->text);
+ menu_item = gtk_check_menu_item_new_with_label(option->text);
+ if (option->selected) {
+ gtk_check_menu_item_set_active(
+ GTK_CHECK_MENU_ITEM(menu_item), TRUE);
+ }
+
+ /*
+ * This casts the item index integer into an integer
+ * the size of a pointer. This allows the callback
+ * parameter to be passed avoiding allocating memory
+ * for a context with a single integer in it.
+ */
+ g_signal_connect(menu_item, "toggled",
+ G_CALLBACK(nsgtk_select_menu_clicked), (gpointer)item);
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(select_menu), menu_item);
+
+ item++;
+ option = form_select_get_option(control, item);
+ }
+
+ gtk_widget_show_all(select_menu);
+
+ gtk_menu_popup(GTK_MENU(select_menu), NULL, NULL, NULL,
+ NULL /* data */, 0, gtk_get_current_event_time());
+
+}
+
+static void
+gui_window_file_gadget_open(struct gui_window *g,
+ hlcache_handle *hl,
+ struct form_control *gadget)
+{
+ GtkWidget *dialog;
+
+ dialog = gtk_file_chooser_dialog_new("Select File",
+ nsgtk_scaffolding_window(g->scaffold),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ NSGTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ NSGTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ LOG("*** open dialog: %p", dialog);
+
+ int ret = gtk_dialog_run(GTK_DIALOG(dialog));
+ LOG("*** return value: %d", ret);
+ if (ret == GTK_RESPONSE_ACCEPT) {
+ char *filename;
+
+ filename = gtk_file_chooser_get_filename(
+ GTK_FILE_CHOOSER(dialog));
+
+ browser_window_set_gadget_filename(g->bw, gadget, filename);
+
+ g_free(filename);
+ }
+
+ gtk_widget_destroy(dialog);
+}
+
+static struct gui_window_table window_table = {
+ .create = gui_window_create,
+ .destroy = gui_window_destroy,
+ .redraw = gui_window_redraw_window,
+ .update = gui_window_update_box,
+ .get_scroll = gui_window_get_scroll,
+ .set_scroll = gui_window_set_scroll,
+ .get_dimensions = gui_window_get_dimensions,
+ .update_extent = gui_window_update_extent,
+ .reformat = nsgtk_window_reformat,
+
+ .set_icon = gui_window_set_icon,
+ .set_status = gui_window_set_status,
+ .set_pointer = gui_window_set_pointer,
+ .place_caret = gui_window_place_caret,
+ .remove_caret = gui_window_remove_caret,
+ .create_form_select_menu = gui_window_create_form_select_menu,
+ .file_gadget_open = gui_window_file_gadget_open,
+ .start_selection = gui_window_start_selection,
+
+ /* from scaffold */
+ .set_title = nsgtk_window_set_title,
+ .set_url = gui_window_set_url,
+ .start_throbber = gui_window_start_throbber,
+ .stop_throbber = gui_window_stop_throbber,
+};
+
+struct gui_window_table *nsgtk_window_table = &window_table;