1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3 * Copyright (C) 2001 Red Hat, Inc.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
22 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
23 * file for a list of people on the GTK+ Team. See the ChangeLog
24 * files for a list of changes. These files are distributed with
25 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
33 #include "gdk/gdkkeysyms.h"
35 #include "gtkmarshalers.h"
36 #include "gtkbindings.h"
37 #include "gtkprivate.h"
42 #define MAX_DIGITS (64) /* don't change this,
43 * a) you don't need to and
44 * b) you might cause buffer owerflows in
45 * unrelated code portions otherwise
48 #define GTK_SCALE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_SCALE, GtkScalePrivate))
50 typedef struct _GtkScalePrivate GtkScalePrivate;
52 struct _GtkScalePrivate
69 static guint signals[LAST_SIGNAL];
71 static void gtk_scale_set_property (GObject *object,
75 static void gtk_scale_get_property (GObject *object,
79 static void gtk_scale_style_set (GtkWidget *widget,
81 static void gtk_scale_get_range_border (GtkRange *range,
83 static void gtk_scale_finalize (GObject *object);
84 static void gtk_scale_screen_changed (GtkWidget *widget,
85 GdkScreen *old_screen);
86 static gboolean gtk_scale_expose (GtkWidget *widget,
87 GdkEventExpose *event);
88 static void gtk_scale_real_get_layout_offsets (GtkScale *scale,
92 G_DEFINE_TYPE (GtkScale, gtk_scale, GTK_TYPE_RANGE)
95 single_string_accumulator (GSignalInvocationHint *ihint,
97 const GValue *handler_return,
100 gboolean continue_emission;
103 str = g_value_get_string (handler_return);
104 g_value_set_string (return_accu, str);
105 continue_emission = str == NULL;
107 return continue_emission;
111 #define add_slider_binding(binding_set, keyval, mask, scroll) \
112 gtk_binding_entry_add_signal (binding_set, keyval, mask, \
113 I_("move-slider"), 1, \
114 GTK_TYPE_SCROLL_TYPE, scroll)
117 gtk_scale_class_init (GtkScaleClass *class)
119 GObjectClass *gobject_class;
120 GtkWidgetClass *widget_class;
121 GtkRangeClass *range_class;
122 GtkBindingSet *binding_set;
124 gobject_class = G_OBJECT_CLASS (class);
125 range_class = (GtkRangeClass*) class;
126 widget_class = (GtkWidgetClass*) class;
128 gobject_class->set_property = gtk_scale_set_property;
129 gobject_class->get_property = gtk_scale_get_property;
130 gobject_class->finalize = gtk_scale_finalize;
132 widget_class->style_set = gtk_scale_style_set;
133 widget_class->screen_changed = gtk_scale_screen_changed;
134 widget_class->expose_event = gtk_scale_expose;
136 range_class->slider_detail = "Xscale";
137 range_class->get_range_border = gtk_scale_get_range_border;
139 class->get_layout_offsets = gtk_scale_real_get_layout_offsets;
141 signals[FORMAT_VALUE] =
142 g_signal_new (I_("format-value"),
143 G_TYPE_FROM_CLASS (gobject_class),
145 G_STRUCT_OFFSET (GtkScaleClass, format_value),
146 single_string_accumulator, NULL,
147 _gtk_marshal_STRING__DOUBLE,
151 g_object_class_install_property (gobject_class,
153 g_param_spec_int ("digits",
155 P_("The number of decimal places that are displayed in the value"),
159 GTK_PARAM_READWRITE));
161 g_object_class_install_property (gobject_class,
163 g_param_spec_boolean ("draw-value",
165 P_("Whether the current value is displayed as a string next to the slider"),
167 GTK_PARAM_READWRITE));
169 g_object_class_install_property (gobject_class,
171 g_param_spec_enum ("value-pos",
172 P_("Value Position"),
173 P_("The position in which the current value is displayed"),
174 GTK_TYPE_POSITION_TYPE,
176 GTK_PARAM_READWRITE));
178 gtk_widget_class_install_style_property (widget_class,
179 g_param_spec_int ("slider-length",
181 P_("Length of scale's slider"),
185 GTK_PARAM_READABLE));
187 gtk_widget_class_install_style_property (widget_class,
188 g_param_spec_int ("value-spacing",
190 P_("Space between value text and the slider/trough area"),
194 GTK_PARAM_READABLE));
196 /* All bindings (even arrow keys) are on both h/v scale, because
197 * blind users etc. don't care about scale orientation.
200 binding_set = gtk_binding_set_by_class (class);
202 add_slider_binding (binding_set, GDK_Left, 0,
203 GTK_SCROLL_STEP_LEFT);
205 add_slider_binding (binding_set, GDK_Left, GDK_CONTROL_MASK,
206 GTK_SCROLL_PAGE_LEFT);
208 add_slider_binding (binding_set, GDK_KP_Left, 0,
209 GTK_SCROLL_STEP_LEFT);
211 add_slider_binding (binding_set, GDK_KP_Left, GDK_CONTROL_MASK,
212 GTK_SCROLL_PAGE_LEFT);
214 add_slider_binding (binding_set, GDK_Right, 0,
215 GTK_SCROLL_STEP_RIGHT);
217 add_slider_binding (binding_set, GDK_Right, GDK_CONTROL_MASK,
218 GTK_SCROLL_PAGE_RIGHT);
220 add_slider_binding (binding_set, GDK_KP_Right, 0,
221 GTK_SCROLL_STEP_RIGHT);
223 add_slider_binding (binding_set, GDK_KP_Right, GDK_CONTROL_MASK,
224 GTK_SCROLL_PAGE_RIGHT);
226 add_slider_binding (binding_set, GDK_Up, 0,
229 add_slider_binding (binding_set, GDK_Up, GDK_CONTROL_MASK,
232 add_slider_binding (binding_set, GDK_KP_Up, 0,
235 add_slider_binding (binding_set, GDK_KP_Up, GDK_CONTROL_MASK,
238 add_slider_binding (binding_set, GDK_Down, 0,
239 GTK_SCROLL_STEP_DOWN);
241 add_slider_binding (binding_set, GDK_Down, GDK_CONTROL_MASK,
242 GTK_SCROLL_PAGE_DOWN);
244 add_slider_binding (binding_set, GDK_KP_Down, 0,
245 GTK_SCROLL_STEP_DOWN);
247 add_slider_binding (binding_set, GDK_KP_Down, GDK_CONTROL_MASK,
248 GTK_SCROLL_PAGE_DOWN);
250 add_slider_binding (binding_set, GDK_Page_Up, GDK_CONTROL_MASK,
251 GTK_SCROLL_PAGE_LEFT);
253 add_slider_binding (binding_set, GDK_KP_Page_Up, GDK_CONTROL_MASK,
254 GTK_SCROLL_PAGE_LEFT);
256 add_slider_binding (binding_set, GDK_Page_Up, 0,
259 add_slider_binding (binding_set, GDK_KP_Page_Up, 0,
262 add_slider_binding (binding_set, GDK_Page_Down, GDK_CONTROL_MASK,
263 GTK_SCROLL_PAGE_RIGHT);
265 add_slider_binding (binding_set, GDK_KP_Page_Down, GDK_CONTROL_MASK,
266 GTK_SCROLL_PAGE_RIGHT);
268 add_slider_binding (binding_set, GDK_Page_Down, 0,
269 GTK_SCROLL_PAGE_DOWN);
271 add_slider_binding (binding_set, GDK_KP_Page_Down, 0,
272 GTK_SCROLL_PAGE_DOWN);
274 /* Logical bindings (vs. visual bindings above) */
276 add_slider_binding (binding_set, GDK_plus, 0,
277 GTK_SCROLL_STEP_FORWARD);
279 add_slider_binding (binding_set, GDK_minus, 0,
280 GTK_SCROLL_STEP_BACKWARD);
282 add_slider_binding (binding_set, GDK_plus, GDK_CONTROL_MASK,
283 GTK_SCROLL_PAGE_FORWARD);
285 add_slider_binding (binding_set, GDK_minus, GDK_CONTROL_MASK,
286 GTK_SCROLL_PAGE_BACKWARD);
289 add_slider_binding (binding_set, GDK_KP_Add, 0,
290 GTK_SCROLL_STEP_FORWARD);
292 add_slider_binding (binding_set, GDK_KP_Subtract, 0,
293 GTK_SCROLL_STEP_BACKWARD);
295 add_slider_binding (binding_set, GDK_KP_Add, GDK_CONTROL_MASK,
296 GTK_SCROLL_PAGE_FORWARD);
298 add_slider_binding (binding_set, GDK_KP_Subtract, GDK_CONTROL_MASK,
299 GTK_SCROLL_PAGE_BACKWARD);
302 add_slider_binding (binding_set, GDK_Home, 0,
305 add_slider_binding (binding_set, GDK_KP_Home, 0,
308 add_slider_binding (binding_set, GDK_End, 0,
311 add_slider_binding (binding_set, GDK_KP_End, 0,
314 g_type_class_add_private (gobject_class, sizeof (GtkScalePrivate));
318 gtk_scale_orientation_notify (GtkRange *range,
319 const GParamSpec *pspec)
321 range->flippable = (range->orientation == GTK_ORIENTATION_HORIZONTAL);
325 gtk_scale_init (GtkScale *scale)
327 GtkRange *range = GTK_RANGE (scale);
329 GTK_WIDGET_SET_FLAGS (scale, GTK_CAN_FOCUS);
331 range->slider_size_fixed = TRUE;
332 range->has_stepper_a = FALSE;
333 range->has_stepper_b = FALSE;
334 range->has_stepper_c = FALSE;
335 range->has_stepper_d = FALSE;
337 scale->draw_value = TRUE;
338 scale->value_pos = GTK_POS_TOP;
340 range->round_digits = scale->digits;
342 gtk_scale_orientation_notify (range, NULL);
343 g_signal_connect (scale, "notify::orientation",
344 G_CALLBACK (gtk_scale_orientation_notify),
349 gtk_scale_set_property (GObject *object,
356 scale = GTK_SCALE (object);
361 gtk_scale_set_digits (scale, g_value_get_int (value));
363 case PROP_DRAW_VALUE:
364 gtk_scale_set_draw_value (scale, g_value_get_boolean (value));
367 gtk_scale_set_value_pos (scale, g_value_get_enum (value));
370 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
376 gtk_scale_get_property (GObject *object,
383 scale = GTK_SCALE (object);
388 g_value_set_int (value, scale->digits);
390 case PROP_DRAW_VALUE:
391 g_value_set_boolean (value, scale->draw_value);
394 g_value_set_enum (value, scale->value_pos);
397 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
404 * @orientation: the scale's orientation.
405 * @adjustment: the #GtkAdjustment which sets the range of the scale, or
406 * %NULL to create a new adjustment.
408 * Creates a new #GtkScale.
410 * Return value: a new #GtkScale
415 gtk_scale_new (GtkOrientation orientation,
416 GtkAdjustment *adjustment)
418 g_return_val_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment),
421 return g_object_new (GTK_TYPE_SCALE,
422 "orientation", orientation,
423 "adjustment", adjustment,
428 * gtk_scale_new_with_range:
429 * @orientation: the scale's orientation.
430 * @min: minimum value
431 * @max: maximum value
432 * @step: step increment (tick size) used with keyboard shortcuts
434 * Creates a new scale widget with the given orientation that lets the
435 * user input a number between @min and @max (including @min and @max)
436 * with the increment @step. @step must be nonzero; it's the distance
437 * the slider moves when using the arrow keys to adjust the scale
440 * Note that the way in which the precision is derived works best if @step
441 * is a power of ten. If the resulting precision is not suitable for your
442 * needs, use gtk_scale_set_digits() to correct it.
444 * Return value: a new #GtkScale
449 gtk_scale_new_with_range (GtkOrientation orientation,
457 g_return_val_if_fail (min < max, NULL);
458 g_return_val_if_fail (step != 0.0, NULL);
460 adj = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
462 if (fabs (step) >= 1.0 || step == 0.0)
468 digits = abs ((gint) floor (log10 (fabs (step))));
473 return g_object_new (GTK_TYPE_SCALE,
474 "orientation", orientation,
481 gtk_scale_set_digits (GtkScale *scale,
486 g_return_if_fail (GTK_IS_SCALE (scale));
488 range = GTK_RANGE (scale);
490 digits = CLAMP (digits, -1, MAX_DIGITS);
492 if (scale->digits != digits)
494 scale->digits = digits;
495 if (scale->draw_value)
496 range->round_digits = digits;
498 _gtk_scale_clear_layout (scale);
499 gtk_widget_queue_resize (GTK_WIDGET (scale));
501 g_object_notify (G_OBJECT (scale), "digits");
506 gtk_scale_get_digits (GtkScale *scale)
508 g_return_val_if_fail (GTK_IS_SCALE (scale), -1);
510 return scale->digits;
514 gtk_scale_set_draw_value (GtkScale *scale,
517 g_return_if_fail (GTK_IS_SCALE (scale));
519 draw_value = draw_value != FALSE;
521 if (scale->draw_value != draw_value)
523 scale->draw_value = draw_value;
525 GTK_RANGE (scale)->round_digits = scale->digits;
527 GTK_RANGE (scale)->round_digits = -1;
529 _gtk_scale_clear_layout (scale);
531 gtk_widget_queue_resize (GTK_WIDGET (scale));
533 g_object_notify (G_OBJECT (scale), "draw-value");
538 gtk_scale_get_draw_value (GtkScale *scale)
540 g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE);
542 return scale->draw_value;
546 gtk_scale_set_value_pos (GtkScale *scale,
549 g_return_if_fail (GTK_IS_SCALE (scale));
551 if (scale->value_pos != pos)
553 scale->value_pos = pos;
555 _gtk_scale_clear_layout (scale);
556 if (GTK_WIDGET_VISIBLE (scale) && GTK_WIDGET_MAPPED (scale))
557 gtk_widget_queue_resize (GTK_WIDGET (scale));
559 g_object_notify (G_OBJECT (scale), "value-pos");
564 gtk_scale_get_value_pos (GtkScale *scale)
566 g_return_val_if_fail (GTK_IS_SCALE (scale), 0);
568 return scale->value_pos;
572 gtk_scale_get_range_border (GtkRange *range,
579 widget = GTK_WIDGET (range);
580 scale = GTK_SCALE (range);
582 _gtk_scale_get_value_size (scale, &w, &h);
589 if (scale->draw_value)
592 gtk_widget_style_get (widget, "value-spacing", &value_spacing, NULL);
594 switch (scale->value_pos)
597 border->left += w + value_spacing;
600 border->right += w + value_spacing;
603 border->top += h + value_spacing;
606 border->bottom += h + value_spacing;
612 /* FIXME this could actually be static at the moment. */
614 _gtk_scale_get_value_size (GtkScale *scale,
620 g_return_if_fail (GTK_IS_SCALE (scale));
622 if (scale->draw_value)
625 PangoRectangle logical_rect;
628 range = GTK_RANGE (scale);
630 layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
632 txt = _gtk_scale_format_value (scale, range->adjustment->lower);
633 pango_layout_set_text (layout, txt, -1);
636 pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
639 *width = logical_rect.width;
641 *height = logical_rect.height;
643 txt = _gtk_scale_format_value (scale, range->adjustment->upper);
644 pango_layout_set_text (layout, txt, -1);
647 pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
650 *width = MAX (*width, logical_rect.width);
652 *height = MAX (*height, logical_rect.height);
654 g_object_unref (layout);
667 gtk_scale_style_set (GtkWidget *widget,
673 range = GTK_RANGE (widget);
675 gtk_widget_style_get (widget,
676 "slider-length", &slider_length,
679 range->min_slider_size = slider_length;
681 _gtk_scale_clear_layout (GTK_SCALE (widget));
683 GTK_WIDGET_CLASS (gtk_scale_parent_class)->style_set (widget, previous);
687 gtk_scale_screen_changed (GtkWidget *widget,
688 GdkScreen *old_screen)
690 _gtk_scale_clear_layout (GTK_SCALE (widget));
694 gtk_scale_expose (GtkWidget *widget,
695 GdkEventExpose *event)
697 GtkScale *scale = GTK_SCALE (widget);
699 /* We need to chain up _first_ so the various geometry members of
700 * GtkRange struct are updated.
702 GTK_WIDGET_CLASS (gtk_scale_parent_class)->expose_event (widget, event);
704 if (scale->draw_value)
706 GtkRange *range = GTK_RANGE (scale);
709 GtkStateType state_type;
711 layout = gtk_scale_get_layout (scale);
712 gtk_scale_get_layout_offsets (scale, &x, &y);
714 state_type = GTK_STATE_NORMAL;
715 if (!GTK_WIDGET_IS_SENSITIVE (scale))
716 state_type = GTK_STATE_INSENSITIVE;
718 gtk_paint_layout (widget->style,
724 range->orientation == GTK_ORIENTATION_HORIZONTAL ?
735 gtk_scale_real_get_layout_offsets (GtkScale *scale,
739 GtkWidget *widget = GTK_WIDGET (scale);
740 GtkRange *range = GTK_RANGE (widget);
741 PangoLayout *layout = gtk_scale_get_layout (scale);
742 PangoRectangle logical_rect;
753 gtk_widget_style_get (widget, "value-spacing", &value_spacing, NULL);
755 pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
757 if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
759 switch (scale->value_pos)
762 *x = range->range_rect.x - value_spacing - logical_rect.width;
763 *y = range->range_rect.y + (range->range_rect.height - logical_rect.height) / 2;
767 *x = range->range_rect.x + range->range_rect.width + value_spacing;
768 *y = range->range_rect.y + (range->range_rect.height - logical_rect.height) / 2;
772 *x = range->slider_start +
773 (range->slider_end - range->slider_start - logical_rect.width) / 2;
774 *x = CLAMP (*x, 0, widget->allocation.width - logical_rect.width);
775 *y = range->range_rect.y - logical_rect.height - value_spacing;
779 *x = range->slider_start +
780 (range->slider_end - range->slider_start - logical_rect.width) / 2;
781 *x = CLAMP (*x, 0, widget->allocation.width - logical_rect.width);
782 *y = range->range_rect.y + range->range_rect.height + value_spacing;
786 g_return_if_reached ();
792 switch (scale->value_pos)
795 *x = range->range_rect.x - logical_rect.width - value_spacing;
796 *y = range->slider_start + (range->slider_end - range->slider_start - logical_rect.height) / 2;
797 *y = CLAMP (*y, 0, widget->allocation.height - logical_rect.height);
801 *x = range->range_rect.x + range->range_rect.width + value_spacing;
802 *y = range->slider_start + (range->slider_end - range->slider_start - logical_rect.height) / 2;
803 *y = CLAMP (*y, 0, widget->allocation.height - logical_rect.height);
807 *x = range->range_rect.x + (range->range_rect.width - logical_rect.width) / 2;
808 *y = range->range_rect.y - logical_rect.height - value_spacing;
812 *x = range->range_rect.x + (range->range_rect.width - logical_rect.width) / 2;
813 *y = range->range_rect.y + range->range_rect.height + value_spacing;
817 g_return_if_reached ();
821 *x += widget->allocation.x;
822 *y += widget->allocation.y;
826 * _gtk_scale_format_value:
827 * @scale: a #GtkScale
828 * @value: adjustment value
830 * Emits #GtkScale::format-value signal to format the value,
831 * if no user signal handlers, falls back to a default format.
833 * Return value: formatted value
836 _gtk_scale_format_value (GtkScale *scale,
841 g_signal_emit (scale,
842 signals[FORMAT_VALUE],
850 /* insert a LRM, to prevent -20 to come out as 20- in RTL locales */
851 return g_strdup_printf ("\342\200\216%0.*f", scale->digits, value);
855 gtk_scale_finalize (GObject *object)
857 GtkScale *scale = GTK_SCALE (object);
859 _gtk_scale_clear_layout (scale);
861 G_OBJECT_CLASS (gtk_scale_parent_class)->finalize (object);
865 * gtk_scale_get_layout:
866 * @scale: A #GtkScale
868 * Gets the #PangoLayout used to display the scale.
869 * The returned object is owned by the scale so does
870 * not need to be freed by the caller.
872 * Return value: the #PangoLayout for this scale, or %NULL
873 * if the #GtkScale:draw-value property is %FALSE.
878 gtk_scale_get_layout (GtkScale *scale)
880 GtkScalePrivate *priv = GTK_SCALE_GET_PRIVATE (scale);
883 g_return_val_if_fail (GTK_IS_SCALE (scale), NULL);
887 if (scale->draw_value)
888 priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (scale), NULL);
891 if (scale->draw_value)
893 txt = _gtk_scale_format_value (scale,
894 GTK_RANGE (scale)->adjustment->value);
895 pango_layout_set_text (priv->layout, txt, -1);
903 * gtk_scale_get_layout_offsets:
904 * @scale: a #GtkScale
905 * @x: location to store X offset of layout, or %NULL
906 * @y: location to store Y offset of layout, or %NULL
908 * Obtains the coordinates where the scale will draw the
909 * #PangoLayout representing the text in the scale. Remember
910 * when using the #PangoLayout function you need to convert to
911 * and from pixels using PANGO_PIXELS() or #PANGO_SCALE.
913 * If the #GtkScale:draw-value property is %FALSE, the return
914 * values are undefined.
919 gtk_scale_get_layout_offsets (GtkScale *scale,
926 g_return_if_fail (GTK_IS_SCALE (scale));
928 if (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets)
929 (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets) (scale, &local_x, &local_y);
939 _gtk_scale_clear_layout (GtkScale *scale)
941 GtkScalePrivate *priv = GTK_SCALE_GET_PRIVATE (scale);
943 g_return_if_fail (GTK_IS_SCALE (scale));
947 g_object_unref (priv->layout);
952 #define __GTK_SCALE_C__
953 #include "gtkaliasdef.c"