/* -*- Mode: C; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 3 -*- */ /* * GImageView * Copyright (C) 2001 Takuro Ashie * * 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. * * This program 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Id: gimv_scrolled.c,v 1.3 2004/09/21 08:44:32 makeinu Exp $ */ /* * These codes are mostly taken from Another X image viewer. * * Another X image viewer Author: * David Ramboz */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "gtk2-compat.h" #include "gimv_scrolled.h" /* for auto-scroll and auto-expand at drag */ #define AUTO_SCROLL_TIMEOUT 100 #define AUTO_SCROLL_EDGE_WIDTH 20 #define AUTO_SCROLL_SCALE_RATE 32 /* times / pixel */ typedef gboolean (*desirable_fn) (GimvScrolled *scrolled, gint x, gint y); typedef gboolean (*scroll_fn) (gpointer data); #define bw(widget) GTK_CONTAINER(widget)->border_width #ifdef USE_GTK2 # define WIDGET_DRAW(widget, area) gdk_window_invalidate_rect (widget->window, area, TRUE) #else # define WIDGET_DRAW(widget, area) (gtk_signal_emit_by_name (GTK_OBJECT(widget), "draw", area)) #endif #define adjustment_check_value(value, adj) \ { \ if (value < adj->lower) \ value = adj->lower; \ if (value > adj->upper - adj->page_size) \ value = adj->upper - adj->page_size; \ } enum { ADJUST_ADJUSTMENTS, LAST_SIGNAL }; static void gimv_scrolled_class_init (GimvScrolledClass *klass); static void gimv_scrolled_init (GimvScrolled *scrolled); static void gimv_scrolled_set_scroll_adjustments (GtkWidget *widget, GtkAdjustment *hadjustment, GtkAdjustment *vadjustment); static gint gimv_scrolled_button_press (GtkWidget *widget, GdkEventButton *event); static gint gimv_scrolled_button_release (GtkWidget *widget, GdkEventButton *event); static gint gimv_scrolled_motion_notify (GtkWidget *widget, GdkEventMotion *event); static gint gimv_scrolled_drag_motion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time); static void gimv_scrolled_drag_leave (GtkWidget *dirtree, GdkDragContext *context, guint time); static void hadjustment_value_changed (GtkAdjustment *hadjustment, gpointer data); static void vadjustment_value_changed (GtkAdjustment *vadjustment, gpointer data); static gboolean horizontal_timeout (gpointer data); static gboolean vertical_timeout (gpointer data); #ifndef USE_GTK2 static void check_exposures (GtkWidget *widget); #endif /* USE_GTK2 */ static GtkWidgetClass *parent_class = NULL; static guint gimv_scrolled_signals [LAST_SIGNAL] = {0}; GtkType gimv_scrolled_get_type (void) { static GtkType type = 0; if (!type) { static const GtkTypeInfo type_info = { "GimvScrolled", sizeof (GimvScrolled), sizeof (GimvScrolledClass), (GtkClassInitFunc) gimv_scrolled_class_init, (GtkObjectInitFunc) gimv_scrolled_init, /* reserved_1 */ NULL, /* reserved_2 */ NULL, (GtkClassInitFunc) NULL, }; type = gtk_type_unique (GTK_TYPE_CONTAINER, &type_info); } return type; } static void gimv_scrolled_class_init (GimvScrolledClass *klass) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; GtkContainerClass *container_class; parent_class = gtk_type_class (GTK_TYPE_CONTAINER); object_class = (GtkObjectClass*) klass; widget_class = (GtkWidgetClass*) klass; container_class = (GtkContainerClass*) klass; widget_class->set_scroll_adjustments_signal = gtk_signal_new ("set_scroll_adjustments", GTK_RUN_LAST, GTK_CLASS_TYPE(object_class), GTK_SIGNAL_OFFSET(GimvScrolledClass, set_scroll_adjustments), gtk_marshal_NONE__POINTER_POINTER, GTK_TYPE_NONE, 2, GTK_TYPE_POINTER, GTK_TYPE_POINTER); gimv_scrolled_signals[ADJUST_ADJUSTMENTS] = gtk_signal_new ("adjust_adjustments", GTK_RUN_FIRST, GTK_CLASS_TYPE(object_class), GTK_SIGNAL_OFFSET(GimvScrolledClass, adjust_adjustments), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); widget_class->button_press_event = gimv_scrolled_button_press; widget_class->button_release_event = gimv_scrolled_button_release; widget_class->motion_notify_event = gimv_scrolled_motion_notify; widget_class->drag_motion = gimv_scrolled_drag_motion; widget_class->drag_leave = gimv_scrolled_drag_leave; klass->set_scroll_adjustments = gimv_scrolled_set_scroll_adjustments; klass->adjust_adjustments = NULL; } static void gimv_scrolled_init (GimvScrolled *scrolled) { scrolled->x_offset = 0; scrolled->y_offset = 0; scrolled->h_adjustment = NULL; scrolled->v_adjustment = NULL; scrolled->freeze_count = 0; scrolled->copy_gc = NULL; /* for auto scroll */ scrolled->autoscroll_flags = 0; scrolled->scroll_edge_x = AUTO_SCROLL_EDGE_WIDTH; scrolled->scroll_edge_y = AUTO_SCROLL_EDGE_WIDTH; scrolled->x_step = -1; scrolled->y_step = -1; scrolled->x_interval = AUTO_SCROLL_TIMEOUT; scrolled->y_interval = AUTO_SCROLL_TIMEOUT; scrolled->pressed = FALSE; scrolled->drag_start_vx = -1; scrolled->drag_start_vy = -1; scrolled->drag_motion_x = -1; scrolled->drag_motion_y = -1; scrolled->step_scale = 0.0; scrolled->hscroll_timer_id = -1; scrolled->vscroll_timer_id = -1; } void gimv_scrolled_realize (GimvScrolled *scrolled) { g_return_if_fail (scrolled && GTK_WIDGET(scrolled)->window); scrolled->copy_gc = gdk_gc_new (GTK_WIDGET(scrolled)->window); gdk_gc_set_exposures (scrolled->copy_gc, TRUE); } void gimv_scrolled_unrealize (GimvScrolled *scrolled) { g_return_if_fail (scrolled); gdk_gc_destroy (scrolled->copy_gc); } static void hadjustment_value_changed (GtkAdjustment *hadjustment, gpointer data) { GimvScrolled *scrolled; GtkWidget *widget; GdkRectangle area; int value, diff; widget = GTK_WIDGET(data); scrolled = GIMV_SCROLLED(data); if (!GTK_WIDGET_DRAWABLE(widget) || scrolled->x_offset == hadjustment->value) return; value = hadjustment->value; if (value < 0) value = 0; if (scrolled->freeze_count) { scrolled->x_offset = value; return; } if (value > scrolled->x_offset) { diff = value - scrolled->x_offset; if (diff >= widget->allocation.width) { scrolled->x_offset = value; WIDGET_DRAW(widget, NULL); return; } if (diff > 0 && !scrolled->freeze_count) gdk_window_copy_area (widget->window, scrolled->copy_gc, 0, 0, widget->window, diff, 0, widget->allocation.width - 2 * bw (widget) - diff, widget->allocation.height - 2 * bw (widget)); area.x = widget->allocation.width - 2 * bw (widget) - diff; area.y = 0; area.width = diff; area.height = widget->allocation.height - 2 * bw (widget); } else { diff = scrolled->x_offset - value; if (diff >= widget->allocation.width) { scrolled->x_offset = value; WIDGET_DRAW(widget, NULL); return; } if (diff > 0) gdk_window_copy_area (widget->window, scrolled->copy_gc, diff, 0, widget->window, 0, 0, widget->allocation.width - 2 * bw (widget) - diff, widget->allocation.height - 2 * bw (widget)); area.x = 0; area.y = 0; area.width = diff; area.height = widget->allocation.height - 2 * bw (widget); } if (diff > 0) { scrolled->x_offset = value; #ifndef USE_GTK2 check_exposures (widget); #endif /* USE_GTK2 */ /* WIDGET_DRAW(widget, &area); */ WIDGET_DRAW(widget, NULL); } } static void vadjustment_value_changed (GtkAdjustment *vadjustment, gpointer data) { GimvScrolled *scrolled; GtkWidget *widget; GdkRectangle area; int value, diff; widget = GTK_WIDGET(data); scrolled = GIMV_SCROLLED(data); if (!GTK_WIDGET_DRAWABLE(widget) || scrolled->y_offset == vadjustment->value) return; value = vadjustment->value; if (value < 0) value = 0; if (scrolled->freeze_count) { scrolled->y_offset = value; return; } if (value > scrolled->y_offset) { diff = value - scrolled->y_offset; if (diff >= widget->allocation.height) { scrolled->y_offset = value; WIDGET_DRAW(widget, NULL); return; } if (diff > 0) gdk_window_copy_area (widget->window, scrolled->copy_gc, 0, 0, widget->window, 0, diff, widget->allocation.width - 2 * bw (widget), widget->allocation.height - 2 * bw (widget) - diff); area.x = 0; area.y = widget->allocation.height - 2 * bw (widget) - diff; area.width = widget->allocation.width - 2 * bw (widget); area.height = diff; } else { diff = scrolled->y_offset - value; if (diff >= widget->allocation.height) { scrolled->y_offset = value; WIDGET_DRAW(widget, NULL); return; } if (diff > 0) gdk_window_copy_area (widget->window, scrolled->copy_gc, 0, diff, widget->window, 0, 0, widget->allocation.width - 2 * bw (widget), widget->allocation.height - 2 * bw (widget) - diff); area.x = 0; area.y = 0; area.width = widget->allocation.width - 2 * bw (widget); area.height = diff; } if (diff > 0) { scrolled->y_offset = value; #ifndef USE_GTK2 check_exposures (widget); #endif /* USE_GTK2 */ /* WIDGET_DRAW(widget, &area); */ WIDGET_DRAW(widget, NULL); } } static void gimv_scrolled_set_scroll_adjustments (GtkWidget *widget, GtkAdjustment *hadjustment, GtkAdjustment *vadjustment) { GimvScrolled *scrolled; scrolled = GIMV_SCROLLED(widget); if (scrolled->h_adjustment != hadjustment) { if (scrolled->h_adjustment) { gtk_signal_disconnect_by_data (GTK_OBJECT(scrolled->h_adjustment), scrolled); gtk_object_unref (GTK_OBJECT(scrolled->h_adjustment)); } scrolled->h_adjustment = hadjustment; if (hadjustment) { gtk_object_ref (GTK_OBJECT(hadjustment)); gtk_signal_connect (GTK_OBJECT(hadjustment), "value_changed", (GtkSignalFunc) hadjustment_value_changed, scrolled); } } if (scrolled->v_adjustment != vadjustment) { if (scrolled->v_adjustment) { gtk_signal_disconnect_by_data (GTK_OBJECT(scrolled->v_adjustment), scrolled); gtk_object_unref (GTK_OBJECT(scrolled->v_adjustment)); } scrolled->v_adjustment = vadjustment; if (vadjustment) { gtk_object_ref (GTK_OBJECT(vadjustment)); gtk_signal_connect (GTK_OBJECT(vadjustment), "value_changed", (GtkSignalFunc) vadjustment_value_changed, scrolled); } } gtk_signal_emit (GTK_OBJECT(scrolled), gimv_scrolled_signals[ADJUST_ADJUSTMENTS]); } void gimv_scrolled_freeze (GimvScrolled *scrolled) { g_return_if_fail (scrolled); g_return_if_fail (scrolled->freeze_count != (guint) -1); scrolled->freeze_count ++; } void gimv_scrolled_thawn (GimvScrolled *scrolled) { g_return_if_fail (scrolled); g_return_if_fail (scrolled->freeze_count); scrolled->freeze_count --; if (!scrolled->freeze_count) { gtk_signal_emit (GTK_OBJECT(scrolled), gimv_scrolled_signals [ADJUST_ADJUSTMENTS]); gtk_widget_draw (GTK_WIDGET(scrolled), NULL); } } #ifndef USE_GTK2 /* from gtkclist.c */ static void check_exposures (GtkWidget *widget) { GdkEvent *event; if (!GTK_WIDGET_REALIZED (widget)) return; /* * Make sure graphics expose events are processed before scrolling * again */ while ((event = gdk_event_get_graphics_expose (widget->window)) != NULL) { gtk_widget_event (widget, event); if (event->expose.count == 0) { gdk_event_free (event); break; } gdk_event_free (event); } } #endif /* USE_GTK2 */ void gimv_scrolled_page_up (GimvScrolled *scrolled) { GtkAdjustment *vadj; g_return_if_fail (scrolled); g_return_if_fail (GIMV_IS_SCROLLED (scrolled)); vadj = scrolled->v_adjustment; vadj->value -= vadj->page_size; adjustment_check_value (vadj->value, vadj); gtk_signal_emit_by_name (GTK_OBJECT(vadj), "value_changed"); } void gimv_scrolled_page_down (GimvScrolled *scrolled) { GtkAdjustment *vadj; g_return_if_fail (scrolled); g_return_if_fail (GIMV_IS_SCROLLED (scrolled)); vadj = scrolled->v_adjustment; vadj->value += vadj->page_size; adjustment_check_value (vadj->value, vadj); gtk_signal_emit_by_name (GTK_OBJECT(vadj), "value_changed"); } void gimv_scrolled_page_left (GimvScrolled *scrolled) { GtkAdjustment *hadj; g_return_if_fail (scrolled); g_return_if_fail (GIMV_IS_SCROLLED (scrolled)); hadj = scrolled->h_adjustment; hadj->value -= hadj->page_size; adjustment_check_value (hadj->value, hadj); gtk_signal_emit_by_name (GTK_OBJECT(hadj), "value_changed"); } void gimv_scrolled_page_right (GimvScrolled *scrolled) { GtkAdjustment *hadj; g_return_if_fail (scrolled); g_return_if_fail (GIMV_IS_SCROLLED (scrolled)); hadj = scrolled->h_adjustment; hadj->value = hadj->value; hadj->value += hadj->page_size; adjustment_check_value (hadj->value, hadj); gtk_signal_emit_by_name (GTK_OBJECT(hadj), "value_changed"); } /****************************************************************************** * * for auto scroll related functions * ******************************************************************************/ enum { HORIZONTAL, VERTICAL }; static void cancel_auto_scroll (GimvScrolled *scrolled) { g_return_if_fail (scrolled); g_return_if_fail (GIMV_IS_SCROLLED (scrolled)); /* horizontal */ if (scrolled->hscroll_timer_id != -1){ gtk_timeout_remove (scrolled->hscroll_timer_id); scrolled->hscroll_timer_id = -1; } /* vertival */ if (scrolled->vscroll_timer_id != -1){ gtk_timeout_remove (scrolled->vscroll_timer_id); scrolled->vscroll_timer_id = -1; } } static gboolean scrolling_is_desirable (GimvScrolled *scrolled, gint direction, gint x, gint y, gfloat *scale) { GtkAdjustment *adj; gint pos, edge_width, flags; gfloat upper; flags = scrolled->autoscroll_flags; if (direction == HORIZONTAL) { if (!(flags & GIMV_SCROLLED_AUTO_SCROLL_HORIZONTAL) && !(flags & GIMV_SCROLLED_AUTO_SCROLL_BOTH)) { return FALSE; } adj = scrolled->h_adjustment; pos = x; edge_width = scrolled->scroll_edge_x; } else if (direction == VERTICAL) { if (!(flags & GIMV_SCROLLED_AUTO_SCROLL_VERTICAL) && !(flags & GIMV_SCROLLED_AUTO_SCROLL_BOTH)) { return FALSE; } adj = scrolled->v_adjustment; pos = y; edge_width = scrolled->scroll_edge_y; } else { return FALSE; } upper = adj->upper - adj->page_size; if ((pos < edge_width) && (adj->value > adj->lower)) { if (scale) *scale = 1 + (0 - pos) / AUTO_SCROLL_SCALE_RATE; return TRUE; } else if ((pos > (adj->page_size - edge_width)) && (adj->value < upper)) { if (scale) *scale = 1 + (pos - adj->page_size) / AUTO_SCROLL_SCALE_RATE; return TRUE; } return 0; } static gboolean vertical_timeout (gpointer data) { GimvScrolled *scrolled = data; GtkAdjustment *vadj; gint step; vadj = scrolled->v_adjustment; if (scrolled->y_step < 0) step = vadj->step_increment; else step = scrolled->y_step; step *= scrolled->step_scale; if (scrolled->drag_motion_y < scrolled->scroll_edge_y) vadj->value = vadj->value - step; else vadj->value = vadj->value + step; adjustment_check_value (vadj->value, vadj); gtk_signal_emit_by_name (GTK_OBJECT(vadj), "value_changed"); return TRUE; } static gboolean horizontal_timeout (gpointer data) { GimvScrolled *scrolled = data; GtkAdjustment *hadj; gint step; hadj = scrolled->h_adjustment; if (scrolled->x_step < 0) step = hadj->step_increment; else step = scrolled->x_step; step *= scrolled->step_scale; if (scrolled->drag_motion_x < scrolled->scroll_edge_x) hadj->value = hadj->value - step; else hadj->value = hadj->value + step; adjustment_check_value (hadj->value, hadj); gtk_signal_emit_by_name (GTK_OBJECT(hadj), "value_changed"); return TRUE; } static void setup_drag_scroll (GimvScrolled *scrolled, gint x, gint y) { GtkAdjustment *hadj, *vadj; gboolean desirable; hadj = scrolled->h_adjustment; vadj = scrolled->v_adjustment; cancel_auto_scroll (scrolled); scrolled->drag_motion_x = x; scrolled->drag_motion_y = y; /* horizonal */ desirable = scrolling_is_desirable (scrolled, HORIZONTAL, x, y, &scrolled->step_scale); if (desirable) scrolled->hscroll_timer_id = gtk_timeout_add (scrolled->x_interval, horizontal_timeout, scrolled); /* vertical */ desirable = scrolling_is_desirable (scrolled, VERTICAL, x, y, &scrolled->step_scale); if (desirable) scrolled->vscroll_timer_id = gtk_timeout_add (scrolled->y_interval, vertical_timeout, scrolled); } static gint gimv_scrolled_button_press (GtkWidget *widget, GdkEventButton *event) { GimvScrolled *scrolled; g_return_val_if_fail (widget && event, FALSE); g_return_val_if_fail (GIMV_IS_SCROLLED(widget), FALSE); scrolled = GIMV_SCROLLED (widget); scrolled->pressed = TRUE; scrolled->drag_start_vx = GIMV_SCROLLED_VX (scrolled, (gint) event->x); scrolled->drag_start_vy = GIMV_SCROLLED_VY (scrolled, (gint) event->y); return FALSE; } static gint gimv_scrolled_button_release (GtkWidget *widget, GdkEventButton *event) { g_return_val_if_fail (widget && event, FALSE); g_return_val_if_fail (GIMV_IS_SCROLLED(widget), FALSE); gimv_scrolled_stop_auto_scroll (GIMV_SCROLLED (widget)); return FALSE; } static gint gimv_scrolled_motion_notify (GtkWidget *widget, GdkEventMotion *event) { GimvScrolled *scrolled; gint flags; gboolean pressed; g_return_val_if_fail (widget && event, FALSE); g_return_val_if_fail (GIMV_IS_SCROLLED(widget), FALSE); scrolled = GIMV_SCROLLED (widget); flags = scrolled->autoscroll_flags; pressed = scrolled->pressed; scrolled->drag_motion_x = event->x; scrolled->drag_motion_y = event->y; if ((pressed && (flags & GIMV_SCROLLED_AUTO_SCROLL_MOTION)) || (flags & GIMV_SCROLLED_AUTO_SCROLL_MOTION_ALL)) { setup_drag_scroll (scrolled, (gint) event->x, (gint) event->y); } return FALSE; } static gint gimv_scrolled_drag_motion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time) { GimvScrolled *scrolled; gint flags; g_return_val_if_fail (widget && context, FALSE); g_return_val_if_fail (GIMV_IS_SCROLLED(widget), FALSE); scrolled = GIMV_SCROLLED (widget); flags = scrolled->autoscroll_flags; if (flags & GIMV_SCROLLED_AUTO_SCROLL_DND) { setup_drag_scroll (GIMV_SCROLLED (widget), x, y); } return FALSE; } static void gimv_scrolled_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time) { g_return_if_fail (widget && context); g_return_if_fail (GIMV_IS_SCROLLED(widget)); cancel_auto_scroll (GIMV_SCROLLED (widget)); } void gimv_scrolled_set_auto_scroll (GimvScrolled *scrolled, GimvScrolledAutoScrollFlags flags) { g_return_if_fail (scrolled); g_return_if_fail (GIMV_IS_SCROLLED(scrolled)); scrolled->autoscroll_flags |= flags; } void gimv_scrolled_unset_auto_scroll (GimvScrolled *scrolled) { g_return_if_fail (scrolled); g_return_if_fail (GIMV_IS_SCROLLED(scrolled)); scrolled->autoscroll_flags = 0; } void gimv_scrolled_set_auto_scroll_edge_width (GimvScrolled *scrolled, gint x_edge, gint y_edge) { g_return_if_fail (scrolled); g_return_if_fail (GIMV_IS_SCROLLED(scrolled)); if (x_edge < 0) scrolled->scroll_edge_x = AUTO_SCROLL_EDGE_WIDTH; else scrolled->scroll_edge_x = x_edge; if (y_edge < 0) scrolled->scroll_edge_y = AUTO_SCROLL_EDGE_WIDTH; else scrolled->scroll_edge_y = y_edge; } void gimv_scrolled_set_h_auto_scroll_resolution (GimvScrolled *scrolled, gint step, gint interval) { g_return_if_fail (scrolled); g_return_if_fail (GIMV_IS_SCROLLED(scrolled)); scrolled->x_step = step; if (interval <= 0) scrolled->x_interval = AUTO_SCROLL_TIMEOUT; else scrolled->x_interval = interval; } void gimv_scrolled_set_v_auto_scroll_resolution (GimvScrolled *scrolled, gint step, gint interval) { g_return_if_fail (scrolled); g_return_if_fail (GIMV_IS_SCROLLED(scrolled)); scrolled->y_step = step; if (interval <= 0) scrolled->y_interval = AUTO_SCROLL_TIMEOUT; else scrolled->y_interval = interval; } gboolean gimv_scrolled_is_dragging (GimvScrolled *scrolled) { g_return_val_if_fail (scrolled, FALSE); g_return_val_if_fail (scrolled, FALSE); if (scrolled->pressed && scrolled->drag_motion_x >= 0 && scrolled->drag_motion_y >= 0) { return TRUE; } return FALSE; } void gimv_scrolled_stop_auto_scroll (GimvScrolled *scrolled) { g_return_if_fail (scrolled); g_return_if_fail (GIMV_IS_SCROLLED(scrolled)); cancel_auto_scroll (scrolled); scrolled->pressed = FALSE; scrolled->drag_start_vx = -1; scrolled->drag_start_vy = -1; scrolled->drag_motion_x = -1; scrolled->drag_motion_y = -1; }