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