8 #include <yui/Libyui_config.h>
9 #include "ygtktimezonepicker.h"
14 static guint zone_clicked_signal;
18 static char *substring (
const char *str,
int start,
int end)
21 return g_strdup (str+start);
22 return g_strndup (str+start, end-start);
25 static gdouble convert_pos (
const char *pos,
int digits)
27 if (strlen (pos) < 4 || digits > 9)
30 gchar *whole = substring (pos, 0, digits+1);
31 gchar *fraction = substring (pos, digits+1, -1);
33 gdouble t1 = g_strtod (whole, NULL);
34 gdouble t2 = g_strtod (fraction, NULL);
36 int fraction_len = strlen (fraction);
41 return t1 + t2/pow (10.0, fraction_len);
43 return t1 - t2/pow (10.0, fraction_len);
49 GdkPixbuf *pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default(),
52 GtkWidget *widget = GTK_WIDGET (picker);
53 GdkDisplay *display = gtk_widget_get_display (widget);
54 GdkCursor *cursor = gdk_cursor_new_from_pixbuf (display, pixbuf, 6, 6);
55 gdk_window_set_cursor (picker->map_window, cursor);
56 g_object_unref (G_OBJECT (pixbuf));
63 GtkWidget *widget = GTK_WIDGET (picker);
64 GdkDisplay *display = gtk_widget_get_display (widget);
65 GdkCursor *cursor = gdk_cursor_new_for_display (display, type);
66 gdk_window_set_cursor (picker->map_window, cursor);
72 gint *map_x, gint *map_y)
74 int win_width, win_height;
75 win_width = gdk_window_get_width(picker->map_window);
76 win_height = gdk_window_get_height(picker->map_window);
78 *map_x = ((win_x - win_width/2) / picker->scale) + picker->map_x;
79 *map_y = ((win_y - win_height/2) / picker->scale) + picker->map_y;
83 gint *win_x, gint *win_y)
85 int win_width, win_height;
86 win_width = gdk_window_get_width(picker->map_window);
87 win_height = gdk_window_get_height(picker->map_window);
89 *win_x = ((map_x - picker->map_x) * picker->scale) + win_width/2;
90 *win_y = ((map_y - picker->map_y) * picker->scale) + win_height/2;
93 static void coordinates_to_map (
YGtkTimeZonePicker *picker, gdouble latitude, gdouble longitude, gint *map_x, gint *map_y)
95 *map_x = picker->map_width/2 + (picker->map_width/2 * longitude/180);
96 *map_y = picker->map_height/2 - (picker->map_height/2 * latitude/90);
100 gint win_x, gint win_y)
103 window_to_map (picker, win_x, win_y, &x, &y);
105 double min_dist = 4000;
108 for (i = picker->locations; i; i = i->next) {
110 gdouble dist = pow (loc->x - x, 2) + pow (loc->y - y, 2);
111 if (dist < min_dist) {
123 if (picker->last_mouse_x)
124 ygtk_time_zone_picker_set_cursor_type (picker, GDK_FLEUR);
125 else if (picker->closeup)
126 ygtk_time_zone_picker_set_cursor_type (picker, GDK_CROSS);
128 ygtk_time_zone_picker_set_cursor_stock (picker,
"zoom-in");
132 gint x, gint y, gboolean animate)
136 gtk_widget_queue_resize (GTK_WIDGET (picker));
140 gint diff_x, gint diff_y)
142 ygtk_time_zone_picker_scroll_to (picker, picker->map_x + diff_x,
143 picker->map_y + diff_y, FALSE);
146 static void ygtk_time_zone_picker_closeup (
YGtkTimeZonePicker *picker, gboolean closeup,
147 gint map_x, gint map_y, gboolean animate)
150 ygtk_time_zone_picker_scroll_to (picker, map_x, map_y, animate);
151 picker->closeup = closeup;
152 gtk_widget_queue_resize (GTK_WIDGET (picker));
153 ygtk_time_zone_picker_sync_cursor (picker);
158 static gint compare_locations (gconstpointer pa, gconstpointer pb)
162 return a->latitude - b->latitude;
166 TimeZoneToName converter_cb, gpointer converter_data)
169 picker->map_pixbuf = gdk_pixbuf_new_from_file (filename, &error);
170 if (picker->map_pixbuf) {
171 picker->map_width = gdk_pixbuf_get_width (picker->map_pixbuf);
172 picker->map_height = gdk_pixbuf_get_height (picker->map_pixbuf);
175 g_warning (
"Couldn't load map: %s\n%s\n", filename, error ? error->message :
"(unknown)");
176 picker->map_width = 300; picker->map_height = 50;
180 FILE *tzfile = fopen (
"/usr/share/zoneinfo/zone.tab",
"r");
181 while (fgets (buf,
sizeof (buf), tzfile)) {
182 if (*buf ==
'#')
continue;
184 gchar *trim = g_strstrip (buf);
185 gchar **arr = g_strsplit (trim,
"\t", -1);
188 for (arr_length = 0; arr [arr_length]; arr_length++) ;
191 loc->country = g_strdup (arr[0]);
192 loc->zone = g_strdup (arr[2]);
194 loc->comment = g_strdup (arr[3]);
195 const gchar *tooltip = converter_cb (loc->zone, converter_data);
197 loc->tooltip = g_strdup (tooltip);
200 while (split_i < strlen (arr[1]) && arr[1][split_i] !=
'-' && arr[1][split_i] !=
'+' )
202 char *latitude = substring (arr[1], 0, split_i);
203 char *longitude = substring (arr[1], split_i, -1);
205 loc->latitude = convert_pos (latitude, 2);
206 loc->longitude = convert_pos (longitude, 3);
210 coordinates_to_map (picker, loc->latitude, loc->longitude, &loc->x, &loc->y);
212 picker->locations = g_list_append (picker->locations, loc);
216 picker->locations = g_list_sort (picker->locations, compare_locations);
221 if (picker->selected_loc)
222 return picker->selected_loc->zone;
227 const gchar *zone, gboolean zoom)
229 if (picker->selected_loc && !strcmp (picker->selected_loc->zone, zone))
232 for (i = picker->locations; i; i = i->next) {
234 if (!strcmp (loc->zone, zone)) {
235 picker->selected_loc = loc;
236 ygtk_time_zone_picker_closeup (picker, zoom, loc->x, loc->y, TRUE);
240 gtk_widget_queue_draw (GTK_WIDGET (picker));
249 gtk_widget_set_has_window (GTK_WIDGET(picker), FALSE);
252 static void ygtk_time_zone_picker_destroy (GtkWidget *widget)
255 if (picker->map_pixbuf) {
256 g_object_unref (G_OBJECT (picker->map_pixbuf));
257 picker->map_pixbuf = NULL;
259 if (picker->locations) {
261 for (i = picker->locations; i; i = i->next) {
263 g_free (loc->country);
265 g_free (loc->comment);
266 g_free (loc->tooltip);
269 g_list_free (picker->locations);
270 picker->locations = NULL;
272 GTK_WIDGET_CLASS (ygtk_time_zone_picker_parent_class)->destroy(widget);
275 static void ygtk_time_zone_picker_realize (GtkWidget *widget)
277 GTK_WIDGET_CLASS (ygtk_time_zone_picker_parent_class)->realize (widget);
281 GdkWindowAttr attributes;
283 gtk_widget_get_allocation(widget, &alloc);
285 attributes.window_type = GDK_WINDOW_CHILD;
286 attributes.x = alloc.x;
287 attributes.y = alloc.y;
288 attributes.width = alloc.width;
289 attributes.height = alloc.height;
290 attributes.wclass = GDK_INPUT_OUTPUT;
291 attributes.event_mask = gtk_widget_get_events (widget);
292 attributes.event_mask |=
293 (GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
294 | GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK);
295 gint attributes_mask;
296 attributes_mask = GDK_WA_X | GDK_WA_Y;
297 picker->map_window = gdk_window_new (gtk_widget_get_window(widget),
298 &attributes, attributes_mask);
299 gdk_window_set_user_data (picker->map_window, widget);
301 GtkStyleContext *style_ctx = gtk_widget_get_style_context(widget);
302 gtk_style_context_set_background(style_ctx, picker->map_window);
304 ygtk_time_zone_picker_closeup (picker, FALSE, 0, 0, FALSE);
307 static void ygtk_time_zone_picker_unrealize (GtkWidget *widget)
310 if (picker->map_window) {
311 gdk_window_set_user_data (picker->map_window, NULL);
312 gdk_window_destroy (picker->map_window);
313 picker->map_window = NULL;
315 GTK_WIDGET_CLASS (ygtk_time_zone_picker_parent_class)->unrealize (widget);
318 static void ygtk_time_zone_picker_map (GtkWidget *widget)
320 GTK_WIDGET_CLASS (ygtk_time_zone_picker_parent_class)->map (widget);
322 if (picker->map_window)
323 gdk_window_show (picker->map_window);
326 static void ygtk_time_zone_picker_unmap (GtkWidget *widget)
329 if (picker->map_window)
330 gdk_window_hide (picker->map_window);
331 GTK_WIDGET_CLASS (ygtk_time_zone_picker_parent_class)->unmap (widget);
334 static gboolean ygtk_time_zone_picker_motion_notify_event (GtkWidget *widget,
335 GdkEventMotion *event)
338 if (event->window == picker->map_window) {
339 if (picker->scale == 1) {
341 loc = find_location_closer_to (picker, event->x, event->y);
342 if (picker->hover_loc != loc) {
343 picker->hover_loc = loc;
344 gtk_widget_queue_draw (widget);
347 if (picker->last_mouse_x) {
348 ygtk_time_zone_picker_move (picker, picker->last_mouse_x - event->x,
349 picker->last_mouse_y - event->y);
350 picker->last_mouse_x =
event->x;
351 picker->last_mouse_y =
event->y;
352 ygtk_time_zone_picker_sync_cursor (picker);
358 static gboolean ygtk_time_zone_picker_leave_notify_event (GtkWidget *widget,
359 GdkEventCrossing *event)
362 if (picker->hover_loc) {
363 picker->hover_loc = NULL;
364 gtk_widget_queue_draw (widget);
369 static gboolean ygtk_time_zone_picker_button_press_event (GtkWidget *widget,
370 GdkEventButton *event)
373 if (event->window == picker->map_window) {
374 if (event->button == 1) {
375 if (picker->scale == 1) {
377 loc = find_location_closer_to (picker, event->x, event->y);
378 if (loc && loc != picker->selected_loc) {
379 picker->selected_loc = loc;
380 g_signal_emit (picker, zone_clicked_signal, 0, picker->selected_loc->zone);
382 picker->last_mouse_x =
event->x;
383 picker->last_mouse_y =
event->y;
387 window_to_map (picker, event->x, event->y, &map_x, &map_y);
388 ygtk_time_zone_picker_closeup (picker, TRUE, map_x, map_y, TRUE);
391 else if (event->button == 3)
392 ygtk_time_zone_picker_closeup (picker, FALSE, 0, 0, TRUE);
395 gtk_widget_queue_draw (widget);
400 static gboolean ygtk_time_zone_picker_button_release_event (GtkWidget *widget,
401 GdkEventButton *event)
404 picker->last_mouse_x = 0;
405 ygtk_time_zone_picker_sync_cursor (picker);
409 static void ygtk_time_zone_picker_get_preferred_width (GtkWidget *widget,
410 gint *minimal_width, gint *natural_width)
412 *minimal_width = *natural_width = 600;
415 static void ygtk_time_zone_picker_get_preferred_height (GtkWidget *widget,
416 gint *minimal_height, gint *natural_height)
418 *minimal_height = *natural_height = 300;
421 static void ygtk_time_zone_picker_size_allocate (GtkWidget *widget,
422 GtkAllocation *allocation)
424 if (!gtk_widget_get_realized (widget))
427 int win_width = allocation->width, win_height = allocation->height;
433 picker->scale = MAX ((
double) win_width / picker->map_width,
434 (
double) win_height / picker->map_height);
435 picker->hover_loc = NULL;
438 int map_win_width = picker->map_width * picker->scale;
439 int map_win_height = picker->map_height * picker->scale;
441 int x = 0, y = 0, w, h;
442 x = MAX (0, (win_width - map_win_width) / 2) + allocation->x;
443 y = MAX (0, (win_height - map_win_height) / 2) + allocation->y;
444 w = MIN (win_width, map_win_width);
445 h = MIN (win_height, map_win_height);
448 picker->map_x = MIN (MAX (picker->map_x, (w/2)/picker->scale),
449 picker->map_width - (w/2)/picker->scale);
450 picker->map_y = MIN (MAX (picker->map_y, (h/2)/picker->scale),
451 picker->map_height - (h/2)/picker->scale);
453 gdk_window_move_resize (picker->map_window, x, y, w, h);
454 GTK_WIDGET_CLASS (ygtk_time_zone_picker_parent_class)->size_allocate
455 (widget, allocation);
458 static gboolean ygtk_time_zone_picker_draw (GtkWidget *widget, cairo_t *cr)
461 GtkStyleContext *style = gtk_widget_get_style_context(widget);
463 int width = gtk_widget_get_allocated_width(widget);
464 int height = gtk_widget_get_allocated_height(widget);
466 if (!picker->map_pixbuf) {
469 layout = gtk_widget_create_pango_layout (widget,
470 "Timezone map could not be found.\nVerify the integrity of the yast2-theme-* package.");
471 cairo_move_to (cr, 10, 10);
472 pango_cairo_show_layout (cr, layout);
473 g_object_unref (layout);
477 gdk_cairo_set_source_pixbuf (cr, picker->map_pixbuf, 0, 0);
478 cairo_matrix_t matrix;
479 cairo_matrix_init_translate (&matrix, picker->map_x - (width/2)/picker->scale,
480 picker->map_y - (height/2)/picker->scale);
481 cairo_matrix_scale (&matrix, 1/picker->scale, 1/picker->scale);
482 cairo_pattern_set_matrix (cairo_get_source (cr), &matrix);
484 cairo_rectangle (cr, 0, 0, width, height);
488 for (i = picker->locations; i; i = i->next) {
491 map_to_window (picker, loc->x, loc->y, &x, &y);
492 int radius = (picker->scale == 1) ? 3 : 0;
494 if (loc == picker->selected_loc) {
495 cairo_set_source_rgb (cr, 232/255.0, 66/255.0, 66/255.0);
498 else if (loc == picker->hover_loc)
499 cairo_set_source_rgb (cr, 255/255.0, 255/255.0, 96/255.0);
501 cairo_set_source_rgb (cr, 192/255.0, 112/255.0, 160/255.0);
504 cairo_arc (cr, x-1, y-1, radius, 0, M_PI*2);
506 cairo_fill_preserve (cr);
507 cairo_set_source_rgb (cr, 90/255.0, 90/255.0, 90/255.0);
508 cairo_set_line_width (cr, 1.0);
518 label_loc = picker->selected_loc;
520 const char *text = label_loc->tooltip;
522 text = label_loc->country;
524 text = label_loc->zone;
528 layout = gtk_widget_create_pango_layout (widget, text);
531 map_to_window (picker, label_loc->x, label_loc->y, &x, &y);
534 pango_layout_get_pixel_size (layout, &fw, NULL);
535 x = MAX (MIN (x, width - fw - 5), x-11-fw);
537 cairo_set_source_rgb (cr, 0, 0, 0);
538 cairo_move_to (cr, x, y);
539 pango_cairo_show_layout (cr, layout);
541 cairo_set_source_rgb (cr, 1, 1, 1);
542 cairo_move_to (cr, x-1, y-1);
543 pango_cairo_show_layout (cr, layout);
544 g_object_unref (G_OBJECT (layout));
549 gtk_render_frame (style, cr, 0, 0, width, height);
555 ygtk_time_zone_picker_parent_class = g_type_class_peek_parent (klass);
557 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
558 widget_class->realize = ygtk_time_zone_picker_realize;
559 widget_class->unrealize = ygtk_time_zone_picker_unrealize;
560 widget_class->map = ygtk_time_zone_picker_map;
561 widget_class->unmap = ygtk_time_zone_picker_unmap;
562 widget_class->draw = ygtk_time_zone_picker_draw;
563 widget_class->get_preferred_width = ygtk_time_zone_picker_get_preferred_width;
564 widget_class->get_preferred_height = ygtk_time_zone_picker_get_preferred_height;
565 widget_class->size_allocate = ygtk_time_zone_picker_size_allocate;
566 widget_class->button_press_event = ygtk_time_zone_picker_button_press_event;
567 widget_class->button_release_event = ygtk_time_zone_picker_button_release_event;
568 widget_class->motion_notify_event = ygtk_time_zone_picker_motion_notify_event;
569 widget_class->leave_notify_event = ygtk_time_zone_picker_leave_notify_event;
570 widget_class->destroy = ygtk_time_zone_picker_destroy;
572 zone_clicked_signal = g_signal_new (
"zone_clicked",
573 G_TYPE_FROM_CLASS (G_OBJECT_CLASS (klass)), G_SIGNAL_RUN_LAST,
575 g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);