libyui-gtk  2.49.0
ygtktimezonepicker.c
1 /********************************************************************
2  * YaST2-GTK - http://en.opensuse.org/YaST2-GTK *
3  ********************************************************************/
4 
5 /* YGtkTimeZonePicker widget */
6 // check the header file for information about this widget
7 
8 #include <yui/Libyui_config.h>
9 #include "ygtktimezonepicker.h"
10 #include <gtk/gtk.h>
11 #include <string.h>
12 #include <math.h>
13 
14 static guint zone_clicked_signal;
15 
16 // General utilities
17 
18 static char *substring (const char *str, int start, int end)
19 {
20  if (end == -1)
21  return g_strdup (str+start);
22  return g_strndup (str+start, end-start);
23 }
24 
25 static gdouble convert_pos (const char *pos, int digits)
26 {
27  if (strlen (pos) < 4 || digits > 9)
28  return 0.0;
29 
30  gchar *whole = substring (pos, 0, digits+1);
31  gchar *fraction = substring (pos, digits+1, -1);
32 
33  gdouble t1 = g_strtod (whole, NULL);
34  gdouble t2 = g_strtod (fraction, NULL);
35 
36  int fraction_len = strlen (fraction);
37  g_free (whole);
38  g_free (fraction);
39 
40  if (t1 >= 0.0)
41  return t1 + t2/pow (10.0, fraction_len);
42  else
43  return t1 - t2/pow (10.0, fraction_len);
44 }
45 
46 static void ygtk_time_zone_picker_set_cursor_stock (YGtkTimeZonePicker *picker,
47  const gchar *stock)
48 {
49  GdkPixbuf *pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default(),
50  stock, 24, 0, NULL);
51  if (pixbuf) {
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));
57  }
58 }
59 
60 static void ygtk_time_zone_picker_set_cursor_type (YGtkTimeZonePicker *picker,
61  GdkCursorType type)
62 {
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);
67 }
68 
69 // Specific utilities
70 
71 static void window_to_map (YGtkTimeZonePicker *picker, gint win_x, gint win_y,
72  gint *map_x, gint *map_y)
73 {
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);
77 
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;
80 }
81 
82 static void map_to_window (YGtkTimeZonePicker *picker, gint map_x, gint map_y,
83  gint *win_x, gint *win_y)
84 {
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);
88 
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;
91 }
92 
93 static void coordinates_to_map (YGtkTimeZonePicker *picker, gdouble latitude, gdouble longitude, gint *map_x, gint *map_y)
94 {
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);
97 }
98 
99 static YGtkTimeZoneLocation *find_location_closer_to (YGtkTimeZonePicker *picker,
100  gint win_x, gint win_y)
101 {
102  gint x, y;
103  window_to_map (picker, win_x, win_y, &x, &y);
104 
105  double min_dist = 4000;
106  YGtkTimeZoneLocation *best = 0;
107  GList *i;
108  for (i = picker->locations; i; i = i->next) {
109  YGtkTimeZoneLocation *loc = i->data;
110  gdouble dist = pow (loc->x - x, 2) + pow (loc->y - y, 2);
111  if (dist < min_dist) {
112  min_dist = dist;
113  best = loc;
114  }
115  }
116  return best;
117 }
118 
119 // Internal methods
120 
121 static void ygtk_time_zone_picker_sync_cursor (YGtkTimeZonePicker *picker)
122 {
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);
127  else
128  ygtk_time_zone_picker_set_cursor_stock (picker, "zoom-in");
129 }
130 
131 static void ygtk_time_zone_picker_scroll_to (YGtkTimeZonePicker *picker,
132  gint x, gint y, gboolean animate)
133 {
134  picker->map_x = x;
135  picker->map_y = y;
136  gtk_widget_queue_resize (GTK_WIDGET (picker));
137 }
138 
139 static void ygtk_time_zone_picker_move (YGtkTimeZonePicker *picker,
140  gint diff_x, gint diff_y)
141 {
142  ygtk_time_zone_picker_scroll_to (picker, picker->map_x + diff_x,
143  picker->map_y + diff_y, FALSE);
144 }
145 
146 static void ygtk_time_zone_picker_closeup (YGtkTimeZonePicker *picker, gboolean closeup,
147  gint map_x, gint map_y, gboolean animate)
148 {
149  if (closeup)
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);
154 }
155 
156 // Public methods
157 
158 static gint compare_locations (gconstpointer pa, gconstpointer pb)
159 {
160  const YGtkTimeZoneLocation *a = pa;
161  const YGtkTimeZoneLocation *b = pb;
162  return a->latitude - b->latitude;
163 }
164 
165 void ygtk_time_zone_picker_set_map (YGtkTimeZonePicker *picker, const char *filename,
166  TimeZoneToName converter_cb, gpointer converter_data)
167 {
168  GError *error = 0;
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);
173  }
174  else {
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;
177  }
178 
179  char buf [4096];
180  FILE *tzfile = fopen ("/usr/share/zoneinfo/zone.tab", "r");
181  while (fgets (buf, sizeof (buf), tzfile)) {
182  if (*buf == '#') continue;
183 
184  gchar *trim = g_strstrip (buf);
185  gchar **arr = g_strsplit (trim, "\t", -1);
186 
187  int arr_length;
188  for (arr_length = 0; arr [arr_length]; arr_length++) ;
189 
190  YGtkTimeZoneLocation *loc = g_new0 (YGtkTimeZoneLocation, 1);
191  loc->country = g_strdup (arr[0]);
192  loc->zone = g_strdup (arr[2]);
193  if (arr_length > 3)
194  loc->comment = g_strdup (arr[3]);
195  const gchar *tooltip = converter_cb (loc->zone, converter_data);
196  if (tooltip)
197  loc->tooltip = g_strdup (tooltip);
198 
199  int split_i = 1;
200  while (split_i < strlen (arr[1]) && arr[1][split_i] != '-' && arr[1][split_i] != '+' )
201  split_i++;
202  char *latitude = substring (arr[1], 0, split_i);
203  char *longitude = substring (arr[1], split_i, -1);
204 
205  loc->latitude = convert_pos (latitude, 2);
206  loc->longitude = convert_pos (longitude, 3);
207  g_free (latitude);
208  g_free (longitude);
209 
210  coordinates_to_map (picker, loc->latitude, loc->longitude, &loc->x, &loc->y);
211 
212  picker->locations = g_list_append (picker->locations, loc);
213  g_strfreev (arr);
214  }
215  fclose (tzfile);
216  picker->locations = g_list_sort (picker->locations, compare_locations);
217 }
218 
219 const gchar *ygtk_time_zone_picker_get_current_zone (YGtkTimeZonePicker *picker)
220 {
221  if (picker->selected_loc)
222  return picker->selected_loc->zone;
223  return NULL;
224 }
225 
226 void ygtk_time_zone_picker_set_current_zone (YGtkTimeZonePicker *picker,
227  const gchar *zone, gboolean zoom)
228 {
229  if (picker->selected_loc && !strcmp (picker->selected_loc->zone, zone))
230  return;
231  GList *i;
232  for (i = picker->locations; i; i = i->next) {
233  YGtkTimeZoneLocation *loc = i->data;
234  if (!strcmp (loc->zone, zone)) {
235  picker->selected_loc = loc;
236  ygtk_time_zone_picker_closeup (picker, zoom, loc->x, loc->y, TRUE);
237  break;
238  }
239  }
240  gtk_widget_queue_draw (GTK_WIDGET (picker));
241 }
242 
243 // GTK stuff
244 
245 G_DEFINE_TYPE (YGtkTimeZonePicker, ygtk_time_zone_picker, GTK_TYPE_WIDGET)
246 
247 static void ygtk_time_zone_picker_init (YGtkTimeZonePicker *picker)
248 {
249  gtk_widget_set_has_window (GTK_WIDGET(picker), FALSE);
250 }
251 
252 static void ygtk_time_zone_picker_destroy (GtkWidget *widget)
253 {
254  YGtkTimeZonePicker *picker = YGTK_TIME_ZONE_PICKER (widget);
255  if (picker->map_pixbuf) {
256  g_object_unref (G_OBJECT (picker->map_pixbuf));
257  picker->map_pixbuf = NULL;
258  }
259  if (picker->locations) {
260  GList *i;
261  for (i = picker->locations; i; i = i->next) {
262  YGtkTimeZoneLocation *loc = i->data;
263  g_free (loc->country);
264  g_free (loc->zone);
265  g_free (loc->comment);
266  g_free (loc->tooltip);
267  g_free (loc);
268  }
269  g_list_free (picker->locations);
270  picker->locations = NULL;
271  }
272  GTK_WIDGET_CLASS (ygtk_time_zone_picker_parent_class)->destroy(widget);
273 }
274 
275 static void ygtk_time_zone_picker_realize (GtkWidget *widget)
276 {
277  GTK_WIDGET_CLASS (ygtk_time_zone_picker_parent_class)->realize (widget);
278 
279  YGtkTimeZonePicker *picker = YGTK_TIME_ZONE_PICKER (widget);
280 
281  GdkWindowAttr attributes;
282  GtkAllocation alloc;
283  gtk_widget_get_allocation(widget, &alloc);
284 
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);
300 
301  GtkStyleContext *style_ctx = gtk_widget_get_style_context(widget);
302  gtk_style_context_set_background(style_ctx, picker->map_window);
303 
304  ygtk_time_zone_picker_closeup (picker, FALSE, 0, 0, FALSE);
305 }
306 
307 static void ygtk_time_zone_picker_unrealize (GtkWidget *widget)
308 {
309  YGtkTimeZonePicker *picker = YGTK_TIME_ZONE_PICKER (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;
314  }
315  GTK_WIDGET_CLASS (ygtk_time_zone_picker_parent_class)->unrealize (widget);
316 }
317 
318 static void ygtk_time_zone_picker_map (GtkWidget *widget)
319 {
320  GTK_WIDGET_CLASS (ygtk_time_zone_picker_parent_class)->map (widget);
321  YGtkTimeZonePicker *picker = YGTK_TIME_ZONE_PICKER (widget);
322  if (picker->map_window)
323  gdk_window_show (picker->map_window);
324 }
325 
326 static void ygtk_time_zone_picker_unmap (GtkWidget *widget)
327 {
328  YGtkTimeZonePicker *picker = YGTK_TIME_ZONE_PICKER (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);
332 }
333 
334 static gboolean ygtk_time_zone_picker_motion_notify_event (GtkWidget *widget,
335  GdkEventMotion *event)
336 {
337  YGtkTimeZonePicker *picker = YGTK_TIME_ZONE_PICKER (widget);
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);
345  }
346  }
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);
353  }
354  }
355  return FALSE;
356 }
357 
358 static gboolean ygtk_time_zone_picker_leave_notify_event (GtkWidget *widget,
359  GdkEventCrossing *event)
360 {
361  YGtkTimeZonePicker *picker = YGTK_TIME_ZONE_PICKER (widget);
362  if (picker->hover_loc) {
363  picker->hover_loc = NULL;
364  gtk_widget_queue_draw (widget);
365  }
366  return FALSE;
367 }
368 
369 static gboolean ygtk_time_zone_picker_button_press_event (GtkWidget *widget,
370  GdkEventButton *event)
371 {
372  YGtkTimeZonePicker *picker = YGTK_TIME_ZONE_PICKER (widget);
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);
381  }
382  picker->last_mouse_x = event->x;
383  picker->last_mouse_y = event->y;
384  }
385  else {
386  int map_x, map_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);
389  }
390  }
391  else if (event->button == 3)
392  ygtk_time_zone_picker_closeup (picker, FALSE, 0, 0, TRUE);
393  else
394  return FALSE;
395  gtk_widget_queue_draw (widget);
396  }
397  return FALSE;
398 }
399 
400 static gboolean ygtk_time_zone_picker_button_release_event (GtkWidget *widget,
401  GdkEventButton *event)
402 {
403  YGtkTimeZonePicker *picker = YGTK_TIME_ZONE_PICKER (widget);
404  picker->last_mouse_x = 0;
405  ygtk_time_zone_picker_sync_cursor (picker);
406  return FALSE;
407 }
408 
409 static void ygtk_time_zone_picker_get_preferred_width (GtkWidget *widget,
410  gint *minimal_width, gint *natural_width)
411 {
412  *minimal_width = *natural_width = 600;
413 }
414 
415 static void ygtk_time_zone_picker_get_preferred_height (GtkWidget *widget,
416  gint *minimal_height, gint *natural_height)
417 {
418  *minimal_height = *natural_height = 300;
419 }
420 
421 static void ygtk_time_zone_picker_size_allocate (GtkWidget *widget,
422  GtkAllocation *allocation)
423 {
424  if (!gtk_widget_get_realized (widget))
425  return;
426  YGtkTimeZonePicker *picker = YGTK_TIME_ZONE_PICKER (widget);
427  int win_width = allocation->width, win_height = allocation->height;
428 
429 
430  if (picker->closeup)
431  picker->scale = 1;
432  else {
433  picker->scale = MAX ((double) win_width / picker->map_width,
434  (double) win_height / picker->map_height);
435  picker->hover_loc = NULL;
436  }
437 
438  int map_win_width = picker->map_width * picker->scale;
439  int map_win_height = picker->map_height * picker->scale;
440 
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);
446 
447  // make sure it clumps to the new window size...
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);
452 
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);
456 }
457 
458 static gboolean ygtk_time_zone_picker_draw (GtkWidget *widget, cairo_t *cr)
459 {
460  YGtkTimeZonePicker *picker = YGTK_TIME_ZONE_PICKER (widget);
461  GtkStyleContext *style = gtk_widget_get_style_context(widget);
462 
463  int width = gtk_widget_get_allocated_width(widget);
464  int height = gtk_widget_get_allocated_height(widget);
465 
466  if (!picker->map_pixbuf) {
467  // show alt text if no image was loaded
468  PangoLayout *layout;
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);
474  goto cleanup;
475  }
476 
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);
483 
484  cairo_rectangle (cr, 0, 0, width, height);
485  cairo_fill (cr);
486 
487  GList *i;
488  for (i = picker->locations; i; i = i->next) {
489  YGtkTimeZoneLocation *loc = i->data;
490  int x, y;
491  map_to_window (picker, loc->x, loc->y, &x, &y);
492  int radius = (picker->scale == 1) ? 3 : 0;
493 
494  if (loc == picker->selected_loc) {
495  cairo_set_source_rgb (cr, 232/255.0, 66/255.0, 66/255.0);
496  radius = 3;
497  }
498  else if (loc == picker->hover_loc)
499  cairo_set_source_rgb (cr, 255/255.0, 255/255.0, 96/255.0);
500  else
501  cairo_set_source_rgb (cr, 192/255.0, 112/255.0, 160/255.0);
502 
503  if (radius) {
504  cairo_arc (cr, x-1, y-1, radius, 0, M_PI*2);
505  if (radius > 1) {
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);
509  cairo_stroke (cr);
510  }
511  else
512  cairo_fill (cr);
513  }
514  }
515 
516  YGtkTimeZoneLocation *label_loc = picker->hover_loc;
517  if (!label_loc)
518  label_loc = picker->selected_loc;
519  if (label_loc) {
520  const char *text = label_loc->tooltip;
521  if (!text) {
522  text = label_loc->country;
523  if (!text)
524  text = label_loc->zone;
525  }
526 
527  PangoLayout *layout;
528  layout = gtk_widget_create_pango_layout (widget, text);
529 
530  int x, y;
531  map_to_window (picker, label_loc->x, label_loc->y, &x, &y);
532  x += 11; y += 4;
533  int fw;
534  pango_layout_get_pixel_size (layout, &fw, NULL);
535  x = MAX (MIN (x, width - fw - 5), x-11-fw);
536 
537  cairo_set_source_rgb (cr, 0, 0, 0);
538  cairo_move_to (cr, x, y);
539  pango_cairo_show_layout (cr, layout);
540 
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));
545  cairo_new_path (cr);
546  }
547 
548 cleanup:
549  gtk_render_frame (style, cr, 0, 0, width, height);
550  return TRUE;
551 }
552 
553 static void ygtk_time_zone_picker_class_init (YGtkTimeZonePickerClass *klass)
554 {
555  ygtk_time_zone_picker_parent_class = g_type_class_peek_parent (klass);
556 
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;
571 
572  zone_clicked_signal = g_signal_new ("zone_clicked",
573  G_TYPE_FROM_CLASS (G_OBJECT_CLASS (klass)), G_SIGNAL_RUN_LAST,
574  G_STRUCT_OFFSET (YGtkTimeZonePickerClass, zone_clicked), NULL, NULL,
575  g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
576 }
577 
_YGtkTimeZoneLocation
Definition: ygtktimezonepicker.h:62
_YGtkTimeZonePicker
Definition: ygtktimezonepicker.h:36
_YGtkTimeZonePickerClass
Definition: ygtktimezonepicker.h:54