Code

Partially fix bug #167500 (rulers being off)
[inkscape.git] / src / widgets / ruler.cpp
1 #define __SP_RULER_C__
3 /*
4  * Customized ruler class for inkscape
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   Frank Felfe <innerspace@iname.com>
9  *   bulia byak <buliabyak@users.sf.net>
10  *
11  * Copyright (C) 1999-2005 authors
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  */
16 #include <cmath>
17 #include <cstdio>
18 #include <string.h>
19 #include "widget-sizes.h"
20 #include "ruler.h"
21 #include "unit-constants.h"
22 #include <iostream>
24 #define MINIMUM_INCR          5
25 #define MAXIMUM_SUBDIVIDE     5
26 #define MAXIMUM_SCALES        10
27 #define UNUSED_PIXELS         2     // There appear to be two pixels that are not being used at each end of the ruler
29 static void sp_hruler_class_init    (SPHRulerClass *klass);
30 static void sp_hruler_init          (SPHRuler      *hruler);
31 static gint sp_hruler_motion_notify (GtkWidget      *widget,
32                                       GdkEventMotion *event);
33 static void sp_hruler_draw_ticks    (GtkRuler       *ruler);
34 static void sp_hruler_draw_pos      (GtkRuler       *ruler);
37 GtkType
38 sp_hruler_get_type (void)
39 {
40   static GtkType hruler_type = 0;
42   if (!hruler_type)
43     {
44       static const GtkTypeInfo hruler_info =
45       {
46         "SPHRuler",
47         sizeof (SPHRuler),
48         sizeof (SPHRulerClass),
49         (GtkClassInitFunc) sp_hruler_class_init,
50         (GtkObjectInitFunc) sp_hruler_init,
51         /* reserved_1 */ NULL,
52         /* reserved_2 */ NULL,
53         (GtkClassInitFunc) NULL,
54       };
55   
56       hruler_type = gtk_type_unique (gtk_ruler_get_type (), &hruler_info);
57     }
59   return hruler_type;
60 }
62 static void
63 sp_hruler_class_init (SPHRulerClass *klass)
64 {
65   GtkWidgetClass *widget_class;
66   GtkRulerClass *ruler_class;
68   widget_class = (GtkWidgetClass*) klass;
69   ruler_class = (GtkRulerClass*) klass;
71   widget_class->motion_notify_event = sp_hruler_motion_notify;
73   ruler_class->draw_ticks = sp_hruler_draw_ticks;
74   ruler_class->draw_pos = sp_hruler_draw_pos;
75 }
77 static void
78 sp_hruler_init (SPHRuler *hruler)
79 {
80   GtkWidget *widget;
82   widget = GTK_WIDGET (hruler);
83   widget->requisition.width = widget->style->xthickness * 2 + 1;
84   widget->requisition.height = widget->style->ythickness * 2 + RULER_HEIGHT;
85 }
88 GtkWidget*
89 sp_hruler_new (void)
90 {
91   return GTK_WIDGET (gtk_type_new (sp_hruler_get_type ()));
92 }
94 static gint
95 sp_hruler_motion_notify (GtkWidget      *widget,
96                           GdkEventMotion *event)
97 {
98   GtkRuler *ruler;
99   
100   g_return_val_if_fail (widget != NULL, FALSE);
101   g_return_val_if_fail (SP_IS_HRULER (widget), FALSE);
102   g_return_val_if_fail (event != NULL, FALSE);
104   ruler = GTK_RULER (widget);
105   double x = event->x; //Although event->x is double according to the docs, it only appears to return integers
106   ruler->position = ruler->lower + (ruler->upper - ruler->lower) * (x + UNUSED_PIXELS) / (widget->allocation.width + 2*UNUSED_PIXELS);
107   
108   /*  Make sure the ruler has been allocated already  */
109   if (ruler->backing_store != NULL)
110     gtk_ruler_draw_pos (ruler);
112   return FALSE;
115 static void
116 sp_hruler_draw_ticks (GtkRuler *ruler)
118   GtkWidget *widget;
119   GdkGC *gc, *bg_gc;
120   PangoFontDescription *pango_desc;
121   PangoContext *pango_context;
122   PangoLayout *pango_layout;
123   gint i, tick_index;
124   gint width, height;
125   gint xthickness;
126   gint ythickness;
127   gint length, ideal_length;
128   double lower, upper;          /* Upper and lower limits, in ruler units */
129   double increment;             /* Number of pixels per unit */
130   gint scale;                   /* Number of units per major unit */
131   double subd_incr;
132   double start, end, cur;
133   gchar unit_str[32];
134   gint digit_height;
135   gint text_width;
136   gint pos;
138   g_return_if_fail (ruler != NULL);
139   g_return_if_fail (SP_IS_HRULER (ruler));
141   if (!GTK_WIDGET_DRAWABLE (ruler)) 
142     return;
144   widget = GTK_WIDGET (ruler);
146   gc = widget->style->fg_gc[GTK_STATE_NORMAL];
147   bg_gc = widget->style->bg_gc[GTK_STATE_NORMAL];
148   
149   pango_desc = widget->style->font_desc;
150   
151   // Create the pango layout
152   pango_context = gtk_widget_get_pango_context (widget);
154   pango_layout = pango_layout_new (pango_context);
156   PangoFontDescription *fs = pango_font_description_new ();
157   pango_font_description_set_size (fs, RULER_FONT_SIZE);
158   pango_layout_set_font_description (pango_layout, fs);
159   pango_font_description_free (fs);
161   digit_height = (int) floor (RULER_FONT_SIZE * RULER_FONT_VERTICAL_SPACING / PANGO_SCALE + 0.5);
163   xthickness = widget->style->xthickness;
164   ythickness = widget->style->ythickness;
166   width = widget->allocation.width; // in pixels; is apparently 2 pixels shorter than the canvas at each end
167   height = widget->allocation.height;// - ythickness * 2;
169   gtk_paint_box (widget->style, ruler->backing_store,
170                  GTK_STATE_NORMAL, GTK_SHADOW_NONE, 
171                  NULL, widget, "hruler",
172                  0, 0, 
173                  widget->allocation.width, widget->allocation.height);
175   upper = ruler->upper / ruler->metric->pixels_per_unit;
176   lower = ruler->lower / ruler->metric->pixels_per_unit;
178   if ((upper - lower) == 0) 
179     return;
180   increment = (double) (width + 2*UNUSED_PIXELS) / (upper - lower);
182   /* determine the scale
183    *  We calculate the text size as for the vruler instead of using
184    *  text_width = gdk_string_width(font, unit_str), so that the result
185    *  for the scale looks consistent with an accompanying vruler
186    */
187   scale = (int)(ceil (ruler->max_size / ruler->metric->pixels_per_unit));
188   sprintf (unit_str, "%d", scale);
189   text_width = strlen (unit_str) * digit_height + 1;
191   for (scale = 0; scale < MAXIMUM_SCALES; scale++)
192     if (ruler->metric->ruler_scale[scale] * fabs(increment) > 2 * text_width)
193       break;
195   if (scale == MAXIMUM_SCALES)
196     scale = MAXIMUM_SCALES - 1;
198   /* drawing starts here */
199   length = 0;
200   for (i = MAXIMUM_SUBDIVIDE - 1; i >= 0; i--)
201     {
202       subd_incr = ruler->metric->ruler_scale[scale] / 
203                   ruler->metric->subdivide[i];
204       if (subd_incr * fabs(increment) <= MINIMUM_INCR) 
205         continue;
207       /* Calculate the length of the tickmarks. Make sure that
208        * this length increases for each set of ticks
209        */
210       ideal_length = height / (i + 1) - 1;
211       if (ideal_length > ++length)
212         length = ideal_length;
214       if (lower < upper)
215         {
216           start = floor (lower / subd_incr) * subd_incr;
217           end   = ceil  (upper / subd_incr) * subd_incr;
218         }
219       else
220         {
221           start = floor (upper / subd_incr) * subd_incr;
222           end   = ceil  (lower / subd_incr) * subd_incr;
223         }
225     tick_index = 0;
226     cur = start;
228         while (cur <= end)
229         {
230           pos = int(round ((cur - lower) * increment) - UNUSED_PIXELS);
232           gdk_draw_line (ruler->backing_store, gc,
233                          pos, height + ythickness, 
234                          pos, height - length + ythickness);
236           /* draw label */
237         double label_spacing_px = (increment*(double)ruler->metric->ruler_scale[scale])/ruler->metric->subdivide[i];
238           if (i == 0 && 
239                                 (label_spacing_px > 6*digit_height || tick_index%2 == 0 || cur == 0) && 
240                                 (label_spacing_px > 3*digit_height || tick_index%4 == 0 ||  cur == 0))
241             {
242                                 if (fabs((int)cur) >= 2000 && (((int) cur)/1000)*1000 == ((int) cur))
243                                         sprintf (unit_str, "%dk", ((int) cur)/1000);
244                                 else
245                                         sprintf (unit_str, "%d", (int) cur);
246         
247                                 pango_layout_set_text (pango_layout, unit_str, -1);
248               
249                                 gdk_draw_layout (ruler->backing_store, gc,
250                                pos + 2, 0, pango_layout);
251             }
253       /* Calculate cur from start rather than incrementing by subd_incr
254        * in each iteration. This is to avoid propagation of floating point 
255        * errors in subd_incr.
256        */
257       ++tick_index;
258       cur = start + (((double)tick_index) * (double)ruler->metric->ruler_scale[scale])/ ruler->metric->subdivide[i];
259         }
260     }
263 static void
264 sp_hruler_draw_pos (GtkRuler *ruler)
266   GtkWidget *widget;
267   GdkGC *gc;
268   int i;
269   gint x, y;
270   gint width, height;
271   gint bs_width, bs_height;
272   gint xthickness;
273   gint ythickness;
274   gfloat increment;
276   g_return_if_fail (ruler != NULL);
277   g_return_if_fail (SP_IS_HRULER (ruler));
279   if (GTK_WIDGET_DRAWABLE (ruler))
280     {
281       widget = GTK_WIDGET (ruler);
283       gc = widget->style->fg_gc[GTK_STATE_NORMAL];
284       xthickness = widget->style->xthickness;
285       ythickness = widget->style->ythickness;
286       width = widget->allocation.width; // in pixels; is apparently 2 pixels shorter than the canvas at each end
287       height = widget->allocation.height - ythickness * 2;
289       bs_width = height / 2;
290       bs_width |= 1;  /* make sure it's odd */
291       bs_height = bs_width / 2 + 1;
293       if ((bs_width > 0) && (bs_height > 0))
294         {
295           /*  If a backing store exists, restore the ruler  */
296           if (ruler->backing_store && ruler->non_gr_exp_gc)
297             gdk_draw_pixmap (ruler->widget.window,
298                              ruler->non_gr_exp_gc,
299                              ruler->backing_store,
300                              ruler->xsrc, ruler->ysrc,
301                              ruler->xsrc, ruler->ysrc,
302                              bs_width, bs_height);
304           increment = (gfloat) (width + 2*UNUSED_PIXELS) / (ruler->upper - ruler->lower);
306           x = int(round ((ruler->position - ruler->lower) * increment) + (xthickness - bs_width) / 2 - 1);
307           y = (height + bs_height) / 2 + ythickness;
309           for (i = 0; i < bs_height; i++)
310             gdk_draw_line (widget->window, gc,
311                            x + i, y + i,
312                            x + bs_width - 1 - i, y + i);
315           ruler->xsrc = x;
316           ruler->ysrc = y;
317         }
318     }
325 // vruler
327 static void sp_vruler_class_init    (SPVRulerClass *klass);
328 static void sp_vruler_init          (SPVRuler      *vruler);
329 static gint sp_vruler_motion_notify (GtkWidget      *widget,
330                                       GdkEventMotion *event);
331 static void sp_vruler_draw_ticks    (GtkRuler       *ruler);
332 static void sp_vruler_draw_pos      (GtkRuler       *ruler);
335 GtkType
336 sp_vruler_get_type (void)
338   static GtkType vruler_type = 0;
340   if (!vruler_type)
341     {
342       static const GtkTypeInfo vruler_info =
343       {
344         "SPVRuler",
345         sizeof (SPVRuler),
346         sizeof (SPVRulerClass),
347         (GtkClassInitFunc) sp_vruler_class_init,
348         (GtkObjectInitFunc) sp_vruler_init,
349         /* reserved_1 */ NULL,
350         /* reserved_2 */ NULL,
351         (GtkClassInitFunc) NULL,
352       };
354       vruler_type = gtk_type_unique (gtk_ruler_get_type (), &vruler_info);
355     }
357   return vruler_type;
360 static void
361 sp_vruler_class_init (SPVRulerClass *klass)
363   GtkWidgetClass *widget_class;
364   GtkRulerClass *ruler_class;
366   widget_class = (GtkWidgetClass*) klass;
367   ruler_class = (GtkRulerClass*) klass;
369   widget_class->motion_notify_event = sp_vruler_motion_notify;
371   ruler_class->draw_ticks = sp_vruler_draw_ticks;
372   ruler_class->draw_pos = sp_vruler_draw_pos;
375 static void
376 sp_vruler_init (SPVRuler *vruler)
378   GtkWidget *widget;
380   widget = GTK_WIDGET (vruler);
381   widget->requisition.width = widget->style->xthickness * 2 + RULER_WIDTH;
382   widget->requisition.height = widget->style->ythickness * 2 + 1;
385 GtkWidget*
386 sp_vruler_new (void)
388   return GTK_WIDGET (gtk_type_new (sp_vruler_get_type ()));
392 static gint
393 sp_vruler_motion_notify (GtkWidget      *widget,
394                           GdkEventMotion *event)
396   GtkRuler *ruler;
397   
398   g_return_val_if_fail (widget != NULL, FALSE);
399   g_return_val_if_fail (SP_IS_VRULER (widget), FALSE);
400   g_return_val_if_fail (event != NULL, FALSE);
402   ruler = GTK_RULER (widget);
403   double y = event->y; //Although event->y is double according to the docs, it only appears to return integers
404   ruler->position = ruler->lower + (ruler->upper - ruler->lower) * (y + UNUSED_PIXELS) / (widget->allocation.height + 2*UNUSED_PIXELS);
406   /*  Make sure the ruler has been allocated already  */
407   if (ruler->backing_store != NULL)
408     gtk_ruler_draw_pos (ruler);
410   return FALSE;
413 static void
414 sp_vruler_draw_ticks (GtkRuler *ruler)
416   GtkWidget *widget;
417   GdkGC *gc, *bg_gc;
418   PangoFontDescription *pango_desc;
419   PangoContext *pango_context;
420   PangoLayout *pango_layout;
421   gint i, j, tick_index;
422   gint width, height;
423   gint xthickness;
424   gint ythickness;
425   gint length, ideal_length;
426   double lower, upper;          /* Upper and lower limits, in ruler units */
427   double increment;             /* Number of pixels per unit */
428   gint scale;                   /* Number of units per major unit */
429   double subd_incr;
430   double start, end, cur;
431   gchar unit_str[32];
432   gchar digit_str[2] = { '\0', '\0' };
433   gint digit_height;
434   gint text_height;
435   gint pos;
437   g_return_if_fail (ruler != NULL);
438   g_return_if_fail (SP_IS_VRULER (ruler));
440   if (!GTK_WIDGET_DRAWABLE (ruler)) 
441     return;
443   widget = GTK_WIDGET (ruler);
445   gc = widget->style->fg_gc[GTK_STATE_NORMAL];
446   bg_gc = widget->style->bg_gc[GTK_STATE_NORMAL];
447   
448   pango_desc = widget->style->font_desc;
449   
450   // Create the pango layout
451   pango_context = gtk_widget_get_pango_context (widget);
453   pango_layout = pango_layout_new (pango_context);
455   PangoFontDescription *fs = pango_font_description_new ();
456   pango_font_description_set_size (fs, RULER_FONT_SIZE);
457   pango_layout_set_font_description (pango_layout, fs);
458   pango_font_description_free (fs);
460   digit_height = (int) floor (RULER_FONT_SIZE * RULER_FONT_VERTICAL_SPACING / PANGO_SCALE + 0.5);
461   
462   xthickness = widget->style->xthickness;
463   ythickness = widget->style->ythickness;
465   width = widget->allocation.height; //in pixels; is apparently 2 pixels shorter than the canvas at each end
466   height = widget->allocation.width;// - ythickness * 2;
468   gtk_paint_box (widget->style, ruler->backing_store,
469                  GTK_STATE_NORMAL, GTK_SHADOW_NONE, 
470                  NULL, widget, "vruler",
471                  0, 0, 
472                  widget->allocation.width, widget->allocation.height);
473   
474   upper = ruler->upper / ruler->metric->pixels_per_unit;
475   lower = ruler->lower / ruler->metric->pixels_per_unit;
477   if ((upper - lower) == 0)
478     return;
479   increment = (double) (width + 2*UNUSED_PIXELS) / (upper - lower);
481   /* determine the scale
482    *   use the maximum extents of the ruler to determine the largest
483    *   possible number to be displayed.  Calculate the height in pixels
484    *   of this displayed text. Use this height to find a scale which
485    *   leaves sufficient room for drawing the ruler.  
486    */
487   scale = (int)ceil (ruler->max_size / ruler->metric->pixels_per_unit);
488   sprintf (unit_str, "%d", scale);
489   text_height = strlen (unit_str) * digit_height + 1;
491   for (scale = 0; scale < MAXIMUM_SCALES; scale++)
492     if (ruler->metric->ruler_scale[scale] * fabs(increment) > 2 * text_height)
493       break;
495   if (scale == MAXIMUM_SCALES)
496     scale = MAXIMUM_SCALES - 1;
498   /* drawing starts here */
499   length = 0;
500   for (i = MAXIMUM_SUBDIVIDE - 1; i >= 0; i--) {
501                 subd_incr = (double) ruler->metric->ruler_scale[scale] / 
502                         (double) ruler->metric->subdivide[i];
503                 if (subd_incr * fabs(increment) <= MINIMUM_INCR) 
504                         continue;
506                 /* Calculate the length of the tickmarks. Make sure that
507                  * this length increases for each set of ticks
508                  */
509                 ideal_length = height / (i + 1) - 1;
510                 if (ideal_length > ++length)
511                         length = ideal_length;
513                 if (lower < upper)
514                         {
515                                 start = floor (lower / subd_incr) * subd_incr;
516                                 end   = ceil  (upper / subd_incr) * subd_incr;
517                         }
518                 else
519                         {
520                                 start = floor (upper / subd_incr) * subd_incr;
521                                 end   = ceil  (lower / subd_incr) * subd_incr;
522                         }
524     tick_index = 0;
525     cur = start;        
527     while (cur < end) {
528                         pos = int(round ((cur - lower) * increment) - UNUSED_PIXELS);
530                         gdk_draw_line (ruler->backing_store, gc,
531                                                                                  height + xthickness - length, pos,
532                                                                                  height + xthickness, pos);
534                         /* draw label */
535                         double label_spacing_px = fabs((increment*(double)ruler->metric->ruler_scale[scale])/ruler->metric->subdivide[i]);
536                         if (i == 0 && 
537                                         (label_spacing_px > 6*digit_height || tick_index%2 == 0 || cur == 0) && 
538                                         (label_spacing_px > 3*digit_height || tick_index%4 == 0 || cur == 0))
539                                 {
540                                         if (fabs((int)cur) >= 2000 && (((int) cur)/1000)*1000 == ((int) cur))
541                                                 sprintf (unit_str, "%dk", ((int) cur)/1000);
542                                         else
543                                                 sprintf (unit_str, "%d", (int) cur);
544                                         for (j = 0; j < (int) strlen (unit_str); j++)
545                                                 {
546                                                         digit_str[0] = unit_str[j];
547                   
548                                                         pango_layout_set_text (pango_layout, digit_str, 1);
549       
550                                                         gdk_draw_layout (ruler->backing_store, gc,
551                                                                                                                          xthickness + 1, 
552                                                                                                                          pos + digit_height * (j) + 1,
553                                                                                                                          pango_layout); 
554                                                 }
555                                 }
557                         /* Calculate cur from start rather than incrementing by subd_incr
558                          * in each iteration. This is to avoid propagation of floating point 
559                          * errors in subd_incr.
560                          */
561                         ++tick_index;
562                         cur = start + (((double)tick_index) * (double)ruler->metric->ruler_scale[scale])/ ruler->metric->subdivide[i];
563                 }
564         }
567 static void
568 sp_vruler_draw_pos (GtkRuler *ruler)
570   GtkWidget *widget;
571   GdkGC *gc;
572   int i;
573   gint x, y;
574   gint width, height;
575   gint bs_width, bs_height;
576   gint xthickness;
577   gint ythickness;
578   gfloat increment;
580   g_return_if_fail (ruler != NULL);
581   g_return_if_fail (SP_IS_VRULER (ruler));
583   if (GTK_WIDGET_DRAWABLE (ruler))
584     {
585       widget = GTK_WIDGET (ruler);
587       gc = widget->style->fg_gc[GTK_STATE_NORMAL];
588       xthickness = widget->style->xthickness;
589       ythickness = widget->style->ythickness;
590       width = widget->allocation.width - xthickness * 2;
591       height = widget->allocation.height; // in pixels; is apparently 2 pixels shorter than the canvas at each end
593       bs_height = width / 2;
594       bs_height |= 1;  /* make sure it's odd */
595       bs_width = bs_height / 2 + 1;
597       if ((bs_width > 0) && (bs_height > 0))
598         {
599           /*  If a backing store exists, restore the ruler  */
600           if (ruler->backing_store && ruler->non_gr_exp_gc)
601             gdk_draw_pixmap (ruler->widget.window,
602                              ruler->non_gr_exp_gc,
603                              ruler->backing_store,
604                              ruler->xsrc, ruler->ysrc,
605                              ruler->xsrc, ruler->ysrc,
606                              bs_width, bs_height);
608           increment = (gfloat) (height + 2*UNUSED_PIXELS) / (ruler->upper - ruler->lower);
610           x = (width + bs_width) / 2 + xthickness;
611           y = int(round ((ruler->position - ruler->lower) * increment) + (ythickness - bs_height) / 2 - 1);
613           for (i = 0; i < bs_width; i++)
614             gdk_draw_line (widget->window, gc,
615                            x + i, y + i,
616                            x + i, y + bs_height - 1 - i);
618           ruler->xsrc = x;
619           ruler->ysrc = y;
620         }
621     }
624 /// Ruler metrics.
625 static GtkRulerMetric const sp_ruler_metrics[] = {
626   // NOTE: the order of records in this struct must correspond to the SPMetric enum.
627   {"NONE",  "", 1, { 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000 }, { 1, 5, 10, 50, 100 }},
628   {"millimeters",  "mm", PX_PER_MM, { 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000 }, { 1, 5, 10, 50, 100 }},
629   {"centimeters", "cm", PX_PER_CM, { 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000 }, { 1, 5, 10, 50, 100 }},
630   {"inches",      "in", PX_PER_IN, { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 }, { 1, 2, 4, 8, 16 }},
631   {"points",      "pt", PX_PER_PT, { 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000 }, { 1, 5, 10, 50, 100 }},
632   {"pixels",      "px", PX_PER_PX, { 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000 }, { 1, 5, 10, 50, 100 }},
633   {"meters",      "m", PX_PER_M, { 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000 }, { 1, 5, 10, 50, 100 }},
634 };
636 void
637 sp_ruler_set_metric (GtkRuler *ruler,
638                      SPMetric  metric)
640   g_return_if_fail (ruler != NULL);
641   g_return_if_fail (GTK_IS_RULER (ruler));
642   g_return_if_fail((unsigned) metric < G_N_ELEMENTS(sp_ruler_metrics));
644   if (metric == 0) 
645         return;
647   ruler->metric = const_cast<GtkRulerMetric *>(&sp_ruler_metrics[metric]);
649   if (GTK_WIDGET_DRAWABLE (ruler))
650     gtk_widget_queue_draw (GTK_WIDGET (ruler));