/* * File: dw_gtk_scrolled_frame.c * * Copyright (C) 2001 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. */ /* * GtkDwScrolledFrame is a container for the GtkDwViewport, it adds * a focus around the child and is responsible for focus, keys and the * button 2 dragging. */ #include #include #include "dw_gtk_scrolled_frame.h" static GtkBinClass *parent_class = NULL; /* object/class initialisation */ static void Dw_gtk_scrolled_frame_init (GtkDwScrolledFrame *frame); static void Dw_gtk_scrolled_frame_class_init (GtkDwScrolledFrameClass *klass); /* GtkObject methods */ static void Dw_gtk_scrolled_frame_destroy (GtkObject *object); /* GtkWidget methods */ static void Dw_gtk_scrolled_frame_realize (GtkWidget *widget); static void Dw_gtk_scrolled_frame_unrealize (GtkWidget *widget); static void Dw_gtk_scrolled_frame_size_request (GtkWidget *widget, GtkRequisition *requisition); static void Dw_gtk_scrolled_frame_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static void Dw_gtk_scrolled_frame_draw (GtkWidget *widget, GdkRectangle *area); static void Dw_gtk_scrolled_frame_draw_focus (GtkWidget *widget); static gint Dw_gtk_scrolled_frame_expose (GtkWidget *widget, GdkEventExpose *event); static gint Dw_gtk_scrolled_frame_key_press (GtkWidget *widget, GdkEventKey *event); static gint Dw_gtk_scrolled_frame_focus_in (GtkWidget *widget, GdkEventFocus *event); static gint Dw_gtk_scrolled_frame_focus_out (GtkWidget *widget, GdkEventFocus *event); static gint Dw_gtk_scrolled_frame_button_press (GtkWidget *widget, GdkEventButton *event); static gint Dw_gtk_scrolled_frame_button_release(GtkWidget *widget, GdkEventButton *event); static gint Dw_gtk_scrolled_frame_motion_notify (GtkWidget *widget, GdkEventMotion *event); /* GtkContainer methods */ static void Dw_gtk_scrolled_frame_add (GtkContainer *container, GtkWidget *widget); static gint Dw_gtk_scrolled_frame_focus (GtkContainer *container, GtkDirectionType direction); /* GtkDwScrolledFrame methods */ static void Dw_gtk_scrolled_frame_set_scroll_adjustments (GtkDwScrolledFrame *frame, GtkAdjustment *hadjustment, GtkAdjustment *vadjustment); static void Dw_gtk_scrolled_frame_move_x_to (GtkDwScrolledFrame *frame, gfloat x); static void Dw_gtk_scrolled_frame_move_y_to (GtkDwScrolledFrame *frame, gfloat y); static void Dw_gtk_scrolled_frame_move_by (GtkDwScrolledFrame *frame, gfloat dx, gfloat dy); static gfloat Dw_gtk_scrolled_frame_correct_adj (GtkAdjustment *adj, gfloat pos); enum { SET_SCROLL_ADJUSTMENTS, USER_HCHANGED, USER_VCHANGED, LAST_SIGNAL }; static guint frame_signals[LAST_SIGNAL] = { 0 }; /* * Standard Gtk+ function */ GtkType a_Dw_gtk_scrolled_frame_get_type (void) { static GtkType type = 0; if (!type) { GtkTypeInfo info = { "GtkDwScrolledFrame", sizeof (GtkDwScrolledFrame), sizeof (GtkDwScrolledFrameClass), (GtkClassInitFunc) Dw_gtk_scrolled_frame_class_init, (GtkObjectInitFunc) Dw_gtk_scrolled_frame_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL, (GtkClassInitFunc) NULL }; type = gtk_type_unique (GTK_TYPE_BIN, &info); } return type; } /* * Standard Gtk+ function */ GtkWidget* a_Dw_gtk_scrolled_frame_new (GtkAdjustment *hadjustment, GtkAdjustment *vadjustment) { GtkWidget *widget; GtkDwScrolledFrame *frame; widget = gtk_widget_new (GTK_TYPE_DW_SCROLLED_FRAME, NULL); frame = GTK_DW_SCROLLED_FRAME (widget); frame->hadjustment = hadjustment; frame->vadjustment = vadjustment; return widget; } /********************************* * * * object/class initialisation * * * *********************************/ /* * Standard Gtk+ function */ static void Dw_gtk_scrolled_frame_init (GtkDwScrolledFrame *frame) { GTK_WIDGET_SET_FLAGS (frame, GTK_CAN_FOCUS); GTK_WIDGET_UNSET_FLAGS (frame, GTK_NO_WINDOW); frame->hadjustment = NULL; frame->vadjustment = NULL; frame->button2_pressed = FALSE; frame->move_idle_id = 0; } /* * Standard Gtk+ function */ static void Dw_gtk_scrolled_frame_class_init (GtkDwScrolledFrameClass *klass) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; GtkContainerClass *container_class; parent_class = gtk_type_class (gtk_bin_get_type ()); object_class = (GtkObjectClass*) klass; widget_class = (GtkWidgetClass*) klass; container_class = (GtkContainerClass*) klass; frame_signals[SET_SCROLL_ADJUSTMENTS] = gtk_signal_new ("set_scroll_adjustments", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (GtkDwScrolledFrameClass, set_scroll_adjustments), gtk_marshal_NONE__POINTER_POINTER, GTK_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT); widget_class->set_scroll_adjustments_signal = frame_signals[SET_SCROLL_ADJUSTMENTS]; frame_signals[USER_HCHANGED] = gtk_signal_new ("user_hchanged", GTK_RUN_LAST | GTK_RUN_ACTION, object_class->type, GTK_SIGNAL_OFFSET (GtkDwScrolledFrameClass, user_hchanged), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); frame_signals[USER_VCHANGED] = gtk_signal_new ("user_vchanged", GTK_RUN_LAST | GTK_RUN_ACTION, object_class->type, GTK_SIGNAL_OFFSET (GtkDwScrolledFrameClass, user_vchanged), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); gtk_object_class_add_signals (object_class, frame_signals, LAST_SIGNAL); object_class->destroy = Dw_gtk_scrolled_frame_destroy; widget_class->realize = Dw_gtk_scrolled_frame_realize; widget_class->unrealize = Dw_gtk_scrolled_frame_unrealize; widget_class->size_request = Dw_gtk_scrolled_frame_size_request; widget_class->size_allocate = Dw_gtk_scrolled_frame_size_allocate; widget_class->draw_focus = Dw_gtk_scrolled_frame_draw_focus; widget_class->draw = Dw_gtk_scrolled_frame_draw; widget_class->expose_event = Dw_gtk_scrolled_frame_expose; widget_class->key_press_event = Dw_gtk_scrolled_frame_key_press; widget_class->focus_in_event = Dw_gtk_scrolled_frame_focus_in; widget_class->focus_out_event = Dw_gtk_scrolled_frame_focus_out; widget_class->button_press_event = Dw_gtk_scrolled_frame_button_press; widget_class->button_release_event = Dw_gtk_scrolled_frame_button_release; widget_class->motion_notify_event = Dw_gtk_scrolled_frame_motion_notify; container_class->add = Dw_gtk_scrolled_frame_add; container_class->focus = Dw_gtk_scrolled_frame_focus; klass->set_scroll_adjustments = Dw_gtk_scrolled_frame_set_scroll_adjustments; klass->user_hchanged = NULL; klass->user_vchanged = NULL; } /*********************** * * * GtkObject methods * * * ***********************/ /* * Standard Gtk+ function */ static void Dw_gtk_scrolled_frame_destroy (GtkObject *object) { GtkDwScrolledFrame *frame; frame = GTK_DW_SCROLLED_FRAME (object); if (frame->move_idle_id != 0) gtk_idle_remove (frame->move_idle_id); GTK_OBJECT_CLASS(parent_class)->destroy (object); } /*********************** * * * GtkWidget methods * * * ***********************/ /* * Standard Gtk+ function */ static void Dw_gtk_scrolled_frame_realize (GtkWidget *widget) { GdkWindowAttr attributes; GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); attributes.window_type = GDK_WINDOW_CHILD; attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes.event_mask = (gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK); widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP); gdk_window_set_user_data (widget->window, widget); widget->style = gtk_style_attach (widget->style, widget->window); gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); GTK_DW_SCROLLED_FRAME(widget)->drag_cursor = gdk_cursor_new (GDK_FLEUR); } /* * Standard Gtk+ function */ static void Dw_gtk_scrolled_frame_unrealize (GtkWidget *widget) { gdk_cursor_destroy (GTK_DW_SCROLLED_FRAME(widget)->drag_cursor); GTK_WIDGET_CLASS(parent_class)->unrealize (widget); } /* * Standard Gtk+ function */ static void Dw_gtk_scrolled_frame_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkBin *bin; gint border_width; GtkAllocation child_allocation; widget->allocation = *allocation; bin = GTK_BIN (widget); if (GTK_WIDGET_REALIZED (widget)) gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height); if (bin->child) { border_width = GTK_CONTAINER(widget)->border_width; child_allocation.width = MAX (allocation->width - 2 * (widget->style->klass->xthickness + border_width), 1); child_allocation.height = MAX (allocation->height - 2 * (widget->style->klass->ythickness + border_width), 1); child_allocation.x = (allocation->width - child_allocation.width) / 2; child_allocation.y = (allocation->height - child_allocation.height) / 2; gtk_widget_size_allocate (bin->child, &child_allocation); } } /* * Standard Gtk+ function */ static void Dw_gtk_scrolled_frame_size_request (GtkWidget *widget, GtkRequisition *requisition) { GtkBin *bin; gint border_width; GtkRequisition child_requisition; bin = GTK_BIN (widget); if (bin->child) { border_width = GTK_CONTAINER(widget)->border_width; gtk_widget_size_request (bin->child, &child_requisition); requisition->width = child_requisition.width + 2 * (widget->style->klass->xthickness + border_width); requisition->height = child_requisition.height + 2 * (widget->style->klass->ythickness + border_width); } else { requisition->width = 100; requisition->height = 100; } } /* * Draw the frame, eventually with a focus border */ static void Dw_gtk_scrolled_frame_paint_shadow (GtkWidget *widget, GdkRectangle *area) { gint border_width; if (GTK_WIDGET_DRAWABLE (widget)) { border_width = GTK_CONTAINER(widget)->border_width; if (GTK_WIDGET_HAS_FOCUS (widget)) { gtk_draw_shadow (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, border_width + 1, border_width + 1, widget->allocation.width - 2 * (border_width + 1), widget->allocation.height - 2 * (border_width + 1)); /* "text" is probably next to what we need */ gtk_paint_focus (widget->style, widget->window, area, widget, "text", border_width, border_width, widget->allocation.width - 2 * border_width - 1, widget->allocation.height - 2 * border_width - 1); } else { gtk_draw_shadow (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, border_width, border_width, widget->allocation.width - 2 * border_width, widget->allocation.height - 2 * border_width); } } } /* * Standard Gtk+ function */ static void Dw_gtk_scrolled_frame_draw_focus (GtkWidget *widget) { Dw_gtk_scrolled_frame_paint_shadow (widget, NULL); } /* * Standard Gtk+ function */ static void Dw_gtk_scrolled_frame_draw (GtkWidget *widget, GdkRectangle *area) { GTK_WIDGET_CLASS(parent_class)->draw (widget, area); Dw_gtk_scrolled_frame_paint_shadow (widget, area); } /* * Standard Gtk+ function */ static gint Dw_gtk_scrolled_frame_expose (GtkWidget *widget, GdkEventExpose *event) { gint ret_val; ret_val = GTK_WIDGET_CLASS(parent_class)->expose_event (widget, event); Dw_gtk_scrolled_frame_paint_shadow (widget, &(event->area)); return ret_val; } /* * Standard Gtk+ function */ static gint Dw_gtk_scrolled_frame_key_press (GtkWidget *widget, GdkEventKey *event) { GtkContainer *container; GtkDwScrolledFrame *frame; container = GTK_CONTAINER (widget); frame = GTK_DW_SCROLLED_FRAME (widget); switch (event->keyval) { case GDK_Up: case GDK_KP_Up: Dw_gtk_scrolled_frame_move_by (frame, 0, - frame->vadjustment->step_increment); return TRUE; case GDK_Down: case GDK_KP_Down: Dw_gtk_scrolled_frame_move_by (frame, 0, + frame->vadjustment->step_increment); return TRUE; case GDK_Left: case GDK_KP_Left: Dw_gtk_scrolled_frame_move_by (frame, - frame->hadjustment->step_increment, 0); return TRUE; case GDK_Right: case GDK_KP_Right: Dw_gtk_scrolled_frame_move_by (frame, + frame->hadjustment->step_increment, 0); return TRUE; case GDK_Tab: if (event->state & GDK_SHIFT_MASK) return gtk_container_focus (container, GTK_DIR_TAB_BACKWARD); else return gtk_container_focus (container, GTK_DIR_TAB_FORWARD); case GDK_b: case GDK_B: if (event->state & GDK_CONTROL_MASK) return FALSE; /* continues */ case GDK_Page_Up: case GDK_KP_Page_Up: if (event->state & GDK_CONTROL_MASK) Dw_gtk_scrolled_frame_move_by (frame, - frame->hadjustment->page_increment, 0); else Dw_gtk_scrolled_frame_move_by (frame, 0, - frame->vadjustment->page_increment); return TRUE; case GDK_Page_Down: case GDK_KP_Page_Down: case GDK_space: if (event->state & GDK_CONTROL_MASK) Dw_gtk_scrolled_frame_move_by (frame, + frame->hadjustment->page_increment, 0); else Dw_gtk_scrolled_frame_move_by (frame, 0, + frame->vadjustment->page_increment); return TRUE; case GDK_Home: case GDK_KP_Home: if (event->state & GDK_CONTROL_MASK) Dw_gtk_scrolled_frame_move_x_to (frame, 0); else Dw_gtk_scrolled_frame_move_y_to (frame, 0); return TRUE; case GDK_End: case GDK_KP_End: if (event->state & GDK_CONTROL_MASK) Dw_gtk_scrolled_frame_move_x_to (frame, G_MAXFLOAT); else Dw_gtk_scrolled_frame_move_y_to (frame, G_MAXFLOAT); return TRUE; default: return FALSE; } } /* * Standard Gtk+ function */ static gint Dw_gtk_scrolled_frame_focus_in (GtkWidget *widget, GdkEventFocus *event) { GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS); gtk_widget_draw_focus (widget); return FALSE; } /* * Standard Gtk+ function */ static gint Dw_gtk_scrolled_frame_focus_out (GtkWidget *widget, GdkEventFocus *event) { GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS); gtk_widget_draw_focus (widget); return FALSE; } /* * Standard Gtk+ function */ static gint Dw_gtk_scrolled_frame_button_press (GtkWidget *widget, GdkEventButton *event) { GtkDwScrolledFrame *frame; if (!GTK_WIDGET_HAS_FOCUS (widget)) gtk_widget_grab_focus (widget); if (event->button == 2) { frame = GTK_DW_SCROLLED_FRAME (widget); frame->button2_pressed = TRUE; frame->start_lmx = event->x; frame->start_lmy = event->y; gdk_pointer_grab (widget->window, FALSE, GDK_BUTTON_RELEASE_MASK | GDK_BUTTON2_MOTION_MASK, NULL, frame->drag_cursor, event->time); } return TRUE; } /* * Standard Gtk+ function */ static gint Dw_gtk_scrolled_frame_button_release (GtkWidget *widget, GdkEventButton *event) { GTK_DW_SCROLLED_FRAME(widget)->button2_pressed = FALSE; gdk_pointer_ungrab (event->time); return TRUE; } /* * Standard Gtk+ function */ static gint Dw_gtk_scrolled_frame_motion_notify (GtkWidget *widget, GdkEventMotion *event) { GtkDwScrolledFrame *frame = GTK_DW_SCROLLED_FRAME (widget); /* frame->button2_pressed makes sure that the button was pressed in the GtkDwScrolledFrame */ if ((event->state & GDK_BUTTON2_MASK) && frame->button2_pressed) { Dw_gtk_scrolled_frame_move_by (frame, frame->start_lmx - event->x, frame->start_lmy - event->y); frame->start_lmx = event->x; frame->start_lmy = event->y; } return TRUE; } /************************** * * * GtkContainer methods * * * **************************/ /* * Standard Gtk+ function */ static void Dw_gtk_scrolled_frame_add (GtkContainer *container, GtkWidget *widget) { GtkDwScrolledFrame *frame; GTK_CONTAINER_CLASS(parent_class)->add (container, widget); frame = GTK_DW_SCROLLED_FRAME (container); if ( !gtk_widget_set_scroll_adjustments ( GTK_WIDGET (container), frame->hadjustment, frame->vadjustment) ) g_warning ("Dw_gtk_scrolled_frame_set_scroll_adjustments(): " "child is non scrollable"); } /* * Standard Gtk+ function */ static gint Dw_gtk_scrolled_frame_focus (GtkContainer *container, GtkDirectionType direction) { /* todo: * This crashed if the child is not a container (what will never happen * in dillo, where GtkDwScrolledFrame always contains a GtkDwViewport). */ GtkDwScrolledFrame *frame; frame = GTK_DW_SCROLLED_FRAME (container); if (!GTK_WIDGET_HAS_FOCUS (container) && container->focus_child == NULL){ /* no focus on the GtkDwScrolledFrame or any child widget => grab focus! */ gtk_widget_grab_focus (GTK_WIDGET (container)); return TRUE; } switch (direction) { case GTK_DIR_TAB_FORWARD: /* deliver to child */ if (!gtk_container_focus (GTK_CONTAINER (GTK_BIN(container)->child), direction)) { /* todo: it would be nice to keep focus on this last child widget, * instead of the container... Anyway this may change with GTK2 */ gtk_widget_grab_focus (GTK_WIDGET (container)); } return TRUE; case GTK_DIR_TAB_BACKWARD: if (GTK_WIDGET_HAS_FOCUS (container)) { /* will focus the widget "before" */ return FALSE; } else { if (!gtk_container_focus (GTK_CONTAINER (GTK_BIN(container)->child), GTK_DIR_TAB_BACKWARD)) /* first child of the child unfocussed */ gtk_widget_grab_focus (GTK_WIDGET (container)); return TRUE; } break; /* case GTK_DIR_LEFT: case GTK_DIR_RIGHT: case GTK_DIR_UP: case GTK_DIR_DOWN: */ default: /* focus the GtkDwScrolledFrame */ gtk_widget_grab_focus (GTK_WIDGET (container)); return TRUE; } /* make compiler happy */ return FALSE; } /******************************** * * * GtkDwScrolledFrame methods * * * ********************************/ /* * Standard Gtk+ function */ static void Dw_gtk_scrolled_frame_set_scroll_adjustments (GtkDwScrolledFrame *frame, GtkAdjustment *hadjustment, GtkAdjustment *vadjustment) { GtkBin *bin; frame->hadjustment = hadjustment; frame->vadjustment = vadjustment; bin = GTK_BIN (frame); if (bin->child) { if (!gtk_widget_set_scroll_adjustments (bin->child, hadjustment, vadjustment)) g_warning ("Dw_gtk_scrolled_frame_set_scroll_adjustments(): " "child is non scrollable"); } } /* * Functions for moving the viewport. This is queued and done in an * idle function, to prevent locks by too frequent keypresses, or too * fast mouse movements. (How to reproduce: start dillo on a slow, * overloaded system with nice priority.) */ /* * The idle function. */ static gint Dw_gtk_scrolled_frame_move_idle (gpointer data) { GtkDwScrolledFrame *frame; frame = GTK_DW_SCROLLED_FRAME (data); g_return_val_if_fail (frame->hadjustment != NULL, FALSE); g_return_val_if_fail (frame->vadjustment != NULL, FALSE); if (frame->moveto_x != frame->hadjustment->value) { gtk_adjustment_set_value (frame->hadjustment, frame->moveto_x); gtk_signal_emit (GTK_OBJECT (frame), frame_signals[USER_HCHANGED]); } if (frame->moveto_y != frame->vadjustment->value) { gtk_adjustment_set_value (frame->vadjustment, frame->moveto_y); gtk_signal_emit (GTK_OBJECT (frame), frame_signals[USER_VCHANGED]); } frame->move_idle_id = 0; return FALSE; } /* * Change horizontall position to x. */ static void Dw_gtk_scrolled_frame_move_x_to (GtkDwScrolledFrame *frame, gfloat x) { frame->moveto_x = Dw_gtk_scrolled_frame_correct_adj (frame->hadjustment, x); if (frame->move_idle_id == 0) frame->move_idle_id = gtk_idle_add (Dw_gtk_scrolled_frame_move_idle, (gpointer) frame); } /* * Change vertical position to y. */ static void Dw_gtk_scrolled_frame_move_y_to (GtkDwScrolledFrame *frame, gfloat y) { frame->moveto_y = Dw_gtk_scrolled_frame_correct_adj (frame->vadjustment, y); if (frame->move_idle_id == 0) frame->move_idle_id = gtk_idle_add (Dw_gtk_scrolled_frame_move_idle, (gpointer) frame); } /* * Move viewport by dx and dy. */ static void Dw_gtk_scrolled_frame_move_by (GtkDwScrolledFrame *frame, gfloat dx, gfloat dy) { if (frame->move_idle_id == 0) { /* * Initialization: if the idle function is not active, set * moveto_x and moveto_y to adjustments values, which may * already have been changed by the scrollbars. This code does * not work when a user uses scrollbars and keys at the same * time. ;-) */ frame->moveto_x = frame->hadjustment->value; frame->moveto_y = frame->vadjustment->value; } frame->moveto_x = Dw_gtk_scrolled_frame_correct_adj (frame->hadjustment, frame->moveto_x + dx); frame->moveto_y = Dw_gtk_scrolled_frame_correct_adj (frame->vadjustment, frame->moveto_y + dy); if (frame->move_idle_id == 0) frame->move_idle_id = gtk_idle_add (Dw_gtk_scrolled_frame_move_idle, (gpointer) frame); } /* * Helper function: Corrects pos to fit into the boundaries of adj. */ static gfloat Dw_gtk_scrolled_frame_correct_adj (GtkAdjustment *adj, gfloat pos) { if (pos < adj->lower) return adj->lower; else if (pos > adj->upper - adj->page_size) return adj->upper - adj->page_size; else return pos; }