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