summaryrefslogtreecommitdiff
path: root/frontends/gtk/treeview.c
diff options
context:
space:
mode:
Diffstat (limited to 'frontends/gtk/treeview.c')
-rw-r--r--frontends/gtk/treeview.c586
1 files changed, 586 insertions, 0 deletions
diff --git a/frontends/gtk/treeview.c b/frontends/gtk/treeview.c
new file mode 100644
index 000000000..9baf57b62
--- /dev/null
+++ b/frontends/gtk/treeview.c
@@ -0,0 +1,586 @@
+/*
+ * Copyright 2004 Richard Wilson <not_ginger_matt@users.sourceforge.net>
+ * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.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/>.
+ */
+
+/** \file
+ * Generic tree handling (implementation).
+ */
+
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+#include <math.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "utils/log.h"
+#include "utils/utf8.h"
+#include "desktop/tree.h"
+#include "desktop/plotters.h"
+
+#include "gtk/warn.h"
+#include "gtk/compat.h"
+#include "gtk/gui.h"
+#include "gtk/plotters.h"
+#include "gtk/treeview.h"
+
+struct nsgtk_treeview {
+ GtkWindow *window;
+ GtkScrolledWindow *scrolled;
+ GtkDrawingArea *drawing_area;
+ GtkIMContext *input_method;
+ bool mouse_pressed;
+ int mouse_pressed_x;
+ int mouse_pressed_y;
+ int last_x, last_y;
+ browser_mouse_state mouse_state;
+ struct tree *tree;
+};
+
+void nsgtk_treeview_destroy(struct nsgtk_treeview *tv)
+{
+ tree_delete(tv->tree);
+ g_object_unref(tv->input_method);
+ gtk_widget_destroy(GTK_WIDGET(tv->window));
+ free(tv);
+}
+
+struct tree *nsgtk_treeview_get_tree(struct nsgtk_treeview *tv)
+{
+ return tv->tree;
+}
+
+static void nsgtk_tree_redraw_request(int x, int y, int width, int height, void *data)
+{
+ struct nsgtk_treeview *tw = data;
+
+ gtk_widget_queue_draw_area(GTK_WIDGET(tw->drawing_area),
+ x, y, width, height);
+}
+
+
+/**
+ * Updates the tree owner following a tree resize
+ *
+ * \param tree the tree to update the owner of
+ * \param width The width to resize to.
+ * \param height The height to resize to.
+ * \param data The treeview resize.
+ */
+static void nsgtk_tree_resized(struct tree *tree, int width, int height, void *data)
+{
+ struct nsgtk_treeview *tw = data;
+
+ gtk_widget_set_size_request(GTK_WIDGET(tw->drawing_area),
+ width, height);
+ return;
+}
+
+/**
+ * Scrolls the tree to make an element visible
+ *
+ * \param y Y coordinate of the element
+ * \param height height of the element
+ * \param data user data assigned to the tree on tree creation
+ */
+static void nsgtk_tree_scroll_visible(int y, int height, void *data)
+{
+ int y0, y1;
+ gdouble page;
+ struct nsgtk_treeview *tw = data;
+ GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(tw->scrolled);
+
+ assert(vadj);
+
+ g_object_get(vadj, "page-size", &page, NULL);
+
+ y0 = (int)(gtk_adjustment_get_value(vadj));
+ y1 = y0 + page;
+
+ if ((y >= y0) && (y + height <= y1))
+ return;
+ if (y + height > y1)
+ y0 = y0 + (y + height - y1);
+ if (y < y0)
+ y0 = y;
+ gtk_adjustment_set_value(vadj, y0);
+}
+
+
+/**
+ * Retrieves the dimensions of the window with the tree
+ *
+ * \param data user data assigned to the tree on tree creation
+ * \param width will be updated to window width if not NULL
+ * \param height will be updated to window height if not NULL
+ */
+static void nsgtk_tree_get_window_dimensions(int *width, int *height, void *data)
+{
+ struct nsgtk_treeview *tw = data;
+ GtkAdjustment *vadj;
+ GtkAdjustment *hadj;
+ gdouble page;
+
+ if (width != NULL) {
+ hadj = gtk_scrolled_window_get_hadjustment(tw->scrolled);
+ g_object_get(hadj, "page-size", &page, NULL);
+ *width = page;
+ }
+
+ if (height != NULL) {
+ vadj = gtk_scrolled_window_get_vadjustment(tw->scrolled);
+ g_object_get(vadj, "page-size", &page, NULL);
+ *height = page;
+ }
+}
+
+#if GTK_CHECK_VERSION(3,0,0)
+
+static gboolean
+nsgtk_tree_window_draw_event(GtkWidget *widget, cairo_t *cr, gpointer data)
+{
+ struct tree *tree = (struct tree *)data;
+ 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);
+
+ tree_draw(tree, 0, 0, x1, y1, x2 - x1, y2 - y1, &ctx);
+
+ current_widget = NULL;
+
+ return FALSE;
+}
+
+#else
+
+/* signal handler functions for a tree window */
+static gboolean
+nsgtk_tree_window_draw_event(GtkWidget *widget, GdkEventExpose *event, gpointer g)
+{
+ struct tree *tree = (struct tree *) g;
+ struct redraw_context ctx = {
+ .interactive = true,
+ .background_images = true,
+ .plot = &nsgtk_plotters
+ };
+ int x, y, width, height;
+
+ x = event->area.x;
+ y = event->area.y;
+ width = event->area.width;
+ height = event->area.height;
+
+ current_widget = widget;
+ current_cr = gdk_cairo_create(nsgtk_widget_get_window(widget));
+
+ tree_draw(tree, 0, 0, x, y, width, height, &ctx);
+
+ current_widget = NULL;
+ cairo_destroy(current_cr);
+
+ return FALSE;
+}
+
+#endif
+
+void nsgtk_tree_window_hide(GtkWidget *widget, gpointer g)
+{
+}
+
+static gboolean
+nsgtk_tree_window_button_press_event(GtkWidget *widget,
+ GdkEventButton *event, gpointer g)
+{
+ struct nsgtk_treeview *tw = g;
+ struct tree *tree = tw->tree;
+
+ gtk_im_context_reset(tw->input_method);
+ gtk_widget_grab_focus(GTK_WIDGET(tw->drawing_area));
+
+ tw->mouse_pressed = true;
+ tw->mouse_pressed_x = event->x;
+ tw->mouse_pressed_y = event->y;
+
+ if (event->type == GDK_2BUTTON_PRESS)
+ tw->mouse_state = BROWSER_MOUSE_DOUBLE_CLICK;
+
+ switch (event->button) {
+ case 1: tw->mouse_state |= BROWSER_MOUSE_PRESS_1; break;
+ case 2: tw->mouse_state |= BROWSER_MOUSE_PRESS_2; break;
+ }
+ /* Handle the modifiers too */
+ if (event->state & GDK_SHIFT_MASK)
+ tw->mouse_state |= BROWSER_MOUSE_MOD_1;
+ if (event->state & GDK_CONTROL_MASK)
+ tw->mouse_state |= BROWSER_MOUSE_MOD_2;
+ if (event->state & GDK_MOD1_MASK)
+ tw->mouse_state |= BROWSER_MOUSE_MOD_3;
+
+ /* Record where we pressed, for use when determining whether to start
+ * a drag in motion notify events. */
+ tw->last_x = event->x;
+ tw->last_y = event->y;
+
+ tree_mouse_action(tree, tw->mouse_state, event->x, event->y);
+
+ return TRUE;
+}
+
+static gboolean
+nsgtk_tree_window_button_release_event(GtkWidget *widget,
+ GdkEventButton *event, gpointer g)
+{
+ bool shift = event->state & GDK_SHIFT_MASK;
+ bool ctrl = event->state & GDK_CONTROL_MASK;
+ bool alt = event->state & GDK_MOD1_MASK;
+ struct nsgtk_treeview *tw = (struct nsgtk_treeview *) g;
+ struct tree *tree = tw->tree;
+
+ /* We consider only button 1 clicks as double clicks.
+ * 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 (tw->mouse_state & BROWSER_MOUSE_DOUBLE_CLICK) {
+
+ if (tw->mouse_state & BROWSER_MOUSE_PRESS_1)
+ tw->mouse_state ^= BROWSER_MOUSE_PRESS_1 |
+ BROWSER_MOUSE_CLICK_1;
+ else if (tw->mouse_state & BROWSER_MOUSE_PRESS_2)
+ tw->mouse_state ^= (BROWSER_MOUSE_PRESS_2 |
+ BROWSER_MOUSE_CLICK_2 |
+ BROWSER_MOUSE_DOUBLE_CLICK);
+
+ } else if (tw->mouse_state & BROWSER_MOUSE_PRESS_1) {
+ tw->mouse_state ^= (BROWSER_MOUSE_PRESS_1 |
+ BROWSER_MOUSE_CLICK_1);
+ } else if (tw->mouse_state & BROWSER_MOUSE_PRESS_2) {
+ tw->mouse_state ^= (BROWSER_MOUSE_PRESS_2 |
+ BROWSER_MOUSE_CLICK_2);
+ } else if (tw->mouse_state & BROWSER_MOUSE_HOLDING_1) {
+ tw->mouse_state ^= (BROWSER_MOUSE_HOLDING_1 |
+ BROWSER_MOUSE_DRAG_ON);
+ } else if (tw->mouse_state & BROWSER_MOUSE_HOLDING_2) {
+ tw->mouse_state ^= (BROWSER_MOUSE_HOLDING_2 |
+ BROWSER_MOUSE_DRAG_ON);
+ }
+
+ /* Handle modifiers being removed */
+ if (tw->mouse_state & BROWSER_MOUSE_MOD_1 && !shift)
+ tw->mouse_state ^= BROWSER_MOUSE_MOD_1;
+ if (tw->mouse_state & BROWSER_MOUSE_MOD_2 && !ctrl)
+ tw->mouse_state ^= BROWSER_MOUSE_MOD_2;
+ if (tw->mouse_state & BROWSER_MOUSE_MOD_3 && !alt)
+ tw->mouse_state ^= BROWSER_MOUSE_MOD_3;
+
+
+ if (tw->mouse_state &
+ ~(BROWSER_MOUSE_MOD_1 |
+ BROWSER_MOUSE_MOD_2 |
+ BROWSER_MOUSE_MOD_3)) {
+ tree_mouse_action(tree, tw->mouse_state,
+ event->x, event->y);
+ } else {
+ tree_drag_end(tree, tw->mouse_state,
+ tw->mouse_pressed_x,
+ tw->mouse_pressed_y,
+ event->x, event->y);
+ }
+
+ tw->mouse_state = 0;
+ tw->mouse_pressed = false;
+
+ return TRUE;
+}
+
+static gboolean
+nsgtk_tree_window_motion_notify_event(GtkWidget *widget,
+ GdkEventMotion *event, gpointer g)
+{
+ bool shift = event->state & GDK_SHIFT_MASK;
+ bool ctrl = event->state & GDK_CONTROL_MASK;
+ bool alt = event->state & GDK_MOD1_MASK;
+ struct nsgtk_treeview *tw = (struct nsgtk_treeview *) g;
+ struct tree *tree = tw->tree;
+
+ if (tw->mouse_pressed == false)
+ return TRUE;
+
+ if ((fabs(event->x - tw->last_x) < 5.0) &&
+ (fabs(event->y - tw->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.
+ */
+ tw->last_x = INT_MIN;
+ tw->last_y = INT_MIN;
+ }
+
+ if (tw->mouse_state & BROWSER_MOUSE_PRESS_1) {
+ /* Start button 1 drag */
+ tree_mouse_action(tree, BROWSER_MOUSE_DRAG_1,
+ tw->mouse_pressed_x, tw->mouse_pressed_y);
+ /* Replace PRESS with HOLDING and declare drag in progress */
+ tw->mouse_state ^= (BROWSER_MOUSE_PRESS_1 |
+ BROWSER_MOUSE_HOLDING_1);
+ tw->mouse_state |= BROWSER_MOUSE_DRAG_ON;
+ return TRUE;
+ }
+ else if (tw->mouse_state & BROWSER_MOUSE_PRESS_2){
+ /* Start button 2s drag */
+ tree_mouse_action(tree, BROWSER_MOUSE_DRAG_2,
+ tw->mouse_pressed_x, tw->mouse_pressed_y);
+ /* Replace PRESS with HOLDING and declare drag in progress */
+ tw->mouse_state ^= (BROWSER_MOUSE_PRESS_2 |
+ BROWSER_MOUSE_HOLDING_2);
+ tw->mouse_state |= BROWSER_MOUSE_DRAG_ON;
+ return TRUE;
+ }
+
+ /* Handle modifiers being removed */
+ if (tw->mouse_state & BROWSER_MOUSE_MOD_1 && !shift)
+ tw->mouse_state ^= BROWSER_MOUSE_MOD_1;
+ if (tw->mouse_state & BROWSER_MOUSE_MOD_2 && !ctrl)
+ tw->mouse_state ^= BROWSER_MOUSE_MOD_2;
+ if (tw->mouse_state & BROWSER_MOUSE_MOD_3 && !alt)
+ tw->mouse_state ^= BROWSER_MOUSE_MOD_3;
+
+ if (tw->mouse_state & (BROWSER_MOUSE_HOLDING_1 |
+ BROWSER_MOUSE_HOLDING_2))
+ tree_mouse_action(tree, tw->mouse_state, event->x,
+ event->y);
+
+ return TRUE;
+}
+
+
+static gboolean
+nsgtk_tree_window_keypress_event(GtkWidget *widget, GdkEventKey *event,
+ gpointer g)
+{
+ struct nsgtk_treeview *tw = (struct nsgtk_treeview *) g;
+ struct tree *tree = tw->tree;
+ uint32_t nskey;
+ double value;
+ GtkAdjustment *vscroll;
+ GtkAdjustment *hscroll;
+ GtkAdjustment *scroll = NULL;
+ gdouble hpage, vpage;
+
+ if (gtk_im_context_filter_keypress(tw->input_method, event))
+ return TRUE;
+
+ nskey = gtk_gui_gdkkey_to_nskey(event);
+
+ if (tree_keypress(tree, nskey) == true)
+ return TRUE;
+
+ vscroll = gtk_scrolled_window_get_vadjustment(tw->scrolled);
+ hscroll = gtk_scrolled_window_get_hadjustment(tw->scrolled);
+ g_object_get(vscroll, "page-size", &vpage, NULL);
+ g_object_get(hscroll, "page-size", &hpage, NULL);
+
+ switch (event->keyval) {
+ case GDK_KEY(Home):
+ case GDK_KEY(KP_Home):
+ scroll = vscroll;
+ value = nsgtk_adjustment_get_lower(scroll);
+ break;
+
+ case GDK_KEY(End):
+ case GDK_KEY(KP_End):
+ scroll = vscroll;
+ value = nsgtk_adjustment_get_upper(scroll) - vpage;
+ if (value < nsgtk_adjustment_get_lower(scroll))
+ value = nsgtk_adjustment_get_lower(scroll);
+ break;
+
+ case GDK_KEY(Left):
+ case GDK_KEY(KP_Left):
+ scroll = hscroll;
+ value = gtk_adjustment_get_value(scroll) -
+ nsgtk_adjustment_get_step_increment(scroll);
+ if (value < nsgtk_adjustment_get_lower(scroll))
+ value = nsgtk_adjustment_get_lower(scroll);
+ break;
+
+ case GDK_KEY(Up):
+ case GDK_KEY(KP_Up):
+ scroll = vscroll;
+ value = gtk_adjustment_get_value(scroll) -
+ nsgtk_adjustment_get_step_increment(scroll);
+ if (value < nsgtk_adjustment_get_lower(scroll))
+ value = nsgtk_adjustment_get_lower(scroll);
+ break;
+
+ case GDK_KEY(Right):
+ case GDK_KEY(KP_Right):
+ scroll = hscroll;
+ value = gtk_adjustment_get_value(scroll) +
+ nsgtk_adjustment_get_step_increment(scroll);
+ if (value > nsgtk_adjustment_get_upper(scroll) - hpage)
+ value = nsgtk_adjustment_get_upper(scroll) - hpage;
+ break;
+
+ case GDK_KEY(Down):
+ case GDK_KEY(KP_Down):
+ scroll = vscroll;
+ value = gtk_adjustment_get_value(scroll) +
+ nsgtk_adjustment_get_step_increment(scroll);
+ if (value > nsgtk_adjustment_get_upper(scroll) - vpage)
+ value = nsgtk_adjustment_get_upper(scroll) - vpage;
+ break;
+
+ case GDK_KEY(Page_Up):
+ case GDK_KEY(KP_Page_Up):
+ scroll = vscroll;
+ value = gtk_adjustment_get_value(scroll) -
+ nsgtk_adjustment_get_page_increment(scroll);
+
+ if (value < nsgtk_adjustment_get_lower(scroll))
+ value = nsgtk_adjustment_get_lower(scroll);
+
+ break;
+
+ case GDK_KEY(Page_Down):
+ case GDK_KEY(KP_Page_Down):
+ scroll = vscroll;
+ value = gtk_adjustment_get_value(scroll) +
+ nsgtk_adjustment_get_page_increment(scroll);
+
+ if (value > nsgtk_adjustment_get_upper(scroll) - vpage)
+ value = nsgtk_adjustment_get_upper(scroll) - vpage;
+ break;
+
+ default:
+ break;
+ }
+
+ if (scroll != NULL)
+ gtk_adjustment_set_value(scroll, value);
+
+ return TRUE;
+}
+
+static gboolean
+nsgtk_tree_window_keyrelease_event(GtkWidget *widget, GdkEventKey *event,
+ gpointer g)
+{
+ struct nsgtk_treeview *tw = (struct nsgtk_treeview *) g;
+
+ return gtk_im_context_filter_keypress(tw->input_method, event);
+}
+
+static void
+nsgtk_tree_window_input_method_commit(GtkIMContext *ctx,
+ const gchar *str, gpointer data)
+{
+ struct nsgtk_treeview *tw = (struct nsgtk_treeview *) data;
+ size_t len = strlen(str), offset = 0;
+
+ while (offset < len) {
+ uint32_t nskey = utf8_to_ucs4(str + offset, len - offset);
+
+ tree_keypress(tw->tree, nskey);
+
+ offset = utf8_next(str, len, offset);
+ }
+}
+
+
+static const struct treeview_table nsgtk_tree_callbacks = {
+ .redraw_request = nsgtk_tree_redraw_request,
+ .resized = nsgtk_tree_resized,
+ .scroll_visible = nsgtk_tree_scroll_visible,
+ .get_window_dimensions = nsgtk_tree_get_window_dimensions
+};
+
+struct nsgtk_treeview *nsgtk_treeview_create(unsigned int flags,
+ GtkWindow *window, GtkScrolledWindow *scrolled,
+ GtkDrawingArea *drawing_area)
+{
+ struct nsgtk_treeview *tv;
+
+ assert(drawing_area != NULL);
+
+ tv = malloc(sizeof(struct nsgtk_treeview));
+ if (tv == NULL) {
+ LOG("malloc failed");
+ nsgtk_warning("NoMemory", 0);
+ return NULL;
+ }
+
+ tv->window = window;
+ tv->scrolled = scrolled;
+ tv->drawing_area = drawing_area;
+ tv->input_method = gtk_im_multicontext_new();
+ tv->tree = tree_create(flags, &nsgtk_tree_callbacks, tv);
+ tv->mouse_state = 0;
+ tv->mouse_pressed = false;
+
+ nsgtk_widget_override_background_color(GTK_WIDGET(drawing_area),
+ GTK_STATE_NORMAL,
+ 0, 0xffff, 0xffff, 0xffff);
+
+ nsgtk_connect_draw_event(GTK_WIDGET(drawing_area), G_CALLBACK(nsgtk_tree_window_draw_event), tv->tree);
+
+#define CONNECT(obj, sig, callback, ptr) \
+ g_signal_connect(G_OBJECT(obj), (sig), G_CALLBACK(callback), (ptr))
+ CONNECT(drawing_area, "button-press-event",
+ nsgtk_tree_window_button_press_event,
+ tv);
+ CONNECT(drawing_area, "button-release-event",
+ nsgtk_tree_window_button_release_event,
+ tv);
+ CONNECT(drawing_area, "motion-notify-event",
+ nsgtk_tree_window_motion_notify_event,
+ tv);
+ CONNECT(drawing_area, "key-press-event",
+ nsgtk_tree_window_keypress_event,
+ tv);
+ CONNECT(drawing_area, "key-release-event",
+ nsgtk_tree_window_keyrelease_event,
+ tv);
+
+
+ /* input method */
+ gtk_im_context_set_client_window(tv->input_method,
+ nsgtk_widget_get_window(GTK_WIDGET(tv->window)));
+ gtk_im_context_set_use_preedit(tv->input_method, FALSE);
+ /* input method signals */
+ CONNECT(tv->input_method, "commit",
+ nsgtk_tree_window_input_method_commit,
+ tv);
+
+ return tv;
+}