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 };
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;
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);
108 /* Make sure the ruler has been allocated already */
109 if (ruler->backing_store != NULL)
110 gtk_ruler_draw_pos (ruler);
112 return FALSE;
113 }
115 static void
116 sp_hruler_draw_ticks (GtkRuler *ruler)
117 {
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];
149 pango_desc = widget->style->font_desc;
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);
247 pango_layout_set_text (pango_layout, unit_str, -1);
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 }
261 }
263 static void
264 sp_hruler_draw_pos (GtkRuler *ruler)
265 {
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 }
319 }
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)
337 {
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;
358 }
360 static void
361 sp_vruler_class_init (SPVRulerClass *klass)
362 {
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;
373 }
375 static void
376 sp_vruler_init (SPVRuler *vruler)
377 {
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;
383 }
385 GtkWidget*
386 sp_vruler_new (void)
387 {
388 return GTK_WIDGET (gtk_type_new (sp_vruler_get_type ()));
389 }
392 static gint
393 sp_vruler_motion_notify (GtkWidget *widget,
394 GdkEventMotion *event)
395 {
396 GtkRuler *ruler;
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;
411 }
413 static void
414 sp_vruler_draw_ticks (GtkRuler *ruler)
415 {
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];
448 pango_desc = widget->style->font_desc;
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);
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);
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];
548 pango_layout_set_text (pango_layout, digit_str, 1);
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 }
565 }
567 static void
568 sp_vruler_draw_pos (GtkRuler *ruler)
569 {
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 }
622 }
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)
639 {
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));
651 }