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