2
* Copyright (C) 2010 Intel, Inc
3
* Copyright (C) 2011 Canonical Ltd.
5
* Portions from Ubiquity, Copyright (C) 2009 Canonical Ltd.
6
* Written by Evan Dandrea <evand@ubuntu.com>
8
* This program is free software; you can redistribute it and/or modify
9
* it under the terms of the GNU General Public License as published by
10
* the Free Software Foundation; either version 2 of the License, or
11
* (at your option) any later version.
13
* This program is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
* GNU General Public License for more details.
18
* You should have received a copy of the GNU General Public License
19
* along with this program; if not, write to the Free Software
20
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22
* Author: Thomas Wood <thomas.wood@intel.com>
26
#include "cc-timezone-map.h"
30
G_DEFINE_TYPE (CcTimezoneMap, cc_timezone_map, GTK_TYPE_WIDGET)
32
#define TIMEZONE_MAP_PRIVATE(o) \
33
(G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_TIMEZONE_MAP, CcTimezoneMapPrivate))
43
} CcTimezoneMapOffset;
45
struct _CcTimezoneMapPrivate
47
GdkPixbuf *orig_background;
48
GdkPixbuf *orig_color_map;
50
GdkPixbuf *background;
54
guchar *visible_map_pixels;
55
gint visible_map_rowstride;
57
gint olsen_map_channels;
58
guchar *olsen_map_pixels;
59
gint olsen_map_rowstride;
61
gdouble selected_offset;
76
static guint signals[LAST_SIGNAL];
79
static CcTimezoneMapOffset color_codes[] =
81
{-11.0, 43, 0, 0, 255 },
82
{-10.0, 85, 0, 0, 255 },
83
{-9.5, 102, 255, 0, 255 },
84
{-9.0, 128, 0, 0, 255 },
85
{-8.0, 170, 0, 0, 255 },
86
{-7.0, 212, 0, 0, 255 },
87
{-6.0, 255, 0, 1, 255 }, // north
88
{-6.0, 255, 0, 0, 255 }, // south
89
{-5.0, 255, 42, 42, 255 },
90
{-4.5, 192, 255, 0, 255 },
91
{-4.0, 255, 85, 85, 255 },
92
{-3.5, 0, 255, 0, 255 },
93
{-3.0, 255, 128, 128, 255 },
94
{-2.0, 255, 170, 170, 255 },
95
{-1.0, 255, 213, 213, 255 },
96
{0.0, 43, 17, 0, 255 },
97
{1.0, 85, 34, 0, 255 },
98
{2.0, 128, 51, 0, 255 },
99
{3.0, 170, 68, 0, 255 },
100
{3.5, 0, 255, 102, 255 },
101
{4.0, 212, 85, 0, 255 },
102
{4.5, 0, 204, 255, 255 },
103
{5.0, 255, 102, 0, 255 },
104
{5.5, 0, 102, 255, 255 },
105
{5.75, 0, 238, 207, 247 },
106
{6.0, 255, 127, 42, 255 },
107
{6.5, 204, 0, 254, 254 },
108
{7.0, 255, 153, 85, 255 },
109
{8.0, 255, 179, 128, 255 },
110
{9.0, 255, 204, 170, 255 },
111
{9.5, 170, 0, 68, 250 },
112
{10.0, 255, 230, 213, 255 },
113
{10.5, 212, 124, 21, 250 },
114
{11.0, 212, 170, 0, 255 },
115
{11.5, 249, 25, 87, 253 },
116
{12.0, 255, 204, 0, 255 },
117
{12.75, 254, 74, 100, 248 },
118
{13.0, 255, 85, 153, 250 },
122
static const gchar * olsen_map_timezones[] = {
125
"Africa/Addis_Ababa",
133
"Africa/Brazzaville",
139
"Africa/Dar_es_Salaam",
146
"Africa/Johannesburg",
167
"Africa/Ouagadougou",
177
"America/Argentina/Buenos_Aires",
178
"America/Argentina/Catamarca",
179
"America/Argentina/Cordoba",
180
"America/Argentina/Jujuy",
181
"America/Argentina/La_Rioja",
182
"America/Argentina/Mendoza",
183
"America/Argentina/Rio_Gallegos",
184
"America/Argentina/San_Juan",
185
"America/Argentina/San_Luis",
186
"America/Argentina/Tucuman",
187
"America/Argentina/Ushuaia",
195
"America/Blanc-Sablon",
199
"America/Cambridge_Bay",
200
"America/Campo_Grande",
207
"America/Coral_Harbour",
208
"America/Costa_Rica",
212
"America/Dawson_Creek",
217
"America/El_Salvador",
221
"America/Grand_Turk",
223
"America/Guadeloupe",
229
"America/Hermosillo",
230
"America/Indiana/Indianapolis",
231
"America/Indiana/Knox",
232
"America/Indiana/Marengo",
233
"America/Indiana/Petersburg",
234
"America/Indiana/Vevay",
235
"America/Indiana/Vincennes",
236
"America/Indiana/Winamac",
241
"America/Kentucky/Louisville",
242
"America/Kentucky/Monticello",
245
"America/Los_Angeles",
250
"America/Martinique",
254
"America/Mexico_City",
258
"America/Montevideo",
260
"America/Montserrat",
265
"America/North_Dakota/Center",
266
"America/North_Dakota/Salem",
268
"America/Pangnirtung",
269
"America/Paramaribo",
271
"America/Port-au-Prince",
272
"America/Port_of_Spain",
273
"America/Porto_Velho",
274
"America/Puerto_Rico",
275
"America/Rainy_River",
276
"America/Rankin_Inlet",
280
"America/Rio_Branco",
283
"America/Santo_Domingo",
285
"America/St_Barthelemy",
290
"America/St_Vincent",
291
"America/Tegucigalpa",
292
"America/Thunder_Bay",
297
"America/Whitehorse",
299
"America/Yellowknife",
300
"Ameriica/Swift_Current",
301
"Arctic/Longyearbyen",
375
"Asia/Yekaterinburg",
380
"Atlantic/Cape_Verde",
383
"Atlantic/Reykjavik",
384
"Atlantic/South_Georgia",
385
"Atlantic/St_Helena",
387
"Australia/Adelaide",
388
"Australia/Brisbane",
389
"Australia/Broken_Hill",
394
"Australia/Lindeman",
395
"Australia/Lord_Howe",
396
"Australia/Melbourne",
414
"Europe/Isle_of_Man",
417
"Europe/Kaliningrad",
454
"Indian/Antananarivo",
476
"Pacific/Guadalcanal",
480
"Pacific/Kiritimati",
494
"Pacific/Port_Moresby",
506
cc_timezone_map_get_property (GObject *object,
514
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
519
cc_timezone_map_set_property (GObject *object,
527
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
532
cc_timezone_map_dispose (GObject *object)
534
CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (object)->priv;
536
if (priv->orig_background)
538
g_object_unref (priv->orig_background);
539
priv->orig_background = NULL;
542
if (priv->orig_color_map)
544
g_object_unref (priv->orig_color_map);
545
priv->orig_color_map = NULL;
550
g_object_unref (priv->olsen_map);
551
priv->olsen_map = NULL;
553
priv->olsen_map_channels = 0;
554
priv->olsen_map_pixels = NULL;
555
priv->olsen_map_rowstride = 0;
558
if (priv->background)
560
g_object_unref (priv->background);
561
priv->background = NULL;
566
g_object_unref (priv->color_map);
567
priv->color_map = NULL;
569
priv->visible_map_pixels = NULL;
570
priv->visible_map_rowstride = 0;
575
g_hash_table_destroy (priv->alias_db);
576
priv->alias_db = NULL;
581
g_free (priv->watermark);
582
priv->watermark = NULL;
585
G_OBJECT_CLASS (cc_timezone_map_parent_class)->dispose (object);
589
cc_timezone_map_finalize (GObject *object)
591
CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (object)->priv;
595
tz_db_free (priv->tzdb);
600
G_OBJECT_CLASS (cc_timezone_map_parent_class)->finalize (object);
603
/* GtkWidget functions */
605
cc_timezone_map_get_preferred_width (GtkWidget *widget,
609
/* choose a minimum size small enough to prevent the window
610
* from growing horizontally
619
cc_timezone_map_get_preferred_height (GtkWidget *widget,
623
CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (widget)->priv;
626
/* The + 20 here is a slight tweak to make the map fill the
627
* panel better without causing horizontal growing
629
size = 300 * gdk_pixbuf_get_height (priv->orig_background) / gdk_pixbuf_get_width (priv->orig_background) + 20;
637
cc_timezone_map_size_allocate (GtkWidget *widget,
638
GtkAllocation *allocation)
640
CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (widget)->priv;
642
if (priv->background)
643
g_object_unref (priv->background);
645
priv->background = gdk_pixbuf_scale_simple (priv->orig_background,
648
GDK_INTERP_BILINEAR);
651
g_object_unref (priv->color_map);
653
priv->color_map = gdk_pixbuf_scale_simple (priv->orig_color_map,
656
GDK_INTERP_BILINEAR);
658
priv->visible_map_pixels = gdk_pixbuf_get_pixels (priv->color_map);
659
priv->visible_map_rowstride = gdk_pixbuf_get_rowstride (priv->color_map);
661
GTK_WIDGET_CLASS (cc_timezone_map_parent_class)->size_allocate (widget,
666
cc_timezone_map_realize (GtkWidget *widget)
668
GdkWindowAttr attr = { 0, };
669
GtkAllocation allocation;
673
gtk_widget_get_allocation (widget, &allocation);
675
gtk_widget_set_realized (widget, TRUE);
677
attr.window_type = GDK_WINDOW_CHILD;
678
attr.wclass = GDK_INPUT_OUTPUT;
679
attr.width = allocation.width;
680
attr.height = allocation.height;
681
attr.x = allocation.x;
682
attr.y = allocation.y;
683
attr.event_mask = gtk_widget_get_events (widget)
684
| GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK;
686
window = gdk_window_new (gtk_widget_get_parent_window (widget), &attr,
687
GDK_WA_X | GDK_WA_Y);
689
gdk_window_set_user_data (window, widget);
691
cursor = gdk_cursor_new (GDK_HAND2);
692
gdk_window_set_cursor (window, cursor);
694
gtk_widget_set_window (widget, window);
699
convert_longtitude_to_x (gdouble longitude, gint map_width)
701
const gdouble xdeg_offset = -6;
704
x = (map_width * (180.0 + longitude) / 360.0)
705
+ (map_width * xdeg_offset / 180.0);
711
radians (gdouble degrees)
713
return (degrees / 360.0) * G_PI * 2;
717
convert_latitude_to_y (gdouble latitude, gdouble map_height)
719
gdouble bottom_lat = -59;
720
gdouble top_lat = 81;
721
gdouble top_per, y, full_range, top_offset, map_range;
723
top_per = top_lat / 180.0;
724
y = 1.25 * log (tan (G_PI_4 + 0.4 * radians (latitude)));
725
full_range = 4.6068250867599998;
726
top_offset = full_range * top_per;
727
map_range = fabs (1.25 * log (tan (G_PI_4 + 0.4 * radians (bottom_lat))) - top_offset);
728
y = fabs (y - top_offset);
736
cc_timezone_map_draw (GtkWidget *widget,
739
CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (widget)->priv;
740
GdkPixbuf *hilight, *orig_hilight, *pin;
744
gdouble pointx, pointy;
748
gtk_widget_get_allocation (widget, &alloc);
750
/* Check if insensitive */
751
if (gtk_widget_get_sensitive (widget))
754
/* paint background */
755
#if GTK_CHECK_VERSION(3,0,0)
757
gtk_style_context_get_background_color (gtk_widget_get_style_context (widget),
758
gtk_widget_get_state_flags (widget),
760
gdk_cairo_set_source_rgba (cr, &rgba);
762
GtkStyle * style = gtk_widget_get_style (widget);
763
gdk_cairo_set_source_color (cr, &style->bg[gtk_widget_get_state (widget)]);
766
gdk_cairo_set_source_pixbuf (cr, priv->background, 0, 0);
767
cairo_paint_with_alpha (cr, alpha);
769
/* paint watermark */
770
if (priv->watermark) {
771
cairo_text_extents_t extent;
772
cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
773
cairo_set_font_size(cr, 12.0);
774
cairo_set_source_rgba(cr, 1, 1, 1, 0.5);
775
cairo_text_extents(cr, priv->watermark, &extent);
776
cairo_move_to(cr, alloc.width - extent.x_advance + extent.x_bearing - 5,
777
alloc.height - extent.height - extent.y_bearing - 5);
778
cairo_show_text(cr, priv->watermark);
782
if (!priv->location) {
787
file = g_strdup_printf (DATADIR "/timezone_%s.png",
788
g_ascii_formatd (buf, sizeof (buf),
789
"%g", priv->selected_offset));
790
orig_hilight = gdk_pixbuf_new_from_file (file, &err);
796
g_warning ("Could not load hilight: %s",
797
(err) ? err->message : "Unknown Error");
799
g_clear_error (&err);
804
hilight = gdk_pixbuf_scale_simple (orig_hilight, alloc.width,
805
alloc.height, GDK_INTERP_BILINEAR);
806
gdk_cairo_set_source_pixbuf (cr, hilight, 0, 0);
808
cairo_paint_with_alpha (cr, alpha);
809
g_object_unref (hilight);
810
g_object_unref (orig_hilight);
814
pin = gdk_pixbuf_new_from_file (DATADIR "/pin.png", &err);
818
g_warning ("Could not load pin icon: %s", err->message);
819
g_clear_error (&err);
822
pointx = convert_longtitude_to_x (priv->location->longitude, alloc.width);
823
pointy = convert_latitude_to_y (priv->location->latitude, alloc.height);
825
if (pointy > alloc.height)
826
pointy = alloc.height;
830
gdk_cairo_set_source_pixbuf (cr, pin, pointx - 8, pointy - 14);
831
cairo_paint_with_alpha (cr, alpha);
832
g_object_unref (pin);
839
cc_timezone_map_class_init (CcTimezoneMapClass *klass)
841
GObjectClass *object_class = G_OBJECT_CLASS (klass);
842
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
844
g_type_class_add_private (klass, sizeof (CcTimezoneMapPrivate));
846
object_class->get_property = cc_timezone_map_get_property;
847
object_class->set_property = cc_timezone_map_set_property;
848
object_class->dispose = cc_timezone_map_dispose;
849
object_class->finalize = cc_timezone_map_finalize;
851
widget_class->get_preferred_width = cc_timezone_map_get_preferred_width;
852
widget_class->get_preferred_height = cc_timezone_map_get_preferred_height;
853
widget_class->size_allocate = cc_timezone_map_size_allocate;
854
widget_class->realize = cc_timezone_map_realize;
855
widget_class->draw = cc_timezone_map_draw;
857
signals[LOCATION_CHANGED] = g_signal_new ("location-changed",
858
CC_TYPE_TIMEZONE_MAP,
863
g_cclosure_marshal_VOID__POINTER,
870
sort_locations (TzLocation *a,
873
if (a->dist > b->dist)
876
if (a->dist < b->dist)
883
set_location (CcTimezoneMap *map,
884
TzLocation *location)
886
CcTimezoneMapPrivate *priv = map->priv;
889
priv->location = location;
891
info = tz_info_from_location (priv->location);
893
priv->selected_offset = tz_location_get_utc_offset (priv->location)
894
/ (60.0*60.0) + ((info->daylight) ? -1.0 : 0.0);
896
g_signal_emit (map, signals[LOCATION_CHANGED], 0, priv->location);
902
get_loc_for_xy (GtkWidget * widget, gint x, gint y)
904
CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (widget)->priv;
910
const GPtrArray *array;
912
GList *distances = NULL;
915
rowstride = priv->visible_map_rowstride;
916
pixels = priv->visible_map_pixels;
918
r = pixels[(rowstride * y + x * 4)];
919
g = pixels[(rowstride * y + x * 4) + 1];
920
b = pixels[(rowstride * y + x * 4) + 2];
921
a = pixels[(rowstride * y + x * 4) + 3];
924
for (i = 0; color_codes[i].offset != -100; i++)
926
if (color_codes[i].red == r && color_codes[i].green == g
927
&& color_codes[i].blue == b && color_codes[i].alpha == a)
929
priv->selected_offset = color_codes[i].offset;
933
gtk_widget_queue_draw (widget);
935
/* work out the co-ordinates */
937
array = tz_get_locations (priv->tzdb);
939
gtk_widget_get_allocation (widget, &alloc);
941
height = alloc.height;
943
for (i = 0; i < array->len; i++)
945
gdouble pointx, pointy, dx, dy;
946
TzLocation *loc = array->pdata[i];
948
pointx = convert_longtitude_to_x (loc->longitude, width);
949
pointy = convert_latitude_to_y (loc->latitude, height);
954
loc->dist = dx * dx + dy * dy;
955
distances = g_list_prepend (distances, loc);
958
distances = g_list_sort (distances, (GCompareFunc) sort_locations);
960
TzLocation * loc = (TzLocation*) distances->data;
962
g_list_free (distances);
968
button_press_event (GtkWidget *widget,
969
GdkEventButton *event)
971
TzLocation * loc = get_loc_for_xy (widget, event->x, event->y);
972
set_location (CC_TIMEZONE_MAP (widget), loc);
977
state_flags_changed (GtkWidget *widget)
979
// To catch sensitivity changes
980
gtk_widget_queue_draw (widget);
984
load_backward_tz (CcTimezoneMap *self)
986
GError *error = NULL;
987
char **lines, *contents;
990
self->priv->alias_db = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
992
if (g_file_get_contents (GNOMECC_DATA_DIR "/datetime/backward", &contents, NULL, &error) == FALSE)
994
g_warning ("Failed to load 'backward' file: %s", error->message);
997
lines = g_strsplit (contents, "\n", -1);
999
for (i = 0; lines[i] != NULL; i++)
1005
if (g_ascii_strncasecmp (lines[i], "Link\t", 5) != 0)
1008
items = g_strsplit (lines[i], "\t", -1);
1011
/* Skip the "Link<tab>" part */
1012
for (j = 1; items[j] != NULL; j++)
1014
if (items[j][0] == '\0')
1025
if (real == NULL || alias == NULL)
1026
g_warning ("Could not parse line: %s", lines[i]);
1028
g_hash_table_insert (self->priv->alias_db, g_strdup (alias), g_strdup (real));
1035
cc_timezone_map_init (CcTimezoneMap *self)
1037
CcTimezoneMapPrivate *priv;
1040
priv = self->priv = TIMEZONE_MAP_PRIVATE (self);
1042
priv->orig_background = gdk_pixbuf_new_from_file (DATADIR "/bg.png",
1045
if (!priv->orig_background)
1047
g_warning ("Could not load background image: %s",
1048
(err) ? err->message : "Unknown error");
1049
g_clear_error (&err);
1052
priv->orig_color_map = gdk_pixbuf_new_from_file (DATADIR "/cc.png",
1054
if (!priv->orig_color_map)
1056
g_warning ("Could not load background image: %s",
1057
(err) ? err->message : "Unknown error");
1058
g_clear_error (&err);
1061
priv->olsen_map = gdk_pixbuf_new_from_file (DATADIR "/olsen_map.png",
1063
if (!priv->olsen_map)
1065
g_warning ("Could not load olsen map: %s",
1066
(err) ? err->message : "Unknown error");
1067
g_clear_error (&err);
1069
priv->olsen_map_channels = gdk_pixbuf_get_n_channels (priv->olsen_map);
1070
priv->olsen_map_pixels = gdk_pixbuf_get_pixels (priv->olsen_map);
1071
priv->olsen_map_rowstride = gdk_pixbuf_get_rowstride (priv->olsen_map);
1073
priv->tzdb = tz_load_db ();
1075
g_signal_connect (self, "button-press-event", G_CALLBACK (button_press_event),
1077
g_signal_connect (self, "state-flags-changed", G_CALLBACK (state_flags_changed),
1080
load_backward_tz (self);
1084
cc_timezone_map_new (void)
1086
return g_object_new (CC_TYPE_TIMEZONE_MAP, NULL);
1090
cc_timezone_map_set_timezone (CcTimezoneMap *map,
1091
const gchar *timezone)
1093
GPtrArray *locations;
1097
real_tz = g_hash_table_lookup (map->priv->alias_db, timezone);
1099
locations = tz_get_locations (map->priv->tzdb);
1101
for (i = 0; i < locations->len; i++)
1103
TzLocation *loc = locations->pdata[i];
1105
if (!g_strcmp0 (loc->zone, real_tz ? real_tz : timezone))
1107
set_location (map, loc);
1112
gtk_widget_queue_draw (GTK_WIDGET (map));
1116
cc_timezone_map_set_coords (CcTimezoneMap *map, gdouble lon, gdouble lat)
1118
const gchar * zone = cc_timezone_map_get_timezone_at_coords (map, lon, lat);
1119
cc_timezone_map_set_timezone (map, zone);
1123
cc_timezone_map_get_timezone_at_coords (CcTimezoneMap *map, gdouble lon, gdouble lat)
1125
gint x = (int)(2048.0 / 360.0 * (180.0 + lon));
1126
gint y = (int)(1024.0 / 180.0 * (90.0 - lat));
1127
gint offset = map->priv->olsen_map_rowstride * y + x * map->priv->olsen_map_channels;
1128
guchar color0 = map->priv->olsen_map_pixels[offset];
1129
guchar color1 = map->priv->olsen_map_pixels[offset + 1];
1130
gint zone = ((color0 & 248) << 1) + ((color1 >>4) & 15);
1132
const gchar * city = NULL;
1133
if (zone < G_N_ELEMENTS(olsen_map_timezones))
1134
city = olsen_map_timezones[zone];
1140
GtkAllocation alloc;
1141
gtk_widget_get_allocation (GTK_WIDGET (map), &alloc);
1142
x = convert_longtitude_to_x(lon, alloc.width);
1143
y = convert_latitude_to_y(lat, alloc.height);
1144
TzLocation * loc = get_loc_for_xy(GTK_WIDGET (map), x, y);
1150
cc_timezone_map_set_watermark (CcTimezoneMap *map, const gchar * watermark)
1152
if (map->priv->watermark)
1153
g_free (map->priv->watermark);
1155
map->priv->watermark = g_strdup (watermark);
1156
gtk_widget_queue_draw (GTK_WIDGET (map));
1160
cc_timezone_map_get_location (CcTimezoneMap *map)
1162
return map->priv->location;