175cd77ad48ce631953b7751532779fde3cbd810
[claws.git] / src / gtk / gtkshruler.c
1 /* LIBGTK - The GTK Library
2  * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
3  *
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 3 of the License, or (at your option) any later version.
8  *
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.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library.  If not, see
16  * <http://www.gnu.org/licenses/>.
17  */
18
19 #include "config.h"
20 #include "claws-features.h"
21
22 #include <math.h>
23 #include <string.h>
24
25 #include <gtk/gtk.h>
26
27 #include "gtkutils.h"
28 #include "gtkshruler.h"
29 #include "gtkunit.h"
30
31 #define ROUND(x) ((int) ((x) + 0.5))
32
33 /**
34  * SECTION: gimpparam
35  * @title: gimpparam
36  * @short_description: Definitions of useful #GParamFlags.
37  *
38  * Definitions of useful #GParamFlags.
39  **/
40
41
42 /**
43  * GTK_PARAM_STATIC_STRINGS:
44  *
45  * Since: GTK 2.4
46  **/
47 #define GTK_PARAM_STATIC_STRINGS (G_PARAM_STATIC_NAME | \
48                                    G_PARAM_STATIC_NICK | \
49                                    G_PARAM_STATIC_BLURB)
50
51 /**
52  * GTK_PARAM_READABLE:
53  *
54  * Since: GTK 2.4
55  **/
56 #define GTK_PARAM_READABLE       (G_PARAM_READABLE    | \
57                                    GTK_PARAM_STATIC_STRINGS)
58
59 /**
60  * GTK_PARAM_WRITABLE:
61  *
62  * Since: GTK 2.4
63  **/
64 #define GTK_PARAM_WRITABLE       (G_PARAM_WRITABLE    | \
65                                    GTK_PARAM_STATIC_STRINGS)
66
67 /**
68  * GTK_PARAM_READWRITE:
69  *
70  * Since: GTK 2.4
71  **/
72 #define GTK_PARAM_READWRITE      (G_PARAM_READWRITE   | \
73                                    GTK_PARAM_STATIC_STRINGS)
74
75
76 /**
77  * SECTION: gimpruler
78  * @title: GtkSHRuler
79  * @short_description: A ruler widget with configurable unit and orientation.
80  *
81  * A ruler widget with configurable unit and orientation.
82  **/
83
84
85 #define DEFAULT_RULER_FONT_SCALE  PANGO_SCALE_SMALL
86 #define MINIMUM_INCR              5
87
88
89 enum
90 {
91   PROP_0,
92   PROP_ORIENTATION,
93   PROP_UNIT,
94   PROP_LOWER,
95   PROP_UPPER,
96   PROP_POSITION,
97   PROP_MAX_SIZE
98 };
99
100
101 /* All distances below are in 1/72nd's of an inch. (According to
102  * Adobe that's a point, but points are really 1/72.27 in.)
103  */
104 typedef struct
105 {
106   GtkOrientation   orientation;
107   GtkCMUnit        unit;
108   gdouble          lower;
109   gdouble          upper;
110   gdouble          position;
111   gdouble          max_size;
112
113   GdkWindow       *input_window;
114   cairo_surface_t *backing_store;
115   PangoLayout     *layout;
116   gdouble          font_scale;
117
118   gint             xsrc;
119   gint             ysrc;
120 } GtkSHRulerPrivate;
121
122 #define GTK_SHRULER_GET_PRIVATE(ruler) \
123   G_TYPE_INSTANCE_GET_PRIVATE (ruler, GTK_TYPE_SHRULER, GtkSHRulerPrivate)
124
125
126 static const struct
127 {
128   const gdouble  ruler_scale[16];
129   const gint     subdivide[3];
130 } ruler_metric =
131 {
132   { 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000 },
133   { 1, 5, 10 }
134 };
135
136
137 static void          gtk_shruler_dispose       (GObject        *object);
138 static void          gtk_shruler_set_property  (GObject        *object,
139                                                guint            prop_id,
140                                                const GValue   *value,
141                                                GParamSpec     *pspec);
142 static void          gtk_shruler_get_property  (GObject        *object,
143                                                guint           prop_id,
144                                                GValue         *value,
145                                                GParamSpec     *pspec);
146
147 static void          gtk_shruler_realize       (GtkWidget      *widget);
148 static void          gtk_shruler_unrealize     (GtkWidget      *widget);
149 static void          gtk_shruler_map           (GtkWidget      *widget);
150 static void          gtk_shruler_unmap         (GtkWidget      *widget);
151 static void          gtk_shruler_size_allocate (GtkWidget      *widget,
152                                               GtkAllocation  *allocation);
153 static void          gtk_shruler_size_request  (GtkWidget      *widget,
154                                               GtkRequisition *requisition);
155 static void          gtk_shruler_style_set     (GtkWidget      *widget,
156                                               GtkStyle       *prev_style);
157 static gboolean      gtk_shruler_motion_notify (GtkWidget      *widget,
158                                               GdkEventMotion *event);
159 static gboolean      gtk_shruler_expose        (GtkWidget      *widget,
160                                               GdkEventExpose *event);
161
162 static void          gtk_shruler_draw_ticks    (GtkSHRuler      *ruler);
163 static void          gtk_shruler_make_pixmap   (GtkSHRuler      *ruler);
164
165 static PangoLayout * gtk_shruler_get_layout    (GtkWidget      *widget,
166                                               const gchar    *text);
167
168
169 G_DEFINE_TYPE (GtkSHRuler, gtk_shruler, GTK_TYPE_WIDGET)
170
171 #define parent_class gtk_shruler_parent_class
172
173
174 static void
175 gtk_shruler_class_init (GtkSHRulerClass *klass)
176 {
177   GObjectClass   *object_class = G_OBJECT_CLASS (klass);
178   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
179
180   object_class->dispose             = gtk_shruler_dispose;
181   object_class->set_property        = gtk_shruler_set_property;
182   object_class->get_property        = gtk_shruler_get_property;
183
184   widget_class->realize             = gtk_shruler_realize;
185   widget_class->unrealize           = gtk_shruler_unrealize;
186   widget_class->map                 = gtk_shruler_map;
187   widget_class->unmap               = gtk_shruler_unmap;
188   widget_class->size_allocate       = gtk_shruler_size_allocate;
189   widget_class->size_request        = gtk_shruler_size_request;
190   widget_class->style_set           = gtk_shruler_style_set;
191   widget_class->motion_notify_event = gtk_shruler_motion_notify;
192   widget_class->expose_event        = gtk_shruler_expose;
193
194   g_type_class_add_private (object_class, sizeof (GtkSHRulerPrivate));
195
196   g_object_class_install_property (object_class,
197                                    PROP_ORIENTATION,
198                                    g_param_spec_enum ("orientation",
199                                                       "Orientation",
200                                                       "The orientation of the ruler",
201                                                       GTK_TYPE_ORIENTATION,
202                                                       GTK_ORIENTATION_HORIZONTAL,
203                                                       GTK_PARAM_READWRITE));
204
205   g_object_class_install_property (object_class,
206                                    PROP_LOWER,
207                                    gtk_param_spec_unit ("unit",
208                                                          "Unit",
209                                                          "Unit of ruler",
210                                                          TRUE, TRUE,
211                                                          CM_UNIT_PIXEL,
212                                                          GTK_PARAM_READWRITE));
213
214   g_object_class_install_property (object_class,
215                                    PROP_LOWER,
216                                    g_param_spec_double ("lower",
217                                                         "Lower",
218                                                         "Lower limit of ruler",
219                                                         -G_MAXDOUBLE,
220                                                         G_MAXDOUBLE,
221                                                         0.0,
222                                                         GTK_PARAM_READWRITE));
223
224   g_object_class_install_property (object_class,
225                                    PROP_UPPER,
226                                    g_param_spec_double ("upper",
227                                                         "Upper",
228                                                         "Upper limit of ruler",
229                                                         -G_MAXDOUBLE,
230                                                         G_MAXDOUBLE,
231                                                         0.0,
232                                                         GTK_PARAM_READWRITE));
233
234   g_object_class_install_property (object_class,
235                                    PROP_POSITION,
236                                    g_param_spec_double ("position",
237                                                         "Position",
238                                                         "Position of mark on the ruler",
239                                                         -G_MAXDOUBLE,
240                                                         G_MAXDOUBLE,
241                                                         0.0,
242                                                         GTK_PARAM_READWRITE));
243
244   g_object_class_install_property (object_class,
245                                    PROP_MAX_SIZE,
246                                    g_param_spec_double ("max-size",
247                                                         "Max Size",
248                                                         "Maximum size of the ruler",
249                                                         -G_MAXDOUBLE,
250                                                         G_MAXDOUBLE,
251                                                         0.0,
252                                                         GTK_PARAM_READWRITE));
253
254   gtk_widget_class_install_style_property (widget_class,
255                                            g_param_spec_double ("font-scale",
256                                                                 NULL, NULL,
257                                                                 0.0,
258                                                                 G_MAXDOUBLE,
259                                                                 DEFAULT_RULER_FONT_SCALE,
260                                                                 GTK_PARAM_READABLE));
261 }
262
263 static void
264 gtk_shruler_init (GtkSHRuler *ruler)
265 {
266   GtkSHRulerPrivate *priv = GTK_SHRULER_GET_PRIVATE (ruler);
267
268   gtkut_widget_set_has_window (GTK_WIDGET (ruler), FALSE);
269
270   priv->orientation   = GTK_ORIENTATION_HORIZONTAL;
271   priv->unit          = GTK_PIXELS;
272   priv->lower         = 0;
273   priv->upper         = 0;
274   priv->position      = 0;
275   priv->max_size      = 0;
276   priv->backing_store = NULL;
277   priv->font_scale    = DEFAULT_RULER_FONT_SCALE;
278 }
279
280 static void
281 gtk_shruler_dispose (GObject *object)
282 {
283   GtkSHRuler        *ruler = GTK_SHRULER (object);
284   GtkSHRulerPrivate *priv  = GTK_SHRULER_GET_PRIVATE (ruler);
285
286   G_OBJECT_CLASS (parent_class)->dispose (object);
287 }
288
289 static void
290 gtk_shruler_set_property (GObject      *object,
291                          guint         prop_id,
292                          const GValue *value,
293                          GParamSpec   *pspec)
294 {
295   GtkSHRuler        *ruler = GTK_SHRULER (object);
296   GtkSHRulerPrivate *priv  = GTK_SHRULER_GET_PRIVATE (ruler);
297
298   switch (prop_id)
299     {
300     case PROP_ORIENTATION:
301       priv->orientation = g_value_get_enum (value);
302       gtk_widget_queue_resize (GTK_WIDGET (ruler));
303       break;
304
305     case PROP_UNIT:
306       gtk_shruler_set_unit (ruler, g_value_get_int (value));
307       break;
308
309     case PROP_LOWER:
310       gtk_shruler_set_range (ruler,
311                             g_value_get_double (value),
312                             priv->upper,
313                             priv->max_size);
314       break;
315     case PROP_UPPER:
316       gtk_shruler_set_range (ruler,
317                             priv->lower,
318                             g_value_get_double (value),
319                             priv->max_size);
320       break;
321
322     case PROP_POSITION:
323       gtk_shruler_set_position (ruler, g_value_get_double (value));
324       break;
325
326     case PROP_MAX_SIZE:
327       gtk_shruler_set_range (ruler,
328                             priv->lower,
329                             priv->upper,
330                             g_value_get_double (value));
331       break;
332
333     default:
334       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
335       break;
336     }
337 }
338
339 static void
340 gtk_shruler_get_property (GObject    *object,
341                          guint       prop_id,
342                          GValue     *value,
343                          GParamSpec *pspec)
344 {
345   GtkSHRuler        *ruler = GTK_SHRULER (object);
346   GtkSHRulerPrivate *priv  = GTK_SHRULER_GET_PRIVATE (ruler);
347
348   switch (prop_id)
349     {
350     case PROP_ORIENTATION:
351       g_value_set_enum (value, priv->orientation);
352       break;
353
354     case PROP_UNIT:
355       g_value_set_int (value, priv->unit);
356       break;
357
358     case PROP_LOWER:
359       g_value_set_double (value, priv->lower);
360       break;
361
362     case PROP_UPPER:
363       g_value_set_double (value, priv->upper);
364       break;
365
366     case PROP_POSITION:
367       g_value_set_double (value, priv->position);
368       break;
369
370     case PROP_MAX_SIZE:
371       g_value_set_double (value, priv->max_size);
372       break;
373
374     default:
375       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
376       break;
377     }
378 }
379
380 /**
381  * gtk_shruler_new:
382  * @orientation: the ruler's orientation.
383  *
384  * Creates a new ruler.
385  *
386  * Return value: a new #GtkSHRuler widget.
387  *
388  * Since: GTK 2.8
389  **/
390 GtkWidget *
391 gtk_shruler_new (GtkOrientation orientation)
392 {
393   return g_object_new (GTK_TYPE_SHRULER,
394                        "orientation", orientation,
395                        NULL);
396 }
397
398 static void
399 gtk_shruler_update_position (GtkSHRuler *ruler,
400                             gdouble    x,
401                             gdouble    y)
402 {
403   GtkSHRulerPrivate *priv = GTK_SHRULER_GET_PRIVATE (ruler);
404   GtkAllocation     allocation;
405   gdouble           lower;
406   gdouble           upper;
407
408   gtk_widget_get_allocation (GTK_WIDGET (ruler), &allocation);
409   gtk_shruler_get_range (ruler, &lower, &upper, NULL);
410
411   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
412     {
413       gtk_shruler_set_position (ruler,
414                                lower +
415                                (upper - lower) * x / allocation.width);
416     }
417   else
418     {
419       gtk_shruler_set_position (ruler,
420                                lower +
421                                (upper - lower) * y / allocation.height);
422     }
423 }
424
425 /**
426  * gtk_shruler_set_unit:
427  * @ruler: a #GtkSHRuler
428  * @unit:  the #GtkCMUnit to set the ruler to
429  *
430  * This sets the unit of the ruler.
431  *
432  * Since: GTK 2.8
433  */
434 void
435 gtk_shruler_set_unit (GtkSHRuler *ruler,
436                      GtkCMUnit  unit)
437 {
438   GtkSHRulerPrivate *priv;
439
440   g_return_if_fail (GTK_IS_SHRULER (ruler));
441
442   priv = GTK_SHRULER_GET_PRIVATE (ruler);
443
444   if (priv->unit != unit)
445     {
446       priv->unit = unit;
447       g_object_notify (G_OBJECT (ruler), "unit");
448
449       gtk_widget_queue_draw (GTK_WIDGET (ruler));
450     }
451 }
452
453 /**
454  * gtk_shruler_get_unit:
455  * @ruler: a #GtkSHRuler
456  *
457  * Return value: the unit currently used in the @ruler widget.
458  *
459  * Since: GTK 2.8
460  **/
461 GtkCMUnit
462 gtk_shruler_get_unit (GtkSHRuler *ruler)
463 {
464   g_return_val_if_fail (GTK_IS_SHRULER (ruler), 0);
465
466   return GTK_SHRULER_GET_PRIVATE (ruler)->unit;
467 }
468
469 /**
470  * gtk_shruler_set_position:
471  * @ruler: a #GtkSHRuler
472  * @position: the position to set the ruler to
473  *
474  * This sets the position of the ruler.
475  *
476  * Since: GTK 2.8
477  */
478 void
479 gtk_shruler_set_position (GtkSHRuler *ruler,
480                          gdouble    position)
481 {
482   GtkSHRulerPrivate *priv;
483
484   g_return_if_fail (GTK_IS_SHRULER (ruler));
485
486   priv = GTK_SHRULER_GET_PRIVATE (ruler);
487
488   if (priv->position != position)
489     {
490       priv->position = position;
491       g_object_notify (G_OBJECT (ruler), "position");
492     }
493 }
494
495 /**
496  * gtk_shruler_get_position:
497  * @ruler: a #GtkSHRuler
498  *
499  * Return value: the current position of the @ruler widget.
500  *
501  * Since: GTK 2.8
502  **/
503 gdouble
504 gtk_shruler_get_position (GtkSHRuler *ruler)
505 {
506   g_return_val_if_fail (GTK_IS_SHRULER (ruler), 0.0);
507
508   return GTK_SHRULER_GET_PRIVATE (ruler)->position;
509 }
510
511 /**
512  * gtk_shruler_set_range:
513  * @ruler: a #GtkSHRuler
514  * @lower: the lower limit of the ruler
515  * @upper: the upper limit of the ruler
516  * @max_size: the maximum size of the ruler used when calculating the space to
517  * leave for the text
518  *
519  * This sets the range of the ruler.
520  *
521  * Since: GTK 2.8
522  */
523 void
524 gtk_shruler_set_range (GtkSHRuler *ruler,
525                       gdouble    lower,
526                       gdouble    upper,
527                       gdouble    max_size)
528 {
529   GtkSHRulerPrivate *priv;
530
531   g_return_if_fail (GTK_IS_SHRULER (ruler));
532
533   priv = GTK_SHRULER_GET_PRIVATE (ruler);
534
535   g_object_freeze_notify (G_OBJECT (ruler));
536   if (priv->lower != lower)
537     {
538       priv->lower = lower;
539       g_object_notify (G_OBJECT (ruler), "lower");
540     }
541   if (priv->upper != upper)
542     {
543       priv->upper = upper;
544       g_object_notify (G_OBJECT (ruler), "upper");
545     }
546   if (priv->max_size != max_size)
547     {
548       priv->max_size = max_size;
549       g_object_notify (G_OBJECT (ruler), "max-size");
550     }
551   g_object_thaw_notify (G_OBJECT (ruler));
552
553   gtk_widget_queue_draw (GTK_WIDGET (ruler));
554 }
555
556 /**
557  * gtk_shruler_get_range:
558  * @ruler: a #GtkSHRuler
559  * @lower: location to store lower limit of the ruler, or %NULL
560  * @upper: location to store upper limit of the ruler, or %NULL
561  * @max_size: location to store the maximum size of the ruler used when
562  *            calculating the space to leave for the text, or %NULL.
563  *
564  * Retrieves values indicating the range and current position of a #GtkSHRuler.
565  * See gtk_shruler_set_range().
566  *
567  * Since: GTK 2.8
568  **/
569 void
570 gtk_shruler_get_range (GtkSHRuler *ruler,
571                       gdouble   *lower,
572                       gdouble   *upper,
573                       gdouble   *max_size)
574 {
575   GtkSHRulerPrivate *priv;
576
577   g_return_if_fail (GTK_IS_SHRULER (ruler));
578
579   priv = GTK_SHRULER_GET_PRIVATE (ruler);
580
581   if (lower)
582     *lower = priv->lower;
583   if (upper)
584     *upper = priv->upper;
585   if (max_size)
586     *max_size = priv->max_size;
587 }
588
589 static void
590 gtk_shruler_realize (GtkWidget *widget)
591 {
592   GtkSHRuler        *ruler = GTK_SHRULER (widget);
593   GtkSHRulerPrivate *priv  = GTK_SHRULER_GET_PRIVATE (ruler);
594   GtkAllocation     allocation;
595   GdkWindowAttr     attributes;
596   gint              attributes_mask;
597
598   GTK_WIDGET_CLASS (gtk_shruler_parent_class)->realize (widget);
599
600   gtk_widget_get_allocation (widget, &allocation);
601
602   attributes.window_type = GDK_WINDOW_CHILD;
603   attributes.x           = allocation.x;
604   attributes.y           = allocation.y;
605   attributes.width       = allocation.width;
606   attributes.height      = allocation.height;
607   attributes.wclass      = GDK_INPUT_ONLY;
608   attributes.event_mask  = (gtk_widget_get_events (widget) |
609                             GDK_EXPOSURE_MASK              |
610                             GDK_POINTER_MOTION_MASK        |
611                             GDK_POINTER_MOTION_HINT_MASK);
612
613   attributes_mask = GDK_WA_X | GDK_WA_Y;
614
615   priv->input_window = gdk_window_new (gtk_widget_get_window (widget),
616                                        &attributes, attributes_mask);
617   gdk_window_set_user_data (priv->input_window, ruler);
618
619   gtk_shruler_make_pixmap (ruler);
620 }
621
622 static void
623 gtk_shruler_unrealize (GtkWidget *widget)
624 {
625   GtkSHRuler        *ruler = GTK_SHRULER (widget);
626   GtkSHRulerPrivate *priv  = GTK_SHRULER_GET_PRIVATE (ruler);
627
628   if (priv->backing_store)
629     {
630       cairo_surface_destroy (priv->backing_store);
631       priv->backing_store = NULL;
632     }
633
634   if (priv->layout)
635     {
636       g_object_unref (priv->layout);
637       priv->layout = NULL;
638     }
639
640   if (priv->input_window)
641     {
642       gdk_window_destroy (priv->input_window);
643       priv->input_window = NULL;
644     }
645
646   GTK_WIDGET_CLASS (gtk_shruler_parent_class)->unrealize (widget);
647 }
648
649 static void
650 gtk_shruler_map (GtkWidget *widget)
651 {
652   GtkSHRulerPrivate *priv = GTK_SHRULER_GET_PRIVATE (widget);
653
654   GTK_WIDGET_CLASS (parent_class)->map (widget);
655
656   if (priv->input_window)
657     gdk_window_show (priv->input_window);
658 }
659
660 static void
661 gtk_shruler_unmap (GtkWidget *widget)
662 {
663   GtkSHRulerPrivate *priv = GTK_SHRULER_GET_PRIVATE (widget);
664
665   if (priv->input_window)
666     gdk_window_hide (priv->input_window);
667
668   GTK_WIDGET_CLASS (parent_class)->unmap (widget);
669 }
670
671 static void
672 gtk_shruler_size_allocate (GtkWidget     *widget,
673                           GtkAllocation *allocation)
674 {
675   GtkSHRuler        *ruler = GTK_SHRULER (widget);
676   GtkSHRulerPrivate *priv  = GTK_SHRULER_GET_PRIVATE (ruler);
677
678   gtk_widget_set_allocation (widget, allocation);
679
680   if (gtk_widget_get_realized (widget))
681     {
682       gdk_window_move_resize (priv->input_window,
683                               allocation->x, allocation->y,
684                               allocation->width, allocation->height);
685
686       gtk_shruler_make_pixmap (ruler);
687     }
688 }
689
690 static void
691 gtk_shruler_size_request (GtkWidget      *widget,
692                          GtkRequisition *requisition)
693 {
694   GtkSHRulerPrivate *priv  = GTK_SHRULER_GET_PRIVATE (widget);
695   GtkStyle         *style = gtk_widget_get_style (widget);
696   PangoLayout      *layout;
697   PangoRectangle    ink_rect;
698   gint              size;
699
700   layout = gtk_shruler_get_layout (widget, "0123456789");
701   pango_layout_get_pixel_extents (layout, &ink_rect, NULL);
702
703   size = 2 + ink_rect.height * 1.7;
704
705   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
706     {
707       requisition->width  = style->xthickness * 2 + 1;
708       requisition->height = style->ythickness * 2 + size;
709     }
710   else
711     {
712       requisition->width  = style->xthickness * 2 + size;
713       requisition->height = style->ythickness * 2 + 1;
714     }
715 }
716
717 static void
718 gtk_shruler_style_set (GtkWidget *widget,
719                       GtkStyle  *prev_style)
720 {
721   GtkSHRulerPrivate *priv = GTK_SHRULER_GET_PRIVATE (widget);
722
723   GTK_WIDGET_CLASS (gtk_shruler_parent_class)->style_set (widget, prev_style);
724
725   gtk_widget_style_get (widget,
726                         "font-scale", &priv->font_scale,
727                         NULL);
728
729   if (priv->layout)
730     {
731       g_object_unref (priv->layout);
732       priv->layout = NULL;
733     }
734 }
735
736 static gboolean
737 gtk_shruler_motion_notify (GtkWidget      *widget,
738                           GdkEventMotion *event)
739 {
740   GtkSHRuler *ruler = GTK_SHRULER (widget);
741
742   gdk_event_request_motions (event);
743
744   gtk_shruler_update_position (ruler, event->x, event->y);
745
746   return FALSE;
747 }
748
749 static gboolean
750 gtk_shruler_expose (GtkWidget      *widget,
751                    GdkEventExpose *event)
752 {
753   if (gtk_widget_is_drawable (widget))
754     {
755       GtkSHRuler        *ruler = GTK_SHRULER (widget);
756       GtkSHRulerPrivate *priv  = GTK_SHRULER_GET_PRIVATE (ruler);
757       GtkAllocation     allocation;
758       cairo_t          *cr;
759
760       gtk_shruler_draw_ticks (ruler);
761
762       cr = gdk_cairo_create (gtk_widget_get_window (widget));
763       gdk_cairo_region (cr, event->region);
764       cairo_clip (cr);
765
766       gtk_widget_get_allocation (widget, &allocation);
767       cairo_translate (cr, allocation.x, allocation.y);
768
769       cairo_set_source_surface (cr, priv->backing_store, 0, 0);
770       cairo_paint (cr);
771
772       cairo_destroy (cr);
773     }
774
775   return FALSE;
776 }
777
778 static void
779 gtk_shruler_draw_ticks (GtkSHRuler *ruler)
780 {
781   GtkWidget        *widget = GTK_WIDGET (ruler);
782   GtkStyle         *style  = gtk_widget_get_style (widget);
783   GtkSHRulerPrivate *priv   = GTK_SHRULER_GET_PRIVATE (ruler);
784   GtkStateType      state  = gtk_widget_get_state (widget);
785   GtkAllocation     allocation;
786   cairo_t          *cr;
787   gint              i;
788   gint              width, height;
789   gint              xthickness;
790   gint              ythickness;
791   gint              length;
792   gdouble           lower, upper;  /* Upper and lower limits, in ruler units */
793   gdouble           increment;     /* Number of pixels per unit */
794   gint              scale;         /* Number of units per major unit */
795   gdouble           start, end, cur;
796   gchar             unit_str[32];
797   gint              digit_height;
798   gint              digit_offset;
799   gint              text_size;
800   gint              pos;
801   gdouble           max_size;
802   GtkCMUnit           unit;
803   PangoLayout      *layout;
804   PangoRectangle    logical_rect, ink_rect;
805
806   if (! gtk_widget_is_drawable (widget))
807     return;
808
809   gtk_widget_get_allocation (widget, &allocation);
810
811   xthickness = style->xthickness;
812   ythickness = style->ythickness;
813
814   layout = gtk_shruler_get_layout (widget, "0123456789");
815   pango_layout_get_extents (layout, &ink_rect, &logical_rect);
816
817   digit_height = PANGO_PIXELS (ink_rect.height) + 2;
818   digit_offset = ink_rect.y;
819
820   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
821     {
822       width  = allocation.width;
823       height = allocation.height - ythickness * 2;
824     }
825   else
826     {
827       width  = allocation.height;
828       height = allocation.width - ythickness * 2;
829     }
830
831   cr = cairo_create (priv->backing_store);
832   gdk_cairo_set_source_color (cr, &style->bg[state]);
833
834   cairo_paint (cr);
835
836   gdk_cairo_set_source_color (cr, &style->fg[state]);
837
838   gtk_shruler_get_range (ruler, &lower, &upper, &max_size);
839
840   if ((upper - lower) == 0)
841     goto out;
842
843   increment = (gdouble) width / (upper - lower);
844
845   /* determine the scale
846    *   use the maximum extents of the ruler to determine the largest
847    *   possible number to be displayed.  Calculate the height in pixels
848    *   of this displayed text. Use this height to find a scale which
849    *   leaves sufficient room for drawing the ruler.
850    *
851    *   We calculate the text size as for the vruler instead of
852    *   actually measuring the text width, so that the result for the
853    *   scale looks consistent with an accompanying vruler.
854    */
855   scale = ceil (max_size);
856   g_snprintf (unit_str, sizeof (unit_str), "%d", scale);
857   text_size = strlen (unit_str) * digit_height + 1;
858
859   for (scale = 0; scale < G_N_ELEMENTS (ruler_metric.ruler_scale); scale++)
860     if (ruler_metric.ruler_scale[scale] * fabs (increment) > 2 * text_size)
861       break;
862
863   if (scale == G_N_ELEMENTS (ruler_metric.ruler_scale))
864     scale = G_N_ELEMENTS (ruler_metric.ruler_scale) - 1;
865
866   unit = gtk_shruler_get_unit (ruler);
867
868   /* drawing starts here */
869   length = 0;
870   for (i = G_N_ELEMENTS (ruler_metric.subdivide) - 1; i >= 0; i--)
871     {
872       gdouble subd_incr;
873
874       /* hack to get proper subdivisions at full pixels */
875       if (unit == CM_UNIT_PIXEL && scale == 1 && i == 1)
876         subd_incr = 1.0;
877       else
878         subd_incr = ((gdouble) ruler_metric.ruler_scale[scale] /
879                      (gdouble) ruler_metric.subdivide[i]);
880
881       if (subd_incr * fabs (increment) <= MINIMUM_INCR)
882         continue;
883
884       /* don't subdivide pixels */
885       if (unit == CM_UNIT_PIXEL && subd_incr < 1.0)
886         continue;
887
888       if (lower < upper)
889         {
890           start = floor (lower / subd_incr) * subd_incr;
891           end   = ceil  (upper / subd_incr) * subd_incr;
892         }
893       else
894         {
895           start = floor (upper / subd_incr) * subd_incr;
896           end   = ceil  (lower / subd_incr) * subd_incr;
897         }
898
899       for (cur = start; cur <= end; cur += subd_incr)
900         {
901           if (((int)cur) % 10 == 0)
902                 length = height * 2 / 3;
903           else if (((int)cur) % 5 == 0)
904             length = height / 3;
905           else
906             length = height / 4;
907
908           pos = ROUND ((cur - lower) * increment);
909
910           if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
911             {
912               cairo_rectangle (cr,
913                                pos, height + ythickness - length,
914                                1,   length);
915             }
916           else
917             {
918               cairo_rectangle (cr,
919                                height + xthickness - length, pos,
920                                length,                       1);
921             }
922
923           /* draw label */
924           if (i == 0 && ((int)cur) % 10 == 0)
925             {
926               g_snprintf (unit_str, sizeof (unit_str), "%d", (int) cur);
927
928               if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
929                 {
930                   pango_layout_set_text (layout, unit_str, -1);
931                   pango_layout_get_extents (layout, &logical_rect, NULL);
932
933                   cairo_move_to (cr,
934                                  pos + 2,
935                                  ythickness + PANGO_PIXELS (logical_rect.y - digit_offset));
936                   pango_cairo_show_layout (cr, layout);
937                 }
938               else
939                 {
940                   gint j;
941
942                   for (j = 0; j < (int) strlen (unit_str); j++)
943                     {
944                       pango_layout_set_text (layout, unit_str + j, 1);
945                       pango_layout_get_extents (layout, NULL, &logical_rect);
946
947                       cairo_move_to (cr,
948                                      xthickness + 1,
949                                      pos + digit_height * j + 2 + PANGO_PIXELS (logical_rect.y - digit_offset));
950                       pango_cairo_show_layout (cr, layout);
951                     }
952                 }
953             }
954         }
955     }
956
957   cairo_fill (cr);
958 out:
959   cairo_destroy (cr);
960 }
961
962 static cairo_surface_t *
963 cm_gdk_window_create_similar_surface (GdkWindow *     window,
964                                    cairo_content_t content,
965                                    int             width,
966                                    int             height)
967 {
968 #if !GTK_CHECK_VERSION(2, 22, 0)
969   cairo_surface_t *window_surface, *surface;
970
971   g_return_val_if_fail (GDK_IS_WINDOW (window), NULL);
972
973   window_surface = GDK_DRAWABLE_GET_CLASS(window)->ref_cairo_surface(window);
974
975   surface = cairo_surface_create_similar (window_surface,
976                                           content,
977                                           width, height);
978
979   cairo_surface_destroy (window_surface);
980
981   return surface;
982 #else
983   return gdk_window_create_similar_surface(window, content, width, height);
984 #endif
985 }
986
987 static void
988 gtk_shruler_make_pixmap (GtkSHRuler *ruler)
989 {
990   GtkWidget        *widget = GTK_WIDGET (ruler);
991   GtkSHRulerPrivate *priv   = GTK_SHRULER_GET_PRIVATE (ruler);
992   GtkAllocation     allocation;
993
994   gtk_widget_get_allocation (widget, &allocation);
995
996   if (priv->backing_store)
997     cairo_surface_destroy (priv->backing_store);
998
999   priv->backing_store =
1000     cm_gdk_window_create_similar_surface (gtk_widget_get_window (widget),
1001                                        CAIRO_CONTENT_COLOR,
1002                                        allocation.width,
1003                                        allocation.height);
1004 }
1005
1006
1007 static PangoLayout *
1008 gtk_shruler_create_layout (GtkWidget   *widget,
1009                           const gchar *text)
1010 {
1011   GtkSHRulerPrivate *priv = GTK_SHRULER_GET_PRIVATE (widget);
1012   PangoLayout      *layout;
1013   PangoAttrList    *attrs;
1014   PangoAttribute   *attr;
1015
1016   layout = gtk_widget_create_pango_layout (widget, text);
1017
1018   attrs = pango_attr_list_new ();
1019
1020   attr = pango_attr_scale_new (priv->font_scale);
1021   attr->start_index = 0;
1022   attr->end_index   = -1;
1023   pango_attr_list_insert (attrs, attr);
1024
1025   pango_layout_set_attributes (layout, attrs);
1026   pango_attr_list_unref (attrs);
1027
1028   return layout;
1029 }
1030
1031 static PangoLayout *
1032 gtk_shruler_get_layout (GtkWidget   *widget,
1033                        const gchar *text)
1034 {
1035   GtkSHRulerPrivate *priv = GTK_SHRULER_GET_PRIVATE (widget);
1036
1037   if (priv->layout)
1038     {
1039       pango_layout_set_text (priv->layout, text, -1);
1040       return priv->layout;
1041     }
1042
1043   priv->layout = gtk_shruler_create_layout (widget, text);
1044
1045   return priv->layout;
1046 }