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