/* * File: dw_gtk_viewport.c * * Copyright (C) 2001-2003 Sebastian Geerken * * This program 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; either version 2 of the License, or * (at your option) any later version. */ #include #include "msg.h" #include "dw_gtk_viewport.h" #include "dw_container.h" #include "list.h" /*#define DEBUG_LEVEL 1*/ #include "debug.h" #include "msg.h" typedef struct { gchar *name; DwWidget *widget; gint32 y; } GtkDwViewportAnchor; static GtkLayoutClass *parent_class = NULL; /* object/class initialisation */ static void Dw_gtk_viewport_init (GtkDwViewport *viewport); static void Dw_gtk_viewport_class_init (GtkDwViewportClass *klass); /* GtkObject methods */ static void Dw_gtk_viewport_destroy (GtkObject *object); /* GtkWidget methods */ static void Dw_gtk_viewport_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static void Dw_gtk_viewport_realize (GtkWidget *widget); static void Dw_gtk_viewport_draw (GtkWidget *widget, GdkRectangle *area); static gint Dw_gtk_viewport_expose (GtkWidget *widget, GdkEventExpose *event); static gint Dw_gtk_viewport_button_press (GtkWidget *widget, GdkEventButton *event); static gint Dw_gtk_viewport_button_release(GtkWidget *widget, GdkEventButton *event); static gint Dw_gtk_viewport_motion_notify (GtkWidget *widget, GdkEventMotion *event); static gint Dw_gtk_viewport_enter_notify (GtkWidget *widget, GdkEventCrossing *event); static gint Dw_gtk_viewport_leave_notify (GtkWidget *widget, GdkEventCrossing *event); static void Dw_gtk_viewport_adj_changed (GtkAdjustment *adj, GtkDwViewport *viewport); /* * Standard Gtk+ function */ GtkType a_Dw_gtk_viewport_get_type (void) { static GtkType type = 0; if (!type) { GtkTypeInfo info = { "GtkDwViewport", sizeof (GtkDwViewport), sizeof (GtkDwViewportClass), (GtkClassInitFunc) Dw_gtk_viewport_class_init, (GtkObjectInitFunc) Dw_gtk_viewport_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL, (GtkClassInitFunc) NULL }; type = gtk_type_unique (GTK_TYPE_LAYOUT, &info); } return type; } /* * Standard Gtk+ function */ GtkWidget* a_Dw_gtk_viewport_new (GtkAdjustment *hadjustment, GtkAdjustment *vadjustment) { GtkWidget *widget; widget = gtk_widget_new (GTK_TYPE_DW_VIEWPORT, NULL); gtk_layout_set_hadjustment (GTK_LAYOUT (widget), hadjustment); gtk_layout_set_vadjustment (GTK_LAYOUT (widget), vadjustment); /* Following two statements expect that the adjustments are passed as * arguments (!= NULL), and don't change. This is the case in dillo, * however, for more general perposes, the signal function * "set_scroll_adjustments" had to be redefined. */ gtk_signal_connect (GTK_OBJECT (hadjustment), "value_changed", GTK_SIGNAL_FUNC (Dw_gtk_viewport_adj_changed), (gpointer) widget); gtk_signal_connect (GTK_OBJECT (vadjustment), "value_changed", GTK_SIGNAL_FUNC (Dw_gtk_viewport_adj_changed), (gpointer) widget); return widget; } /********************************* * * * object/class initialisation * * * *********************************/ /* * Standard Gtk+ function */ static void Dw_gtk_viewport_init (GtkDwViewport *viewport) { DBG_OBJ_CREATE (viewport, "GtkDwViewport"); GTK_WIDGET_UNSET_FLAGS (viewport, GTK_NO_WINDOW); GTK_WIDGET_UNSET_FLAGS (viewport, GTK_CAN_FOCUS); /* Without this, gtk_layout_{draw|expose} will clear the window. Look at gtklayout.c */ GTK_WIDGET_SET_FLAGS (viewport, GTK_APP_PAINTABLE); viewport->back_pixmap = NULL; viewport->child = NULL; viewport->last_entered = NULL; viewport->draw_resize_idle_id = 0; viewport->anchor = NULL; viewport->anchor_idle_id = 0; viewport->findtext_state = a_Findtext_state_new (); viewport->selection = a_Selection_new (); viewport->anchors_table = g_hash_table_new (g_str_hash, g_str_equal); viewport->draw_areas = NULL; viewport->num_draw_areas = 0; viewport->num_draw_areas_max = 4; DBG_OBJ_ASSOC (viewport->findtext_state, viewport); DBG_OBJ_ASSOC (viewport->selection, viewport); } /* * Standard Gtk+ function */ static void Dw_gtk_viewport_class_init (GtkDwViewportClass *klass) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; parent_class = gtk_type_class (gtk_layout_get_type ()); object_class = (GtkObjectClass*) klass; widget_class = (GtkWidgetClass*) klass; object_class->destroy = Dw_gtk_viewport_destroy; widget_class->size_allocate = Dw_gtk_viewport_size_allocate; widget_class->realize = Dw_gtk_viewport_realize; widget_class->draw = Dw_gtk_viewport_draw; widget_class->expose_event = Dw_gtk_viewport_expose; widget_class->button_press_event = Dw_gtk_viewport_button_press; widget_class->button_release_event = Dw_gtk_viewport_button_release; widget_class->motion_notify_event = Dw_gtk_viewport_motion_notify; widget_class->enter_notify_event = Dw_gtk_viewport_enter_notify; widget_class->leave_notify_event = Dw_gtk_viewport_leave_notify; } /*********************** * * * GtkObject methods * * * ***********************/ static gboolean Dw_gtk_viewport_destroy_anchor (gpointer key, gpointer value, gpointer user_data) { g_free (value); return TRUE; } /* * Standard Gtk+ function */ static void Dw_gtk_viewport_destroy (GtkObject *object) { GtkDwViewport *viewport; viewport = GTK_DW_VIEWPORT (object); if (viewport->back_pixmap) gdk_pixmap_unref (viewport->back_pixmap); if (viewport->child) gtk_object_destroy (GTK_OBJECT (viewport->child)); if (viewport->draw_resize_idle_id != 0) gtk_idle_remove (viewport->draw_resize_idle_id); if (viewport->anchor_idle_id != 0) gtk_idle_remove (viewport->anchor_idle_id); g_free (viewport->anchor); g_hash_table_foreach_remove (viewport->anchors_table, Dw_gtk_viewport_destroy_anchor, NULL); g_hash_table_destroy (viewport->anchors_table); g_free (viewport->draw_areas); a_Findtext_state_destroy (viewport->findtext_state); a_Selection_free (viewport->selection); GTK_OBJECT_CLASS(parent_class)->destroy (object); } /*********************** * * * GtkWidget methods * * * ***********************/ /* * Standard Gtk+ function */ static void Dw_gtk_viewport_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GTK_WIDGET_CLASS(parent_class)->size_allocate (widget, allocation); /* gtk_layout_size_allocate() sets them to different values. */ GTK_LAYOUT(widget)->hadjustment->page_increment = allocation->width; GTK_LAYOUT(widget)->vadjustment->page_increment = 0.96 * allocation->height; } /* * Standard Gtk+ function */ static void Dw_gtk_viewport_realize (GtkWidget *widget) { GtkDwViewport *viewport; GTK_WIDGET_CLASS(parent_class)->realize (widget); gdk_window_set_events (widget->window, gdk_window_get_events (widget->window) | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); viewport = GTK_DW_VIEWPORT (widget); gdk_window_get_geometry (widget->window, NULL, NULL, NULL, NULL, &viewport->depth); Dw_gtk_viewport_update_background (viewport); if (viewport->child) Dw_widget_update_cursor (viewport->child); } /* * (Nearly) standard Gtk+ function */ static void Dw_gtk_viewport_paint (GtkWidget *widget, GdkRectangle *area, GdkEventExpose *event) { GtkLayout *layout; DwRectangle parent_area, child_area, intersection; GtkDwViewport *viewport; gboolean new_back_pixmap; if (GTK_WIDGET_DRAWABLE (widget)) { layout = GTK_LAYOUT (widget); viewport = GTK_DW_VIEWPORT (widget); DEBUG_MSG (2, "Drawing (%d, %d), %d x %d\n", area->x, area->y, area->width, area->height); /* Make sure the backing pixmap is large enough. */ if (viewport->child) { if (viewport->back_pixmap) new_back_pixmap = (widget->allocation.width > viewport->back_width || widget->allocation.height > viewport->back_height); else new_back_pixmap = TRUE; if (new_back_pixmap) { if (viewport->back_pixmap) gdk_pixmap_unref (viewport->back_pixmap); viewport->back_pixmap = gdk_pixmap_new (widget->window, widget->allocation.width, widget->allocation.height, viewport->depth); viewport->back_width = widget->allocation.width; viewport->back_height = widget->allocation.height; DEBUG_MSG (1, " Creating new pixmap, size = %d x %d\n", widget->allocation.width, widget->allocation.height); } /* Draw top-level Dw widget. */ parent_area.x = p_Dw_widget_x_viewport_to_world (viewport->child, area->x); parent_area.y = p_Dw_widget_y_viewport_to_world (viewport->child, area->y); parent_area.width = area->width; parent_area.height = area->height; child_area.x = viewport->child->allocation.x; child_area.y = viewport->child->allocation.y; child_area.width = viewport->child->allocation.width; child_area.height = DW_WIDGET_HEIGHT(viewport->child); if (p_Dw_rectangle_intersect (&parent_area, &child_area, &intersection)) { intersection.x -= viewport->child->allocation.x; intersection.y -= viewport->child->allocation.y; /* "Clear" backing pixmap. */ gdk_draw_rectangle (viewport->back_pixmap, viewport->child->style->background_color->gc, TRUE, area->x, area->y, area->width, area->height); /* Widgets draw in backing pixmap. */ p_Dw_widget_draw (viewport->child, &intersection, event); /* Copy backing pixmap into window. */ gdk_draw_pixmap (layout->bin_window, widget->style->black_gc, viewport->back_pixmap, area->x, area->y, area->x, area->y, area->width, area->height); } } else gdk_window_clear_area (layout->bin_window, area->x, area->y, area->width, area->height); } } /* * Standard Gtk+ function */ static void Dw_gtk_viewport_draw (GtkWidget *widget, GdkRectangle *area) { Dw_gtk_viewport_paint (widget, area, NULL); GTK_WIDGET_CLASS(parent_class)->draw (widget, area); } /* * Standard Gtk+ function */ static gint Dw_gtk_viewport_expose (GtkWidget *widget, GdkEventExpose *event) { Dw_gtk_viewport_paint (widget, &(event->area), event); return GTK_WIDGET_CLASS(parent_class)->expose_event (widget, event); } /* * Handle the mouse event and deliver it to the Dw widget. * Most is done in a_Dw_widget_mouse_event. */ static gint Dw_gtk_viewport_mouse_event (GtkWidget *widget, gint32 x, gint32 y, GdkEvent *event) { GtkDwViewport *viewport; DwWidget *dw_widget; gint32 world_x, world_y; if (event == NULL || event->any.window == widget->window) { viewport = GTK_DW_VIEWPORT (widget); if (viewport->child) { world_x = x + gtk_layout_get_hadjustment(GTK_LAYOUT(viewport))->value; world_y = y + gtk_layout_get_vadjustment(GTK_LAYOUT(viewport))->value; dw_widget = Dw_gtk_viewport_widget_at_point (viewport, world_x, world_y); return Dw_widget_mouse_event (dw_widget, widget, world_x, world_y, event); } } return FALSE; } /* * Standard Gtk+ function */ static gint Dw_gtk_viewport_button_press (GtkWidget *widget, GdkEventButton *event) { /* We focus always the viewport window (i.e., more precisely, the * GktDwScrolledFrame). */ if (widget->parent) gtk_widget_grab_focus(widget->parent); return Dw_gtk_viewport_mouse_event (widget, event->x, event->y, (GdkEvent*) event); } /* * Standard Gtk+ function */ static gint Dw_gtk_viewport_button_release (GtkWidget *widget, GdkEventButton *event) { return Dw_gtk_viewport_mouse_event (widget, event->x, event->y, (GdkEvent*) event); } /* * Standard Gtk+ function */ static gint Dw_gtk_viewport_motion_notify (GtkWidget *widget, GdkEventMotion *event) { GtkDwViewport *viewport = GTK_DW_VIEWPORT (widget); viewport->mouse_x = event->x; viewport->mouse_y = event->y; return Dw_gtk_viewport_mouse_event (widget, event->x, event->y, (GdkEvent*) event); } /* * Standard Gtk+ function */ static gint Dw_gtk_viewport_enter_notify (GtkWidget *widget, GdkEventCrossing *event) { return Dw_gtk_viewport_mouse_event (widget, event->x, event->y, NULL); } /* * Standard Gtk+ function */ static gint Dw_gtk_viewport_leave_notify (GtkWidget *widget, GdkEventCrossing *event) { /* There will anyway be no Dw widget, thus this simple call */ return Dw_widget_mouse_event (NULL, widget, 0, 0, NULL); } /* * This function is called when the viewport changes, and causes * motion_notify events to be simulated. */ static void Dw_gtk_viewport_adj_changed (GtkAdjustment *adj, GtkDwViewport *viewport) { Dw_gtk_viewport_mouse_event (GTK_WIDGET (viewport), viewport->mouse_x, viewport->mouse_y, NULL); } /* * This function sets the background and the viewport. */ void Dw_gtk_viewport_update_background (GtkDwViewport *viewport) { /* The toplevel widget should always have a defined background color, * except at the beginning. Searching a defined background is not * necessary. */ if (viewport->child && viewport->child->style && viewport->child->style->background_color) gdk_window_set_background (GTK_LAYOUT(viewport)->bin_window, &viewport->child->style->background_color ->color); } /********************** * * * public functions * * * **********************/ /* * Set the top-level Dw widget. * If there is already one, you must destroy it before, otherwise the * function will fail. */ void a_Dw_gtk_viewport_add_dw (GtkDwViewport *viewport, DwWidget *widget) { g_return_if_fail (viewport->child == NULL); viewport->child = widget; DBG_OBJ_ASSOC(widget, viewport); widget->parent = NULL; widget->viewport = GTK_WIDGET (viewport); Dw_gtk_viewport_update_background (viewport); Dw_widget_update_cursor (viewport->child); Dw_gtk_viewport_calc_size (viewport); Dw_gtk_viewport_remove_anchor (viewport); a_Findtext_state_set_widget (viewport->findtext_state, widget); a_Selection_reset (viewport->selection); } /************************************************** * * * Functions used by GtkDwViewport and DwWidget * * * **************************************************/ /* * This function only *recognizes* that the top-level Dw widget has * been destroyed, and only removes it from the viewport. It is called * by Dw_widget_destroy. Don't use this function directly! * * N.b.: a widget, which stores anchors, should already have removed * them from the widget, by calling a_Dw_gtk_viewport_remove_anchor(). * A good place for this is the implementation of GtkObject::destroy, * the call to the parent destroy method should then be done at the * end of the function, since Dw_widget_destroy calls this function. */ void Dw_gtk_viewport_remove_dw (GtkDwViewport *viewport) { /* Test, that all anchors have been removed properly. */ gint num_anchors_left = g_hash_table_size (viewport->anchors_table); /* g_assert (num_anchors_left == 0); */ if (num_anchors_left != 0) g_warning ("%d anchors left", num_anchors_left); a_Findtext_state_set_widget (viewport->findtext_state, NULL); viewport->child = NULL; Dw_gtk_viewport_remove_anchor (viewport); Dw_gtk_viewport_calc_size (viewport); } /* * Used by Dw_gtk_viewport_calc_size. */ static void Dw_gtk_viewport_calc_child_size (GtkDwViewport *viewport, gint32 child_width, gint32 child_height, DwRequisition *child_requisition) { if (child_width < 0) child_width = 0; if (child_height < 0) child_height = 0; DEBUG_MSG (2, " width = %d, height = %d ...\n", child_width, child_height); p_Dw_widget_set_width (viewport->child, child_width); p_Dw_widget_set_ascent (viewport->child, child_height); p_Dw_widget_set_descent (viewport->child, 0); p_Dw_widget_size_request (viewport->child, child_requisition); } /* * Calculate the size of the scrolled area and allocate the top-level * widget. This function is called when the top-level Dw widget has * changed its size etc. */ void Dw_gtk_viewport_calc_size (GtkDwViewport *viewport) { GtkWidget *widget; GtkScrolledWindow *scrolled; DwRequisition child_requisition; DwAllocation child_allocation; gint border_width, space; GtkRequisition bar_requisition; gint max_width, max_height, bar_width_diff, bar_height_diff, child_height; if (viewport->calc_size_blocked) return; viewport->calc_size_blocked = TRUE; if (viewport->child) { /* * Determine the size hints for the Dw widget. This is a bit * tricky, because you must know if scrollbars are visible or * not, which depends on the size of the Dw widget, which then * depends on the hints. The idea is to test several * configurations, there are four of them, from combining the * cases horizontal/vertical scrollbar visible/invisible. * * For optimization, the horizontal scrollbar is currently not * regarded, the height hint is always the same, as if the * scrollbar was allways visible. In future, this may be * implemented correctly, by using the minimal width to optimize * most cases. (Minimal widths will also be used by tables.) * * Furthermore, the last result (vertical scrollbar visible or * not) is stored in the viewport, and tested first. This will * make a second test only necessary when the visibility * switches, which normally happens only once when filling the * page with text. (Actually, this assumes that the page size is * always *growing*, but this is nevertheless true in dillo.) */ widget = GTK_WIDGET (viewport); scrolled = GTK_SCROLLED_WINDOW (widget->parent->parent); space = GTK_SCROLLED_WINDOW_CLASS(GTK_OBJECT(scrolled)->klass) ->scrollbar_spacing; border_width = GTK_CONTAINER(viewport)->border_width; gtk_widget_size_request (scrolled->vscrollbar, &bar_requisition); bar_width_diff = bar_requisition.width + space; max_width = widget->allocation.width - 2 * border_width; if (scrolled->vscrollbar_visible) max_width += bar_width_diff; gtk_widget_size_request (scrolled->hscrollbar, &bar_requisition); bar_height_diff = bar_requisition.height + space; max_height = widget->allocation.height - 2 * border_width; if (scrolled->hscrollbar_visible) max_height += bar_height_diff; DEBUG_MSG (2, "------------------------------------------------->\n"); DEBUG_MSG (2, "Dw_gtk_viewport_calc_size: %d x %d (%c/%c) -> %d x %d\n", widget->allocation.width, widget->allocation.height, scrolled->vscrollbar_visible ? 't' : 'f', scrolled->hscrollbar_visible ? 't' : 'f', max_width, max_height); if (scrolled->vscrollbar_policy == GTK_POLICY_NEVER) child_height = max_height; else child_height = max_height - bar_height_diff; switch (scrolled->vscrollbar_policy) { case GTK_POLICY_ALWAYS: Dw_gtk_viewport_calc_child_size (viewport, max_width - bar_width_diff, child_height, &child_requisition); break; case GTK_POLICY_AUTOMATIC: if (viewport->vscrollbar_used) { DEBUG_MSG (2, "Testing with vertical scrollbar ...\n"); Dw_gtk_viewport_calc_child_size (viewport, max_width - bar_width_diff, child_height, &child_requisition); if (child_requisition.ascent + child_requisition.descent <= child_height) { DEBUG_MSG (2, " failed!\n"); Dw_gtk_viewport_calc_child_size (viewport, max_width, child_height, &child_requisition); viewport->vscrollbar_used = TRUE; } } else { DEBUG_MSG (2, "Testing without vertical scrollbar ...\n"); Dw_gtk_viewport_calc_child_size (viewport, max_width, child_height, &child_requisition); /* todo: see above */ if (child_requisition.ascent + child_requisition.descent > child_height) { DEBUG_MSG (2, " failed!\n"); Dw_gtk_viewport_calc_child_size (viewport, max_width - bar_width_diff, child_height, &child_requisition); viewport->vscrollbar_used = TRUE; } } break; case GTK_POLICY_NEVER: Dw_gtk_viewport_calc_child_size (viewport, max_width, child_height, &child_requisition); } child_allocation.x = border_width; child_allocation.y = border_width; child_allocation.width = child_requisition.width; child_allocation.ascent = child_requisition.ascent; child_allocation.descent = child_requisition.descent; p_Dw_widget_size_allocate (viewport->child, &child_allocation); gtk_layout_set_size (GTK_LAYOUT (viewport), child_requisition.width + 2 * border_width, child_requisition.ascent + child_requisition.descent + 2 * border_width); DEBUG_MSG (1, "Setting size to %d x %d\n", child_requisition.width + 2 * border_width, child_requisition.ascent + child_requisition.descent + 2 * border_width); DEBUG_MSG (2, "<-------------------------------------------------\n"); } else { gtk_layout_set_size (GTK_LAYOUT (viewport), 1, 1); viewport->hscrollbar_used = FALSE; viewport->vscrollbar_used = FALSE; } Dw_gtk_viewport_update_anchor (viewport); gtk_widget_queue_draw (GTK_WIDGET (viewport)); viewport->calc_size_blocked = FALSE; } /* used by Dw_gtk_viewport_widget_at_point */ typedef struct { gint32 x; gint32 y; DwWidget *widget; } WidgetAtPointData; /* used by Dw_gtk_viewport_widget_at_point */ static void Dw_gtk_viewport_widget_at_point_callback (DwWidget *widget, gpointer data) { WidgetAtPointData *callback_data; callback_data = (WidgetAtPointData*) data; DEBUG_MSG (1, " Checking %p ...\n", widget); if (/* As a special exception, for the top-level widget, not the * allocation is regarded, but the whole viewport. This makes * selections more useful, since so the user can start the * selection outside of the allocation. */ widget->parent == NULL || /* Otherwise, check whether pointer is in the allocation. */ (callback_data->x >= widget->allocation.x && callback_data->y >= widget->allocation.y && callback_data->x < widget->allocation.x + widget->allocation.width && callback_data->y < widget->allocation.y + DW_WIDGET_HEIGHT(widget))) { DEBUG_MSG (1, " yes\n"); if (DW_IS_CONTAINER (widget)) a_Dw_container_forall (DW_CONTAINER (widget), Dw_gtk_viewport_widget_at_point_callback, data); if (callback_data->widget == NULL) callback_data->widget = widget; } } /* * Return the widget at point (x, y) (world coordinates). */ DwWidget* Dw_gtk_viewport_widget_at_point (GtkDwViewport *viewport, gint32 x, gint32 y) { WidgetAtPointData callback_data; callback_data.x = x; callback_data.y = y; callback_data.widget = NULL; if (viewport->child) Dw_gtk_viewport_widget_at_point_callback (viewport->child, &callback_data); return callback_data.widget; } /************* * * * Anchors * * * *************/ /* * todo: Currently, no horizontal scrolling is done. This is generally * possible, DW_HPOS_INTO_VIEW should be used for this, but it is * rather complicated to determine the width of an anchor. This would * be the lenght of the region between and , the page widget * would have to have two kinds of content types (opening and closing * anchor), and some changes in the HTML parser are necessary. */ /* * Add an anchor, and assign a position for it. For all widgets * directly or indirectly assigned to a viewports, anchors must be * unique, this is tested. "name" is copied, so no strdup is * neccessary for the caller. * * Return the copy on success, or NULL, when this anchor had already * been added to the widget tree. * * The viewport gets the responsibility to free "name". */ gchar* p_Dw_gtk_viewport_add_anchor (DwWidget *widget, const gchar *name, gint32 y) { GtkDwViewport *viewport; GtkDwViewportAnchor *anchor; _MSG("new anchor %p/'%s' -> %d\n", widget, name, y); g_return_val_if_fail (widget->viewport != NULL, NULL); viewport = GTK_DW_VIEWPORT (widget->viewport); if (g_hash_table_lookup_extended (viewport->anchors_table, name, NULL,NULL)) /* Anchor does already exist. */ return NULL; else { anchor = g_new (GtkDwViewportAnchor, 1); anchor->name = g_strdup (name); anchor->widget = widget; anchor->y = y; g_hash_table_insert (viewport->anchors_table, anchor->name, anchor); Dw_gtk_viewport_update_anchor (viewport); return anchor->name; } } /* * Assign a position for an already existing anchor. */ void p_Dw_gtk_viewport_change_anchor (DwWidget *widget, gchar *name, gint32 y) { GtkDwViewport *viewport; GtkDwViewportAnchor *anchor; gpointer tmp_anchor; gboolean exists; _MSG("changing anchor %p/'%s' -> %d\n", widget, name, y); g_return_if_fail (widget->viewport != NULL); viewport = GTK_DW_VIEWPORT (widget->viewport); exists = g_hash_table_lookup_extended (viewport->anchors_table, name, NULL, &tmp_anchor); g_return_if_fail(exists); anchor = tmp_anchor; g_return_if_fail(anchor->widget == widget); anchor->y = y; Dw_gtk_viewport_update_anchor (viewport); } /* * Remove an anchor from the table in the viewport. Notice that "name" * is freed here. */ void p_Dw_gtk_viewport_remove_anchor (DwWidget *widget, gchar *name) { GtkDwViewport *viewport; GtkDwViewportAnchor *anchor; gpointer tmp_anchor; gboolean exists; _MSG("removing anchor %p/'%s'\n", widget, name); g_return_if_fail (widget->viewport != NULL); viewport = GTK_DW_VIEWPORT (widget->viewport); exists = g_hash_table_lookup_extended (viewport->anchors_table, name, NULL, &tmp_anchor); g_return_if_fail(exists); anchor = tmp_anchor; g_return_if_fail(anchor->widget == widget); g_hash_table_remove (viewport->anchors_table, name); g_free (anchor->name); g_free (anchor); } /* * Used by Dw_gtk_viewport_update_anchor_idle. */ static gboolean Dw_gtk_viewport_calc_into (gint32 requested_value, gint32 requested_size, gint32 current_value, gint32 size, gint32 *return_value) { if (requested_size > size) { /* The viewport size is smaller than the size of the region which will * be shown. If the region is already visible, do not change the * position. Otherwise, show the left/upper border, this is most likely * what is needed. */ if (current_value >= requested_value && current_value + size < requested_value + requested_size) return FALSE; else requested_size = size; } if (requested_value < current_value) { *return_value = requested_value; return TRUE; } else if (requested_value + requested_size > current_value + size) { *return_value = requested_value - size + requested_size; return TRUE; } else return FALSE; } /* * See Dw_gtk_viewport_scroll_to. */ static gint Dw_gtk_viewport_update_anchor_idle (gpointer data) { gint32 vp_width, vp_height, x = 0, y = 0; GtkScrolledWindow *scrolled; GtkDwViewport *viewport; GtkWidget *vwidget; GtkAdjustment *vadj, *hadj; gboolean change_x, change_y; DBG_MSG (data, "scrolling", 0, "Dw_gtk_viewport_update_anchor_idle"); DBG_MSG_START (data); vwidget = GTK_WIDGET (data); viewport = GTK_DW_VIEWPORT (vwidget); scrolled = GTK_SCROLLED_WINDOW (vwidget->parent->parent); hadj = GTK_LAYOUT(viewport)->hadjustment; vadj = GTK_LAYOUT(viewport)->vadjustment; vp_width = vwidget->allocation.width - GTK_CONTAINER(viewport)->border_width; vp_height = vwidget->allocation.height - GTK_CONTAINER(viewport)->border_width; DBG_MSGF (viewport, "scrolling", 0, "vp_width = %d", vp_width); DBG_MSGF (viewport, "scrolling", 0, "vp_height = %d", vp_height); change_x = TRUE; switch (viewport->anchor_pos.hpos) { case DW_HPOS_LEFT: DBG_MSG (viewport, "scrolling", 0, "DW_HPOS_LEFT"); x = viewport->anchor_pos.x; break; case DW_HPOS_CENTER: DBG_MSG (viewport, "scrolling", 0, "DW_HPOS_CENTER"); x = viewport->anchor_pos.x - (vp_width - viewport->anchor_pos.width) / 2; break; case DW_HPOS_RIGHT: DBG_MSG (viewport, "scrolling", 0, "DW_HPOS_RIGHT"); x = viewport->anchor_pos.x - (vp_width - viewport->anchor_pos.width); break; case DW_HPOS_INTO_VIEW: DBG_MSG (viewport, "scrolling", 0, "DW_HPOS_INTO_VIEW"); change_x = Dw_gtk_viewport_calc_into (viewport->anchor_pos.x, viewport->anchor_pos.width, hadj->value, vp_width, &x); break; case DW_HPOS_NO_CHANGE: DBG_MSG (viewport, "scrolling", 0, "DW_HPOS_NO_CHANGE"); change_x = FALSE; break; } change_y = TRUE; switch (viewport->anchor_pos.vpos) { case DW_VPOS_TOP: DBG_MSG (viewport, "scrolling", 0, "DW_VPOS_TOP"); y = viewport->anchor_pos.y; break; case DW_VPOS_CENTER: DBG_MSG (viewport, "scrolling", 0, "DW_VPOS_CENTER"); y = viewport->anchor_pos.y - (vp_height - viewport->anchor_pos.height) / 2; break; case DW_VPOS_BOTTOM: DBG_MSG (viewport, "scrolling", 0, "DW_VPOS_BOTTOM"); y = viewport->anchor_pos.y - (vp_height - viewport->anchor_pos.height); break; case DW_VPOS_INTO_VIEW: DBG_MSG (viewport, "scrolling", 0, "DW_VPOS_INTO_VIEW"); change_y = Dw_gtk_viewport_calc_into (viewport->anchor_pos.y, viewport->anchor_pos.height, vadj->value, vp_height, &y); case DW_VPOS_NO_CHANGE: DBG_MSG (viewport, "scrolling", 0, "DW_VPOS_NO_CHANGE"); change_y = FALSE; break; } DBG_MSGF (viewport, "scrolling", 0, "scrolling to (%d, %d)\n", x, y); DBG_MSGF (viewport, "scrolling", 0, "hadj->upper = %g, hadj->page_size = %g", hadj->upper, hadj->page_size); DBG_MSGF (viewport, "scrolling", 0, "vadj->upper = %g, vadj->page_size = %g", vadj->upper, vadj->page_size); if (change_x) { if (x > hadj->upper - hadj->page_size) gtk_adjustment_set_value (hadj, hadj->upper - hadj->page_size); else gtk_adjustment_set_value (hadj, x); } if (change_y) { if (y > vadj->upper - vadj->page_size) gtk_adjustment_set_value (vadj, vadj->upper - vadj->page_size); else gtk_adjustment_set_value (vadj, y); } viewport->anchor_idle_id = 0; DBG_MSG_END (viewport); return FALSE; } /* * Called when possibly the scroll position has to be changed because * of anchors. */ void Dw_gtk_viewport_update_anchor (GtkDwViewport *viewport) { GtkDwViewportAnchor *anchor; gpointer tmp_anchor; if (viewport->anchor && g_hash_table_lookup_extended (viewport->anchors_table, viewport->anchor, NULL, &tmp_anchor)) { anchor = tmp_anchor; Dw_gtk_viewport_scroll_to (viewport, DW_HPOS_NO_CHANGE, DW_VPOS_TOP, 0, anchor->y + anchor->widget->allocation.y, 0, 0); } } /* * Sets the anchor to scroll to. */ void a_Dw_gtk_viewport_set_anchor (GtkDwViewport *viewport, const gchar *anchor) { Dw_gtk_viewport_remove_anchor (viewport); if (anchor) { viewport->anchor = g_strdup (anchor); Dw_gtk_viewport_update_anchor (viewport); } else { viewport->anchor = NULL; gtk_adjustment_set_value (GTK_LAYOUT(viewport)->vadjustment, 0); } } /* * Sets the position to scroll to. The current anchor will be removed. */ void a_Dw_gtk_viewport_set_scrolling_position (GtkDwViewport *viewport, gint32 x, gint32 y) { Dw_gtk_viewport_remove_anchor (viewport); Dw_gtk_viewport_scroll_to (viewport, DW_HPOS_LEFT, DW_VPOS_TOP, x, y, 0, 0); } /* * Scrolls the viewport, so that the region [x, y, width, height] (world * coordinates) is seen, according to hpos and vpos. * * The actual scrolling is done in an idle function. */ void Dw_gtk_viewport_scroll_to (GtkDwViewport *viewport, DwHPosition hpos, DwVPosition vpos, gint32 x, gint32 y, gint32 width, gint32 height) { viewport->anchor_pos.hpos = hpos; viewport->anchor_pos.vpos = vpos; viewport->anchor_pos.x = x; viewport->anchor_pos.y = y; viewport->anchor_pos.width = width; viewport->anchor_pos.height = height; DBG_OBJ_SET_NUM (viewport, "anchor_pos.hpos", viewport->anchor_pos.hpos); DBG_OBJ_SET_NUM (viewport, "anchor_pos.vpos", viewport->anchor_pos.vpos); DBG_OBJ_SET_NUM (viewport, "anchor_pos.x", viewport->anchor_pos.x); DBG_OBJ_SET_NUM (viewport, "anchor_pos.y", viewport->anchor_pos.y); DBG_OBJ_SET_NUM (viewport, "anchor_pos.width", viewport->anchor_pos.width); DBG_OBJ_SET_NUM (viewport, "anchor_pos.height", viewport->anchor_pos.height); if (viewport->anchor_idle_id == 0) viewport->anchor_idle_id = gtk_idle_add (Dw_gtk_viewport_update_anchor_idle, (gpointer)viewport); } /* * Remove anchor and idle function. */ void Dw_gtk_viewport_remove_anchor (GtkDwViewport *viewport) { if (viewport->anchor) { g_free (viewport->anchor); viewport->anchor = NULL; } if (viewport->anchor_idle_id != 0) { gtk_idle_remove (viewport->anchor_idle_id); viewport->anchor_idle_id = 0; } } /* * Drawing and resizing is done in this idle function. */ static gint Dw_gtk_viewport_draw_resize_idle (gpointer data) { GtkDwViewport *viewport; GtkLayout *layout; GtkWidget *widget; DwRectangle viewport_area, world_area; GdkRectangle gtk_area; int i; viewport = GTK_DW_VIEWPORT (data); switch (viewport->draw_resize_action) { case DW_GTK_VIEWPORT_DRAW: for (i = 0; i < viewport->num_draw_areas; i++) { widget = GTK_WIDGET (viewport); layout = GTK_LAYOUT (viewport); viewport_area.x = gtk_layout_get_hadjustment(layout)->value; viewport_area.y = gtk_layout_get_vadjustment(layout)->value;; viewport_area.width = widget->allocation.width; viewport_area.height = widget->allocation.height; if (p_Dw_rectangle_intersect (&viewport->draw_areas[i], &viewport_area, &world_area)) { gtk_area.x = world_area.x - viewport_area.x; gtk_area.y = world_area.y - viewport_area.y; gtk_area.width = world_area.width; gtk_area.height = world_area.height; gtk_widget_draw (widget, >k_area); } } /* No more areas to be drawn. */ viewport->num_draw_areas = 0; break; case DW_GTK_VIEWPORT_RESIZE: Dw_gtk_viewport_calc_size (viewport); break; } viewport->draw_resize_idle_id = 0; return FALSE; } /* * Queue an area for drawing. This function is called by * p_Dw_widget_queue_draw_area. x and y are passed in world coordinates. */ void Dw_gtk_viewport_queue_draw (GtkDwViewport *viewport, gint32 x, gint32 y, gint32 width, gint32 height) { DwRectangle area; int i; if (viewport->draw_resize_idle_id == 0) { viewport->draw_resize_action = DW_GTK_VIEWPORT_DRAW; viewport->draw_resize_idle_id = gtk_idle_add (Dw_gtk_viewport_draw_resize_idle, (gpointer)viewport); } else if (viewport->draw_resize_action == DW_GTK_VIEWPORT_RESIZE) /* Drawing is always overridden by resizing. */ return; area.x = x; area.y = y; area.width = width; area.height = height; /* First, try to keep the list as clean as possible. Check whether other * rectangles interfer with this one in some way. */ /* An idea for optimization: The list could be sorted, and so the part of * the list we have to consider here, may be reduced, the start may be * found via linear search. However, this probably makes balanced binary * trees necessary, since moving elements within the array may be quite * time-consuming. */ _MSG(" num_draw_areas = %d\n", viewport->num_draw_areas); for (i = 0; i < viewport->num_draw_areas; i++) { if (p_Dw_rectangle_is_subset (&area, &viewport->draw_areas[i])) /* First case: area is a subset of an already queued rectangle * -> nothing to do. */ return; else if (p_Dw_rectangle_is_subset (&viewport->draw_areas[i], &area)) { /* Second case: area is a subset of an already queued rectangle * -> replace the other one with area. */ viewport->draw_areas[i] = area; return; } /* Maybe some more tests: if both areas may exactly be combined to a * rectangle? Very unlikely case ... */ } /* No interference: add the new area to the list. */ viewport->num_draw_areas++; a_List_add (viewport->draw_areas, viewport->num_draw_areas, viewport->num_draw_areas_max); viewport->draw_areas[viewport->num_draw_areas - 1] = area; } /* * Start the resizing idle. This function is called by * p_Dw_widget_queue_resize, after the appropriate attributes have been set in * the widgets, where necessary. */ void Dw_gtk_viewport_queue_resize (GtkDwViewport *viewport) { /* Resizing always overrides drawing. */ viewport->draw_resize_action = DW_GTK_VIEWPORT_RESIZE; viewport->num_draw_areas = 0; if (viewport->draw_resize_idle_id == 0) viewport->draw_resize_idle_id = gtk_idle_add (Dw_gtk_viewport_draw_resize_idle, (gpointer)viewport); } /* * Return the DwWidget which is at position (vx, vy) in viewport coordinates. */ DwWidget* a_Dw_gtk_viewport_widget_at_viewport_point (GtkDwViewport *viewport, gint32 vx, gint32 vy) { gint32 world_x, world_y; if (viewport->child) { world_x = vx + gtk_layout_get_hadjustment(GTK_LAYOUT(viewport))->value; world_y = vy + gtk_layout_get_vadjustment(GTK_LAYOUT(viewport))->value; return Dw_gtk_viewport_widget_at_point (viewport, world_x, world_y); } else return NULL; }