/* * File: dw_widget.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 #include #include "msg.h" #include "dw_widget.h" #include "dw_marshal.h" #include "dw_container.h" #include "dw_gtk_viewport.h" #define DEBUG_EVENT 10 #define DEBUG_SIZE 0 #define DEBUG_ALLOC 0 /*#define DEBUG_LEVEL 10*/ #include "debug.h" static void Dw_widget_init (DwWidget *widget); static void Dw_widget_class_init (DwWidgetClass *klass); static void Dw_widget_destroy (GtkObject *object); static void Dw_widget_real_size_request (DwWidget *widget, DwRequisition *requisition); static void Dw_widget_real_get_extremes (DwWidget *widget, DwExtremes *extremes); enum { BUTTON_PRESS_EVENT, BUTTON_RELEASE_EVENT, MOTION_NOTIFY_EVENT, ENTER_NOTIFY_EVENT, LEAVE_NOTIFY_EVENT, LAST_SIGNAL }; static GtkObjectClass *parent_class; static guint widget_signals[LAST_SIGNAL] = { 0 }; /* * Standard Gtk+ function */ GtkType a_Dw_widget_get_type (void) { static GtkType type = 0; if (!type) { GtkTypeInfo info = { "DwWidget", sizeof (DwWidget), sizeof (DwWidgetClass), (GtkClassInitFunc) Dw_widget_class_init, (GtkObjectInitFunc) Dw_widget_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL, (GtkClassInitFunc) NULL }; type = gtk_type_unique (GTK_TYPE_OBJECT, &info); } return type; } /* * Standard Gtk+ function */ static void Dw_widget_init (DwWidget *widget) { widget->flags = DW_NEEDS_RESIZE | DW_EXTREMES_CHANGED | DW_HAS_CONTENT; widget->parent = NULL; widget->viewport = NULL; widget->allocation.x = -1; widget->allocation.y = -1; widget->allocation.width = 1; widget->allocation.ascent = 1; widget->allocation.descent = 0; widget->cursor = NULL; widget->style = NULL; widget->bg_color = NULL; widget->button_sensitive = TRUE; widget->button_sensitive_set = FALSE; } /* * Standard Gtk+ function */ static void Dw_widget_class_init (DwWidgetClass *klass) { GtkObjectClass *object_class; parent_class = gtk_type_class (gtk_object_get_type ()); object_class = GTK_OBJECT_CLASS (klass); widget_signals[BUTTON_PRESS_EVENT] = gtk_signal_new ("button_press_event", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (DwWidgetClass, button_press_event), p_Dw_marshal_BOOL__INT_INT_POINTER, GTK_TYPE_BOOL, 3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_GDK_EVENT); widget_signals[BUTTON_RELEASE_EVENT] = gtk_signal_new ("button_release_event", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (DwWidgetClass, button_release_event), p_Dw_marshal_BOOL__INT_INT_POINTER, GTK_TYPE_BOOL, 3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_GDK_EVENT); widget_signals[MOTION_NOTIFY_EVENT] = gtk_signal_new ("motion_notify_event", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (DwWidgetClass, motion_notify_event), p_Dw_marshal_BOOL__INT_INT_POINTER, GTK_TYPE_BOOL, 3, GTK_TYPE_INT, GTK_TYPE_INT, GTK_TYPE_GDK_EVENT); widget_signals[ENTER_NOTIFY_EVENT] = gtk_signal_new ("enter_notify_event", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (DwWidgetClass, enter_notify_event), p_Dw_marshal_BOOL__POINTER_POINTER, GTK_TYPE_BOOL, 2, GTK_TYPE_POINTER, GTK_TYPE_GDK_EVENT); widget_signals[LEAVE_NOTIFY_EVENT] = gtk_signal_new ("leave_notify_event", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (DwWidgetClass, leave_notify_event), p_Dw_marshal_BOOL__POINTER_POINTER, GTK_TYPE_BOOL, 2, GTK_TYPE_POINTER, GTK_TYPE_GDK_EVENT); gtk_object_class_add_signals (object_class, widget_signals, LAST_SIGNAL); object_class->destroy = Dw_widget_destroy; klass->size_request = Dw_widget_real_size_request; klass->get_extremes = Dw_widget_real_get_extremes; klass->size_allocate = NULL; klass->mark_size_change = NULL; klass->mark_extremes_change = NULL; klass->set_width = NULL; klass->set_ascent = NULL; klass->set_descent = NULL; klass->draw = NULL; klass->button_press_event = NULL; klass->button_release_event = NULL; klass->motion_notify_event = NULL; klass->enter_notify_event = NULL; klass->leave_notify_event = NULL; klass->iterator = NULL; } /* * Standard Gtk+ function */ static void Dw_widget_destroy (GtkObject *object) { DwWidget *widget; widget = DW_WIDGET (object); /* The widget the pointer is in? */ if (widget->viewport != NULL && widget == GTK_DW_VIEWPORT(widget->viewport)->last_entered) /* todo: perhaps call the leave_notify function? */ GTK_DW_VIEWPORT(widget->viewport)->last_entered = NULL; if (widget->style) a_Dw_style_unref (widget->style); if (widget->parent) Dw_container_remove (DW_CONTAINER (widget->parent), widget); else Dw_gtk_viewport_remove_dw (GTK_DW_VIEWPORT (widget->viewport)); parent_class->destroy (object); } /* * Standard Dw function */ static void Dw_widget_real_size_request (DwWidget *widget, DwRequisition *requisition) { g_warning ("DwWidget::size_request not implemented for `%s'", gtk_type_name (GTK_OBJECT_TYPE (widget))); /* return random size to prevent crashes*/ requisition->width = 50; requisition->ascent = 50; requisition->descent = 50; } /* * Standard Dw function */ static void Dw_widget_real_get_extremes (DwWidget *widget, DwExtremes *extremes) { /* Simply return the requisition width */ DwRequisition requisition; p_Dw_widget_size_request (widget, &requisition); extremes->min_width = extremes->max_width = requisition.width; } /* * This function is a wrapper for DwWidget::size_request; it calls * this method only when needed. */ void p_Dw_widget_size_request (DwWidget *widget, DwRequisition *requisition) { DwWidgetClass *klass; if (DW_WIDGET_NEEDS_RESIZE (widget)) { /* todo: check requisition == &(widget->requisition) and do what? */ klass = DW_WIDGET_CLASS (GTK_OBJECT(widget)->klass); (* (klass->size_request)) (widget, requisition); widget->requisition = *requisition; DW_WIDGET_UNSET_FLAGS (widget, DW_NEEDS_RESIZE); DBG_OBJ_SET_NUM (widget, "requisition.width", widget->requisition.width); DBG_OBJ_SET_NUM (widget, "requisition.ascent", widget->requisition.ascent); DBG_OBJ_SET_NUM (widget, "requisition.descent", widget->requisition.descent); } else *requisition = widget->requisition; } /* * Wrapper for DwWidget::get_extremes. */ void p_Dw_widget_get_extremes (DwWidget *widget, DwExtremes *extremes) { DwWidgetClass *klass; if (DW_WIDGET_EXTREMES_CHANGED (widget)) { klass = DW_WIDGET_CLASS (GTK_OBJECT(widget)->klass); (* (klass->get_extremes)) (widget, extremes); widget->extremes = *extremes; DW_WIDGET_UNSET_FLAGS (widget, DW_EXTREMES_CHANGED); DBG_OBJ_SET_NUM (widget, "extremes.min_width", widget->extremes.min_width); DBG_OBJ_SET_NUM (widget, "extremes.max_width", widget->extremes.max_width); } else *extremes = widget->extremes; } /* * Wrapper for DwWidget::size_allocate, only called when needed. */ void p_Dw_widget_size_allocate (DwWidget *widget, DwAllocation *allocation) { DwWidgetClass *klass; if (DW_WIDGET_NEEDS_ALLOCATE (widget) || allocation->x != widget->allocation.x || allocation->y != widget->allocation.y || allocation->width != widget->allocation.width || allocation->ascent != widget->allocation.ascent || allocation->descent != widget->allocation.descent) { DEBUG_MSG (DEBUG_ALLOC, "a %stop-level %s with parent_ref = %d is newly allocated " "from %d, %d, %d x %d x %d ...\n", widget->parent ? "non-" : "", gtk_type_name (GTK_OBJECT_TYPE (widget)), widget->parent_ref, widget->allocation.x, widget->allocation.y, widget->allocation.width, widget->allocation.ascent, widget->allocation.descent); klass = DW_WIDGET_CLASS (GTK_OBJECT(widget)->klass); if (klass->size_allocate) (* (klass->size_allocate)) (widget, allocation); DEBUG_MSG (DEBUG_ALLOC, "... to %d, %d, %d x %d x %d\n", widget->allocation.x, widget->allocation.y, widget->allocation.width, widget->allocation.ascent, widget->allocation.descent); widget->allocation = *allocation; DW_WIDGET_UNSET_FLAGS (widget, DW_NEEDS_ALLOCATE); DBG_OBJ_SET_NUM (widget, "allocation.x", widget->allocation.x); DBG_OBJ_SET_NUM (widget, "allocation.y", widget->allocation.y); DBG_OBJ_SET_NUM (widget, "allocation.width", widget->allocation.width); DBG_OBJ_SET_NUM (widget, "allocation.ascent", widget->allocation.ascent); DBG_OBJ_SET_NUM (widget, "allocation.descent", widget->allocation.descent); } /*DW_WIDGET_UNSET_FLAGS (widget, DW_NEEDS_RESIZE);*/ } void p_Dw_widget_set_width (DwWidget *widget, gint32 width) { DwWidgetClass *klass; klass = DW_WIDGET_CLASS (GTK_OBJECT(widget)->klass); if (klass->set_width) (* (klass->set_width)) (widget, width); } void p_Dw_widget_set_ascent (DwWidget *widget, gint32 ascent) { DwWidgetClass *klass; klass = DW_WIDGET_CLASS (GTK_OBJECT(widget)->klass); if (klass->set_ascent) (* (klass->set_ascent)) (widget, ascent); } void p_Dw_widget_set_descent (DwWidget *widget, gint32 descent) { DwWidgetClass *klass; klass = DW_WIDGET_CLASS (GTK_OBJECT(widget)->klass); if (klass->set_descent) (* (klass->set_descent)) (widget, descent); } void p_Dw_widget_draw (DwWidget *widget, DwRectangle *area, GdkEventExpose *event) { /* NOTE: This function depends on that widgets are always drawn top-down. * The initial draw call is done for the top-level widget by the viewport, * all other draw calls are done for children. */ GtkDwViewport *viewport; GdkDrawable *orig_pixmap, *dest; gint x, y; DwWidgetClass *klass; orig_pixmap = widget->parent ? widget->parent->clip_pixmap : NULL; widget->clip_pixmap = orig_pixmap; klass = DW_WIDGET_CLASS (GTK_OBJECT(widget)->klass); if (klass->draw) (* (klass->draw)) (widget, area, event); if (widget->clip_pixmap && (widget->parent == NULL || widget->clip_pixmap != widget->parent->clip_pixmap)) { /* Copy clipping pixmap into backing pixmap, when this widget has called * p_Dw_widget_will_clip(). */ viewport = GTK_DW_VIEWPORT (widget->viewport); dest = orig_pixmap ? orig_pixmap : viewport->back_pixmap; x = area->x + widget->allocation.x - gtk_layout_get_hadjustment(GTK_LAYOUT(viewport))->value; y = area->y + widget->allocation.y - gtk_layout_get_vadjustment(GTK_LAYOUT(viewport))->value; gdk_draw_pixmap (dest, widget->viewport->style->black_gc, widget->clip_pixmap, x, y, x, y, area->width, area->height); gdk_pixmap_unref (widget->clip_pixmap); } } /* * Handles a mouse event. * * This function is called by Dw_gtk_viewport_mouse_event, the type of * the event is determined by event->type. x and y are world coordinates. * widget may be NULL (if the pointer is outside the top-level widget). * * When event is NULL, GDK_MOTION_NOTIFY is used as the type. This will * soon be the case when GDK_MOTION_NOTIFY events are simulated as a * result of viewport changes (bug #94) */ gint Dw_widget_mouse_event (DwWidget *widget, GtkWidget *viewwidget, gint32 x, gint32 y, GdkEvent *event) { gint signal_no; gboolean return_val; DwWidgetClass *klass; GtkDwViewport *viewport = GTK_DW_VIEWPORT (viewwidget); GdkEventType event_type; DwWidget *ancestor, *w1, *w2, **track; gint track_len, i; DEBUG_MSG(DEBUG_EVENT, "------------------------- EVENT -------------------------\n"); /* simulate crossing events */ /* todo: resizing/moving widgets */ if (widget != viewport->last_entered) { DEBUG_MSG (DEBUG_EVENT, "----------> crossing event\n"); DEBUG_MSG (DEBUG_EVENT, " last = %p, now = %p\n", viewport->last_entered, widget); /* Determine the next common ancestor of the widgets. */ if (viewport->last_entered == NULL || widget == NULL) ancestor = NULL; else { /* There is probably a faster algorithm. ;-) */ ancestor = NULL; for (w1 = viewport->last_entered; ancestor == NULL && w1 != NULL; w1 = w1->parent) for (w2 = widget; ancestor == NULL && w2 != NULL; w2 = w2->parent) if (w1 == w2) ancestor = w1; } /* Construct the track: * viewport->last_entered => anchestor => (current) widget */ DEBUG_MSG (DEBUG_EVENT, "common ancestor: %s %p\n", (ancestor ? gtk_type_name (GTK_OBJECT_TYPE (ancestor)) : "(none)"), ancestor); track_len = 0; if (viewport->last_entered) for (w1 = viewport->last_entered; w1 != ancestor; w1 = w1->parent) track_len++; if (ancestor) track_len++; /* for the ancestor */ if (widget) for (w1 = widget; w1 != ancestor; w1 = w1->parent) track_len++; track = g_new (DwWidget*, track_len); i = 0; if (viewport->last_entered) for (w1 = viewport->last_entered; w1 != ancestor; w1 = w1->parent) track[i++] = w1; if (ancestor) track[i++] = ancestor; if (widget) { i = track_len - 1; for (w1 = widget; w1 != ancestor; w1 = w1->parent) track[i--] = w1; } /* Send events to all events on the track */ /* todo: emit signals */ for (i = 0; i < track_len; i++) { klass = DW_WIDGET_CLASS (GTK_OBJECT(track[i])->klass); if (i != 0) { if (klass->enter_notify_event) klass->enter_notify_event (track[i], track[i - 1], (GdkEventMotion*) event); DEBUG_MSG (DEBUG_EVENT, "entering %s %p\n", gtk_type_name (GTK_OBJECT_TYPE (track[i])), track[i]); } if (i != track_len - 1) { if (klass->leave_notify_event) klass->leave_notify_event (track[i], track[i + 1], (GdkEventMotion*) event); DEBUG_MSG (DEBUG_EVENT, "leaving %s %p\n", gtk_type_name (GTK_OBJECT_TYPE (track[i])), track[i]); } } DEBUG_MSG (DEBUG_EVENT, "<----------\n"); g_free (track); viewport->last_entered = widget; if (widget) Dw_widget_update_cursor(widget); else gdk_window_set_cursor (GTK_LAYOUT(viewport)->bin_window, NULL); } /* other events */ event_type = event ? event->type : GDK_MOTION_NOTIFY; while (widget) { switch (event_type) { case GDK_BUTTON_PRESS: case GDK_2BUTTON_PRESS: case GDK_3BUTTON_PRESS: if (widget->button_sensitive) signal_no = widget_signals[BUTTON_PRESS_EVENT]; else signal_no = -1; break; case GDK_BUTTON_RELEASE: if (widget->button_sensitive) signal_no = widget_signals[BUTTON_RELEASE_EVENT]; else signal_no = -1; break; case GDK_MOTION_NOTIFY: signal_no = widget_signals[MOTION_NOTIFY_EVENT]; break; default: signal_no = -1; break; } DEBUG_MSG (DEBUG_EVENT, "Sending %s event to %p, a %s.\n", (event_type == GDK_MOTION_NOTIFY ? "motion notify" : event_type == GDK_BUTTON_RELEASE ? "button release" : "button press"), widget, gtk_type_name (GTK_OBJECT_TYPE (widget))); if (signal_no != -1) { return_val = FALSE; gtk_signal_emit (GTK_OBJECT (widget), signal_no, x - widget->allocation.x, y - widget->allocation.y, event, &return_val); if (return_val) { DEBUG_MSG (DEBUG_EVENT, "-> Processed.\n"); return TRUE; } } widget = widget->parent; } DEBUG_MSG (DEBUG_EVENT, "-> Not processed at all.\n"); return FALSE; } /* * Change the style of a widget. The old style is automatically * unreferred, the new is referred. If this call causes the widget to * change its size, Dw_widget_queue_resize is called. */ void a_Dw_widget_set_style (DwWidget *widget, DwStyle *style) { gboolean size_changed; if (widget->style) { a_Dw_style_unref (widget->style); size_changed = a_Dw_style_size_diffs (widget->style, style); } else size_changed = TRUE; a_Dw_style_ref (style); widget->style = style; if (widget->parent == NULL && widget->viewport != NULL) Dw_gtk_viewport_update_background (GTK_DW_VIEWPORT (widget->viewport)); if (size_changed) p_Dw_widget_queue_resize (widget, 0, TRUE); else p_Dw_widget_queue_draw (widget); } /* * Set the cursor of the viewport. * Called from several other functions. */ void Dw_widget_update_cursor (DwWidget *widget) { GtkDwViewport *viewport = GTK_DW_VIEWPORT (widget->viewport); DwWidget *cursor_widget; if (GTK_WIDGET_REALIZED (viewport)) { /* Search cursor to use, going up from last_entered (not from widget!). */ cursor_widget = viewport->last_entered; while (cursor_widget && cursor_widget->cursor == NULL) cursor_widget = cursor_widget->parent; if (cursor_widget) gdk_window_set_cursor (GTK_LAYOUT(viewport)->bin_window, cursor_widget->cursor); else gdk_window_set_cursor (GTK_LAYOUT(viewport)->bin_window, NULL); } } /* * Set the cursor for a DwWidget. cursor has to be stored elsewhere, it * is not copied (and not destroyed). If cursor is NULL, the cursor of * the parent widget is used. */ void a_Dw_widget_set_cursor (DwWidget *widget, GdkCursor *cursor) { widget->cursor = cursor; Dw_widget_update_cursor (widget); } /* * If this function is called with button_sensitive == FALSE, the widget will * never receive button press/release events, instead they are sent to the * parent widgets. This attribute is inherited from the parent, if this * function is never called. * * TODO: A bit hackish, this is only needed for disabling selection * within