static void
cc_timezone_map_dispose (GObject *object)
{
  CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (object)->priv;

  if (priv->orig_background)
    {
      g_object_unref (priv->orig_background);
      priv->orig_background = NULL;
    }

  if (priv->orig_color_map)
    {
      g_object_unref (priv->orig_color_map);
      priv->orig_color_map = NULL;
    }

  if (priv->background)
    {
      g_object_unref (priv->background);
      priv->background = NULL;
    }

  if (priv->color_map)
    {
      g_object_unref (priv->color_map);
      priv->color_map = NULL;

      priv->visible_map_pixels = NULL;
      priv->visible_map_rowstride = 0;
    }

  G_OBJECT_CLASS (cc_timezone_map_parent_class)->dispose (object);
}
static void
cc_timezone_map_size_allocate (GtkWidget     *widget,
                               GtkAllocation *allocation)
{
  CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (widget)->priv;

  if (priv->background)
    g_object_unref (priv->background);

  priv->background = gdk_pixbuf_scale_simple (priv->orig_background,
                                              allocation->width,
                                              allocation->height,
                                              GDK_INTERP_BILINEAR);

  if (priv->color_map)
    g_object_unref (priv->color_map);

  priv->color_map = gdk_pixbuf_scale_simple (priv->orig_color_map,
                                             allocation->width,
                                             allocation->height,
                                             GDK_INTERP_BILINEAR);

  priv->visible_map_pixels = gdk_pixbuf_get_pixels (priv->color_map);
  priv->visible_map_rowstride = gdk_pixbuf_get_rowstride (priv->color_map);

  GTK_WIDGET_CLASS (cc_timezone_map_parent_class)->size_allocate (widget,
                                                                  allocation);
}
static void
city_changed_cb (GtkComboBox     *box,
                 CcDateTimePanel *self)
{
  static gboolean inside = FALSE;
  GtkTreeIter iter;
  gchar *zone;

  /* prevent re-entry from location changed callback */
  if (inside)
    return;

  inside = TRUE;

  if (gtk_combo_box_get_active_iter (box, &iter))
    {
      gtk_tree_model_get (gtk_combo_box_get_model (box), &iter,
                          CITY_COL_ZONE, &zone, -1);

      cc_timezone_map_set_timezone (CC_TIMEZONE_MAP (self->priv->map), zone);

      g_free (zone);
    }

  inside = FALSE;
}
static void
get_initial_timezone (CcDateTimePanel *self)
{
  const gchar *timezone;

  timezone = timedate1_get_timezone (self->priv->dtm);

  if (timezone == NULL ||
      !cc_timezone_map_set_timezone (CC_TIMEZONE_MAP (self->priv->map), timezone))
    {
      g_warning ("Timezone '%s' is unhandled, setting %s as default", timezone ? timezone : "(null)", DEFAULT_TZ);
      cc_timezone_map_set_timezone (CC_TIMEZONE_MAP (self->priv->map), DEFAULT_TZ);
    }
  self->priv->current_location = cc_timezone_map_get_location (CC_TIMEZONE_MAP (self->priv->map));
  update_timezone (self);
}
static void
get_timezone_cb (GObject      *source,
                 GAsyncResult *res,
                 gpointer      user_data)
{
  CcDateTimePanel *self = user_data;
  GtkWidget *widget;
  gchar *timezone;
  GError *error;

  error = NULL;
  if (!date_time_mechanism_call_get_timezone_finish (self->priv->dtm, &timezone, res, &error))
    {
      g_warning ("Could not get current timezone: %s", error->message);
      g_error_free (error);
    }
  else
    {
      if (!cc_timezone_map_set_timezone (CC_TIMEZONE_MAP (self->priv->map), timezone))
        {
          g_warning ("Timezone '%s' is unhandled, setting %s as default", timezone, DEFAULT_TZ);
          cc_timezone_map_set_timezone (CC_TIMEZONE_MAP (self->priv->map), DEFAULT_TZ);
        }
      self->priv->current_location = cc_timezone_map_get_location (CC_TIMEZONE_MAP (self->priv->map));
      update_timezone (self);
    }

  /* now that the initial state is loaded set connect the signals */
  widget = (GtkWidget*) gtk_builder_get_object (self->priv->builder,
                                                "region_combobox");
  g_signal_connect (widget, "changed", G_CALLBACK (region_changed_cb), self);

  widget = (GtkWidget*) gtk_builder_get_object (self->priv->builder,
                                                "city_combobox");
  g_signal_connect (widget, "changed", G_CALLBACK (city_changed_cb), self);

  g_signal_connect (self->priv->map, "location-changed",
                    G_CALLBACK (location_changed_cb), self);


  g_free (timezone);
}
static void
cc_timezone_map_finalize (GObject *object)
{
  CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (object)->priv;

  if (priv->tzdb)
    {
      tz_db_free (priv->tzdb);
      priv->tzdb = NULL;
    }


  G_OBJECT_CLASS (cc_timezone_map_parent_class)->finalize (object);
}
static void
cc_timezone_map_get_preferred_height (GtkWidget *widget,
                                      gint      *minimum,
                                      gint      *natural)
{
  CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (widget)->priv;
  gint size;

  size = gdk_pixbuf_get_height (priv->orig_background);

  if (minimum != NULL)
    *minimum = size;
  if (natural != NULL)
    *natural = size;
}
static void
cc_timezone_map_get_preferred_height (GtkWidget *widget,
                                      gint      *minimum,
                                      gint      *natural)
{
  CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (widget)->priv;
  gint size;

  /* The + 20 here is a slight tweak to make the map fill the
   * panel better without causing horizontal growing
   */
  size = 300 * gdk_pixbuf_get_height (priv->orig_background) / gdk_pixbuf_get_width (priv->orig_background) + 20;
  if (minimum != NULL)
    *minimum = size;
  if (natural != NULL)
    *natural = size;
}
static gboolean
city_changed_cb (GtkEntryCompletion *entry_completion,
                 GtkTreeModel       *model,
                 GtkTreeIter        *iter,
                 CcDateTimePanel *self)
{
  GtkWidget *entry;
  gchar *zone;

  gtk_tree_model_get (model, iter,
                      CITY_COL_ZONE, &zone, -1);
  cc_timezone_map_set_timezone (CC_TIMEZONE_MAP (self->priv->map), zone);
  g_free (zone);

  entry = gtk_entry_completion_get_entry (GTK_ENTRY_COMPLETION (entry_completion));
  gtk_entry_set_text (GTK_ENTRY (entry), "");

  return TRUE;
}
static void
cc_timezone_map_dispose (GObject *object)
{
  CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (object)->priv;

  g_clear_object (&priv->orig_background);
  g_clear_object (&priv->orig_background_dim);
  g_clear_object (&priv->orig_color_map);
  g_clear_object (&priv->background);
  g_clear_object (&priv->pin);
  g_clear_pointer (&priv->bubble_text, g_free);

  if (priv->color_map)
    {
      g_clear_object (&priv->color_map);

      priv->visible_map_pixels = NULL;
      priv->visible_map_rowstride = 0;
    }

  G_OBJECT_CLASS (cc_timezone_map_parent_class)->dispose (object);
}
static gboolean
button_press_event (GtkWidget      *widget,
                    GdkEventButton *event)
{
  CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (widget)->priv;
  gint x, y;
  guchar r, g, b, a;
  guchar *pixels;
  gint rowstride;
  gsize i;

  const GPtrArray *array;
  gint width, height;
  GList *distances = NULL;
  GtkAllocation alloc;

  x = event->x;
  y = event->y;


  rowstride = priv->visible_map_rowstride;
  pixels = priv->visible_map_pixels;

  r = pixels[(rowstride * y + x * 4)];
  g = pixels[(rowstride * y + x * 4) + 1];
  b = pixels[(rowstride * y + x * 4) + 2];
  a = pixels[(rowstride * y + x * 4) + 3];


  for (i = 0; color_codes[i].offset != -100; i++)
    {
       if (color_codes[i].red == r && color_codes[i].green == g
           && color_codes[i].blue == b && color_codes[i].alpha == a)
         {
           priv->selected_offset = color_codes[i].offset;
         }
    }

  gtk_widget_queue_draw (widget);

  /* work out the co-ordinates */

  array = tz_get_locations (priv->tzdb);

  gtk_widget_get_allocation (widget, &alloc);
  width = alloc.width;
  height = alloc.height;

  for (i = 0; i < array->len; i++)
    {
      gdouble pointx, pointy, dx, dy;
      TzLocation *loc = array->pdata[i];

      pointx = convert_longtitude_to_x (loc->longitude, width);
      pointy = convert_latitude_to_y (loc->latitude, height);

      dx = pointx - x;
      dy = pointy - y;

      loc->dist = dx * dx + dy * dy;
      distances = g_list_prepend (distances, loc);

    }
  distances = g_list_sort (distances, (GCompareFunc) sort_locations);


  set_location (CC_TIMEZONE_MAP (widget), (TzLocation*) distances->data);

  g_list_free (distances);

  return TRUE;
}
static gboolean
cc_timezone_map_draw (GtkWidget *widget,
                      cairo_t   *cr)
{
  CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (widget)->priv;
  GdkPixbuf *hilight, *orig_hilight, *pin;
  GtkAllocation alloc;
  gchar *file;
  GError *err = NULL;
  gdouble pointx, pointy;
  char buf[16];

  gtk_widget_get_allocation (widget, &alloc);

  /* paint background */
  gdk_cairo_set_source_pixbuf (cr, priv->background, 0, 0);
  cairo_paint (cr);

  /* paint hilight */
  file = g_strdup_printf (DATADIR "/timezone_%s.png",
                          g_ascii_formatd (buf, sizeof (buf),
                                           "%g", priv->selected_offset));
  orig_hilight = gdk_pixbuf_new_from_file (file, &err);
  g_free (file);
  file = NULL;

  if (!orig_hilight)
    {
      g_warning ("Could not load hilight: %s",
                 (err) ? err->message : "Unknown Error");
      if (err)
        g_clear_error (&err);
    }
  else
    {

      hilight = gdk_pixbuf_scale_simple (orig_hilight, alloc.width,
                                         alloc.height, GDK_INTERP_BILINEAR);
      gdk_cairo_set_source_pixbuf (cr, hilight, 0, 0);

      cairo_paint (cr);
      g_object_unref (hilight);
      g_object_unref (orig_hilight);
    }

  /* load pin icon */
  pin = gdk_pixbuf_new_from_file (DATADIR "/pin.png", &err);

  if (err)
    {
      g_warning ("Could not load pin icon: %s", err->message);
      g_clear_error (&err);
    }

  if (priv->location)
    {
      pointx = convert_longtitude_to_x (priv->location->longitude, alloc.width);
      pointy = convert_latitude_to_y (priv->location->latitude, alloc.height);

      if (pointy > alloc.height)
        pointy = alloc.height;

      if (pin)
        {
          gdk_cairo_set_source_pixbuf (cr, pin, pointx - 8, pointy - 14);
          cairo_paint (cr);
        }
    }

  if (pin)
    {
      g_object_unref (pin);
    }

  return TRUE;
}
static gboolean
cc_timezone_map_draw (GtkWidget *widget,
                      cairo_t   *cr)
{
  CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (widget)->priv;
  GdkPixbuf *hilight, *orig_hilight;
  GtkAllocation alloc;
  gchar *file;
  GError *err = NULL;
  gdouble pointx, pointy;
  char buf[16];
  const char *fmt;

  gtk_widget_get_allocation (widget, &alloc);

  /* paint background */
  gdk_cairo_set_source_pixbuf (cr, priv->background, 0, 0);
  cairo_paint (cr);

  /* paint hilight */
  if (gtk_widget_is_sensitive (widget))
    fmt = DATETIME_RESOURCE_PATH "/timezone_%s.png";
  else
    fmt = DATETIME_RESOURCE_PATH "/timezone_%s_dim.png";

  file = g_strdup_printf (fmt,
                          g_ascii_formatd (buf, sizeof (buf),
                                           "%g", priv->selected_offset));
  orig_hilight = gdk_pixbuf_new_from_resource (file, &err);
  g_free (file);
  file = NULL;

  if (!orig_hilight)
    {
      g_warning ("Could not load hilight: %s",
                 (err) ? err->message : "Unknown Error");
      if (err)
        g_clear_error (&err);
    }
  else
    {

      hilight = gdk_pixbuf_scale_simple (orig_hilight, alloc.width,
                                         alloc.height, GDK_INTERP_BILINEAR);
      gdk_cairo_set_source_pixbuf (cr, hilight, 0, 0);

      cairo_paint (cr);
      g_object_unref (hilight);
      g_object_unref (orig_hilight);
    }

  if (priv->location)
    {
      pointx = convert_longitude_to_x (priv->location->longitude, alloc.width);
      pointy = convert_latitude_to_y (priv->location->latitude, alloc.height);

      pointx = CLAMP (floor (pointx), 0, alloc.width);
      pointy = CLAMP (floor (pointy), 0, alloc.height);

      draw_text_bubble (cr, widget, pointx, pointy);

      if (priv->pin)
        {
          gdk_cairo_set_source_pixbuf (cr, priv->pin,
                                       pointx - PIN_HOT_POINT_X,
                                       pointy - PIN_HOT_POINT_Y);
          cairo_paint (cr);
        }
    }

  return TRUE;
}
static void
draw_text_bubble (cairo_t *cr,
                  GtkWidget *widget,
                  gdouble pointx,
                  gdouble pointy)
{
  static const double corner_radius = 9.0;
  static const double margin_top = 12.0;
  static const double margin_bottom = 12.0;
  static const double margin_left = 24.0;
  static const double margin_right = 24.0;

  CcTimezoneMapPrivate *priv = CC_TIMEZONE_MAP (widget)->priv;
  GtkAllocation alloc;
  PangoLayout *layout;
  PangoRectangle text_rect;
  double x;
  double y;
  double width;
  double height;

  if (!priv->bubble_text)
    return;

  gtk_widget_get_allocation (widget, &alloc);
  layout = gtk_widget_create_pango_layout (widget, NULL);

  /* Layout the text */
  pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
  pango_layout_set_spacing (layout, 3);
  pango_layout_set_markup (layout, priv->bubble_text, -1);

  pango_layout_get_pixel_extents (layout, NULL, &text_rect);

  /* Calculate the bubble size based on the text layout size */
  width = text_rect.width + margin_left + margin_right;
  height = text_rect.height + margin_top + margin_bottom;

  if (pointx < alloc.width / 2)
    x = pointx + 25;
  else
    x = pointx - width - 25;

  y = pointy - height / 2;

  /* Make sure it fits in the visible area */
  x = CLAMP (x, 0, alloc.width - width);
  y = CLAMP (y, 0, alloc.height - height);

  cairo_save (cr);
  cairo_translate (cr, x, y);

  /* Draw the bubble */
  cairo_new_sub_path (cr);
  cairo_arc (cr, width - corner_radius, corner_radius, corner_radius, radians (-90), radians (0));
  cairo_arc (cr, width - corner_radius, height - corner_radius, corner_radius, radians (0), radians (90));
  cairo_arc (cr, corner_radius, height - corner_radius, corner_radius, radians (90), radians (180));
  cairo_arc (cr, corner_radius, corner_radius, corner_radius, radians (180), radians (270));
  cairo_close_path (cr);

  cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 0.7);
  cairo_fill (cr);

  /* And finally draw the text */
  cairo_set_source_rgb (cr, 1, 1, 1);
  cairo_move_to (cr, margin_left, margin_top);
  pango_cairo_show_layout (cr, layout);

  g_object_unref (layout);
  cairo_restore (cr);
}
static void
update_timezone (CcDateTimePanel *self)
{
  CcDateTimePanelPrivate *priv = self->priv;
  char *bubble_text;
  char *city_country;
  char *label;
  char *time_label;
  char *utc_label;
  char *tz_desc;
  gboolean use_ampm;

  if (priv->clock_format == G_DESKTOP_CLOCK_FORMAT_12H)
    use_ampm = TRUE;
  else
    use_ampm = FALSE;

  city_country = translated_city_name (priv->current_location);

  /* Update the timezone on the listbow row */
  /* Translators: "timezone (details)" */
  label = g_strdup_printf (C_("timezone desc", "%s (%s)"),
                           g_date_time_get_timezone_abbreviation (self->priv->date),
                           city_country);
  gtk_label_set_text (GTK_LABEL (W ("timezone_label")), label);
  g_free (label);

  /* Translators: UTC here means the Coordinated Universal Time.
   * %:::z will be replaced by the offset from UTC e.g. UTC+02 */
  utc_label = g_date_time_format (priv->date, _("UTC%:::z"));

  if (use_ampm)
    {
      /* Translators: This is the time format used in 12-hour mode. */
      time_label = g_date_time_format (priv->date, _("%l:%M %p"));
    }
  else
    {
      /* Translators: This is the time format used in 24-hour mode. */
      time_label = g_date_time_format (priv->date, _("%R"));
    }

  /* Update the text bubble in the timezone map */
  /* Translators: "timezone (utc shift)" */
  tz_desc = g_strdup_printf (C_("timezone map", "%s (%s)"),
                             g_date_time_get_timezone_abbreviation (self->priv->date),
                             utc_label);
  bubble_text = g_strdup_printf ("<b>%s</b>\n"
                                 "<small>%s</small>\n"
                                 "<b>%s</b>",
                                 tz_desc,
                                 city_country,
                                 time_label);
  cc_timezone_map_set_bubble_text (CC_TIMEZONE_MAP (priv->map), bubble_text);

  g_free (tz_desc);
  g_free (bubble_text);
  g_free (city_country);
  g_free (time_label);
  g_free (utc_label);
}