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