1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
21 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
22 * file for a list of people on the GTK+ Team. See the ChangeLog
23 * files for a list of changes. These files are distributed with
24 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
32 #include "gtkorientable.h"
34 #include "gtkprivate.h"
37 #define RULER_WIDTH 14
38 #define MINIMUM_INCR 5
39 #define MAXIMUM_SUBDIVIDE 5
40 #define MAXIMUM_SCALES 10
42 #define ROUND(x) ((int) ((x) + 0.5))
44 struct _GtkRulerPrivate
46 GtkOrientation orientation;
47 GtkRulerMetric *metric;
49 cairo_surface_t *backing_store;
55 gdouble lower; /* The upper limit of the ruler (in points) */
56 gdouble max_size; /* The maximum size of the ruler */
57 gdouble position; /* The position of the mark on the ruler */
58 gdouble upper; /* The lower limit of the ruler */
72 static void gtk_ruler_set_property (GObject *object,
76 static void gtk_ruler_get_property (GObject *object,
80 static void gtk_ruler_realize (GtkWidget *widget);
81 static void gtk_ruler_unrealize (GtkWidget *widget);
82 static void gtk_ruler_size_request (GtkWidget *widget,
83 GtkRequisition *requisition);
84 static void gtk_ruler_size_allocate (GtkWidget *widget,
85 GtkAllocation *allocation);
86 static gboolean gtk_ruler_motion_notify (GtkWidget *widget,
87 GdkEventMotion *event);
88 static gboolean gtk_ruler_expose (GtkWidget *widget,
89 GdkEventExpose *event);
90 static void gtk_ruler_make_pixmap (GtkRuler *ruler);
91 static void gtk_ruler_real_draw_ticks (GtkRuler *ruler,
93 static void gtk_ruler_real_draw_pos (GtkRuler *ruler);
96 static const GtkRulerMetric ruler_metrics[] =
98 { "Pixel", "Pi", 1.0, { 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000 }, { 1, 5, 10, 50, 100 }},
99 { "Inches", "In", 72.0, { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 }, { 1, 2, 4, 8, 16 }},
100 { "Centimeters", "Cn", 28.35, { 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000 }, { 1, 5, 10, 50, 100 }},
104 G_DEFINE_TYPE_WITH_CODE (GtkRuler, gtk_ruler, GTK_TYPE_WIDGET,
105 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,
110 gtk_ruler_class_init (GtkRulerClass *class)
112 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
113 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
115 gobject_class->set_property = gtk_ruler_set_property;
116 gobject_class->get_property = gtk_ruler_get_property;
118 widget_class->realize = gtk_ruler_realize;
119 widget_class->unrealize = gtk_ruler_unrealize;
120 widget_class->size_request = gtk_ruler_size_request;
121 widget_class->size_allocate = gtk_ruler_size_allocate;
122 widget_class->motion_notify_event = gtk_ruler_motion_notify;
123 widget_class->expose_event = gtk_ruler_expose;
125 class->draw_ticks = gtk_ruler_real_draw_ticks;
126 class->draw_pos = gtk_ruler_real_draw_pos;
128 g_object_class_override_property (gobject_class,
132 g_object_class_install_property (gobject_class,
134 g_param_spec_double ("lower",
136 P_("Lower limit of ruler"),
140 GTK_PARAM_READWRITE));
142 g_object_class_install_property (gobject_class,
144 g_param_spec_double ("upper",
146 P_("Upper limit of ruler"),
150 GTK_PARAM_READWRITE));
152 g_object_class_install_property (gobject_class,
154 g_param_spec_double ("position",
156 P_("Position of mark on the ruler"),
160 GTK_PARAM_READWRITE));
162 g_object_class_install_property (gobject_class,
164 g_param_spec_double ("max-size",
166 P_("Maximum size of the ruler"),
170 GTK_PARAM_READWRITE));
174 * The metric used for the ruler.
178 g_object_class_install_property (gobject_class,
180 g_param_spec_enum ("metric",
182 P_("The metric used for the ruler"),
183 GTK_TYPE_METRIC_TYPE,
185 GTK_PARAM_READWRITE));
187 g_type_class_add_private (gobject_class, sizeof (GtkRulerPrivate));
191 gtk_ruler_init (GtkRuler *ruler)
193 GtkRulerPrivate *priv;
195 ruler->priv = G_TYPE_INSTANCE_GET_PRIVATE (ruler,
200 priv->orientation = GTK_ORIENTATION_HORIZONTAL;
202 priv->backing_store = NULL;
205 priv->slider_size = 0;
211 gtk_ruler_set_metric (ruler, GTK_PIXELS);
215 gtk_ruler_set_property (GObject *object,
220 GtkRuler *ruler = GTK_RULER (object);
221 GtkRulerPrivate *priv = ruler->priv;
225 case PROP_ORIENTATION:
226 priv->orientation = g_value_get_enum (value);
227 gtk_widget_queue_resize (GTK_WIDGET (ruler));
230 gtk_ruler_set_range (ruler, g_value_get_double (value), priv->upper,
231 priv->position, priv->max_size);
234 gtk_ruler_set_range (ruler, priv->lower, g_value_get_double (value),
235 priv->position, priv->max_size);
238 gtk_ruler_set_range (ruler, priv->lower, priv->upper,
239 g_value_get_double (value), priv->max_size);
242 gtk_ruler_set_range (ruler, priv->lower, priv->upper,
243 priv->position, g_value_get_double (value));
246 gtk_ruler_set_metric (ruler, g_value_get_enum (value));
249 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
255 gtk_ruler_get_property (GObject *object,
260 GtkRuler *ruler = GTK_RULER (object);
261 GtkRulerPrivate *priv = ruler->priv;
265 case PROP_ORIENTATION:
266 g_value_set_enum (value, priv->orientation);
269 g_value_set_double (value, priv->lower);
272 g_value_set_double (value, priv->upper);
275 g_value_set_double (value, priv->position);
278 g_value_set_double (value, priv->max_size);
281 g_value_set_enum (value, gtk_ruler_get_metric (ruler));
284 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
291 * @orientation: the ruler's orientation.
293 * Creates a new #GtkRuler with the given orientation.
295 * Return value: a new #GtkRuler.
300 gtk_ruler_new (GtkOrientation orientation)
302 return g_object_new (GTK_TYPE_RULER,
303 "orientation", orientation,
308 * gtk_ruler_invalidate_ticks:
309 * @ruler: the ruler to invalidate
311 * For performance reasons, #GtkRuler keeps a backbuffer containing the
312 * prerendered contents of the ticks. To cause a repaint this buffer,
313 * call this function instead of gtk_widget_queue_draw().
316 gtk_ruler_invalidate_ticks (GtkRuler *ruler)
318 g_return_if_fail (GTK_IS_RULER (ruler));
320 if (ruler->priv->backing_store == NULL)
323 gtk_ruler_draw_ticks (ruler);
324 gtk_widget_queue_draw (GTK_WIDGET (ruler));
328 gtk_ruler_set_metric (GtkRuler *ruler,
329 GtkMetricType metric)
331 GtkRulerPrivate *priv;
333 g_return_if_fail (GTK_IS_RULER (ruler));
337 priv->metric = (GtkRulerMetric *) &ruler_metrics[metric];
339 g_object_notify (G_OBJECT (ruler), "metric");
341 gtk_ruler_invalidate_ticks (ruler);
345 * gtk_ruler_get_metric:
346 * @ruler: a #GtkRuler
348 * Gets the units used for a #GtkRuler. See gtk_ruler_set_metric().
350 * Return value: the units currently used for @ruler
353 gtk_ruler_get_metric (GtkRuler *ruler)
355 GtkRulerPrivate *priv;
358 g_return_val_if_fail (GTK_IS_RULER (ruler), 0);
362 for (i = 0; i < G_N_ELEMENTS (ruler_metrics); i++)
363 if (priv->metric == &ruler_metrics[i])
366 g_assert_not_reached ();
372 * gtk_ruler_set_range:
373 * @ruler: the gtkruler
374 * @lower: the lower limit of the ruler
375 * @upper: the upper limit of the ruler
376 * @position: the mark on the ruler
377 * @max_size: the maximum size of the ruler used when calculating the space to
380 * This sets the range of the ruler.
383 gtk_ruler_set_range (GtkRuler *ruler,
389 GtkRulerPrivate *priv;
391 g_return_if_fail (GTK_IS_RULER (ruler));
395 g_object_freeze_notify (G_OBJECT (ruler));
396 if (priv->lower != lower)
399 g_object_notify (G_OBJECT (ruler), "lower");
401 if (priv->upper != upper)
404 g_object_notify (G_OBJECT (ruler), "upper");
406 if (priv->position != position)
408 priv->position = position;
409 g_object_notify (G_OBJECT (ruler), "position");
411 if (priv->max_size != max_size)
413 priv->max_size = max_size;
414 g_object_notify (G_OBJECT (ruler), "max-size");
416 g_object_thaw_notify (G_OBJECT (ruler));
418 gtk_ruler_invalidate_ticks (ruler);
422 * gtk_ruler_get_range:
423 * @ruler: a #GtkRuler
424 * @lower: (allow-none): location to store lower limit of the ruler, or %NULL
425 * @upper: (allow-none): location to store upper limit of the ruler, or %NULL
426 * @position: (allow-none): location to store the current position of the mark on the ruler, or %NULL
427 * @max_size: location to store the maximum size of the ruler used when calculating
428 * the space to leave for the text, or %NULL.
430 * Retrieves values indicating the range and current position of a #GtkRuler.
431 * See gtk_ruler_set_range().
434 gtk_ruler_get_range (GtkRuler *ruler,
440 GtkRulerPrivate *priv;
442 g_return_if_fail (GTK_IS_RULER (ruler));
447 *lower = priv->lower;
449 *upper = priv->upper;
451 *position = priv->position;
453 *max_size = priv->max_size;
457 gtk_ruler_draw_ticks (GtkRuler *ruler)
459 GtkRulerPrivate *priv = ruler->priv;
462 g_return_if_fail (GTK_IS_RULER (ruler));
464 if (priv->backing_store == NULL)
467 cr = cairo_create (priv->backing_store);
469 if (GTK_RULER_GET_CLASS (ruler)->draw_ticks)
470 GTK_RULER_GET_CLASS (ruler)->draw_ticks (ruler, cr);
476 gtk_ruler_draw_pos (GtkRuler *ruler)
478 g_return_if_fail (GTK_IS_RULER (ruler));
480 if (GTK_RULER_GET_CLASS (ruler)->draw_pos)
481 GTK_RULER_GET_CLASS (ruler)->draw_pos (ruler);
486 gtk_ruler_realize (GtkWidget *widget)
488 GtkAllocation allocation;
491 GdkWindowAttr attributes;
492 gint attributes_mask;
494 ruler = GTK_RULER (widget);
496 gtk_widget_set_realized (widget, TRUE);
498 gtk_widget_get_allocation (widget, &allocation);
500 attributes.window_type = GDK_WINDOW_CHILD;
501 attributes.x = allocation.x;
502 attributes.y = allocation.y;
503 attributes.width = allocation.width;
504 attributes.height = allocation.height;
505 attributes.wclass = GDK_INPUT_OUTPUT;
506 attributes.visual = gtk_widget_get_visual (widget);
507 attributes.event_mask = gtk_widget_get_events (widget);
508 attributes.event_mask |= (GDK_EXPOSURE_MASK |
509 GDK_POINTER_MOTION_MASK |
510 GDK_POINTER_MOTION_HINT_MASK);
512 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
514 window = gdk_window_new (gtk_widget_get_parent_window (widget),
515 &attributes, attributes_mask);
516 gtk_widget_set_window (widget, window);
517 gdk_window_set_user_data (window, ruler);
519 gtk_widget_style_attach (widget);
520 gtk_style_set_background (gtk_widget_get_style (widget),
521 window, GTK_STATE_ACTIVE);
523 gtk_ruler_make_pixmap (ruler);
527 gtk_ruler_unrealize (GtkWidget *widget)
529 GtkRuler *ruler = GTK_RULER (widget);
530 GtkRulerPrivate *priv = ruler->priv;
532 if (priv->backing_store)
534 cairo_surface_destroy (priv->backing_store);
535 priv->backing_store = NULL;
538 GTK_WIDGET_CLASS (gtk_ruler_parent_class)->unrealize (widget);
542 gtk_ruler_size_request (GtkWidget *widget,
543 GtkRequisition *requisition)
545 GtkRuler *ruler = GTK_RULER (widget);
546 GtkRulerPrivate *priv = ruler->priv;
549 style = gtk_widget_get_style (widget);
551 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
553 requisition->width = style->xthickness * 2 + 1;
554 requisition->height = style->ythickness * 2 + RULER_WIDTH;
558 requisition->width = style->xthickness * 2 + RULER_WIDTH;
559 requisition->height = style->ythickness * 2 + 1;
564 gtk_ruler_size_allocate (GtkWidget *widget,
565 GtkAllocation *allocation)
567 GtkRuler *ruler = GTK_RULER (widget);
568 GtkAllocation old_allocation;
571 gtk_widget_get_allocation (widget, &old_allocation);
572 resized = (old_allocation.width != allocation->width ||
573 old_allocation.height != allocation->height);
575 gtk_widget_set_allocation (widget, allocation);
577 if (gtk_widget_get_realized (widget))
579 gdk_window_move_resize (gtk_widget_get_window (widget),
580 allocation->x, allocation->y,
581 allocation->width, allocation->height);
584 gtk_ruler_make_pixmap (ruler);
589 gtk_ruler_motion_notify (GtkWidget *widget,
590 GdkEventMotion *event)
592 GtkAllocation allocation;
593 GtkRuler *ruler = GTK_RULER (widget);
594 GtkRulerPrivate *priv = ruler->priv;
598 gdk_event_request_motions (event);
602 gtk_widget_get_allocation (widget, &allocation);
603 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
604 priv->position = priv->lower + ((priv->upper - priv->lower) * x) / allocation.width;
606 priv->position = priv->lower + ((priv->upper - priv->lower) * y) / allocation.height;
608 g_object_notify (G_OBJECT (ruler), "position");
610 gtk_widget_queue_draw (widget);
616 gtk_ruler_expose (GtkWidget *widget,
617 GdkEventExpose *event)
619 if (gtk_widget_is_drawable (widget))
621 GtkRuler *ruler = GTK_RULER (widget);
622 GtkRulerPrivate *priv = ruler->priv;
625 cr = gdk_cairo_create (gtk_widget_get_window (widget));
626 cairo_set_source_surface (cr, priv->backing_store, 0, 0);
627 gdk_cairo_region (cr, event->region);
631 gtk_ruler_draw_pos (ruler);
638 gtk_ruler_make_pixmap (GtkRuler *ruler)
640 GtkRulerPrivate *priv = ruler->priv;
641 GtkAllocation allocation;
644 widget = GTK_WIDGET (ruler);
646 gtk_widget_get_allocation (widget, &allocation);
648 if (priv->backing_store)
649 cairo_surface_destroy (priv->backing_store);
651 priv->backing_store = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
659 gtk_ruler_draw_ticks (ruler);
663 gtk_ruler_real_draw_ticks (GtkRuler *ruler,
666 GtkWidget *widget = GTK_WIDGET (ruler);
667 GtkRulerPrivate *priv = ruler->priv;
674 gint length, ideal_length;
675 gdouble lower, upper; /* Upper and lower limits, in ruler units */
676 gdouble increment; /* Number of pixels per unit */
677 gint scale; /* Number of units per major unit */
679 gdouble start, end, cur;
687 PangoRectangle logical_rect, ink_rect;
689 style = gtk_widget_get_style (widget);
691 xthickness = style->xthickness;
692 ythickness = style->ythickness;
694 layout = gtk_widget_create_pango_layout (widget, "012456789");
695 pango_layout_get_extents (layout, &ink_rect, &logical_rect);
697 digit_height = PANGO_PIXELS (ink_rect.height) + 2;
698 digit_offset = ink_rect.y;
700 w = gtk_widget_get_allocated_width (widget);
701 h = gtk_widget_get_allocated_height (widget);
703 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
706 height = h - ythickness * 2;
711 height = w - ythickness * 2;
714 #define DETAILE(private) (priv->orientation == GTK_ORIENTATION_HORIZONTAL ? "hruler" : "vruler");
716 gdk_cairo_set_source_color (cr, &style->fg[gtk_widget_get_state (widget)]);
718 gtk_cairo_paint_box (style, cr,
719 GTK_STATE_NORMAL, GTK_SHADOW_OUT,
721 priv->orientation == GTK_ORIENTATION_HORIZONTAL ?
726 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
743 upper = priv->upper / priv->metric->pixels_per_unit;
744 lower = priv->lower / priv->metric->pixels_per_unit;
746 if ((upper - lower) == 0)
749 increment = (gdouble) width / (upper - lower);
751 /* determine the scale H
752 * We calculate the text size as for the vruler, so that the result
753 * for the scale looks consistent with an accompanying vruler
755 /* determine the scale V
756 * use the maximum extents of the ruler to determine the largest
757 * possible number to be displayed. Calculate the height in pixels
758 * of this displayed text. Use this height to find a scale which
759 * leaves sufficient room for drawing the ruler.
761 scale = ceil (priv->max_size / priv->metric->pixels_per_unit);
762 g_snprintf (unit_str, sizeof (unit_str), "%d", scale);
764 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
766 text_width = strlen (unit_str) * digit_height + 1;
768 for (scale = 0; scale < MAXIMUM_SCALES; scale++)
769 if (priv->metric->ruler_scale[scale] * fabs(increment) > 2 * text_width)
774 text_height = strlen (unit_str) * digit_height + 1;
776 for (scale = 0; scale < MAXIMUM_SCALES; scale++)
777 if (priv->metric->ruler_scale[scale] * fabs(increment) > 2 * text_height)
781 if (scale == MAXIMUM_SCALES)
782 scale = MAXIMUM_SCALES - 1;
784 /* drawing starts here */
786 for (i = MAXIMUM_SUBDIVIDE - 1; i >= 0; i--)
788 subd_incr = (gdouble) priv->metric->ruler_scale[scale] /
789 (gdouble) priv->metric->subdivide[i];
790 if (subd_incr * fabs(increment) <= MINIMUM_INCR)
793 /* Calculate the length of the tickmarks. Make sure that
794 * this length increases for each set of ticks
796 ideal_length = height / (i + 1) - 1;
797 if (ideal_length > ++length)
798 length = ideal_length;
802 start = floor (lower / subd_incr) * subd_incr;
803 end = ceil (upper / subd_incr) * subd_incr;
807 start = floor (upper / subd_incr) * subd_incr;
808 end = ceil (lower / subd_incr) * subd_incr;
811 for (cur = start; cur <= end; cur += subd_incr)
813 pos = ROUND ((cur - lower) * increment);
815 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
818 pos, height + ythickness - length,
824 height + xthickness - length, pos,
832 g_snprintf (unit_str, sizeof (unit_str), "%d", (int) cur);
834 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
836 pango_layout_set_text (layout, unit_str, -1);
837 pango_layout_get_extents (layout, &logical_rect, NULL);
839 gtk_cairo_paint_layout (style,
841 gtk_widget_get_state (widget),
845 pos + 2, ythickness + PANGO_PIXELS (logical_rect.y - digit_offset),
850 for (j = 0; j < (int) strlen (unit_str); j++)
852 pango_layout_set_text (layout, unit_str + j, 1);
853 pango_layout_get_extents (layout, NULL, &logical_rect);
855 gtk_cairo_paint_layout (style,
857 gtk_widget_get_state (widget),
862 pos + digit_height * j + 2 + PANGO_PIXELS (logical_rect.y - digit_offset),
873 g_object_unref (layout);
877 gtk_ruler_real_draw_pos (GtkRuler *ruler)
879 GtkAllocation allocation;
880 GtkWidget *widget = GTK_WIDGET (ruler);
881 GtkRulerPrivate *priv = ruler->priv;
885 gint bs_width, bs_height;
890 if (gtk_widget_is_drawable (widget))
892 style = gtk_widget_get_style (widget);
893 gtk_widget_get_allocation (widget, &allocation);
895 xthickness = style->xthickness;
896 ythickness = style->ythickness;
897 width = allocation.width;
898 height = allocation.height;
900 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
902 height -= ythickness * 2;
904 bs_width = height / 2 + 2;
905 bs_width |= 1; /* make sure it's odd */
906 bs_height = bs_width / 2 + 1;
910 width -= xthickness * 2;
912 bs_height = width / 2 + 2;
913 bs_height |= 1; /* make sure it's odd */
914 bs_width = bs_height / 2 + 1;
917 if ((bs_width > 0) && (bs_height > 0))
921 window = gtk_widget_get_window (widget);
923 cairo_t *cr = gdk_cairo_create (window);
925 /* If a backing store exists, restore the ruler */
926 if (priv->backing_store) {
927 cairo_set_source_surface (cr, priv->backing_store, 0, 0);
928 cairo_rectangle (cr, priv->xsrc, priv->ysrc, bs_width, bs_height);
932 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
934 increment = (gdouble) width / (priv->upper - priv->lower);
936 x = ROUND ((priv->position - priv->lower) * increment) + (xthickness - bs_width) / 2 - 1;
937 y = (height + bs_height) / 2 + ythickness;
941 increment = (gdouble) height / (priv->upper - priv->lower);
943 x = (width + bs_width) / 2 + xthickness;
944 y = ROUND ((priv->position - priv->lower) * increment) + (ythickness - bs_height) / 2 - 1;
947 gdk_cairo_set_source_color (cr, &style->fg[gtk_widget_get_state (widget)]);
949 cairo_move_to (cr, x, y);
951 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
953 cairo_line_to (cr, x + bs_width / 2.0, y + bs_height);
954 cairo_line_to (cr, x + bs_width, y);
958 cairo_line_to (cr, x + bs_width, y + bs_height / 2.0);
959 cairo_line_to (cr, x, y + bs_height);