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