Back to index

indicator-datetime  12.10.0
datetime-prefs-locations.c
Go to the documentation of this file.
00001 /* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*-
00002 
00003 A dialog for setting time and date preferences.
00004 
00005 Copyright 2011 Canonical Ltd.
00006 
00007 Authors:
00008     Michael Terry <michael.terry@canonical.com>
00009 
00010 This program is free software: you can redistribute it and/or modify it 
00011 under the terms of the GNU General Public License version 3, as published 
00012 by the Free Software Foundation.
00013 
00014 This program is distributed in the hope that it will be useful, but 
00015 WITHOUT ANY WARRANTY; without even the implied warranties of 
00016 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR 
00017 PURPOSE.  See the GNU General Public License for more details.
00018 
00019 You should have received a copy of the GNU General Public License along 
00020 with this program.  If not, see <http://www.gnu.org/licenses/>.
00021 */
00022 
00023 #ifdef HAVE_CONFIG_H
00024 #include "config.h"
00025 #endif
00026 
00027 #include <stdlib.h>
00028 #include <glib/gi18n-lib.h>
00029 #include <gtk/gtk.h>
00030 #include <timezonemap/timezone-completion.h>
00031 
00032 #include "datetime-prefs-locations.h"
00033 #include "settings-shared.h"
00034 #include "utils.h"
00035 
00036 #define DATETIME_DIALOG_UI_FILE PKGDATADIR "/datetime-dialog.ui"
00037 
00038 #define COL_NAME         0
00039 #define COL_TIME         1
00040 #define COL_ZONE         2
00041 #define COL_VISIBLE_NAME 3
00042 #define COL_ICON         4
00043 
00044 static gboolean update_times (GtkWidget * dlg);
00045 static void save_when_idle (GtkWidget * dlg);
00046 
00047 /***
00048 **** Sorting
00049 ***/
00050 
00054 struct TimeLocation
00055 {
00056   gchar * collated_name;
00057   gint pos;
00058   gint32 offset;
00059 };
00060 
00061 static struct TimeLocation*
00062 time_location_new (const char * zone, const char * name, int pos, time_t now)
00063 {
00064   struct TimeLocation * loc = g_new (struct TimeLocation, 1);
00065   GTimeZone * tz = g_time_zone_new (zone);
00066   const gint interval = g_time_zone_find_interval (tz, G_TIME_TYPE_UNIVERSAL, now);
00067   loc->offset = g_time_zone_get_offset (tz, interval);
00068   loc->collated_name = g_utf8_collate_key (name, -1);
00069   loc->pos = pos;
00070   g_time_zone_unref (tz);
00071   return loc;
00072 }
00073 
00074 static void
00075 time_location_free (struct TimeLocation * loc)
00076 {
00077   g_free (loc->collated_name);
00078   g_free (loc);
00079 }
00080 
00081 static GSList*
00082 time_location_array_new_from_model (GtkTreeModel * model)
00083 {
00084   int pos = 0;
00085   GtkTreeIter iter;
00086   GSList * list = NULL;
00087   const time_t now = time (NULL);
00088 
00089   if (gtk_tree_model_get_iter_first (model, &iter)) do
00090     {
00091       gchar * zone = NULL;
00092       gchar * name = NULL;
00093 
00094       gtk_tree_model_get (model, &iter,
00095                           COL_ZONE, &zone,
00096                           COL_VISIBLE_NAME, &name,
00097                           -1);
00098       list = g_slist_prepend (list, time_location_new (zone, name, pos++, now));
00099 
00100       g_free (name);
00101       g_free (zone);
00102     }
00103   while (gtk_tree_model_iter_next (model, &iter));
00104 
00105   return g_slist_reverse (list);
00106 }
00107 
00108 static void
00109 handle_sort(GtkWidget * button, GtkTreeView * tree_view, GCompareFunc compare)
00110 {
00111   GtkTreeModel * model = gtk_tree_view_get_model (tree_view);
00112   GSList * l;
00113   GSList * list = g_slist_sort (time_location_array_new_from_model(model), compare);
00114 
00115   gint i;
00116   gint * reorder = g_new (gint, g_slist_length(list));
00117   for (i=0, l=list; l!=NULL; l=l->next, i++)
00118       reorder[i] = ((struct TimeLocation*)l->data)->pos;
00119   gtk_list_store_reorder (GTK_LIST_STORE(model), reorder);
00120 
00121   g_free (reorder);
00122   g_slist_free_full (list, (GDestroyNotify)time_location_free);
00123 }
00124 
00125 static gint
00126 time_location_compare_by_name (gconstpointer ga, gconstpointer gb)
00127 {
00128   const struct TimeLocation * a = ga;
00129   const struct TimeLocation * b = gb;
00130   int ret = g_strcmp0 (a->collated_name, b->collated_name); /* primary key */
00131   if (!ret)
00132     ret = a->offset - b->offset; /* secondary key */
00133   return ret;
00134 }
00135 static void
00136 handle_sort_by_name (GtkWidget * button, GtkTreeView * tree_view)
00137 {
00138   handle_sort (button, tree_view, time_location_compare_by_name);
00139 }
00140 
00141 static gint
00142 time_location_compare_by_time (gconstpointer ga, gconstpointer gb)
00143 {
00144   const struct TimeLocation * a = ga;
00145   const struct TimeLocation * b = gb;
00146   int ret = a->offset - b->offset; /* primary key */
00147   if (!ret)
00148     ret = g_strcmp0 (a->collated_name, b->collated_name); /* secondary key */
00149   return ret;
00150 }
00151 static void
00152 handle_sort_by_time (GtkWidget * button, GtkTreeView * tree_view)
00153 {
00154   handle_sort (button, tree_view, time_location_compare_by_time);
00155 }
00156 
00157 static gboolean
00158 time_location_list_test_sorted (GSList * list, GCompareFunc compare)
00159 {
00160   GSList * l;
00161   for (l=list; l!=NULL && l->next!=NULL; l=l->next)
00162     if (compare(l->data, l->next->data) > 0)
00163       return FALSE;
00164   return TRUE;
00165 }
00166 static void
00167 location_model_test_sorted (GtkTreeModel * model, gboolean * is_sorted_by_name, gboolean * is_sorted_by_time)
00168 {
00169   GSList * list = time_location_array_new_from_model(model);
00170   *is_sorted_by_name = time_location_list_test_sorted (list, time_location_compare_by_name);
00171   *is_sorted_by_time = time_location_list_test_sorted (list, time_location_compare_by_time);
00172   g_slist_free_full (list, (GDestroyNotify)time_location_free);
00173 }
00174 
00175 /***
00176 ****
00177 ***/
00178 
00179 static void
00180 handle_add (GtkWidget * button, GtkTreeView * tree)
00181 {
00182   GtkListStore * store = GTK_LIST_STORE (gtk_tree_view_get_model (tree));
00183 
00184   GtkTreeIter iter;
00185   gtk_list_store_append (store, &iter);
00186 
00187   GtkTreePath * path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
00188   gtk_tree_view_set_cursor (tree, path, gtk_tree_view_get_column (tree, 0), TRUE);
00189   gtk_tree_path_free (path);
00190 }
00191 
00192 static void
00193 handle_remove (GtkWidget * button, GtkTreeView * tree)
00194 {
00195   GtkListStore * store = GTK_LIST_STORE (gtk_tree_view_get_model (tree));
00196   GtkTreeSelection * selection = gtk_tree_view_get_selection (tree);
00197 
00198   GList * paths = gtk_tree_selection_get_selected_rows (selection, NULL);
00199 
00200   /* Convert all paths to iters so we can safely delete multiple paths.  For a
00201      GtkListStore, iters persist past model changes. */
00202   GList * tree_iters = NULL;
00203   GList * iter;
00204   for (iter = paths; iter; iter = iter->next) {
00205     GtkTreeIter * tree_iter = g_new(GtkTreeIter, 1);
00206     if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), tree_iter, (GtkTreePath *)iter->data)) {
00207       tree_iters = g_list_prepend (tree_iters, tree_iter);
00208     }
00209     gtk_tree_path_free (iter->data);
00210   }
00211   g_list_free (paths);
00212   
00213   // Find the next item to select
00214   GtkTreeIter *last_selected = g_list_nth_data(tree_iters, 0);
00215   GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL (store), last_selected);
00216   GtkTreeIter titer;
00217   if (!gtk_tree_model_get_iter(GTK_TREE_MODEL (store), &titer, path)) {
00218     g_debug("Failed to get last selected iter from path");
00219        last_selected = NULL;
00220   } else {
00221          if (!gtk_tree_model_iter_next(GTK_TREE_MODEL (store), &titer)) {
00222               if (gtk_tree_path_prev(path)) {
00223                      if (!gtk_tree_model_get_iter(GTK_TREE_MODEL (store), &titer, path)) {
00224                             g_debug("Failed to get iter from path");
00225                             last_selected = NULL;
00226                      } else {
00227                             last_selected = &titer;
00228                      }
00229               } else {
00230                      g_debug("handle_remove: Failed to find another location to select (assume single selected)");
00231                      last_selected = NULL;
00232               }
00233          } else {
00234               g_debug("Got next item in model");
00235               last_selected = &titer;
00236          }
00237   }
00238  
00239   if (last_selected) {
00240          gboolean clear = TRUE;
00241          path = gtk_tree_model_get_path(GTK_TREE_MODEL (store), last_selected);
00242          
00243       // Step over the path to find an item which isn't in the delete list
00244          if (g_list_length(tree_iters) > 1) {
00245                 for (iter = tree_iters; iter; iter = iter->next) {
00246                      GtkTreePath *ipath = gtk_tree_model_get_path(GTK_TREE_MODEL (store), (GtkTreeIter *)iter->data);
00247                      if (gtk_tree_path_compare(path, ipath) == 0) {
00248                             clear = FALSE;
00249                             break;
00250                      }
00251                 }
00252                 while (clear == FALSE) {
00253                      if (gtk_tree_path_prev(path)) {
00254                             clear = TRUE;
00255                             for (iter = tree_iters; iter; iter = iter->next) {
00256                                    GtkTreePath *ipath = gtk_tree_model_get_path(GTK_TREE_MODEL (store), (GtkTreeIter *)iter->data);
00257                                    if (gtk_tree_path_compare(path, ipath) == 0) {
00258                                           clear = FALSE;
00259                                           break;
00260                                    }
00261                             }
00262                             if (clear) {
00263                                    if (!gtk_tree_model_get_iter(GTK_TREE_MODEL (store), &titer, path)) {
00264                                           g_debug("Failed to get iter from path");
00265                                           last_selected = NULL;
00266                                    } else {
00267                                           last_selected = &titer;
00268                                    }
00269                             }
00270                      } else {
00271                             last_selected = NULL;
00272                             break;
00273                      }
00274                 }
00275          }
00276   }
00277   
00278   /* Now delete each iterator */
00279   for (iter = tree_iters; iter; iter = iter->next) {
00280     gtk_list_store_remove (store, (GtkTreeIter *)iter->data);
00281     g_free (iter->data);
00282   }
00283   g_list_free (tree_iters);
00284   
00285   if (last_selected)
00286          gtk_tree_selection_select_iter(selection, last_selected);
00287 }
00288 
00289 static void
00290 handle_edit (GtkCellRendererText * renderer, gchar * path, gchar * new_text,
00291              GtkListStore * store)
00292 {
00293   GtkTreeIter iter;
00294 
00295   // Manual user edits are always wrong (unless they are undoing a previous
00296   // edit), so we set the error icon here if needed.  Common way to get to
00297   // this code path is to lose entry focus.
00298   if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (store), &iter, path)) {
00299     gchar * name;
00300     gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_NAME, &name, -1);
00301     gboolean correct = g_strcmp0 (name, new_text) == 0;
00302     g_free (name);
00303 
00304     gtk_list_store_set (store, &iter,
00305                         COL_VISIBLE_NAME, new_text,
00306                         COL_ICON, correct ? NULL : GTK_STOCK_DIALOG_ERROR,
00307                         -1);
00308   }
00309 }
00310 
00311 static gboolean
00312 timezone_selected (GtkEntryCompletion * widget, GtkTreeModel * model,
00313                    GtkTreeIter * iter, GtkWidget * dlg)
00314 {
00315   gchar * zone = NULL;
00316   gchar * name = NULL;
00317 
00318   gtk_tree_model_get (model, iter,
00319                       CC_TIMEZONE_COMPLETION_ZONE, &zone,
00320                       CC_TIMEZONE_COMPLETION_NAME, &name,
00321                       -1);
00322 
00323   /* if no explicit timezone, try to determine one from latlon */
00324   if (!zone || !*zone)
00325   {
00326     gchar * strlat = NULL;
00327     gchar * strlon = NULL;
00328     gdouble lat = 0;
00329     gdouble lon = 0;
00330 
00331     gtk_tree_model_get (model, iter,
00332                         CC_TIMEZONE_COMPLETION_LATITUDE, &strlat,
00333                         CC_TIMEZONE_COMPLETION_LONGITUDE, &strlon,
00334                         -1);
00335 
00336     if (strlat && *strlat) lat = g_ascii_strtod(strlat, NULL);
00337     if (strlon && *strlon) lon = g_ascii_strtod(strlon, NULL);
00338 
00339     CcTimezoneMap * tzmap = CC_TIMEZONE_MAP (g_object_get_data (G_OBJECT (widget), "tzmap"));
00340     g_free (zone);
00341     zone = g_strdup (cc_timezone_map_get_timezone_at_coords (tzmap, lon, lat));
00342 
00343     g_free (strlat);
00344     g_free (strlon);
00345   }
00346 
00347   GtkListStore * store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (widget), "store"));
00348   GtkTreeIter * store_iter = (GtkTreeIter *)g_object_get_data (G_OBJECT (widget), "store_iter");
00349   if (store != NULL && store_iter != NULL) {
00350     gtk_list_store_set (store, store_iter,
00351                         COL_VISIBLE_NAME, name,
00352                         COL_ICON, NULL,
00353                         COL_NAME, name,
00354                         COL_ZONE, zone, -1);
00355   }
00356 
00357   update_times (dlg);
00358 
00359   /* cleanup */
00360   g_free (name);
00361   g_free (zone);
00362 
00363   return FALSE; // Do normal action too
00364 }
00365 
00366 static gboolean
00367 query_tooltip (GtkTreeView * tree, gint x, gint y, gboolean keyboard_mode,
00368                GtkTooltip * tooltip, GtkCellRenderer * cell)
00369 {
00370   GtkTreeModel * model;
00371   GtkTreeIter iter;
00372   if (!gtk_tree_view_get_tooltip_context (tree, &x, &y, keyboard_mode,
00373                                           &model, NULL, &iter))
00374     return FALSE;
00375 
00376   const gchar * icon;
00377   gtk_tree_model_get (model, &iter, COL_ICON, &icon, -1);
00378   if (icon == NULL)
00379     return FALSE;
00380 
00381   GtkTreeViewColumn * col = gtk_tree_view_get_column (tree, 0);
00382   gtk_tree_view_set_tooltip_cell (tree, tooltip, NULL, col, cell);
00383   gtk_tooltip_set_text (tooltip, _("You need to complete this location for it to appear in the menu."));
00384   return TRUE;
00385 }
00386 
00387 static void
00388 handle_edit_started (GtkCellRendererText * renderer, GtkCellEditable * editable,
00389                      gchar * path, CcTimezoneCompletion * completion)
00390 {
00391   if (GTK_IS_ENTRY (editable)) {
00392     GtkEntry *entry = GTK_ENTRY (editable);
00393     cc_timezone_completion_watch_entry (completion, entry);
00394 
00395     GtkListStore * store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (completion), "store"));
00396     GtkTreeIter * store_iter = g_new(GtkTreeIter, 1);
00397     if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (store), store_iter, path)) {
00398       g_object_set_data_full (G_OBJECT (completion), "store_iter", store_iter, g_free);
00399     }
00400   }
00401 }
00402 
00403 static gboolean
00404 update_times (GtkWidget * dlg)
00405 {
00406   /* For each entry, check zone in column 2 and set column 1 to it's time */
00407   CcTimezoneCompletion * completion = CC_TIMEZONE_COMPLETION (g_object_get_data (G_OBJECT (dlg), "completion"));
00408   GtkListStore * store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (completion), "store"));
00409   GObject * cell = G_OBJECT (g_object_get_data (G_OBJECT (completion), "name-cell"));
00410 
00411   gboolean editing;
00412   g_object_get (cell, "editing", &editing, NULL);
00413   if (editing) { /* No updates while editing, it cancels the edit */
00414     return TRUE;
00415   }
00416 
00417   g_signal_handlers_block_by_func (store, save_when_idle, dlg);
00418 
00419   GDateTime * now = g_date_time_new_now_local ();
00420 
00421   GtkTreeIter iter;
00422   if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) {
00423     do {
00424       gchar * strzone;
00425 
00426       gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_ZONE, &strzone, -1);
00427 
00428       if (strzone && *strzone) {
00429         GTimeZone * tz = g_time_zone_new (strzone);
00430         GDateTime * now_tz = g_date_time_to_timezone (now, tz);
00431         gchar * format = generate_format_string_at_time (now_tz);
00432         gchar * time_str = g_date_time_format (now_tz, format);
00433         gchar * old_time_str;
00434 
00435         gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_TIME, &old_time_str, -1);
00436         if (g_strcmp0 (old_time_str, time_str))
00437           gtk_list_store_set (store, &iter, COL_TIME, time_str, -1);
00438 
00439         g_free (old_time_str);
00440         g_free (time_str);
00441         g_free (format);
00442         g_date_time_unref (now_tz);
00443         g_time_zone_unref (tz);
00444       }
00445       g_free (strzone);
00446     } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
00447   }
00448 
00449   g_date_time_unref (now);
00450 
00451   g_signal_handlers_unblock_by_func (store, save_when_idle, dlg);
00452 
00453   return TRUE;
00454 }
00455 
00456 static void
00457 fill_from_settings (GObject * store, GSettings * conf)
00458 {
00459   gchar ** locations = g_settings_get_strv (conf, SETTINGS_LOCATIONS_S);
00460 
00461   gtk_list_store_clear (GTK_LIST_STORE (store));
00462 
00463   gchar ** striter;
00464   GtkTreeIter iter;
00465   for (striter = locations; *striter; ++striter) {
00466     gchar * zone, * name;
00467     split_settings_location (*striter, &zone, &name);
00468 
00469     gtk_list_store_append (GTK_LIST_STORE (store), &iter);
00470     gtk_list_store_set (GTK_LIST_STORE (store), &iter,
00471                         COL_VISIBLE_NAME, name,
00472                         COL_ICON, NULL,
00473                         COL_NAME, name,
00474                         COL_ZONE, zone, -1);
00475 
00476     g_free (zone);
00477     g_free (name);
00478   }
00479 
00480   g_strfreev (locations);
00481 }
00482 
00483 static void
00484 save_to_settings (GObject * store, GSettings * conf)
00485 {
00486   gboolean empty = TRUE;
00487   GVariantBuilder builder;
00488   g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY);
00489 
00490   GtkTreeIter iter;
00491   if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) {
00492     GString * gstr = g_string_new (NULL);
00493     do {
00494       gchar * strname;
00495       gchar * strzone;
00496       gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
00497                           COL_NAME, &strname,
00498                           COL_ZONE, &strzone,
00499                           -1);
00500       if (strzone && *strzone && strname && *strname) {
00501         g_string_printf (gstr, "%s %s", strzone, strname);
00502         g_variant_builder_add (&builder, "s", gstr->str);
00503         empty = FALSE;
00504       }
00505       g_free (strname);
00506       g_free (strzone);
00507     } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
00508     g_string_free (gstr, TRUE);
00509   }
00510 
00511   if (empty) {
00512     /* Empty list */
00513     g_variant_builder_clear (&builder);
00514     g_settings_set_strv (conf, SETTINGS_LOCATIONS_S, NULL);
00515   }
00516   else {
00517     g_settings_set_value (conf, SETTINGS_LOCATIONS_S, g_variant_builder_end (&builder));
00518   }
00519 }
00520 
00521 static gboolean
00522 save_now (GtkWidget *dlg)
00523 {
00524   GSettings * conf = G_SETTINGS (g_object_get_data (G_OBJECT (dlg), "conf"));
00525   GObject * completion = G_OBJECT (g_object_get_data (G_OBJECT (dlg), "completion"));
00526   GObject * store = G_OBJECT (g_object_get_data (completion, "store"));
00527 
00528   save_to_settings (store, conf);
00529 
00530   g_object_set_data (G_OBJECT (dlg), "save-id", GINT_TO_POINTER(0));
00531 
00532   return FALSE;
00533 }
00534 
00535 static void
00536 save_when_idle (GtkWidget *dlg)
00537 {
00538   guint save_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dlg), "save-id"));
00539 
00540   if (save_id == 0) {
00541     save_id = g_idle_add ((GSourceFunc)save_now, dlg);
00542     g_object_set_data (G_OBJECT (dlg), "save-id", GINT_TO_POINTER(save_id));
00543   }
00544 }
00545 
00546 static void
00547 dialog_closed (GtkWidget * dlg, GObject * store)
00548 {
00549   /* Cleanup a tad */
00550   guint time_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dlg), "time-id"));
00551   g_source_remove (time_id);
00552 
00553   guint save_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dlg), "save-id"));
00554   if (save_id > 0)
00555     g_source_remove (save_id);
00556 }
00557 
00558 static void
00559 selection_changed (GtkTreeSelection * selection, GtkWidget * remove_button)
00560 {
00561   gint count = gtk_tree_selection_count_selected_rows (selection);
00562   gtk_widget_set_sensitive (remove_button, count > 0);
00563 }
00564 
00565 static void
00566 update_button_sensitivity (GtkWidget * dlg)
00567 {
00568   GObject * odlg = G_OBJECT(dlg);
00569   GObject * completion = g_object_get_data(odlg, "completion");
00570   GtkTreeModel * model = GTK_TREE_MODEL (g_object_get_data (completion, "store")); 
00571   gboolean is_sorted_by_name;
00572   gboolean is_sorted_by_time;
00573   location_model_test_sorted (model, &is_sorted_by_name, &is_sorted_by_time);
00574   gtk_widget_set_sensitive (GTK_WIDGET(g_object_get_data(odlg, "sortByNameButton")), !is_sorted_by_name);
00575   gtk_widget_set_sensitive (GTK_WIDGET(g_object_get_data(odlg, "sortByTimeButton")), !is_sorted_by_time);
00576 }
00577 
00578 static void
00579 model_changed (GtkWidget * dlg)
00580 {
00581   update_button_sensitivity (dlg);
00582   save_when_idle (dlg);
00583 }
00584 
00585 GtkWidget *
00586 datetime_setup_locations_dialog (CcTimezoneMap * map)
00587 {
00588   GError * error = NULL;
00589   GtkBuilder * builder = gtk_builder_new ();
00590   gtk_builder_set_translation_domain (builder, GETTEXT_PACKAGE);
00591   gtk_builder_add_from_file (builder, DATETIME_DIALOG_UI_FILE, &error);
00592   if (error != NULL) {
00593     /* We have to abort, we can't continue without the ui file */
00594     g_error ("Could not load ui file %s: %s", DATETIME_DIALOG_UI_FILE, error->message);
00595     g_error_free (error);
00596     return NULL;
00597   }
00598 
00599   GSettings * conf = g_settings_new (SETTINGS_INTERFACE);
00600 
00601 #define WIG(name) GTK_WIDGET (gtk_builder_get_object (builder, name))
00602 
00603   GtkWidget * dlg = WIG ("locationsDialog");
00604   GtkWidget * tree = WIG ("locationsView");
00605   GObject * store = gtk_builder_get_object (builder, "locationsStore");
00606 
00607   /* Configure tree */
00608   CcTimezoneCompletion * completion = cc_timezone_completion_new ();
00609   g_object_set_data (G_OBJECT (completion), "tzmap", map);
00610   g_object_set_data (G_OBJECT (completion), "store", store);
00611   g_signal_connect (completion, "match-selected", G_CALLBACK (timezone_selected), dlg);
00612 
00613   GtkCellRenderer * cell = gtk_cell_renderer_text_new ();
00614   g_object_set (cell, "editable", TRUE, NULL);
00615   g_signal_connect (cell, "editing-started", G_CALLBACK (handle_edit_started), completion);
00616   g_signal_connect (cell, "edited", G_CALLBACK (handle_edit), store);
00617   gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1,
00618                                                _("Location"), cell,
00619                                                "text", COL_VISIBLE_NAME, NULL);
00620   GtkTreeViewColumn * loc_col = gtk_tree_view_get_column (GTK_TREE_VIEW (tree), 0);
00621   gtk_tree_view_column_set_expand (loc_col, TRUE);
00622   g_object_set_data (G_OBJECT (completion), "name-cell", cell);
00623 
00624   cell = gtk_cell_renderer_pixbuf_new ();
00625   gtk_tree_view_column_pack_start (loc_col, cell, FALSE);
00626   gtk_tree_view_column_add_attribute (loc_col, cell, "icon-name", COL_ICON);
00627 
00628   gtk_widget_set_has_tooltip (tree, TRUE);
00629   g_signal_connect (tree, "query-tooltip", G_CALLBACK (query_tooltip), cell);
00630 
00631   cell = gtk_cell_renderer_text_new ();
00632   gtk_cell_renderer_set_alignment (cell, 1.0f, 0.5f);
00633   gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1,
00634                                                _("Time"), cell,
00635                                                "text", COL_TIME, NULL);
00636 
00637   GtkTreeSelection * selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
00638   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
00639   g_signal_connect (selection, "changed", G_CALLBACK (selection_changed), WIG ("removeButton"));
00640   selection_changed (selection, WIG ("removeButton"));
00641 
00642   g_signal_connect (WIG ("addButton"), "clicked", G_CALLBACK (handle_add), tree);
00643   g_signal_connect (WIG ("removeButton"), "clicked", G_CALLBACK (handle_remove), tree);
00644 
00645   GtkWidget * w = WIG ("sortByNameButton");
00646   g_signal_connect (w, "clicked", G_CALLBACK (handle_sort_by_name), tree);
00647   g_object_set_data (G_OBJECT(dlg), "sortByNameButton", w);
00648 
00649   w = WIG ("sortByTimeButton");
00650   g_signal_connect (w, "clicked", G_CALLBACK (handle_sort_by_time), tree);
00651   g_object_set_data (G_OBJECT(dlg), "sortByTimeButton", w);
00652 
00653   fill_from_settings (store, conf);
00654   g_signal_connect_swapped (store, "row-deleted", G_CALLBACK (model_changed), dlg);
00655   g_signal_connect_swapped (store, "row-inserted", G_CALLBACK (model_changed), dlg);
00656   g_signal_connect_swapped (store, "row-changed", G_CALLBACK (model_changed), dlg);
00657   g_signal_connect_swapped (store, "rows-reordered", G_CALLBACK (model_changed), dlg);
00658   g_object_set_data_full (G_OBJECT (dlg), "conf", g_object_ref (conf), g_object_unref);
00659   g_object_set_data_full (G_OBJECT (dlg), "completion", completion, g_object_unref);
00660   g_signal_connect (dlg, "destroy", G_CALLBACK (dialog_closed), store);
00661 
00662   guint time_id = g_timeout_add_seconds (2, (GSourceFunc)update_times, dlg);
00663   g_object_set_data (G_OBJECT (dlg), "time-id", GINT_TO_POINTER(time_id));
00664   update_times (dlg);
00665 
00666 #undef WIG
00667 
00668   g_object_unref (conf);
00669   g_object_unref (builder);
00670 
00671   return dlg;
00672 }
00673