/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ #include #include "gtkintl.h" #include "gtkmain.h" #include "gtkrange.h" #include "gtksignal.h" #include "gtkintl.h" #define SCROLL_TIMER_LENGTH 20 #define SCROLL_INITIAL_DELAY 250 /* must hold button this long before ... */ #define SCROLL_LATER_DELAY 100 /* ... it starts repeating at this rate */ #define SCROLL_DELAY_LENGTH 300 #define RANGE_CLASS(w) GTK_RANGE_GET_CLASS (w) enum { PROP_0, PROP_UPDATE_POLICY }; enum { MOVE_SLIDER, LAST_SIGNAL }; static void gtk_range_class_init (GtkRangeClass *klass); static void gtk_range_init (GtkRange *range); static void gtk_range_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void gtk_range_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gtk_range_destroy (GtkObject *object); static void gtk_range_unrealize (GtkWidget *widget); static gint gtk_range_expose (GtkWidget *widget, GdkEventExpose *event); static gint gtk_range_button_press (GtkWidget *widget, GdkEventButton *event); static gint gtk_range_button_release (GtkWidget *widget, GdkEventButton *event); static gint gtk_range_motion_notify (GtkWidget *widget, GdkEventMotion *event); static gint gtk_range_enter_notify (GtkWidget *widget, GdkEventCrossing *event); static gint gtk_range_leave_notify (GtkWidget *widget, GdkEventCrossing *event); static gint gtk_range_scroll_event (GtkWidget *widget, GdkEventScroll *event); static void gtk_range_style_set (GtkWidget *widget, GtkStyle *previous_style); static void gtk_range_move_slider (GtkRange *range, GtkScrollType scroll, GtkTroughType trough); static void gtk_real_range_draw_trough (GtkRange *range); static void gtk_real_range_draw_slider (GtkRange *range); static gint gtk_real_range_timer (GtkRange *range); static gint gtk_range_scroll (GtkRange *range, gdouble jump_perc); static void gtk_range_add_timer (GtkRange *range); static void gtk_range_remove_timer (GtkRange *range); static void gtk_range_adjustment_changed (GtkAdjustment *adjustment, gpointer data); static void gtk_range_adjustment_value_changed (GtkAdjustment *adjustment, gpointer data); static void gtk_range_trough_hdims (GtkRange *range, gint *left, gint *right); static void gtk_range_trough_vdims (GtkRange *range, gint *top, gint *bottom); static GtkWidgetClass *parent_class = NULL; static guint signals[LAST_SIGNAL]; GtkType gtk_range_get_type (void) { static GtkType range_type = 0; if (!range_type) { static const GtkTypeInfo range_info = { "GtkRange", sizeof (GtkRange), sizeof (GtkRangeClass), (GtkClassInitFunc) gtk_range_class_init, (GtkObjectInitFunc) gtk_range_init, /* reserved_1 */ NULL, /* reserved_2 */ NULL, (GtkClassInitFunc) NULL, }; range_type = gtk_type_unique (GTK_TYPE_WIDGET, &range_info); } return range_type; } static void gtk_range_class_init (GtkRangeClass *class) { GObjectClass *gobject_class; GtkObjectClass *object_class; GtkWidgetClass *widget_class; gobject_class = G_OBJECT_CLASS (class); object_class = (GtkObjectClass*) class; widget_class = (GtkWidgetClass*) class; parent_class = gtk_type_class (GTK_TYPE_WIDGET); gobject_class->set_property = gtk_range_set_property; gobject_class->get_property = gtk_range_get_property; object_class->destroy = gtk_range_destroy; widget_class->unrealize = gtk_range_unrealize; widget_class->expose_event = gtk_range_expose; widget_class->button_press_event = gtk_range_button_press; widget_class->button_release_event = gtk_range_button_release; widget_class->motion_notify_event = gtk_range_motion_notify; widget_class->scroll_event = gtk_range_scroll_event; widget_class->enter_notify_event = gtk_range_enter_notify; widget_class->leave_notify_event = gtk_range_leave_notify; widget_class->style_set = gtk_range_style_set; class->min_slider_size = 7; class->trough = 1; class->slider = 2; class->step_forw = 3; class->step_back = 4; class->move_slider = gtk_range_move_slider; class->draw_background = NULL; class->clear_background = NULL; class->draw_trough = gtk_real_range_draw_trough; class->draw_slider = gtk_real_range_draw_slider; class->draw_step_forw = NULL; class->draw_step_back = NULL; class->trough_click = NULL; class->motion = NULL; class->timer = gtk_real_range_timer; signals[MOVE_SLIDER] = g_signal_newc ("move_slider", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GtkRangeClass, move_slider), NULL, NULL, gtk_marshal_VOID__ENUM_ENUM, G_TYPE_NONE, 2, GTK_TYPE_SCROLL_TYPE, GTK_TYPE_TROUGH_TYPE); g_object_class_install_property (gobject_class, PROP_UPDATE_POLICY, g_param_spec_enum ("update_policy", _("Update policy"), _("How the range should be updated on the screen"), GTK_TYPE_UPDATE_TYPE, GTK_UPDATE_CONTINUOUS, G_PARAM_READWRITE)); gtk_widget_class_install_style_property (widget_class, g_param_spec_int ("slider_width", _("Slider Width"), _("Width of scrollbar or scale thumb"), 0, G_MAXINT, 14, G_PARAM_READABLE)); gtk_widget_class_install_style_property (widget_class, g_param_spec_int ("trough_border", _("Trough Border"), _("Width of border around range"), 0, G_MAXINT, 1, G_PARAM_READABLE)); gtk_widget_class_install_style_property (widget_class, g_param_spec_int ("stepper_size", _("Stepper Size"), _("Size of step buttons at ends"), 0, G_MAXINT, 14, G_PARAM_READABLE)); gtk_widget_class_install_style_property (widget_class, g_param_spec_int ("stepper_spacing", _("Stepper Spacing"), _("Spacing between step buttons and thumb"), G_MININT, G_MAXINT, 0, G_PARAM_READABLE)); } static void gtk_range_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkRange *range; range = GTK_RANGE (object); switch (prop_id) { case PROP_UPDATE_POLICY: gtk_range_set_update_policy (range, g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_range_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkRange *range; range = GTK_RANGE (object); switch (prop_id) { case PROP_UPDATE_POLICY: g_value_set_enum (value, range->policy); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_range_init (GtkRange *range) { range->trough = NULL; range->slider = NULL; range->step_forw = NULL; range->step_back = NULL; range->x_click_point = 0; range->y_click_point = 0; range->button = 0; range->digits = -1; range->policy = GTK_UPDATE_CONTINUOUS; range->scroll_type = GTK_SCROLL_NONE; range->in_child = 0; range->click_child = 0; range->need_timer = FALSE; range->timer = 0; range->flippable = 0; range->old_value = 0.0; range->old_lower = 0.0; range->old_upper = 0.0; range->old_page_size = 0.0; range->adjustment = NULL; } GtkAdjustment* gtk_range_get_adjustment (GtkRange *range) { g_return_val_if_fail (range != NULL, NULL); g_return_val_if_fail (GTK_IS_RANGE (range), NULL); if (!range->adjustment) gtk_range_set_adjustment (range, NULL); return range->adjustment; } void gtk_range_set_update_policy (GtkRange *range, GtkUpdateType policy) { g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); if (range->policy != policy) { range->policy = policy; g_object_notify (G_OBJECT (range), "update_policy"); } } void gtk_range_set_adjustment (GtkRange *range, GtkAdjustment *adjustment) { g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); if (!adjustment) adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); else g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment)); if (range->adjustment != adjustment) { if (range->adjustment) { gtk_signal_disconnect_by_data (GTK_OBJECT (range->adjustment), (gpointer) range); gtk_object_unref (GTK_OBJECT (range->adjustment)); } range->adjustment = adjustment; gtk_object_ref (GTK_OBJECT (adjustment)); gtk_object_sink (GTK_OBJECT (adjustment)); gtk_signal_connect (GTK_OBJECT (adjustment), "changed", (GtkSignalFunc) gtk_range_adjustment_changed, (gpointer) range); gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed", (GtkSignalFunc) gtk_range_adjustment_value_changed, (gpointer) range); range->old_value = adjustment->value; range->old_lower = adjustment->lower; range->old_upper = adjustment->upper; range->old_page_size = adjustment->page_size; gtk_range_adjustment_changed (adjustment, (gpointer) range); } } void gtk_range_set_inverted (GtkRange *range, gboolean setting) { g_return_if_fail (GTK_IS_RANGE (range)); setting = setting != FALSE; if (setting != range->inverted) { range->inverted = setting; gtk_widget_queue_resize (GTK_WIDGET (range)); } } gboolean gtk_range_get_inverted (GtkRange *range) { g_return_val_if_fail (GTK_IS_RANGE (range), FALSE); return range->inverted; } void _gtk_range_draw_background (GtkRange *range) { g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); if (range->trough && RANGE_CLASS (range)->draw_background) (* RANGE_CLASS (range)->draw_background) (range); } void _gtk_range_clear_background (GtkRange *range) { g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); if (range->trough && RANGE_CLASS (range)->clear_background) (* RANGE_CLASS (range)->clear_background) (range); } void _gtk_range_draw_trough (GtkRange *range) { g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); if (range->trough && RANGE_CLASS (range)->draw_trough) (* RANGE_CLASS (range)->draw_trough) (range); } void _gtk_range_draw_slider (GtkRange *range) { g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); if (range->slider && RANGE_CLASS (range)->draw_slider) (* RANGE_CLASS (range)->draw_slider) (range); } void _gtk_range_draw_step_forw (GtkRange *range) { g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); if (range->step_forw && RANGE_CLASS (range)->draw_step_forw) (* RANGE_CLASS (range)->draw_step_forw) (range); } void _gtk_range_draw_step_back (GtkRange *range) { g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); if (range->step_back && RANGE_CLASS (range)->draw_step_back) (* RANGE_CLASS (range)->draw_step_back) (range); } void _gtk_range_slider_update (GtkRange *range) { g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); if (RANGE_CLASS (range)->slider_update) (* RANGE_CLASS (range)->slider_update) (range); } gboolean _gtk_range_trough_click (GtkRange *range, gint x, gint y, gdouble *jump_perc) { g_return_val_if_fail (range != NULL, GTK_TROUGH_NONE); g_return_val_if_fail (GTK_IS_RANGE (range), GTK_TROUGH_NONE); if (RANGE_CLASS (range)->trough_click) return (* RANGE_CLASS (range)->trough_click) (range, x, y, jump_perc); return GTK_TROUGH_NONE; } static GdkRegion * get_window_region (GdkWindow *window) { GdkRectangle rect; gdk_window_get_position (window, &rect.x, &rect.y); gdk_window_get_size (window, &rect.width, &rect.height); return gdk_region_rectangle (&rect); } static void move_and_update_window (GdkWindow *window, gint x, gint y) { GdkRegion *old_region; GdkRegion *new_region; GdkWindow *parent = gdk_window_get_parent (window); old_region = get_window_region (window); gdk_window_move (window, x, y); new_region = get_window_region (window); gdk_region_subtract (old_region, new_region); gdk_window_invalidate_region (parent, old_region, TRUE); gdk_region_destroy (old_region); gdk_region_destroy (new_region); gdk_window_process_updates (parent, TRUE); } static gboolean should_invert (GtkRange *range, gboolean horizontal) { if (horizontal) return (range->inverted && !range->flippable) || (range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) || (!range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL); else return range->inverted; } void _gtk_range_default_hslider_update (GtkRange *range) { gint left; gint right; gint x; gint trough_border; g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); _gtk_range_get_props (range, NULL, &trough_border, NULL, NULL); if (GTK_WIDGET_REALIZED (range)) { gtk_range_trough_hdims (range, &left, &right); x = left; if (range->adjustment->value < range->adjustment->lower) { range->adjustment->value = range->adjustment->lower; gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); } else if (range->adjustment->value > range->adjustment->upper) { range->adjustment->value = range->adjustment->upper; gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); } if (range->adjustment->lower != (range->adjustment->upper - range->adjustment->page_size)) x += ((right - left) * (range->adjustment->value - range->adjustment->lower) / (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size)); if (x < left) x = left; else if (x > right) x = right; if (should_invert (range, TRUE)) x = right - (x - left); move_and_update_window (range->slider, x, trough_border); } } void _gtk_range_default_vslider_update (GtkRange *range) { gint top; gint bottom; gint y; gint trough_border; g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); _gtk_range_get_props (range, NULL, &trough_border, NULL, NULL); if (GTK_WIDGET_REALIZED (range)) { gtk_range_trough_vdims (range, &top, &bottom); y = top; if (range->adjustment->value < range->adjustment->lower) { range->adjustment->value = range->adjustment->lower; gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); } else if (range->adjustment->value > range->adjustment->upper) { range->adjustment->value = range->adjustment->upper; gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); } if (range->adjustment->lower != (range->adjustment->upper - range->adjustment->page_size)) y += ((bottom - top) * (range->adjustment->value - range->adjustment->lower) / (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size)); if (y < top) y = top; else if (y > bottom) y = bottom; if (should_invert (range, FALSE)) y = bottom - (y - top); move_and_update_window (range->slider, trough_border, y); } } gboolean _gtk_range_default_htrough_click (GtkRange *range, gint x, gint y, gdouble *jump_perc) { gint trough_border; gint trough_width; gint trough_height; gint slider_x; gint slider_length; gint left, right; g_return_val_if_fail (range != NULL, GTK_TROUGH_NONE); g_return_val_if_fail (GTK_IS_RANGE (range), GTK_TROUGH_NONE); _gtk_range_get_props (range, NULL, &trough_border, NULL, NULL); gtk_range_trough_hdims (range, &left, &right); gdk_window_get_size (range->slider, &slider_length, NULL); right += slider_length; if (should_invert (range, TRUE)) x = (right - x) + left; if ((x > left) && (y > trough_border)) { gdk_window_get_size (range->trough, &trough_width, &trough_height); if ((x < right) && (y < (trough_height - trough_border))) { if (jump_perc) { *jump_perc = ((gdouble) (x - left)) / ((gdouble) (right - left)); return GTK_TROUGH_JUMP; } gdk_window_get_position (range->slider, &slider_x, NULL); if (x < slider_x) return GTK_TROUGH_START; else return GTK_TROUGH_END; } } return GTK_TROUGH_NONE; } gboolean _gtk_range_default_vtrough_click (GtkRange *range, gint x, gint y, gdouble *jump_perc) { gint trough_border; gint trough_width; gint trough_height; gint slider_y; gint top, bottom; gint slider_length; g_return_val_if_fail (range != NULL, GTK_TROUGH_NONE); g_return_val_if_fail (GTK_IS_RANGE (range), GTK_TROUGH_NONE); _gtk_range_get_props (range, NULL, &trough_border, NULL, NULL); gtk_range_trough_vdims (range, &top, &bottom); gdk_window_get_size (range->slider, NULL, &slider_length); bottom += slider_length; if (should_invert (range, FALSE)) y = (bottom - y) + top; if ((x > trough_border) && (y > top)) { gdk_window_get_size (range->trough, &trough_width, &trough_height); if ((x < (trough_width - trough_border) && (y < bottom))) { if (jump_perc) { *jump_perc = ((gdouble) (y - top)) / ((gdouble) (bottom - top)); return GTK_TROUGH_JUMP; } gdk_window_get_position (range->slider, NULL, &slider_y); if (y < slider_y) return GTK_TROUGH_START; else return GTK_TROUGH_END; } } return GTK_TROUGH_NONE; } void _gtk_range_default_hmotion (GtkRange *range, gint xdelta, gint ydelta) { gdouble old_value; gint left, right; gint slider_x, slider_y; gint new_pos; g_return_if_fail (GTK_IS_RANGE (range)); g_return_if_fail (GTK_WIDGET_REALIZED (range)); gdk_window_get_position (range->slider, &slider_x, &slider_y); gtk_range_trough_hdims (range, &left, &right); if (left == right) return; new_pos = slider_x + xdelta; if (should_invert (range, TRUE)) new_pos = (right - new_pos) + left; if (new_pos < left) new_pos = left; else if (new_pos > right) new_pos = right; old_value = range->adjustment->value; range->adjustment->value = ((range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size) * (new_pos - left) / (right - left) + range->adjustment->lower); if (range->digits >= 0) { char buffer[64]; sprintf (buffer, "%0.*f", range->digits, range->adjustment->value); sscanf (buffer, "%lf", &range->adjustment->value); } if (old_value != range->adjustment->value) { if (range->policy == GTK_UPDATE_CONTINUOUS) { gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); } else { _gtk_range_slider_update (range); _gtk_range_clear_background (range); if (range->policy == GTK_UPDATE_DELAYED) { gtk_range_remove_timer (range); range->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH, (GtkFunction) RANGE_CLASS (range)->timer, (gpointer) range); } } } } void _gtk_range_default_vmotion (GtkRange *range, gint xdelta, gint ydelta) { gdouble old_value; gint top, bottom; gint slider_x, slider_y; gint new_pos; g_return_if_fail (GTK_IS_RANGE (range)); g_return_if_fail (GTK_WIDGET_REALIZED (range)); range = GTK_RANGE (range); gdk_window_get_position (range->slider, &slider_x, &slider_y); gtk_range_trough_vdims (range, &top, &bottom); if (bottom == top) return; new_pos = slider_y + ydelta; if (should_invert (range, FALSE)) new_pos = (bottom - new_pos) + top; if (new_pos < top) new_pos = top; else if (new_pos > bottom) new_pos = bottom; old_value = range->adjustment->value; range->adjustment->value = ((range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size) * (new_pos - top) / (bottom - top) + range->adjustment->lower); if (range->digits >= 0) { char buffer[64]; sprintf (buffer, "%0.*f", range->digits, range->adjustment->value); sscanf (buffer, "%lf", &range->adjustment->value); } if (old_value != range->adjustment->value) { if (range->policy == GTK_UPDATE_CONTINUOUS) { gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); } else { _gtk_range_slider_update (range); _gtk_range_clear_background (range); if (range->policy == GTK_UPDATE_DELAYED) { gtk_range_remove_timer (range); range->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH, (GtkFunction) RANGE_CLASS (range)->timer, (gpointer) range); } } } } static void gtk_range_destroy (GtkObject *object) { GtkRange *range; g_return_if_fail (object != NULL); g_return_if_fail (GTK_IS_RANGE (object)); range = GTK_RANGE (object); gtk_range_remove_timer (range); if (range->adjustment) { if (range->adjustment) gtk_signal_disconnect_by_data (GTK_OBJECT (range->adjustment), (gpointer) range); gtk_object_unref (GTK_OBJECT (range->adjustment)); range->adjustment = NULL; } (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); } static void gtk_range_unrealize (GtkWidget *widget) { GtkRange *range; g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_RANGE (widget)); range = GTK_RANGE (widget); if (range->slider) { gdk_window_set_user_data (range->slider, NULL); gdk_window_destroy (range->slider); range->slider = NULL; } if (range->trough) { gdk_window_set_user_data (range->trough, NULL); gdk_window_destroy (range->trough); range->trough = NULL; } if (range->step_forw) { gdk_window_set_user_data (range->step_forw, NULL); gdk_window_destroy (range->step_forw); range->step_forw = NULL; } if (range->step_back) { gdk_window_set_user_data (range->step_back, NULL); gdk_window_destroy (range->step_back); range->step_back = NULL; } if (GTK_WIDGET_CLASS (parent_class)->unrealize) (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); } static gint gtk_range_expose (GtkWidget *widget, GdkEventExpose *event) { GtkRange *range; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); range = GTK_RANGE (widget); /* We should really pass another argument - *the redrawn area - to all the drawing functions) */ if (event->window == range->trough) { _gtk_range_draw_trough (range); } else if (event->window == widget->window) { _gtk_range_draw_background (range); } else if (event->window == range->slider) { _gtk_range_draw_slider (range); } else if (event->window == range->step_forw) { _gtk_range_draw_step_forw (range); } else if (event->window == range->step_back) { _gtk_range_draw_step_back (range); } return FALSE; } static gint gtk_range_button_press (GtkWidget *widget, GdkEventButton *event) { GtkRange *range; gint trough_part; gdouble jump_perc; g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); if (!GTK_WIDGET_HAS_FOCUS (widget)) gtk_widget_grab_focus (widget); jump_perc = -1; range = GTK_RANGE (widget); if (range->button == 0) { gtk_grab_add (widget); range->button = event->button; range->x_click_point = event->x; range->y_click_point = event->y; if (event->window == range->trough) { range->click_child = RANGE_CLASS (range)->trough; if (range->button == 2) trough_part = _gtk_range_trough_click (range, event->x, event->y, &jump_perc); else trough_part = _gtk_range_trough_click (range, event->x, event->y, NULL); range->scroll_type = GTK_SCROLL_NONE; if (trough_part == GTK_TROUGH_START) range->scroll_type = GTK_SCROLL_PAGE_BACKWARD; else if (trough_part == GTK_TROUGH_END) range->scroll_type = GTK_SCROLL_PAGE_FORWARD; else if (trough_part == GTK_TROUGH_JUMP && jump_perc >= 0 && jump_perc <= 1) range->scroll_type = GTK_SCROLL_JUMP; if (range->scroll_type != GTK_SCROLL_NONE) { gtk_range_scroll (range, jump_perc); gtk_range_add_timer (range); } } else if (event->window == range->slider) { range->click_child = RANGE_CLASS (range)->slider; range->scroll_type = GTK_SCROLL_NONE; } else if (event->window == range->step_forw || event->window == range->step_back) { gboolean back = (event->window == range->step_back); if (range->button == 3) { range->scroll_type = GTK_SCROLL_JUMP; gtk_range_scroll (range, back ? 0.0 : 1.0); } else { range->click_child = back ? RANGE_CLASS (range)->step_back : RANGE_CLASS (range)->step_forw; if (range->button == 2) range->scroll_type = back ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_PAGE_FORWARD; else range->scroll_type = back ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD; gtk_range_scroll (range, -1); gtk_range_add_timer (range); if (back) _gtk_range_draw_step_back (range); else _gtk_range_draw_step_forw (range); } } } return TRUE; } static gint gtk_range_button_release (GtkWidget *widget, GdkEventButton *event) { GtkRange *range; g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); range = GTK_RANGE (widget); if (range->button == event->button) { gtk_grab_remove (widget); range->button = 0; range->x_click_point = -1; range->y_click_point = -1; if (range->click_child == RANGE_CLASS (range)->slider) { if (range->policy == GTK_UPDATE_DELAYED) gtk_range_remove_timer (range); if ((range->policy != GTK_UPDATE_CONTINUOUS) && (range->old_value != range->adjustment->value)) gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); } else if ((range->click_child == RANGE_CLASS (range)->trough) || (range->click_child == RANGE_CLASS (range)->step_forw) || (range->click_child == RANGE_CLASS (range)->step_back)) { gtk_range_remove_timer (range); if ((range->policy != GTK_UPDATE_CONTINUOUS) && (range->old_value != range->adjustment->value)) gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); if (range->click_child == RANGE_CLASS (range)->step_forw) { range->click_child = 0; _gtk_range_draw_step_forw (range); } else if (range->click_child == RANGE_CLASS (range)->step_back) { range->click_child = 0; _gtk_range_draw_step_back (range); } } range->click_child = 0; } return TRUE; } static gint gtk_range_scroll_event (GtkWidget *widget, GdkEventScroll *event) { GtkRange *range; g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); range = GTK_RANGE (widget); if (GTK_WIDGET_REALIZED (range)) { GtkAdjustment *adj = GTK_RANGE (range)->adjustment; gdouble new_value = adj->value + ((event->direction == GDK_SCROLL_UP) ? -adj->page_increment / 2: adj->page_increment / 2); new_value = CLAMP (new_value, adj->lower, adj->upper - adj->page_size); gtk_adjustment_set_value (adj, new_value); } return TRUE; } static gint gtk_range_motion_notify (GtkWidget *widget, GdkEventMotion *event) { GtkRange *range; g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); range = GTK_RANGE (widget); if (range->click_child == RANGE_CLASS (range)->slider) { GdkModifierType mods; gint x, y, mask, x2, y2; gdk_window_get_pointer (range->trough, &x, &y, &mods); gdk_window_get_position (range->slider, &x2, &y2); x -= x2; y -= y2; switch (range->button) { case 1: mask = GDK_BUTTON1_MASK; break; case 2: mask = GDK_BUTTON2_MASK; break; case 3: mask = GDK_BUTTON3_MASK; break; default: mask = 0; break; } if (mods & mask) { if (RANGE_CLASS (range)->motion) (* RANGE_CLASS (range)->motion) (range, x - range->x_click_point, y - range->y_click_point); } } return TRUE; } static void gtk_range_move_slider (GtkRange *range, GtkScrollType scroll, GtkTroughType pos) { if (scroll != GTK_SCROLL_NONE) { range->scroll_type = scroll; gtk_range_scroll (range, -1); if (range->old_value != range->adjustment->value) { gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); switch (range->scroll_type) { case GTK_SCROLL_STEP_LEFT: if (should_invert (range, TRUE)) _gtk_range_draw_step_forw (range); else _gtk_range_draw_step_back (range); break; case GTK_SCROLL_STEP_UP: if (should_invert (range, FALSE)) _gtk_range_draw_step_forw (range); else _gtk_range_draw_step_back (range); break; case GTK_SCROLL_STEP_RIGHT: if (should_invert (range, TRUE)) _gtk_range_draw_step_back (range); else _gtk_range_draw_step_forw (range); break; case GTK_SCROLL_STEP_DOWN: if (should_invert (range, FALSE)) _gtk_range_draw_step_back (range); else _gtk_range_draw_step_forw (range); break; case GTK_SCROLL_STEP_BACKWARD: _gtk_range_draw_step_back (range); break; case GTK_SCROLL_STEP_FORWARD: _gtk_range_draw_step_forw (range); break; } } } if (pos != GTK_TROUGH_NONE) { if (pos == GTK_TROUGH_START) range->adjustment->value = range->adjustment->lower; else if (pos == GTK_TROUGH_END) range->adjustment->value = range->adjustment->upper - range->adjustment->page_size; if (range->old_value != range->adjustment->value) { gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); _gtk_range_slider_update (range); _gtk_range_clear_background (range); } } } static gint gtk_range_enter_notify (GtkWidget *widget, GdkEventCrossing *event) { GtkRange *range; g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); range = GTK_RANGE (widget); if (event->window == range->trough) { range->in_child = RANGE_CLASS (range)->trough; } else if (event->window == range->slider) { range->in_child = RANGE_CLASS (range)->slider; if ((range->click_child == 0) || (range->click_child == RANGE_CLASS (range)->trough)) _gtk_range_draw_slider (range); } else if (event->window == range->step_forw) { range->in_child = RANGE_CLASS (range)->step_forw; if ((range->click_child == 0) || (range->click_child == RANGE_CLASS (range)->trough)) _gtk_range_draw_step_forw (range); } else if (event->window == range->step_back) { range->in_child = RANGE_CLASS (range)->step_back; if ((range->click_child == 0) || (range->click_child == RANGE_CLASS (range)->trough)) _gtk_range_draw_step_back (range); } return TRUE; } static gint gtk_range_leave_notify (GtkWidget *widget, GdkEventCrossing *event) { GtkRange *range; g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); range = GTK_RANGE (widget); range->in_child = 0; if (event->window == range->trough) { } else if (event->window == range->slider) { if ((range->click_child == 0) || (range->click_child == RANGE_CLASS (range)->trough)) _gtk_range_draw_slider (range); } else if (event->window == range->step_forw) { if ((range->click_child == 0) || (range->click_child == RANGE_CLASS (range)->trough)) _gtk_range_draw_step_forw (range); } else if (event->window == range->step_back) { if ((range->click_child == 0) || (range->click_child == RANGE_CLASS (range)->trough)) _gtk_range_draw_step_back (range); } return TRUE; } static void gtk_real_range_draw_trough (GtkRange *range) { g_return_if_fail (GTK_IS_RANGE (range)); if (range->trough) { gtk_paint_box (GTK_WIDGET (range)->style, range->trough, GTK_STATE_ACTIVE, GTK_SHADOW_IN, NULL, GTK_WIDGET(range), "trough", 0, 0, -1, -1); if (GTK_WIDGET_HAS_FOCUS (range)) gtk_paint_focus (GTK_WIDGET (range)->style, range->trough, NULL, GTK_WIDGET(range), "trough", 0, 0, -1, -1); } } static void gtk_real_range_draw_slider (GtkRange *range) { GtkStateType state_type; g_return_if_fail (GTK_IS_RANGE (range)); if (range->slider) { if ((range->in_child == RANGE_CLASS (range)->slider) || (range->click_child == RANGE_CLASS (range)->slider)) state_type = GTK_STATE_PRELIGHT; else state_type = GTK_STATE_NORMAL; gtk_paint_box (GTK_WIDGET (range)->style, range->slider, state_type, GTK_SHADOW_OUT, NULL, GTK_WIDGET (range), "slider", 0, 0, -1, -1); } } static gint gtk_real_range_timer (GtkRange *range) { gint return_val; GDK_THREADS_ENTER (); return_val = TRUE; if (range->click_child == RANGE_CLASS (range)->slider) { if (range->policy == GTK_UPDATE_DELAYED) gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); return_val = FALSE; } else { GdkModifierType mods, mask; if (!range->timer) { return_val = FALSE; if (range->need_timer) range->timer = gtk_timeout_add (SCROLL_TIMER_LENGTH, (GtkFunction) RANGE_CLASS (range)->timer, (gpointer) range); else { GDK_THREADS_LEAVE (); return FALSE; } range->need_timer = FALSE; } switch (range->button) { case 1: mask = GDK_BUTTON1_MASK; break; case 2: mask = GDK_BUTTON2_MASK; break; case 3: mask = GDK_BUTTON3_MASK; break; default: mask = 0; break; } gdk_window_get_pointer (range->slider, NULL, NULL, &mods); if (mods & mask) return_val = gtk_range_scroll (range, -1); } GDK_THREADS_LEAVE (); return return_val; } static gint gtk_range_scroll (GtkRange *range, gdouble jump_perc) { gdouble new_value; gint return_val; GtkScrollType scroll_type; g_return_val_if_fail (GTK_IS_RANGE (range), FALSE); new_value = range->adjustment->value; return_val = TRUE; /* Translate visual to logical */ scroll_type = range->scroll_type; switch (scroll_type) { case GTK_SCROLL_STEP_UP: if (should_invert (range, FALSE)) scroll_type = GTK_SCROLL_STEP_FORWARD; else scroll_type = GTK_SCROLL_STEP_BACKWARD; break; case GTK_SCROLL_STEP_DOWN: if (should_invert (range, FALSE)) scroll_type = GTK_SCROLL_STEP_BACKWARD; else scroll_type = GTK_SCROLL_STEP_FORWARD; break; case GTK_SCROLL_PAGE_UP: if (should_invert (range, FALSE)) scroll_type = GTK_SCROLL_PAGE_FORWARD; else scroll_type = GTK_SCROLL_PAGE_BACKWARD; break; case GTK_SCROLL_PAGE_DOWN: if (should_invert (range, FALSE)) scroll_type = GTK_SCROLL_PAGE_BACKWARD; else scroll_type = GTK_SCROLL_PAGE_FORWARD; break; case GTK_SCROLL_STEP_LEFT: if (should_invert (range, TRUE)) scroll_type = GTK_SCROLL_STEP_FORWARD; else scroll_type = GTK_SCROLL_STEP_BACKWARD; break; case GTK_SCROLL_STEP_RIGHT: if (should_invert (range, TRUE)) scroll_type = GTK_SCROLL_STEP_BACKWARD; else scroll_type = GTK_SCROLL_STEP_FORWARD; break; case GTK_SCROLL_PAGE_LEFT: if (should_invert (range, TRUE)) scroll_type = GTK_SCROLL_PAGE_FORWARD; else scroll_type = GTK_SCROLL_PAGE_BACKWARD; break; case GTK_SCROLL_PAGE_RIGHT: if (should_invert (range, TRUE)) scroll_type = GTK_SCROLL_PAGE_BACKWARD; else scroll_type = GTK_SCROLL_PAGE_FORWARD; break; default: break; } switch (scroll_type) { case GTK_SCROLL_NONE: break; case GTK_SCROLL_JUMP: if (jump_perc >= 0 && jump_perc <= 1) { new_value = (range->adjustment->lower + (range->adjustment->upper - range->adjustment->page_size - range->adjustment->lower) * jump_perc); } break; case GTK_SCROLL_STEP_BACKWARD: new_value -= range->adjustment->step_increment; if (new_value <= range->adjustment->lower) { new_value = range->adjustment->lower; return_val = FALSE; range->timer = 0; } break; case GTK_SCROLL_STEP_FORWARD: new_value += range->adjustment->step_increment; if (new_value >= (range->adjustment->upper - range->adjustment->page_size)) { new_value = range->adjustment->upper - range->adjustment->page_size; return_val = FALSE; range->timer = 0; } break; case GTK_SCROLL_PAGE_BACKWARD: new_value -= range->adjustment->page_increment; if (new_value <= range->adjustment->lower) { new_value = range->adjustment->lower; return_val = FALSE; range->timer = 0; } break; case GTK_SCROLL_PAGE_FORWARD: new_value += range->adjustment->page_increment; if (new_value >= (range->adjustment->upper - range->adjustment->page_size)) { new_value = range->adjustment->upper - range->adjustment->page_size; return_val = FALSE; range->timer = 0; } break; case GTK_SCROLL_STEP_UP: case GTK_SCROLL_STEP_DOWN: case GTK_SCROLL_PAGE_UP: case GTK_SCROLL_PAGE_DOWN: case GTK_SCROLL_STEP_LEFT: case GTK_SCROLL_STEP_RIGHT: case GTK_SCROLL_PAGE_LEFT: case GTK_SCROLL_PAGE_RIGHT: g_assert_not_reached (); break; } if (new_value != range->adjustment->value) { range->adjustment->value = new_value; if ((range->policy == GTK_UPDATE_CONTINUOUS) || (!return_val && (range->policy == GTK_UPDATE_DELAYED))) { gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); } else { _gtk_range_slider_update (range); _gtk_range_clear_background (range); } } return return_val; } static gboolean gtk_range_timer_1st_time (GtkRange *range) { /* * If the real timeout function succeeds and the timeout is still set, * replace it with a quicker one so successive scrolling goes faster. */ gtk_object_ref (GTK_OBJECT (range)); if (RANGE_CLASS (range)->timer (range)) { if (range->timer) { /* We explicitely remove ourselves here in the paranoia * that due to things happening above in the callback * above, we might have been removed, and another added. */ g_source_remove (range->timer); range->timer = gtk_timeout_add (SCROLL_LATER_DELAY, (GtkFunction) RANGE_CLASS (range)->timer, range); } } gtk_object_unref (GTK_OBJECT (range)); return FALSE; /* don't keep calling this function */ } static void gtk_range_add_timer (GtkRange *range) { g_return_if_fail (GTK_IS_RANGE (range)); if (!range->timer) { range->need_timer = TRUE; range->timer = gtk_timeout_add (SCROLL_INITIAL_DELAY, (GtkFunction) gtk_range_timer_1st_time, range); } } static void gtk_range_remove_timer (GtkRange *range) { g_return_if_fail (GTK_IS_RANGE (range)); if (range->timer) { gtk_timeout_remove (range->timer); range->timer = 0; } range->need_timer = FALSE; } static void gtk_range_adjustment_changed (GtkAdjustment *adjustment, gpointer data) { GtkRange *range; g_return_if_fail (adjustment != NULL); g_return_if_fail (data != NULL); range = GTK_RANGE (data); if (((range->old_lower != adjustment->lower) || (range->old_upper != adjustment->upper) || (range->old_page_size != adjustment->page_size)) && (range->old_value == adjustment->value)) { if ((adjustment->lower == adjustment->upper) || (range->old_lower == (range->old_upper - range->old_page_size))) { adjustment->value = adjustment->lower; gtk_signal_emit_by_name (GTK_OBJECT (adjustment), "value_changed"); } } if ((range->old_value != adjustment->value) || (range->old_lower != adjustment->lower) || (range->old_upper != adjustment->upper) || (range->old_page_size != adjustment->page_size)) { _gtk_range_slider_update (range); _gtk_range_clear_background (range); range->old_value = adjustment->value; range->old_lower = adjustment->lower; range->old_upper = adjustment->upper; range->old_page_size = adjustment->page_size; } } static void gtk_range_adjustment_value_changed (GtkAdjustment *adjustment, gpointer data) { GtkRange *range; g_return_if_fail (adjustment != NULL); g_return_if_fail (data != NULL); range = GTK_RANGE (data); if (range->old_value != adjustment->value) { _gtk_range_slider_update (range); _gtk_range_clear_background (range); range->old_value = adjustment->value; } } static void gtk_range_trough_hdims (GtkRange *range, gint *left, gint *right) { gint trough_width; gint slider_length; gint tmp_width; gint tleft; gint tright; gint stepper_spacing; gint trough_border; g_return_if_fail (range != NULL); gdk_window_get_size (range->trough, &trough_width, NULL); gdk_window_get_size (range->slider, &slider_length, NULL); _gtk_range_get_props (range, NULL, &trough_border, NULL, &stepper_spacing); tleft = trough_border; tright = trough_width - slider_length - trough_border; if (range->step_back) { gdk_window_get_size (range->step_back, &tmp_width, NULL); tleft += (tmp_width + stepper_spacing); } if (range->step_forw) { gdk_window_get_size (range->step_forw, &tmp_width, NULL); tright -= (tmp_width + stepper_spacing); } if (left) *left = tleft; if (right) *right = tright; } static void gtk_range_trough_vdims (GtkRange *range, gint *top, gint *bottom) { gint trough_height; gint slider_length; gint tmp_height; gint ttop; gint tbottom; gint stepper_spacing; gint trough_border; g_return_if_fail (range != NULL); _gtk_range_get_props (range, NULL, &trough_border, NULL, &stepper_spacing); gdk_window_get_size (range->trough, NULL, &trough_height); gdk_window_get_size (range->slider, NULL, &slider_length); ttop = trough_border; tbottom = trough_height - slider_length - trough_border; if (range->step_back) { gdk_window_get_size (range->step_back, NULL, &tmp_height); ttop += (tmp_height + stepper_spacing); } if (range->step_forw) { gdk_window_get_size (range->step_forw, NULL, &tmp_height); tbottom -= (tmp_height + stepper_spacing); } if (top) *top = ttop; if (bottom) *bottom = tbottom; } static void gtk_range_style_set (GtkWidget *widget, GtkStyle *previous_style) { GtkRange *range; g_return_if_fail (widget != NULL); g_return_if_fail (GTK_IS_RANGE (widget)); range = GTK_RANGE (widget); if (GTK_WIDGET_REALIZED (widget)) { if (range->trough) gtk_style_set_background (widget->style, range->trough, GTK_STATE_ACTIVE); if (range->slider) gtk_style_set_background (widget->style, range->slider, GTK_STATE_NORMAL); /* The backgrounds of the step_forw and step_back never actually * get drawn in draw calls, so we call gdk_window_clear() here * so they get the correct colors. This is a hack. OWT. */ if (range->step_forw) { gtk_style_set_background (widget->style, range->step_forw, GTK_STATE_ACTIVE); gdk_window_clear (range->step_forw); } if (range->step_back) { gtk_style_set_background (widget->style, range->step_back, GTK_STATE_ACTIVE); gdk_window_clear (range->step_back); } } } void _gtk_range_get_props (GtkRange *range, gint *slider_width, gint *trough_border, gint *stepper_size, gint *stepper_spacing) { GtkWidget *widget = GTK_WIDGET (range); if (slider_width) gtk_widget_style_get (widget, "slider_width", slider_width, NULL); if (trough_border) gtk_widget_style_get (widget, "trough_border", trough_border, NULL); if (stepper_size) gtk_widget_style_get (widget, "stepper_size", stepper_size, NULL); if (stepper_spacing) gtk_widget_style_get (widget, "stepper_spacing", stepper_spacing, NULL); }