1
/* LIBGIMP - The GIMP Library
2
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
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.
26
#include "libgimpbase/gimpbase.h"
27
#include "libgimpmath/gimpmath.h"
29
#include "gimpwidgetstypes.h"
31
#include "gimpruler.h"
34
#define RULER_WIDTH 13
35
#define MINIMUM_INCR 5
50
/* All distances below are in 1/72nd's of an inch. (According to
51
* Adobe that's a point, but points are really 1/72.27 in.)
55
GtkOrientation orientation;
62
GdkPixmap *backing_store;
72
gdouble ruler_scale[16];
76
{ 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000 },
81
static void gimp_ruler_set_property (GObject *object,
85
static void gimp_ruler_get_property (GObject *object,
90
static void gimp_ruler_realize (GtkWidget *widget);
91
static void gimp_ruler_unrealize (GtkWidget *widget);
92
static void gimp_ruler_size_allocate (GtkWidget *widget,
93
GtkAllocation *allocation);
94
static gboolean gimp_ruler_motion_notify (GtkWidget *widget,
95
GdkEventMotion *event);
96
static gboolean gimp_ruler_expose (GtkWidget *widget,
97
GdkEventExpose *event);
99
static void gimp_ruler_draw_ticks (GimpRuler *ruler);
100
static void gimp_ruler_draw_pos (GimpRuler *ruler);
101
static void gimp_ruler_make_pixmap (GimpRuler *ruler);
102
static PangoLayout * gimp_ruler_create_layout (GtkWidget *widget,
106
G_DEFINE_TYPE (GimpRuler, gimp_ruler, GTK_TYPE_WIDGET)
108
#define GIMP_RULER_GET_PRIVATE(ruler) \
109
G_TYPE_INSTANCE_GET_PRIVATE (ruler, GIMP_TYPE_RULER, GimpRulerPrivate)
113
gimp_ruler_class_init (GimpRulerClass *klass)
115
GObjectClass *object_class = G_OBJECT_CLASS (klass);
116
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
118
object_class->set_property = gimp_ruler_set_property;
119
object_class->get_property = gimp_ruler_get_property;
121
widget_class->realize = gimp_ruler_realize;
122
widget_class->unrealize = gimp_ruler_unrealize;
123
widget_class->size_allocate = gimp_ruler_size_allocate;
124
widget_class->motion_notify_event = gimp_ruler_motion_notify;
125
widget_class->expose_event = gimp_ruler_expose;
127
g_type_class_add_private (object_class, sizeof (GimpRulerPrivate));
129
g_object_class_install_property (object_class,
131
g_param_spec_enum ("orientation",
133
"The orientation of the ruler",
134
GTK_TYPE_ORIENTATION,
135
GTK_ORIENTATION_HORIZONTAL,
136
GIMP_PARAM_READWRITE));
138
g_object_class_install_property (object_class,
140
gimp_param_spec_unit ("unit",
145
GIMP_PARAM_READWRITE));
147
g_object_class_install_property (object_class,
149
g_param_spec_double ("lower",
151
"Lower limit of ruler",
155
GIMP_PARAM_READWRITE));
157
g_object_class_install_property (object_class,
159
g_param_spec_double ("upper",
161
"Upper limit of ruler",
165
GIMP_PARAM_READWRITE));
167
g_object_class_install_property (object_class,
169
g_param_spec_double ("position",
171
"Position of mark on the ruler",
175
GIMP_PARAM_READWRITE));
177
g_object_class_install_property (object_class,
179
g_param_spec_double ("max-size",
181
"Maximum size of the ruler",
185
GIMP_PARAM_READWRITE));
189
gimp_ruler_init (GimpRuler *ruler)
191
GtkWidget *widget = GTK_WIDGET (ruler);
192
GtkStyle *style = gtk_widget_get_style (widget);
193
GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler);
195
widget->requisition.width = style->xthickness * 2 + 1;
196
widget->requisition.height = style->ythickness * 2 + RULER_WIDTH;
198
priv->orientation = GTK_ORIENTATION_HORIZONTAL;
199
priv->unit = GIMP_PIXELS;
204
priv->backing_store = NULL;
205
priv->non_gr_exp_gc = NULL;
209
gimp_ruler_set_property (GObject *object,
214
GimpRuler *ruler = GIMP_RULER (object);
215
GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler);
219
case PROP_ORIENTATION:
221
GtkWidget *widget = GTK_WIDGET (ruler);
222
GtkStyle *style = gtk_widget_get_style (widget);
224
priv->orientation = g_value_get_enum (value);
225
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
227
widget->requisition.width = style->xthickness * 2 + 1;
228
widget->requisition.height = style->ythickness * 2 + RULER_WIDTH;
232
widget->requisition.width = style->xthickness * 2 + RULER_WIDTH;
233
widget->requisition.height = style->ythickness * 2 + 1;
235
gtk_widget_queue_resize (widget);
239
gimp_ruler_set_unit (ruler, g_value_get_int (value));
242
gimp_ruler_set_range (ruler,
243
g_value_get_double (value),
248
gimp_ruler_set_range (ruler,
250
g_value_get_double (value),
254
gimp_ruler_set_position (ruler, g_value_get_double (value));
257
gimp_ruler_set_range (ruler,
260
g_value_get_double (value));
264
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
270
gimp_ruler_get_property (GObject *object,
275
GimpRuler *ruler = GIMP_RULER (object);
276
GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler);
280
case PROP_ORIENTATION:
281
g_value_set_enum (value, priv->orientation);
284
g_value_set_int (value, priv->unit);
287
g_value_set_double (value, priv->lower);
290
g_value_set_double (value, priv->upper);
293
g_value_set_double (value, priv->position);
296
g_value_set_double (value, priv->max_size);
300
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
307
* @orientation: the ruler's orientation.
309
* Creates a new ruler.
311
* Return value: a new #GimpRuler widget.
316
gimp_ruler_new (GtkOrientation orientation)
318
return g_object_new (GIMP_TYPE_RULER,
319
"orientation", orientation,
324
* gimp_ruler_set_position:
325
* @ruler: a #GimpRuler
326
* @unit: the #GimpUnit to set the ruler to
328
* This sets the unit of the ruler.
333
gimp_ruler_set_unit (GimpRuler *ruler,
336
GimpRulerPrivate *priv;
338
g_return_if_fail (GIMP_IS_RULER (ruler));
340
priv = GIMP_RULER_GET_PRIVATE (ruler);
342
if (priv->unit != unit)
345
g_object_notify (G_OBJECT (ruler), "unit");
347
gtk_widget_queue_draw (GTK_WIDGET (ruler));
352
* gimp_ruler_get_unit:
353
* @ruler: a #GimpRuler
355
* Return value: the unit currently used in the @ruler widget.
360
gimp_ruler_get_unit (GimpRuler *ruler)
362
g_return_val_if_fail (GIMP_IS_RULER (ruler), 0);
364
return GIMP_RULER_GET_PRIVATE (ruler)->unit;
368
* gimp_ruler_set_position:
369
* @ruler: a #GimpRuler
370
* @position: the position to set the ruler to
372
* This sets the position of the ruler.
377
gimp_ruler_set_position (GimpRuler *ruler,
380
GimpRulerPrivate *priv;
382
g_return_if_fail (GIMP_IS_RULER (ruler));
384
priv = GIMP_RULER_GET_PRIVATE (ruler);
386
if (priv->position != position)
388
priv->position = position;
389
g_object_notify (G_OBJECT (ruler), "position");
391
gimp_ruler_draw_pos (ruler);
396
* gimp_ruler_get_position:
397
* @ruler: a #GimpRuler
399
* Return value: the current position of the @ruler widget.
404
gimp_ruler_get_position (GimpRuler *ruler)
406
g_return_val_if_fail (GIMP_IS_RULER (ruler), 0.0);
408
return GIMP_RULER_GET_PRIVATE (ruler)->position;
412
* gimp_ruler_set_range:
413
* @ruler: a #GimpRuler
414
* @lower: the lower limit of the ruler
415
* @upper: the upper limit of the ruler
416
* @max_size: the maximum size of the ruler used when calculating the space to
419
* This sets the range of the ruler.
424
gimp_ruler_set_range (GimpRuler *ruler,
429
GimpRulerPrivate *priv;
431
g_return_if_fail (GIMP_IS_RULER (ruler));
433
priv = GIMP_RULER_GET_PRIVATE (ruler);
435
g_object_freeze_notify (G_OBJECT (ruler));
436
if (priv->lower != lower)
439
g_object_notify (G_OBJECT (ruler), "lower");
441
if (priv->upper != upper)
444
g_object_notify (G_OBJECT (ruler), "upper");
446
if (priv->max_size != max_size)
448
priv->max_size = max_size;
449
g_object_notify (G_OBJECT (ruler), "max-size");
451
g_object_thaw_notify (G_OBJECT (ruler));
453
gtk_widget_queue_draw (GTK_WIDGET (ruler));
457
* gimp_ruler_get_range:
458
* @ruler: a #GimpRuler
459
* @lower: location to store lower limit of the ruler, or %NULL
460
* @upper: location to store upper limit of the ruler, or %NULL
461
* @max_size: location to store the maximum size of the ruler used when
462
* calculating the space to leave for the text, or %NULL.
464
* Retrieves values indicating the range and current position of a #GimpRuler.
465
* See gimp_ruler_set_range().
470
gimp_ruler_get_range (GimpRuler *ruler,
475
GimpRulerPrivate *priv;
477
g_return_if_fail (GIMP_IS_RULER (ruler));
479
priv = GIMP_RULER_GET_PRIVATE (ruler);
482
*lower = priv->lower;
484
*upper = priv->upper;
486
*max_size = priv->max_size;
490
gimp_ruler_realize (GtkWidget *widget)
492
GimpRuler *ruler = GIMP_RULER (widget);
493
GdkWindowAttr attributes;
494
gint attributes_mask;
496
GTK_WIDGET_SET_FLAGS (ruler, GTK_REALIZED);
498
attributes.window_type = GDK_WINDOW_CHILD;
499
attributes.x = widget->allocation.x;
500
attributes.y = widget->allocation.y;
501
attributes.width = widget->allocation.width;
502
attributes.height = widget->allocation.height;
503
attributes.wclass = GDK_INPUT_OUTPUT;
504
attributes.visual = gtk_widget_get_visual (widget);
505
attributes.colormap = gtk_widget_get_colormap (widget);
506
attributes.event_mask = (gtk_widget_get_events (widget) |
508
GDK_POINTER_MOTION_MASK |
509
GDK_POINTER_MOTION_HINT_MASK);
511
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
513
widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
514
&attributes, attributes_mask);
515
gdk_window_set_user_data (widget->window, ruler);
517
widget->style = gtk_style_attach (widget->style, widget->window);
518
gtk_style_set_background (gtk_widget_get_style (widget), widget->window,
521
gimp_ruler_make_pixmap (ruler);
525
gimp_ruler_unrealize (GtkWidget *widget)
527
GimpRuler *ruler = GIMP_RULER (widget);
528
GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler);
530
if (priv->backing_store)
532
g_object_unref (priv->backing_store);
533
priv->backing_store = NULL;
536
if (priv->non_gr_exp_gc)
538
g_object_unref (priv->non_gr_exp_gc);
539
priv->non_gr_exp_gc = NULL;
542
GTK_WIDGET_CLASS (gimp_ruler_parent_class)->unrealize (widget);
546
gimp_ruler_size_allocate (GtkWidget *widget,
547
GtkAllocation *allocation)
549
GimpRuler *ruler = GIMP_RULER (widget);
551
widget->allocation = *allocation;
553
if (GTK_WIDGET_REALIZED (widget))
555
gdk_window_move_resize (widget->window,
556
allocation->x, allocation->y,
557
allocation->width, allocation->height);
559
gimp_ruler_make_pixmap (ruler);
564
gimp_ruler_motion_notify (GtkWidget *widget,
565
GdkEventMotion *event)
567
GimpRuler *ruler = GIMP_RULER (widget);
568
GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler);
572
gdk_event_request_motions (event);
574
gimp_ruler_get_range (ruler, &lower, &upper, NULL);
576
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
578
gimp_ruler_set_position (ruler,
580
(upper - lower) * event->x /
581
widget->allocation.width);
585
gimp_ruler_set_position (ruler,
587
(upper - lower) * event->y /
588
widget->allocation.height);
595
gimp_ruler_expose (GtkWidget *widget,
596
GdkEventExpose *event)
598
if (GTK_WIDGET_DRAWABLE (widget))
600
GimpRuler *ruler = GIMP_RULER (widget);
601
GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler);
603
gimp_ruler_draw_ticks (ruler);
605
gdk_draw_drawable (widget->window,
609
widget->allocation.width,
610
widget->allocation.height);
612
gimp_ruler_draw_pos (ruler);
619
gimp_ruler_draw_ticks (GimpRuler *ruler)
621
GtkWidget *widget = GTK_WIDGET (ruler);
622
GtkStyle *style = gtk_widget_get_style (widget);
623
GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler);
629
gint length, ideal_length;
630
gdouble lower, upper; /* Upper and lower limits, in ruler units */
631
gdouble increment; /* Number of pixels per unit */
632
gint scale; /* Number of units per major unit */
633
gdouble start, end, cur;
642
PangoRectangle logical_rect, ink_rect;
644
if (! GTK_WIDGET_DRAWABLE (widget))
647
xthickness = style->xthickness;
648
ythickness = style->ythickness;
650
layout = gimp_ruler_create_layout (widget, "012456789");
651
pango_layout_get_extents (layout, &ink_rect, &logical_rect);
653
digit_height = PANGO_PIXELS (ink_rect.height) + 2;
654
digit_offset = ink_rect.y;
656
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
658
width = widget->allocation.width;
659
height = widget->allocation.height - ythickness * 2;
663
width = widget->allocation.height;
664
height = widget->allocation.width - ythickness * 2;
667
gtk_paint_box (style, priv->backing_store,
668
GTK_STATE_NORMAL, GTK_SHADOW_OUT,
670
priv->orientation == GTK_ORIENTATION_HORIZONTAL ?
673
widget->allocation.width, widget->allocation.height);
675
cr = gdk_cairo_create (priv->backing_store);
676
gdk_cairo_set_source_color (cr, &style->fg[widget->state]);
678
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
683
widget->allocation.width - 2 * xthickness,
692
widget->allocation.height - 2 * ythickness);
695
gimp_ruler_get_range (ruler, &lower, &upper, &max_size);
697
if ((upper - lower) == 0)
700
increment = (gdouble) width / (upper - lower);
702
/* determine the scale
703
* use the maximum extents of the ruler to determine the largest
704
* possible number to be displayed. Calculate the height in pixels
705
* of this displayed text. Use this height to find a scale which
706
* leaves sufficient room for drawing the ruler.
708
* We calculate the text size as for the vruler instead of using
709
* text_size = gdk_string_width(font, unit_str), so that the result
710
* for the scale looks consistent with an accompanying vruler
712
scale = ceil (max_size);
713
g_snprintf (unit_str, sizeof (unit_str), "%d", scale);
714
text_size = strlen (unit_str) * digit_height + 1;
716
for (scale = 0; scale < G_N_ELEMENTS (ruler_metric.ruler_scale); scale++)
717
if (ruler_metric.ruler_scale[scale] * fabs (increment) > 2 * text_size)
720
if (scale == G_N_ELEMENTS (ruler_metric.ruler_scale))
721
scale = G_N_ELEMENTS (ruler_metric.ruler_scale) - 1;
723
unit = gimp_ruler_get_unit (ruler);
725
/* drawing starts here */
727
for (i = G_N_ELEMENTS (ruler_metric.subdivide) - 1; i >= 0; i--)
731
/* hack to get proper subdivisions at full pixels */
732
if (unit == GIMP_UNIT_PIXEL && scale == 1 && i == 1)
735
subd_incr = ((gdouble) ruler_metric.ruler_scale[scale] /
736
(gdouble) ruler_metric.subdivide[i]);
738
if (subd_incr * fabs (increment) <= MINIMUM_INCR)
741
/* don't subdivide pixels */
742
if (unit == GIMP_UNIT_PIXEL && subd_incr < 1.0)
745
/* Calculate the length of the tickmarks. Make sure that
746
* this length increases for each set of ticks
748
ideal_length = height / (i + 1) - 1;
749
if (ideal_length > ++length)
750
length = ideal_length;
754
start = floor (lower / subd_incr) * subd_incr;
755
end = ceil (upper / subd_incr) * subd_incr;
759
start = floor (upper / subd_incr) * subd_incr;
760
end = ceil (lower / subd_incr) * subd_incr;
763
for (cur = start; cur <= end; cur += subd_incr)
765
pos = ROUND ((cur - lower) * increment);
767
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
770
pos, height + ythickness - length,
776
height + xthickness - length, pos,
783
g_snprintf (unit_str, sizeof (unit_str), "%d", (int) cur);
785
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
787
pango_layout_set_text (layout, unit_str, -1);
788
pango_layout_get_extents (layout, &logical_rect, NULL);
790
gtk_paint_layout (style,
792
GTK_WIDGET_STATE (widget),
798
ythickness + PANGO_PIXELS (logical_rect.y - digit_offset),
805
for (j = 0; j < (int) strlen (unit_str); j++)
807
pango_layout_set_text (layout, unit_str + j, 1);
808
pango_layout_get_extents (layout, NULL, &logical_rect);
810
gtk_paint_layout (style,
812
GTK_WIDGET_STATE (widget),
818
pos + digit_height * j + 2 + PANGO_PIXELS (logical_rect.y - digit_offset),
830
g_object_unref (layout);
834
gimp_ruler_draw_pos (GimpRuler *ruler)
836
GtkWidget *widget = GTK_WIDGET (ruler);
837
GtkStyle *style = gtk_widget_get_style (widget);
838
GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler);
841
gint bs_width, bs_height;
845
if (! GTK_WIDGET_DRAWABLE (ruler))
848
xthickness = style->xthickness;
849
ythickness = style->ythickness;
851
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
853
width = widget->allocation.width;
854
height = widget->allocation.height - ythickness * 2;
856
bs_width = height / 2 + 2;
857
bs_width |= 1; /* make sure it's odd */
858
bs_height = bs_width / 2 + 1;
862
width = widget->allocation.width - xthickness * 2;
863
height = widget->allocation.height;
865
bs_height = width / 2 + 2;
866
bs_height |= 1; /* make sure it's odd */
867
bs_width = bs_height / 2 + 1;
870
if ((bs_width > 0) && (bs_height > 0))
872
cairo_t *cr = gdk_cairo_create (widget->window);
878
/* If a backing store exists, restore the ruler */
879
if (priv->backing_store)
880
gdk_draw_drawable (widget->window,
883
priv->xsrc, priv->ysrc,
884
priv->xsrc, priv->ysrc,
885
bs_width, bs_height);
887
position = gimp_ruler_get_position (ruler);
889
gimp_ruler_get_range (ruler, &lower, &upper, NULL);
891
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
893
increment = (gdouble) width / (upper - lower);
895
x = ROUND ((position - lower) * increment) + (xthickness - bs_width) / 2 - 1;
896
y = (height + bs_height) / 2 + ythickness;
900
increment = (gdouble) height / (upper - lower);
902
x = (width + bs_width) / 2 + xthickness;
903
y = ROUND ((position - lower) * increment) + (ythickness - bs_height) / 2 - 1;
906
gdk_cairo_set_source_color (cr, &style->fg[widget->state]);
908
cairo_move_to (cr, x, y);
910
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
912
cairo_line_to (cr, x + bs_width / 2.0, y + bs_height);
913
cairo_line_to (cr, x + bs_width, y);
917
cairo_line_to (cr, x + bs_width, y + bs_height / 2.0);
918
cairo_line_to (cr, x, y + bs_height);
931
gimp_ruler_make_pixmap (GimpRuler *ruler)
933
GtkWidget *widget = GTK_WIDGET (ruler);
934
GimpRulerPrivate *priv = GIMP_RULER_GET_PRIVATE (ruler);
938
if (priv->backing_store)
940
gdk_drawable_get_size (priv->backing_store, &width, &height);
941
if ((width == widget->allocation.width) &&
942
(height == widget->allocation.height))
945
g_object_unref (priv->backing_store);
948
priv->backing_store = gdk_pixmap_new (widget->window,
949
widget->allocation.width,
950
widget->allocation.height,
953
if (!priv->non_gr_exp_gc)
955
priv->non_gr_exp_gc = gdk_gc_new (widget->window);
956
gdk_gc_set_exposures (priv->non_gr_exp_gc, FALSE);
961
gimp_ruler_create_layout (GtkWidget *widget,
965
PangoAttrList *attrs;
966
PangoAttribute *attr;
968
layout = gtk_widget_create_pango_layout (widget, text);
970
attrs = pango_attr_list_new ();
972
attr = pango_attr_scale_new (PANGO_SCALE_X_SMALL);
973
attr->start_index = 0;
974
attr->end_index = -1;
975
pango_attr_list_insert (attrs, attr);
977
pango_layout_set_attributes (layout, attrs);
978
pango_attr_list_unref (attrs);