Branch data Line data Source code
1 : : /*
2 : : * Copyright (C) 2011-2013 Jiri Techet <techet@gmail.com>
3 : : * Copyright (C) 2019 Marcus Lundblad <ml@update.uu.se>
4 : : * Copyright (C) 2020 Collabora Ltd. (https://www.collabora.com)
5 : : * Copyright (C) 2020 Corentin Noël <corentin.noel@collabora.com>
6 : : *
7 : : * This library is free software; you can redistribute it and/or
8 : : * modify it under the terms of the GNU Lesser General Public
9 : : * License as published by the Free Software Foundation; either
10 : : * version 2.1 of the License, or (at your option) any later version.
11 : : *
12 : : * This library is distributed in the hope that it will be useful,
13 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : : * Lesser General Public License for more details.
16 : : *
17 : : * You should have received a copy of the GNU Lesser General Public
18 : : * License along with this library; if not, write to the Free Software
19 : : * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 : : */
21 : :
22 : : /**
23 : : * ShumateScale:
24 : : *
25 : : * A widget displaying a scale.
26 : : *
27 : : * # CSS nodes
28 : : *
29 : : * ```
30 : : * map-scale
31 : : * ├── label[.metric][.imperial]
32 : : * ```
33 : : *
34 : : * `ShumateScale` uses a single CSS node with name map-scale, it has up to two
35 : : * childs different labels.
36 : : */
37 : :
38 : : #include "shumate-scale.h"
39 : : #include "shumate-enum-types.h"
40 : :
41 : : #include <glib-object.h>
42 : : #include <glib/gi18n.h>
43 : : #include <math.h>
44 : : #include <string.h>
45 : :
46 : : enum
47 : : {
48 : : PROP_UNIT = 1,
49 : : PROP_MAX_SCALE_WIDTH,
50 : : PROP_VIEWPORT,
51 : : N_PROPERTIES,
52 : : };
53 : :
54 : : static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
55 : :
56 : : struct _ShumateScale
57 : : {
58 : : GtkWidget parent_instance;
59 : :
60 : : ShumateUnit unit;
61 : : guint max_scale_width;
62 : :
63 : : ShumateViewport *viewport;
64 : :
65 : : GtkWidget *metric_label;
66 : : GtkWidget *imperial_label;
67 : : };
68 : :
69 [ + + + - ]: 4 : G_DEFINE_TYPE (ShumateScale, shumate_scale, GTK_TYPE_WIDGET);
70 : :
71 : : #define FEET_IN_METERS 3.280839895
72 : :
73 : : #define FEET_IN_A_MILE 5280.0
74 : :
75 : : /*
76 : : * shumate_scale_compute_length:
77 : : * @self: a #ShumateScale
78 : : *
79 : : * This loop will find the pretty value to display on the scale.
80 : : * It will be run once for metric units, and twice for imperials
81 : : * so that both feet and miles have pretty numbers.
82 : : */
83 : : static gboolean
84 : 0 : shumate_scale_compute_length (ShumateScale *self,
85 : : ShumateUnit unit,
86 : : float *out_scale_width,
87 : : float *out_base,
88 : : gboolean *out_is_small_unit)
89 : : {
90 : 0 : ShumateMapSource *map_source;
91 : 0 : double zoom_level;
92 : 0 : double lat, lon;
93 : 0 : float scale_width;
94 : 0 : float base;
95 : 0 : float factor;
96 : 0 : gboolean is_small_unit = TRUE;
97 : 0 : double m_per_pixel;
98 : :
99 [ # # ]: 0 : g_assert (SHUMATE_IS_SCALE (self));
100 : :
101 [ # # ]: 0 : if (out_scale_width)
102 : 0 : *out_scale_width = 0;
103 : :
104 [ # # ]: 0 : if (out_base)
105 : 0 : *out_base = 1;
106 : :
107 [ # # ]: 0 : if (out_is_small_unit)
108 : 0 : *out_is_small_unit = TRUE;
109 : :
110 [ # # ]: 0 : if (!self->viewport)
111 : : return FALSE;
112 : :
113 : 0 : scale_width = self->max_scale_width;
114 : 0 : zoom_level = shumate_viewport_get_zoom_level (self->viewport);
115 : 0 : map_source = shumate_viewport_get_reference_map_source (self->viewport);
116 [ # # ]: 0 : if (!map_source)
117 : : return FALSE;
118 : :
119 : 0 : lat = shumate_location_get_latitude (SHUMATE_LOCATION (self->viewport));
120 : 0 : lon = shumate_location_get_longitude (SHUMATE_LOCATION (self->viewport));
121 : 0 : m_per_pixel = shumate_map_source_get_meters_per_pixel (map_source,
122 : : zoom_level,
123 : : lat,
124 : : lon);
125 : :
126 [ # # ]: 0 : if (unit == SHUMATE_UNIT_IMPERIAL)
127 : 0 : m_per_pixel *= FEET_IN_METERS; /* m_per_pixel is now in ft */
128 : :
129 : 0 : do
130 : : {
131 : : /* Keep the previous power of 10 */
132 : 0 : base = floor (log (m_per_pixel * scale_width) / log (10));
133 : 0 : base = pow (10, base);
134 : :
135 : : /* How many times can it be fitted in our max scale width */
136 [ # # ]: 0 : g_assert (base > 0);
137 [ # # ]: 0 : g_assert (m_per_pixel * scale_width / base > 0);
138 : 0 : scale_width /= m_per_pixel * scale_width / base;
139 [ # # ]: 0 : g_assert (scale_width > 0);
140 : 0 : factor = floor (self->max_scale_width / scale_width);
141 : 0 : base *= factor;
142 : 0 : scale_width *= factor;
143 : :
144 [ # # ]: 0 : if (unit == SHUMATE_UNIT_METRIC)
145 : : {
146 [ # # ]: 0 : if (base / 1000.0 >= 1)
147 : : {
148 : 0 : base /= 1000.0; /* base is now in km */
149 : 0 : is_small_unit = FALSE;
150 : : }
151 : :
152 : : break;
153 : : }
154 [ # # ]: 0 : else if (unit == SHUMATE_UNIT_IMPERIAL)
155 : : {
156 [ # # # # ]: 0 : if (is_small_unit && base / FEET_IN_A_MILE >= 1)
157 : : {
158 : 0 : m_per_pixel /= FEET_IN_A_MILE; /* m_per_pixel is now in miles */
159 : 0 : is_small_unit = FALSE;
160 : : /* we need to recompute the base because 1000 ft != 1 mile */
161 : : }
162 : : else
163 : : break;
164 : : }
165 : : } while (TRUE);
166 : :
167 : :
168 [ # # ]: 0 : if (out_scale_width)
169 : 0 : *out_scale_width = scale_width;
170 : :
171 [ # # ]: 0 : if (out_base)
172 : 0 : *out_base = base;
173 : :
174 [ # # ]: 0 : if (out_is_small_unit)
175 : 0 : *out_is_small_unit = is_small_unit;
176 : :
177 : : return TRUE;
178 : : }
179 : :
180 : : static void
181 : 0 : shumate_scale_on_scale_changed (ShumateScale *self)
182 : : {
183 : 0 : float metric_scale_width, metric_base;
184 : 0 : float imperial_scale_width, imperial_base;
185 : 0 : gboolean metric_is_small_unit, imperial_is_small_unit;
186 : 0 : g_autofree char *metric_label = NULL;
187 : 0 : g_autofree char *imperial_label = NULL;
188 : :
189 : 0 : shumate_scale_compute_length (self, SHUMATE_UNIT_METRIC, &metric_scale_width, &metric_base, &metric_is_small_unit);
190 : 0 : shumate_scale_compute_length (self, SHUMATE_UNIT_IMPERIAL, &imperial_scale_width, &imperial_base, &imperial_is_small_unit);
191 : :
192 : 0 : gtk_widget_set_size_request (self->metric_label, metric_scale_width, -1);
193 : 0 : gtk_widget_set_size_request (self->imperial_label, imperial_scale_width, -1);
194 : :
195 [ # # ]: 0 : if (metric_is_small_unit)
196 : : // m is the unit for meters
197 : 0 : metric_label = g_strdup_printf (_("%d m"), (int) metric_base);
198 : : else
199 : : // km is the unit for kilometers
200 : 0 : metric_label = g_strdup_printf (_("%d km"), (int) metric_base);
201 : :
202 : 0 : gtk_label_set_label (GTK_LABEL (self->metric_label), metric_label);
203 : :
204 [ # # ]: 0 : if (imperial_is_small_unit)
205 : : // ft is the unit for feet
206 : 0 : imperial_label = g_strdup_printf (_("%d ft"), (int) imperial_base);
207 : : else
208 : : // mi is the unit for miles
209 : 0 : imperial_label = g_strdup_printf (_("%d mi"), (int) imperial_base);
210 : :
211 : 0 : gtk_label_set_label (GTK_LABEL (self->imperial_label), imperial_label);
212 : 0 : gtk_widget_queue_resize (GTK_WIDGET (self));
213 : 0 : }
214 : :
215 : : static void
216 : 0 : on_viewport_props_changed (ShumateScale *self,
217 : : G_GNUC_UNUSED GParamSpec *pspec,
218 : : ShumateViewport *viewport)
219 : : {
220 : 0 : shumate_scale_on_scale_changed (self);
221 : 0 : }
222 : :
223 : :
224 : : static void
225 : 0 : shumate_scale_get_property (GObject *object,
226 : : guint prop_id,
227 : : GValue *value,
228 : : GParamSpec *pspec)
229 : : {
230 : 0 : ShumateScale *scale = SHUMATE_SCALE (object);
231 : :
232 [ # # # # ]: 0 : switch (prop_id)
233 : : {
234 : 0 : case PROP_UNIT:
235 : 0 : g_value_set_enum (value, scale->unit);
236 : 0 : break;
237 : :
238 : 0 : case PROP_MAX_SCALE_WIDTH:
239 : 0 : g_value_set_uint (value, scale->max_scale_width);
240 : 0 : break;
241 : :
242 : 0 : case PROP_VIEWPORT:
243 : 0 : g_value_set_object (value, scale->viewport);
244 : 0 : break;
245 : :
246 : 0 : default:
247 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
248 : : }
249 : 0 : }
250 : :
251 : :
252 : : static void
253 : 0 : shumate_scale_set_property (GObject *object,
254 : : guint prop_id,
255 : : const GValue *value,
256 : : GParamSpec *pspec)
257 : : {
258 : 0 : ShumateScale *scale = SHUMATE_SCALE (object);
259 : :
260 [ # # # # ]: 0 : switch (prop_id)
261 : : {
262 : 0 : case PROP_UNIT:
263 : 0 : shumate_scale_set_unit (scale, g_value_get_enum (value));
264 : 0 : break;
265 : :
266 : 0 : case PROP_MAX_SCALE_WIDTH:
267 : 0 : shumate_scale_set_max_width (scale, g_value_get_uint (value));
268 : 0 : break;
269 : :
270 : 0 : case PROP_VIEWPORT:
271 : 0 : shumate_scale_set_viewport (scale, g_value_get_object (value));
272 : 0 : break;
273 : :
274 : 0 : default:
275 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
276 : : }
277 : 0 : }
278 : :
279 : : static void
280 : 0 : shumate_scale_dispose (GObject *object)
281 : : {
282 : 0 : ShumateScale *scale = SHUMATE_SCALE (object);
283 : :
284 : 0 : g_signal_handlers_disconnect_by_data (scale->viewport, scale);
285 [ # # ]: 0 : g_clear_object (&scale->viewport);
286 [ # # ]: 0 : g_clear_pointer (&scale->metric_label, gtk_widget_unparent);
287 [ # # ]: 0 : g_clear_pointer (&scale->imperial_label, gtk_widget_unparent);
288 : :
289 : 0 : G_OBJECT_CLASS (shumate_scale_parent_class)->dispose (object);
290 : 0 : }
291 : :
292 : : static void
293 : 1 : shumate_scale_class_init (ShumateScaleClass *klass)
294 : : {
295 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
296 : 1 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
297 : 1 : GdkDisplay *display;
298 : :
299 : 1 : object_class->dispose = shumate_scale_dispose;
300 : 1 : object_class->get_property = shumate_scale_get_property;
301 : 1 : object_class->set_property = shumate_scale_set_property;
302 : :
303 : : /**
304 : : * ShumateScale:max-width:
305 : : *
306 : : * The size of the map scale on screen in pixels.
307 : : */
308 : 2 : obj_properties[PROP_MAX_SCALE_WIDTH] =
309 : 1 : g_param_spec_uint ("max-width",
310 : : "The width of the scale",
311 : : "The max width of the scale on screen",
312 : : 1, G_MAXUINT, 150,
313 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
314 : :
315 : : /**
316 : : * ShumateScale:unit:
317 : : *
318 : : * The scale's units.
319 : : */
320 : 2 : obj_properties[PROP_UNIT] =
321 : 1 : g_param_spec_enum ("unit",
322 : : "The scale's unit",
323 : : "The map scale's unit",
324 : : SHUMATE_TYPE_UNIT,
325 : : SHUMATE_UNIT_BOTH,
326 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
327 : :
328 : : /**
329 : : * ShumateScale:viewport:
330 : : *
331 : : * The viewport to use.
332 : : */
333 : 2 : obj_properties[PROP_VIEWPORT] =
334 : 1 : g_param_spec_object ("viewport",
335 : : "The viewport",
336 : : "The viewport",
337 : : SHUMATE_TYPE_VIEWPORT,
338 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
339 : :
340 : 1 : g_object_class_install_properties (object_class, N_PROPERTIES, obj_properties);
341 : :
342 : 1 : gtk_widget_class_set_css_name (widget_class, "map-scale");
343 : 1 : gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
344 : :
345 : 1 : display = gdk_display_get_default ();
346 [ - + ]: 1 : if (display)
347 : : {
348 : 0 : g_autoptr(GtkCssProvider) provider = gtk_css_provider_new ();
349 : 0 : gtk_css_provider_load_from_resource (provider, "/org/gnome/shumate/scale.css");
350 [ # # ]: 0 : gtk_style_context_add_provider_for_display (display,
351 : : GTK_STYLE_PROVIDER (provider),
352 : : GTK_STYLE_PROVIDER_PRIORITY_FALLBACK);
353 : : }
354 : 1 : }
355 : :
356 : : static void
357 : 0 : shumate_scale_init (ShumateScale *self)
358 : : {
359 : 0 : GtkWidget *self_widget = GTK_WIDGET (self);
360 : 0 : GtkLayoutManager *layout = gtk_widget_get_layout_manager (self_widget);
361 : :
362 : 0 : self->unit = SHUMATE_UNIT_BOTH;
363 : 0 : self->max_scale_width = 150;
364 : :
365 : 0 : gtk_orientable_set_orientation (GTK_ORIENTABLE (layout), GTK_ORIENTATION_VERTICAL);
366 : 0 : gtk_widget_add_css_class (self_widget, "vertical");
367 : :
368 : 0 : self->metric_label = gtk_label_new (NULL);
369 : 0 : g_object_set (G_OBJECT (self->metric_label),
370 : : "xalign", 0.0f,
371 : : "halign", GTK_ALIGN_START,
372 : : "ellipsize", PANGO_ELLIPSIZE_END,
373 : : NULL);
374 : 0 : gtk_widget_add_css_class (self->metric_label, "metric");
375 : 0 : self->imperial_label = gtk_label_new (NULL);
376 : 0 : g_object_set (G_OBJECT (self->imperial_label),
377 : : "xalign", 0.0f,
378 : : "halign", GTK_ALIGN_START,
379 : : "ellipsize", PANGO_ELLIPSIZE_END,
380 : : NULL);
381 : 0 : gtk_widget_add_css_class (self->imperial_label, "imperial");
382 : :
383 : 0 : gtk_widget_insert_after (self->metric_label, self_widget, NULL);
384 : 0 : gtk_widget_insert_after (self->imperial_label, self_widget, self->metric_label);
385 : 0 : }
386 : :
387 : :
388 : : /**
389 : : * shumate_scale_new:
390 : : * @viewport: (nullable): a #ShumateViewport
391 : : *
392 : : * Creates an instance of #ShumateScale.
393 : : *
394 : : * Returns: a new #ShumateScale.
395 : : */
396 : : ShumateScale *
397 : 0 : shumate_scale_new (ShumateViewport *viewport)
398 : : {
399 : 0 : return SHUMATE_SCALE (g_object_new (SHUMATE_TYPE_SCALE,
400 : : "viewport", viewport,
401 : : NULL));
402 : : }
403 : :
404 : :
405 : : /**
406 : : * shumate_scale_set_max_width:
407 : : * @scale: a #ShumateScale
408 : : * @value: the number of pixels
409 : : *
410 : : * Sets the maximum width of the scale on the screen in pixels
411 : : */
412 : : void
413 : 0 : shumate_scale_set_max_width (ShumateScale *scale,
414 : : guint value)
415 : : {
416 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_SCALE (scale));
417 : :
418 [ # # ]: 0 : if (scale->max_scale_width == value)
419 : : return;
420 : :
421 : 0 : scale->max_scale_width = value;
422 : 0 : g_object_notify_by_pspec(G_OBJECT (scale), obj_properties[PROP_MAX_SCALE_WIDTH]);
423 : 0 : shumate_scale_on_scale_changed (scale);
424 : : }
425 : :
426 : :
427 : : /**
428 : : * shumate_scale_set_unit:
429 : : * @scale: a #ShumateScale
430 : : * @unit: a #ShumateUnit
431 : : *
432 : : * Sets the scale unit.
433 : : */
434 : : void
435 : 0 : shumate_scale_set_unit (ShumateScale *scale,
436 : : ShumateUnit unit)
437 : : {
438 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_SCALE (scale));
439 : :
440 [ # # ]: 0 : if (scale->unit == unit)
441 : : return;
442 : :
443 : 0 : scale->unit = unit;
444 : :
445 : 0 : gtk_widget_set_visible (scale->metric_label, unit == SHUMATE_UNIT_METRIC || unit == SHUMATE_UNIT_BOTH);
446 : 0 : gtk_widget_set_visible (scale->imperial_label, unit == SHUMATE_UNIT_IMPERIAL || unit == SHUMATE_UNIT_BOTH);
447 : :
448 : 0 : g_object_notify_by_pspec(G_OBJECT (scale), obj_properties[PROP_UNIT]);
449 : 0 : shumate_scale_on_scale_changed (scale);
450 : : }
451 : :
452 : : /**
453 : : * shumate_scale_set_viewport:
454 : : * @scale: a #ShumateScale
455 : : * @viewport: (nullable): a #ShumateViewport
456 : : *
457 : : * Sets the scale viewport.
458 : : */
459 : : void
460 : 0 : shumate_scale_set_viewport (ShumateScale *scale,
461 : : ShumateViewport *viewport)
462 : : {
463 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_SCALE (scale));
464 : :
465 [ # # ]: 0 : if (scale->viewport)
466 : 0 : g_signal_handlers_disconnect_by_data (scale->viewport, scale);
467 : :
468 [ # # ]: 0 : if (g_set_object (&scale->viewport, viewport))
469 : : {
470 : 0 : g_object_notify_by_pspec(G_OBJECT (scale), obj_properties[PROP_VIEWPORT]);
471 [ # # ]: 0 : if (scale->viewport)
472 : : {
473 : 0 : g_signal_connect_swapped (scale->viewport, "notify::latitude", G_CALLBACK (on_viewport_props_changed), scale);
474 : 0 : g_signal_connect_swapped (scale->viewport, "notify::zoom-level", G_CALLBACK (on_viewport_props_changed), scale);
475 : 0 : g_signal_connect_swapped (scale->viewport, "notify::reference-map-source", G_CALLBACK (on_viewport_props_changed), scale);
476 : : }
477 : :
478 : 0 : shumate_scale_on_scale_changed (scale);
479 : : }
480 : : }
481 : :
482 : :
483 : : /**
484 : : * shumate_scale_get_max_width:
485 : : * @scale: a #ShumateScale
486 : : *
487 : : * Gets the maximum scale width.
488 : : *
489 : : * Returns: The maximum scale width in pixels.
490 : : */
491 : : guint
492 : 0 : shumate_scale_get_max_width (ShumateScale *scale)
493 : : {
494 [ # # ]: 0 : g_return_val_if_fail (SHUMATE_IS_SCALE (scale), FALSE);
495 : :
496 : 0 : return scale->max_scale_width;
497 : : }
498 : :
499 : :
500 : : /**
501 : : * shumate_scale_get_unit:
502 : : * @scale: a #ShumateScale
503 : : *
504 : : * Gets the unit used by the scale.
505 : : *
506 : : * Returns: The unit used by the scale
507 : : */
508 : : ShumateUnit
509 : 0 : shumate_scale_get_unit (ShumateScale *scale)
510 : : {
511 [ # # ]: 0 : g_return_val_if_fail (SHUMATE_IS_SCALE (scale), FALSE);
512 : :
513 : 0 : return scale->unit;
514 : : }
515 : :
516 : : /**
517 : : * shumate_scale_get_viewport:
518 : : * @scale: a #ShumateScale
519 : : *
520 : : * Gets the viewport used by the scale.
521 : : *
522 : : * Returns: (transfer none) (nullable): The #ShumateViewport used by the scale
523 : : */
524 : : ShumateViewport *
525 : 0 : shumate_scale_get_viewport (ShumateScale *scale)
526 : : {
527 [ # # ]: 0 : g_return_val_if_fail (SHUMATE_IS_SCALE (scale), NULL);
528 : :
529 : 0 : return scale->viewport;
530 : : }
|