diff options
Diffstat (limited to 'panel')
-rw-r--r-- | panel/CMakeLists.txt | 25 | ||||
-rw-r--r-- | panel/datetime-prefs-locations.c | 683 | ||||
-rw-r--r-- | panel/datetime-prefs-locations.h | 35 | ||||
-rw-r--r-- | panel/datetime-prefs.c | 863 |
4 files changed, 1606 insertions, 0 deletions
diff --git a/panel/CMakeLists.txt b/panel/CMakeLists.txt new file mode 100644 index 0000000..b3fcc7b --- /dev/null +++ b/panel/CMakeLists.txt @@ -0,0 +1,25 @@ +set (PANEL_LIB "indicator-datetime") + +add_definitions (-DPKGDATADIR="${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}") + +add_library (${PANEL_LIB} SHARED + datetime-prefs.c + datetime-prefs-locations.c + datetime-prefs-locations.h + ${CMAKE_SOURCE_DIR}/src/utils.c + ${CMAKE_SOURCE_DIR}/src/utils.h + ${CMAKE_SOURCE_DIR}/src/settings-shared.h) + +include_directories (${PANEL_DEPS_INCLUDE_DIRS}) + +link_directories (${PANEL_DEPS_LIBRARY_DIRS}) + +set_property (TARGET ${PANEL_LIB} + APPEND_STRING PROPERTY COMPILE_FLAGS + " -g ${CC_WARNING_ARGS} ${GCOV_FLAGS}") + +target_link_libraries (${PANEL_LIB} ${PANEL_DEPS_LIBRARIES} ${GCOV_LIBS}) + +install (TARGETS ${PANEL_LIB} + DESTINATION ${CMAKE_INSTALL_LIBDIR}/control-center-1/panels) + diff --git a/panel/datetime-prefs-locations.c b/panel/datetime-prefs-locations.c new file mode 100644 index 0000000..54ab8f4 --- /dev/null +++ b/panel/datetime-prefs-locations.c @@ -0,0 +1,683 @@ +/* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*- + +A dialog for setting time and date preferences. + +Copyright 2011 Canonical Ltd. + +Authors: + Michael Terry <michael.terry@canonical.com> + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License version 3, as published +by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranties of +MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <time.h> /* time_t */ +#include <glib/gi18n-lib.h> +#include <gtk/gtk.h> +#include <timezonemap/timezone-completion.h> + +#include "datetime-prefs-locations.h" +#include "settings-shared.h" +#include "utils.h" + +#define DATETIME_DIALOG_UI_FILE PKGDATADIR "/datetime-dialog.ui" + +#define COL_NAME 0 +#define COL_TIME 1 +#define COL_ZONE 2 +#define COL_VISIBLE_NAME 3 +#define COL_ICON 4 + +static gboolean update_times (GtkWidget * dlg); +static void save_when_idle (GtkWidget * dlg); + +/*** +**** Sorting +***/ + +/** + * A temporary struct used for sorting + */ +struct TimeLocation +{ + gchar * collated_name; + gint pos; + gint32 offset; +}; + +static struct TimeLocation* +time_location_new (const char * zone, const char * name, int pos, time_t now) +{ + struct TimeLocation * loc = g_new (struct TimeLocation, 1); + GTimeZone * tz = g_time_zone_new (zone); + const gint interval = g_time_zone_find_interval (tz, G_TIME_TYPE_UNIVERSAL, now); + loc->offset = g_time_zone_get_offset (tz, interval); + loc->collated_name = g_utf8_collate_key (name, -1); + loc->pos = pos; + g_time_zone_unref (tz); + return loc; +} + +static void +time_location_free (struct TimeLocation * loc) +{ + g_free (loc->collated_name); + g_free (loc); +} + +static GSList* +time_location_array_new_from_model (GtkTreeModel * model) +{ + int pos = 0; + GtkTreeIter iter; + GSList * list = NULL; + const time_t now = time (NULL); + + if (gtk_tree_model_get_iter_first (model, &iter)) do + { + gchar * zone = NULL; + gchar * name = NULL; + + gtk_tree_model_get (model, &iter, + COL_ZONE, &zone, + COL_VISIBLE_NAME, &name, + -1); + + if (zone && name) + list = g_slist_prepend (list, time_location_new (zone, name, pos++, now)); + + g_free (name); + g_free (zone); + } + while (gtk_tree_model_iter_next (model, &iter)); + + return g_slist_reverse (list); +} + +static void +handle_sort(GtkWidget * button G_GNUC_UNUSED, + GtkTreeView * tree_view, + GCompareFunc compare) +{ + GtkTreeModel * model = gtk_tree_view_get_model (tree_view); + GSList * l; + GSList * list = g_slist_sort (time_location_array_new_from_model(model), compare); + + gint i; + gint * reorder = g_new (gint, g_slist_length(list)); + for (i=0, l=list; l!=NULL; l=l->next, i++) + reorder[i] = ((struct TimeLocation*)l->data)->pos; + gtk_list_store_reorder (GTK_LIST_STORE(model), reorder); + + g_free (reorder); + g_slist_free_full (list, (GDestroyNotify)time_location_free); +} + +static gint +time_location_compare_by_name (gconstpointer ga, gconstpointer gb) +{ + const struct TimeLocation * a = ga; + const struct TimeLocation * b = gb; + int ret = g_strcmp0 (a->collated_name, b->collated_name); /* primary key */ + if (!ret) + ret = a->offset - b->offset; /* secondary key */ + return ret; +} +static void +handle_sort_by_name (GtkWidget * button, GtkTreeView * tree_view) +{ + handle_sort (button, tree_view, time_location_compare_by_name); +} + +static gint +time_location_compare_by_time (gconstpointer ga, gconstpointer gb) +{ + const struct TimeLocation * a = ga; + const struct TimeLocation * b = gb; + int ret = a->offset - b->offset; /* primary key */ + if (!ret) + ret = g_strcmp0 (a->collated_name, b->collated_name); /* secondary key */ + return ret; +} +static void +handle_sort_by_time (GtkWidget * button, GtkTreeView * tree_view) +{ + handle_sort (button, tree_view, time_location_compare_by_time); +} + +static gboolean +time_location_list_test_sorted (GSList * list, GCompareFunc compare) +{ + GSList * l; + for (l=list; l!=NULL && l->next!=NULL; l=l->next) + if (compare(l->data, l->next->data) > 0) + return FALSE; + return TRUE; +} +static void +location_model_test_sorted (GtkTreeModel * model, gboolean * is_sorted_by_name, gboolean * is_sorted_by_time) +{ + GSList * list = time_location_array_new_from_model(model); + *is_sorted_by_name = time_location_list_test_sorted (list, time_location_compare_by_name); + *is_sorted_by_time = time_location_list_test_sorted (list, time_location_compare_by_time); + g_slist_free_full (list, (GDestroyNotify)time_location_free); +} + +/*** +**** +***/ + +static void +handle_add (GtkWidget * button G_GNUC_UNUSED, GtkTreeView * tree) +{ + GtkListStore * store = GTK_LIST_STORE (gtk_tree_view_get_model (tree)); + + GtkTreeIter iter; + gtk_list_store_append (store, &iter); + + GtkTreePath * path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter); + gtk_tree_view_set_cursor (tree, path, gtk_tree_view_get_column (tree, 0), TRUE); + gtk_tree_path_free (path); +} + +static void +handle_remove (GtkWidget * button G_GNUC_UNUSED, GtkTreeView * tree) +{ + GtkListStore * store = GTK_LIST_STORE (gtk_tree_view_get_model (tree)); + GtkTreeSelection * selection = gtk_tree_view_get_selection (tree); + + GList * paths = gtk_tree_selection_get_selected_rows (selection, NULL); + + /* Convert all paths to iters so we can safely delete multiple paths. For a + GtkListStore, iters persist past model changes. */ + GList * tree_iters = NULL; + GList * iter; + for (iter = paths; iter; iter = iter->next) { + GtkTreeIter * tree_iter = g_new(GtkTreeIter, 1); + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), tree_iter, (GtkTreePath *)iter->data)) { + tree_iters = g_list_prepend (tree_iters, tree_iter); + } + gtk_tree_path_free (iter->data); + } + g_list_free (paths); + + // Find the next item to select + GtkTreeIter *last_selected = g_list_nth_data(tree_iters, 0); + GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL (store), last_selected); + GtkTreeIter titer; + if (!gtk_tree_model_get_iter(GTK_TREE_MODEL (store), &titer, path)) { + g_debug("Failed to get last selected iter from path"); + last_selected = NULL; + } else { + if (!gtk_tree_model_iter_next(GTK_TREE_MODEL (store), &titer)) { + if (gtk_tree_path_prev(path)) { + if (!gtk_tree_model_get_iter(GTK_TREE_MODEL (store), &titer, path)) { + g_debug("Failed to get iter from path"); + last_selected = NULL; + } else { + last_selected = &titer; + } + } else { + g_debug("handle_remove: Failed to find another location to select (assume single selected)"); + last_selected = NULL; + } + } else { + g_debug("Got next item in model"); + last_selected = &titer; + } + } + + if (last_selected) { + gboolean clear = TRUE; + path = gtk_tree_model_get_path(GTK_TREE_MODEL (store), last_selected); + + // Step over the path to find an item which isn't in the delete list + if (g_list_length(tree_iters) > 1) { + for (iter = tree_iters; iter; iter = iter->next) { + GtkTreePath *ipath = gtk_tree_model_get_path(GTK_TREE_MODEL (store), (GtkTreeIter *)iter->data); + if (gtk_tree_path_compare(path, ipath) == 0) { + clear = FALSE; + break; + } + } + while (clear == FALSE) { + if (gtk_tree_path_prev(path)) { + clear = TRUE; + for (iter = tree_iters; iter; iter = iter->next) { + GtkTreePath *ipath = gtk_tree_model_get_path(GTK_TREE_MODEL (store), (GtkTreeIter *)iter->data); + if (gtk_tree_path_compare(path, ipath) == 0) { + clear = FALSE; + break; + } + } + if (clear) { + if (!gtk_tree_model_get_iter(GTK_TREE_MODEL (store), &titer, path)) { + g_debug("Failed to get iter from path"); + last_selected = NULL; + } else { + last_selected = &titer; + } + } + } else { + last_selected = NULL; + break; + } + } + } + } + + /* Now delete each iterator */ + for (iter = tree_iters; iter; iter = iter->next) { + gtk_list_store_remove (store, (GtkTreeIter *)iter->data); + g_free (iter->data); + } + g_list_free (tree_iters); + + if (last_selected) + gtk_tree_selection_select_iter(selection, last_selected); +} + +static void +handle_edit (GtkCellRendererText * renderer G_GNUC_UNUSED, + gchar * path, + gchar * new_text, + GtkListStore * store) +{ + GtkTreeIter iter; + + // Manual user edits are always wrong (unless they are undoing a previous + // edit), so we set the error icon here if needed. Common way to get to + // this code path is to lose entry focus. + if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (store), &iter, path)) { + gchar * name; + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_NAME, &name, -1); + gboolean correct = g_strcmp0 (name, new_text) == 0; + g_free (name); + + gtk_list_store_set (store, &iter, + COL_VISIBLE_NAME, new_text, + COL_ICON, correct ? NULL : GTK_STOCK_DIALOG_ERROR, + -1); + } +} + +static gboolean +timezone_selected (GtkEntryCompletion * widget, GtkTreeModel * model, + GtkTreeIter * iter, GtkWidget * dlg) +{ + gchar * zone = NULL; + gchar * name = NULL; + + gtk_tree_model_get (model, iter, + CC_TIMEZONE_COMPLETION_ZONE, &zone, + CC_TIMEZONE_COMPLETION_NAME, &name, + -1); + + /* if no explicit timezone, try to determine one from latlon */ + if (!zone || !*zone) + { + gchar * strlat = NULL; + gchar * strlon = NULL; + gdouble lat = 0; + gdouble lon = 0; + + gtk_tree_model_get (model, iter, + CC_TIMEZONE_COMPLETION_LATITUDE, &strlat, + CC_TIMEZONE_COMPLETION_LONGITUDE, &strlon, + -1); + + if (strlat && *strlat) lat = g_ascii_strtod(strlat, NULL); + if (strlon && *strlon) lon = g_ascii_strtod(strlon, NULL); + + CcTimezoneMap * tzmap = CC_TIMEZONE_MAP (g_object_get_data (G_OBJECT (widget), "tzmap")); + g_free (zone); + zone = g_strdup (cc_timezone_map_get_timezone_at_coords (tzmap, lon, lat)); + + g_free (strlat); + g_free (strlon); + } + + GtkListStore * store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (widget), "store")); + GtkTreeIter * store_iter = (GtkTreeIter *)g_object_get_data (G_OBJECT (widget), "store_iter"); + if (store != NULL && store_iter != NULL) { + gtk_list_store_set (store, store_iter, + COL_VISIBLE_NAME, name, + COL_ICON, NULL, + COL_NAME, name, + COL_ZONE, zone, -1); + } + + update_times (dlg); + + /* cleanup */ + g_free (name); + g_free (zone); + + return FALSE; // Do normal action too +} + +static gboolean +query_tooltip (GtkTreeView * tree, gint x, gint y, gboolean keyboard_mode, + GtkTooltip * tooltip, GtkCellRenderer * cell) +{ + GtkTreeModel * model; + GtkTreeIter iter; + if (!gtk_tree_view_get_tooltip_context (tree, &x, &y, keyboard_mode, + &model, NULL, &iter)) + return FALSE; + + const gchar * icon; + gtk_tree_model_get (model, &iter, COL_ICON, &icon, -1); + if (icon == NULL) + return FALSE; + + GtkTreeViewColumn * col = gtk_tree_view_get_column (tree, 0); + gtk_tree_view_set_tooltip_cell (tree, tooltip, NULL, col, cell); + gtk_tooltip_set_text (tooltip, _("You need to complete this location for it to appear in the menu.")); + return TRUE; +} + +static void +handle_edit_started (GtkCellRendererText * renderer G_GNUC_UNUSED, + GtkCellEditable * editable, + gchar * path, + CcTimezoneCompletion * completion) +{ + if (GTK_IS_ENTRY (editable)) { + GtkEntry *entry = GTK_ENTRY (editable); + cc_timezone_completion_watch_entry (completion, entry); + + GtkListStore * store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (completion), "store")); + GtkTreeIter * store_iter = g_new(GtkTreeIter, 1); + if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (store), store_iter, path)) { + g_object_set_data_full (G_OBJECT (completion), "store_iter", store_iter, g_free); + } + } +} + +static gboolean +update_times (GtkWidget * dlg) +{ + /* For each entry, check zone in column 2 and set column 1 to it's time */ + CcTimezoneCompletion * completion = CC_TIMEZONE_COMPLETION (g_object_get_data (G_OBJECT (dlg), "completion")); + GtkListStore * store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (completion), "store")); + GObject * cell = G_OBJECT (g_object_get_data (G_OBJECT (completion), "name-cell")); + + gboolean editing; + g_object_get (cell, "editing", &editing, NULL); + if (editing) { /* No updates while editing, it cancels the edit */ + return TRUE; + } + + g_signal_handlers_block_by_func (store, save_when_idle, dlg); + + GSettings * settings = g_settings_new (SETTINGS_INTERFACE); + GtkTreeIter iter; + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) { + GDateTime * now = g_date_time_new_now_local (); + do { + gchar * strzone; + + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_ZONE, &strzone, -1); + + if (strzone && *strzone) { + GTimeZone * tz = g_time_zone_new (strzone); + GDateTime * now_tz = g_date_time_to_timezone (now, tz); + gchar * format = generate_full_format_string_at_time (now, now_tz, settings); + gchar * time_str = g_date_time_format (now_tz, format); + gchar * old_time_str; + + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_TIME, &old_time_str, -1); + if (g_strcmp0 (old_time_str, time_str)) + gtk_list_store_set (store, &iter, COL_TIME, time_str, -1); + + g_free (old_time_str); + g_free (time_str); + g_free (format); + g_date_time_unref (now_tz); + g_time_zone_unref (tz); + } + g_free (strzone); + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)); + g_date_time_unref (now); + } + + g_object_unref (settings); + + g_signal_handlers_unblock_by_func (store, save_when_idle, dlg); + + return TRUE; +} + +static void +fill_from_settings (GObject * store, GSettings * conf) +{ + gchar ** locations = g_settings_get_strv (conf, SETTINGS_LOCATIONS_S); + + gtk_list_store_clear (GTK_LIST_STORE (store)); + + gchar ** striter; + GtkTreeIter iter; + for (striter = locations; *striter; ++striter) { + gchar * zone, * name; + split_settings_location (*striter, &zone, &name); + + gtk_list_store_append (GTK_LIST_STORE (store), &iter); + gtk_list_store_set (GTK_LIST_STORE (store), &iter, + COL_VISIBLE_NAME, name, + COL_ICON, NULL, + COL_NAME, name, + COL_ZONE, zone, -1); + + g_free (zone); + g_free (name); + } + + g_strfreev (locations); +} + +static void +save_to_settings (GObject * store, GSettings * conf) +{ + gboolean empty = TRUE; + GVariantBuilder builder; + g_variant_builder_init (&builder, G_VARIANT_TYPE_STRING_ARRAY); + + GtkTreeIter iter; + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) { + GString * gstr = g_string_new (NULL); + do { + gchar * strname; + gchar * strzone; + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, + COL_NAME, &strname, + COL_ZONE, &strzone, + -1); + if (strzone && *strzone && strname && *strname) { + g_string_printf (gstr, "%s %s", strzone, strname); + g_variant_builder_add (&builder, "s", gstr->str); + empty = FALSE; + } + g_free (strname); + g_free (strzone); + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)); + g_string_free (gstr, TRUE); + } + + if (empty) { + /* Empty list */ + g_variant_builder_clear (&builder); + g_settings_set_strv (conf, SETTINGS_LOCATIONS_S, NULL); + } + else { + g_settings_set_value (conf, SETTINGS_LOCATIONS_S, g_variant_builder_end (&builder)); + } +} + +static gboolean +save_now (GtkWidget *dlg) +{ + GSettings * conf = G_SETTINGS (g_object_get_data (G_OBJECT (dlg), "conf")); + GObject * completion = G_OBJECT (g_object_get_data (G_OBJECT (dlg), "completion")); + GObject * store = G_OBJECT (g_object_get_data (completion, "store")); + + save_to_settings (store, conf); + + g_object_set_data (G_OBJECT (dlg), "save-id", GINT_TO_POINTER(0)); + + return FALSE; +} + +static void +save_when_idle (GtkWidget *dlg) +{ + guint save_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dlg), "save-id")); + + if (save_id == 0) { + save_id = g_idle_add ((GSourceFunc)save_now, dlg); + g_object_set_data (G_OBJECT (dlg), "save-id", GINT_TO_POINTER(save_id)); + } +} + +static void +dialog_closed (GtkWidget * dlg, GObject * store G_GNUC_UNUSED) +{ + /* Cleanup a tad */ + guint time_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dlg), "time-id")); + g_source_remove (time_id); + + guint save_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dlg), "save-id")); + if (save_id > 0) + g_source_remove (save_id); +} + +static void +selection_changed (GtkTreeSelection * selection, GtkWidget * remove_button) +{ + gint count = gtk_tree_selection_count_selected_rows (selection); + gtk_widget_set_sensitive (remove_button, count > 0); +} + +static void +update_button_sensitivity (GtkWidget * dlg) +{ + GObject * odlg = G_OBJECT(dlg); + GObject * completion = g_object_get_data(odlg, "completion"); + GtkTreeModel * model = GTK_TREE_MODEL (g_object_get_data (completion, "store")); + gboolean is_sorted_by_name; + gboolean is_sorted_by_time; + location_model_test_sorted (model, &is_sorted_by_name, &is_sorted_by_time); + gtk_widget_set_sensitive (GTK_WIDGET(g_object_get_data(odlg, "sortByNameButton")), !is_sorted_by_name); + gtk_widget_set_sensitive (GTK_WIDGET(g_object_get_data(odlg, "sortByTimeButton")), !is_sorted_by_time); +} + +static void +model_changed (GtkWidget * dlg) +{ + update_button_sensitivity (dlg); + save_when_idle (dlg); +} + +GtkWidget * +datetime_setup_locations_dialog (CcTimezoneMap * map) +{ + GError * error = NULL; + GtkBuilder * builder = gtk_builder_new (); + gtk_builder_set_translation_domain (builder, GETTEXT_PACKAGE); + gtk_builder_add_from_file (builder, DATETIME_DIALOG_UI_FILE, &error); + if (error != NULL) { + /* We have to abort, we can't continue without the ui file */ + g_error ("Could not load ui file %s: %s", DATETIME_DIALOG_UI_FILE, error->message); + g_error_free (error); + return NULL; + } + + GSettings * conf = g_settings_new (SETTINGS_INTERFACE); + +#define WIG(name) GTK_WIDGET (gtk_builder_get_object (builder, name)) + + GtkWidget * dlg = WIG ("locationsDialog"); + GtkWidget * tree = WIG ("locationsView"); + GObject * store = gtk_builder_get_object (builder, "locationsStore"); + + /* Configure tree */ + CcTimezoneCompletion * completion = cc_timezone_completion_new (); + g_object_set_data (G_OBJECT (completion), "tzmap", map); + g_object_set_data (G_OBJECT (completion), "store", store); + g_signal_connect (completion, "match-selected", G_CALLBACK (timezone_selected), dlg); + + GtkCellRenderer * cell = gtk_cell_renderer_text_new (); + g_object_set (cell, "editable", TRUE, NULL); + g_signal_connect (cell, "editing-started", G_CALLBACK (handle_edit_started), completion); + g_signal_connect (cell, "edited", G_CALLBACK (handle_edit), store); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, + _("Location"), cell, + "text", COL_VISIBLE_NAME, NULL); + GtkTreeViewColumn * loc_col = gtk_tree_view_get_column (GTK_TREE_VIEW (tree), 0); + gtk_tree_view_column_set_expand (loc_col, TRUE); + g_object_set_data (G_OBJECT (completion), "name-cell", cell); + + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (loc_col, cell, FALSE); + gtk_tree_view_column_add_attribute (loc_col, cell, "icon-name", COL_ICON); + + gtk_widget_set_has_tooltip (tree, TRUE); + g_signal_connect (tree, "query-tooltip", G_CALLBACK (query_tooltip), cell); + + cell = gtk_cell_renderer_text_new (); + gtk_cell_renderer_set_alignment (cell, 1.0f, 0.5f); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, + _("Time"), cell, + "text", COL_TIME, NULL); + + GtkTreeSelection * selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); + g_signal_connect (selection, "changed", G_CALLBACK (selection_changed), WIG ("removeButton")); + selection_changed (selection, WIG ("removeButton")); + + g_signal_connect (WIG ("addButton"), "clicked", G_CALLBACK (handle_add), tree); + g_signal_connect (WIG ("removeButton"), "clicked", G_CALLBACK (handle_remove), tree); + + GtkWidget * w = WIG ("sortByNameButton"); + g_signal_connect (w, "clicked", G_CALLBACK (handle_sort_by_name), tree); + g_object_set_data (G_OBJECT(dlg), "sortByNameButton", w); + + w = WIG ("sortByTimeButton"); + g_signal_connect (w, "clicked", G_CALLBACK (handle_sort_by_time), tree); + g_object_set_data (G_OBJECT(dlg), "sortByTimeButton", w); + + fill_from_settings (store, conf); + g_signal_connect_swapped (store, "row-deleted", G_CALLBACK (model_changed), dlg); + g_signal_connect_swapped (store, "row-inserted", G_CALLBACK (model_changed), dlg); + g_signal_connect_swapped (store, "row-changed", G_CALLBACK (model_changed), dlg); + g_signal_connect_swapped (store, "rows-reordered", G_CALLBACK (model_changed), dlg); + g_object_set_data_full (G_OBJECT (dlg), "conf", g_object_ref (conf), g_object_unref); + g_object_set_data_full (G_OBJECT (dlg), "completion", completion, g_object_unref); + g_signal_connect (dlg, "destroy", G_CALLBACK (dialog_closed), store); + + guint time_id = g_timeout_add_seconds (2, (GSourceFunc)update_times, dlg); + g_object_set_data (G_OBJECT (dlg), "time-id", GINT_TO_POINTER(time_id)); + update_times (dlg); + +#undef WIG + + g_object_unref (conf); + g_object_unref (builder); + + return dlg; +} + diff --git a/panel/datetime-prefs-locations.h b/panel/datetime-prefs-locations.h new file mode 100644 index 0000000..45d3b23 --- /dev/null +++ b/panel/datetime-prefs-locations.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*- + +A dialog for setting time and date preferences. + +Copyright 2011 Canonical Ltd. + +Authors: + Michael Terry <michael.terry@canonical.com> + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License version 3, as published +by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranties of +MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __DATETIME_PREFS_LOCATIONS_H__ +#define __DATETIME_PREFS_LOCATIONS_H__ + +#include <gtk/gtk.h> +#include <timezonemap/cc-timezone-map.h> + +G_BEGIN_DECLS + +GtkWidget * datetime_setup_locations_dialog (CcTimezoneMap * map); + +G_END_DECLS + +#endif diff --git a/panel/datetime-prefs.c b/panel/datetime-prefs.c new file mode 100644 index 0000000..55456ac --- /dev/null +++ b/panel/datetime-prefs.c @@ -0,0 +1,863 @@ +/* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*- + +A dialog for setting time and date preferences. + +Copyright 2011 Canonical Ltd. + +Authors: + Ted Gould <ted@canonical.com> + Michael Terry <michael.terry@canonical.com> + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License version 3, as published +by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranties of +MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <libintl.h> +#include <locale.h> +#include <langinfo.h> +#include <glib/gi18n-lib.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <polkit/polkit.h> +#include <libgnome-control-center/cc-panel.h> +#include <timezonemap/cc-timezone-map.h> +#include <timezonemap/timezone-completion.h> + +#include "dbus-shared.h" +#include "settings-shared.h" +#include "utils.h" +#include "datetime-prefs-locations.h" + +#define DATETIME_DIALOG_UI_FILE PKGDATADIR "/datetime-dialog.ui" + +#define INDICATOR_DATETIME_TYPE_PANEL indicator_datetime_panel_get_type() + +typedef struct _IndicatorDatetimePanel IndicatorDatetimePanel; +typedef struct _IndicatorDatetimePanelPrivate IndicatorDatetimePanelPrivate; +typedef struct _IndicatorDatetimePanelClass IndicatorDatetimePanelClass; + +struct _IndicatorDatetimePanel +{ + CcPanel parent; + IndicatorDatetimePanelPrivate * priv; +}; + +struct _IndicatorDatetimePanelPrivate +{ + guint name_watch_id; + GtkBuilder * builder; + GDBusProxy * proxy; + GtkWidget * auto_radio; + GtkWidget * tz_entry; + CcTimezoneMap * tzmap; + GtkWidget * time_spin; + GtkWidget * date_spin; + guint save_time_id; + gboolean user_edited_time; + gboolean changing_time; + GtkWidget * loc_dlg; + GSettings * settings; + CcTimezoneCompletion * completion; +}; + +struct _IndicatorDatetimePanelClass +{ + CcPanelClass parent_class; +}; + +G_DEFINE_DYNAMIC_TYPE (IndicatorDatetimePanel, indicator_datetime_panel, CC_TYPE_PANEL) + +/* Turns the boolean property into a string gsettings */ +static GVariant * +bind_hours_set (const GValue * value, + const GVariantType * type G_GNUC_UNUSED, + gpointer user_data) +{ + const gchar * output = NULL; + gboolean is_12hour_button = (gboolean)GPOINTER_TO_INT(user_data); + + if (g_value_get_boolean(value)) { + /* Only do anything if we're setting active = true */ + output = is_12hour_button ? "12-hour" : "24-hour"; + } else { + return NULL; + } + + return g_variant_new_string (output); +} + +/* Turns a string gsettings into a boolean property */ +static gboolean +bind_hours_get (GValue * value, GVariant * variant, gpointer user_data) +{ + const gchar * str = g_variant_get_string(variant, NULL); + gboolean output = FALSE; + gboolean is_12hour_button = (gboolean)GPOINTER_TO_INT(user_data); + + if (g_strcmp0(str, "locale-default") == 0) { + output = (is_12hour_button == is_locale_12h ()); + } else if (g_strcmp0(str, "12-hour") == 0) { + output = is_12hour_button; + } else if (g_strcmp0(str, "24-hour") == 0) { + output = !is_12hour_button; + } else { + return FALSE; + } + + g_value_set_boolean (value, output); + return TRUE; +} + +static void +widget_dependency_cb (GtkWidget * parent, GParamSpec *pspec G_GNUC_UNUSED, GtkWidget * dependent) +{ + gboolean active, sensitive; + g_object_get (G_OBJECT (parent), + "active", &active, + "sensitive", &sensitive, NULL); + gtk_widget_set_sensitive (dependent, active && sensitive); +} + +static void +add_widget_dependency (GtkWidget * parent, GtkWidget * dependent) +{ + g_signal_connect (parent, "notify::active", G_CALLBACK(widget_dependency_cb), + dependent); + g_signal_connect (parent, "notify::sensitive", G_CALLBACK(widget_dependency_cb), + dependent); + widget_dependency_cb (parent, NULL, dependent); +} + +static void +polkit_dependency_cb (GPermission * permission, GParamSpec *pspec G_GNUC_UNUSED, GtkWidget * dependent) +{ + gboolean allowed = FALSE; + + g_object_get (G_OBJECT (permission), + "allowed", &allowed, NULL); + + gtk_widget_set_sensitive (dependent, allowed); +} + +static void +add_polkit_dependency_helper (GtkWidget * parent, GParamSpec *pspec G_GNUC_UNUSED, GtkWidget * dependent) +{ + GtkLockButton * button = GTK_LOCK_BUTTON (parent); + GPermission * permission = gtk_lock_button_get_permission (button); + g_signal_connect (permission, "notify::allowed", + G_CALLBACK(polkit_dependency_cb), dependent); + polkit_dependency_cb (permission, NULL, dependent); +} + +static void +add_polkit_dependency (GtkWidget * parent, GtkWidget * dependent) +{ + /* polkit async hasn't finished at this point, so wait for permission to come in */ + g_signal_connect (parent, "notify::permission", G_CALLBACK(add_polkit_dependency_helper), + dependent); + gtk_widget_set_sensitive (dependent, FALSE); +} + +static void +polkit_perm_ready (GObject *source_object G_GNUC_UNUSED, GAsyncResult *res, gpointer user_data) +{ + GError * error = NULL; + GPermission * permission = polkit_permission_new_finish (res, &error); + + if (error != NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Could not get permission object: %s", error->message); + g_error_free (error); + return; + } + + GtkLockButton * button = GTK_LOCK_BUTTON (user_data); + gtk_lock_button_set_permission (button, permission); +} + +static void +dbus_set_answered (GObject *object, GAsyncResult *res, gpointer command) +{ + GError * error = NULL; + GVariant * answers = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), res, &error); + + if (error != NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("Could not set '%s' using timedated: %s", (gchar *)command, error->message); + g_error_free(error); + return; + } + + g_variant_unref (answers); +} + +static void +toggle_ntp (GtkWidget * radio, GParamSpec * pspec G_GNUC_UNUSED, IndicatorDatetimePanel * self) +{ + gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (radio)); + + g_dbus_proxy_call (self->priv->proxy, "SetNTP", g_variant_new ("(bb)", active, TRUE), + G_DBUS_CALL_FLAGS_NONE, -1, NULL, dbus_set_answered, "NTP"); +} + +static void +sync_entry (IndicatorDatetimePanel * self, const gchar * location) +{ + gchar * name = get_current_zone_name (location, self->priv->settings); + gtk_entry_set_text (GTK_ENTRY (self->priv->tz_entry), name); + g_free (name); + + gtk_entry_set_icon_from_stock (GTK_ENTRY (self->priv->tz_entry), + GTK_ENTRY_ICON_SECONDARY, NULL); +} + +static void +tz_changed (CcTimezoneMap * map G_GNUC_UNUSED, + CcTimezoneLocation * location, + IndicatorDatetimePanel * self) +{ + if (location == NULL) + return; + + gchar * zone; + g_object_get (location, "zone", &zone, NULL); + + g_dbus_proxy_call (self->priv->proxy, "SetTimezone", g_variant_new ("(sb)", zone, TRUE), + G_DBUS_CALL_FLAGS_NONE, -1, NULL, dbus_set_answered, "timezone"); + + sync_entry (self, zone); + + g_free (zone); +} + +static void +proxy_ready (GObject *object G_GNUC_UNUSED, + GAsyncResult *res, + IndicatorDatetimePanel * self) +{ + GError * error = NULL; + IndicatorDatetimePanelPrivate * priv = self->priv; + GVariant *value; + + self->priv->proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + + if (error != NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_critical("Could not grab DBus proxy for timedated: %s", error->message); + g_error_free(error); + return; + } + + /* And now, do initial proxy configuration */ + value = g_dbus_proxy_get_cached_property (priv->proxy, "CanNTP"); + if (value != NULL) + { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)) + gtk_widget_set_sensitive (priv->auto_radio, g_variant_get_boolean (value)); + g_variant_unref (value); + } + + value = g_dbus_proxy_get_cached_property (priv->proxy, "NTP"); + if (value != NULL) + { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)) + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->auto_radio), g_variant_get_boolean (value)); + g_signal_connect (priv->auto_radio, "notify::active", G_CALLBACK (toggle_ntp), self); + } + g_variant_unref (value); + } + + value = g_dbus_proxy_get_cached_property (priv->proxy, "Timezone"); + if (value != NULL) + { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) + { + const gchar *timezone = g_variant_get_string (value, NULL); + + cc_timezone_map_set_timezone (priv->tzmap, timezone); + sync_entry (self, timezone); + g_signal_connect (priv->tzmap, "location-changed", G_CALLBACK (tz_changed), self); + } + g_variant_unref (value); + } +} + +#define WIG(name) GTK_WIDGET (gtk_builder_get_object(self->priv->builder, name)) + +static void +set_show_clock_check_sensitive (IndicatorDatetimePanel * self, + gboolean sensitive) +{ + gtk_widget_set_sensitive (WIG("showClockCheck"), sensitive); +} + +static void +on_bus_name_appeared (GDBusConnection * connection G_GNUC_UNUSED, + const char * name G_GNUC_UNUSED, + const char * name_owner, + gpointer self) +{ + set_show_clock_check_sensitive (self, name_owner && *name_owner); +} + +static void +on_bus_name_vanished (GDBusConnection * connection G_GNUC_UNUSED, + const char * name G_GNUC_UNUSED, + gpointer self) +{ + set_show_clock_check_sensitive (self, FALSE); +} + +static gboolean +are_spinners_focused (IndicatorDatetimePanel * self) +{ + // save_time_id means that we were in focus and haven't finished our save + // yet, so act like we are still focused. + return self->priv->save_time_id || + gtk_widget_has_focus (self->priv->time_spin) || + gtk_widget_has_focus (self->priv->date_spin); +} + +static gboolean +save_time (IndicatorDatetimePanel * self) +{ + if (self->priv->user_edited_time) { + gdouble current_value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (self->priv->date_spin)); + g_dbus_proxy_call (self->priv->proxy, "SetTime", + g_variant_new ("(xbb)", (gint64) (current_value * G_TIME_SPAN_SECOND), FALSE, TRUE), + G_DBUS_CALL_FLAGS_NONE, -1, NULL, dbus_set_answered, "time"); + } + self->priv->user_edited_time = FALSE; + self->priv->save_time_id = 0; + return FALSE; +} + +static gboolean +spin_focus_in (IndicatorDatetimePanel * self) +{ + if (self->priv->save_time_id > 0) { + g_source_remove (self->priv->save_time_id); + self->priv->save_time_id = 0; + } + return FALSE; +} + +static gboolean +spin_focus_out (IndicatorDatetimePanel * self) +{ + /* We want to only save when both spinners are unfocused. But it's difficult + to tell who is about to get focus during a focus-out. So we set an idle + callback to save the time if we don't focus in to another spinner by that + time. */ + if (self->priv->save_time_id == 0) { + self->priv->save_time_id = g_idle_add ((GSourceFunc)save_time, self); + } + return FALSE; +} + +static int +input_time_text (GtkWidget * spinner, gdouble * value, IndicatorDatetimePanel * self) +{ + gboolean is_time = (gboolean)GPOINTER_TO_INT (g_object_get_data (G_OBJECT (spinner), "is-time")); + const gchar * text = gtk_entry_get_text (GTK_ENTRY (spinner)); + + gdouble current_value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (spinner)); + *value = current_value; + + GDateTime * now = g_date_time_new_from_unix_local (current_value); + gint year, month, day, hour, minute, second; + year = g_date_time_get_year (now); + month = g_date_time_get_month (now); + day = g_date_time_get_day_of_month (now); + hour = g_date_time_get_hour (now); + minute = g_date_time_get_minute (now); + second = g_date_time_get_second (now); + g_date_time_unref (now); + + /* Parse this string as if it were in the output format */ + gint scanned = 0; + gboolean passed = TRUE, skip = FALSE; + if (is_time) { + gint hour_in, minute_in, second_in; + + if (is_locale_12h ()) { // TODO: make this look-at/watch gsettings? + char ampm[51]; + + /* coverity[secure_coding] */ + scanned = sscanf (text, "%u:%u:%u %50s", &hour_in, &minute_in, &second_in, ampm); + passed = (scanned == 4); + + if (passed) { + const char *pm_str = nl_langinfo (PM_STR); + if (g_ascii_strcasecmp (pm_str, ampm) == 0) { + hour_in += 12; + } + } + } else { + /* coverity[secure_coding] */ + scanned = sscanf (text, "%u:%u:%u", &hour_in, &minute_in, &second_in); + passed = (scanned == 3); + } + + if (passed && (hour_in > 23 || minute_in > 59 || second_in > 59)) { + passed = FALSE; + } + if (passed && hour == hour_in && minute == minute_in && second == second_in) { + skip = TRUE; // no change + } else { + hour = hour_in; + minute = minute_in; + second = second_in; + } + } + else { + gint year_in, month_in, day_in; + + /* coverity[secure_coding] */ + scanned = sscanf (text, "%u-%u-%u", &year_in, &month_in, &day_in); + + if (scanned != 3 || year_in < 1 || year_in > 9999 || + month_in < 1 || month_in > 12 || day_in < 1 || day_in > 31) { + passed = FALSE; + } + if (passed && year == year_in && month == month_in && day == day_in) { + skip = TRUE; // no change + } else { + year = year_in; + month = month_in; + day = day_in; + } + } + + if (!passed) { + g_warning ("Could not understand %s", text); + return TRUE; + } + + if (skip) { + return TRUE; + } + + gboolean prev_changing = self->priv->changing_time; + self->priv->changing_time = TRUE; + GDateTime * new_time = g_date_time_new_local (year, month, day, hour, minute, second); + *value = g_date_time_to_unix (new_time); + self->priv->user_edited_time = TRUE; + g_date_time_unref (new_time); + self->priv->changing_time = prev_changing; + + return TRUE; +} + +static gboolean +format_time_text (GtkWidget * spinner, gpointer user_data G_GNUC_UNUSED) +{ + gboolean is_time = (gboolean)GPOINTER_TO_INT (g_object_get_data (G_OBJECT (spinner), "is-time")); + + const gchar * format; + if (is_time) { + if (is_locale_12h ()) { // TODO: make this look-at/watch gsettings? + format = "%I:%M:%S %p"; + } else { + format = "%H:%M:%S"; + } + } + else { + // This is intentionally not "%x". See https://launchpad.net/bugs/1149696 + // If you are willing to do the hard work of writing a locale-sensitive + // date parser, there is an open bug: https://launchpad.net/bugs/729056 + format = "%Y-%m-%d"; + } + + GDateTime * datetime = g_date_time_new_from_unix_local (gtk_spin_button_get_value (GTK_SPIN_BUTTON (spinner))); + gchar * formatted = g_date_time_format (datetime, format); + gtk_entry_set_text (GTK_ENTRY (spinner), formatted); + g_date_time_unref (datetime); + + return TRUE; +} + +static void +spin_copy_value (GtkSpinButton * spinner, IndicatorDatetimePanel * self) +{ + GtkSpinButton * other = NULL; + if (GTK_WIDGET (spinner) == self->priv->date_spin) + other = GTK_SPIN_BUTTON (self->priv->time_spin); + else + other = GTK_SPIN_BUTTON (self->priv->date_spin); + + if (gtk_spin_button_get_value (spinner) != gtk_spin_button_get_value (other)) { + gtk_spin_button_set_value (other, gtk_spin_button_get_value (spinner)); + } + if (!self->priv->changing_time) { /* Means user pressed spin buttons */ + self->priv->user_edited_time = TRUE; + } +} + +static gboolean +update_spinners (IndicatorDatetimePanel * self) +{ + /* Add datetime object to spinner, which will hold the real time value, rather + then using the value of the spinner itself. And don't update while user is + editing. */ + if (!are_spinners_focused (self)) { + gboolean prev_changing = self->priv->changing_time; + self->priv->changing_time = TRUE; + GDateTime * now = g_date_time_new_now_local (); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->priv->time_spin), + (gdouble)g_date_time_to_unix (now)); + /* will be copied to other spin button */ + g_date_time_unref (now); + self->priv->changing_time = prev_changing; + } + return TRUE; +} + +static void +setup_time_spinners (IndicatorDatetimePanel * self, GtkWidget * time, GtkWidget * date) +{ + g_signal_connect (time, "input", G_CALLBACK (input_time_text), self); + g_signal_connect (date, "input", G_CALLBACK (input_time_text), self); + + g_signal_connect (time, "output", G_CALLBACK (format_time_text), date); + g_signal_connect (date, "output", G_CALLBACK (format_time_text), time); + + g_signal_connect_swapped (time, "focus-in-event", G_CALLBACK (spin_focus_in), self); + g_signal_connect_swapped (date, "focus-in-event", G_CALLBACK (spin_focus_in), self); + + g_signal_connect_swapped (time, "focus-out-event", G_CALLBACK (spin_focus_out), self); + g_signal_connect_swapped (date, "focus-out-event", G_CALLBACK (spin_focus_out), self); + + g_signal_connect (time, "value-changed", G_CALLBACK (spin_copy_value), self); + g_signal_connect (date, "value-changed", G_CALLBACK (spin_copy_value), self); + + g_object_set_data (G_OBJECT (time), "is-time", GINT_TO_POINTER (TRUE)); + g_object_set_data (G_OBJECT (date), "is-time", GINT_TO_POINTER (FALSE)); + + self->priv->time_spin = time; + self->priv->date_spin = date; + + /* 2 seconds is what the indicator itself uses */ + guint time_id = g_timeout_add_seconds (2, (GSourceFunc)update_spinners, self); + g_signal_connect_swapped (self->priv->time_spin, "destroy", + G_CALLBACK (g_source_remove), GINT_TO_POINTER (time_id)); + update_spinners (self); +} + +static void +show_locations (IndicatorDatetimePanel * self) +{ + if (self->priv->loc_dlg == NULL) { + self->priv->loc_dlg = datetime_setup_locations_dialog (self->priv->tzmap); + GtkWidget * dlg = gtk_widget_get_toplevel (GTK_WIDGET (self)); + gtk_window_set_type_hint (GTK_WINDOW(self->priv->loc_dlg), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_transient_for (GTK_WINDOW (self->priv->loc_dlg), GTK_WINDOW (dlg)); + g_signal_connect (self->priv->loc_dlg, "destroy", G_CALLBACK (gtk_widget_destroyed), &self->priv->loc_dlg); + gtk_widget_show_all (self->priv->loc_dlg); + } + else { + gtk_window_present_with_time (GTK_WINDOW (self->priv->loc_dlg), gtk_get_current_event_time ()); + } +} + +static gboolean +timezone_selected (GtkEntryCompletion * widget G_GNUC_UNUSED, + GtkTreeModel * model, + GtkTreeIter * iter, + IndicatorDatetimePanel * self) +{ + const gchar * name, * zone; + + gtk_tree_model_get (model, iter, + CC_TIMEZONE_COMPLETION_NAME, &name, + CC_TIMEZONE_COMPLETION_ZONE, &zone, + -1); + + if (zone == NULL || zone[0] == 0) { + const gchar * strlon, * strlat; + gdouble lon = 0.0, lat = 0.0; + + gtk_tree_model_get (model, iter, + CC_TIMEZONE_COMPLETION_LONGITUDE, &strlon, + CC_TIMEZONE_COMPLETION_LATITUDE, &strlat, + -1); + + if (strlon != NULL && strlon[0] != 0) { + lon = g_ascii_strtod(strlon, NULL); + } + + if (strlat != NULL && strlat[0] != 0) { + lat = g_ascii_strtod(strlat, NULL); + } + + zone = cc_timezone_map_get_timezone_at_coords (self->priv->tzmap, lon, lat); + } + + gchar * tz_name = g_strdup_printf ("%s %s", zone, name); + g_settings_set_string (self->priv->settings, SETTINGS_TIMEZONE_NAME_S, tz_name); + g_free (tz_name); + + cc_timezone_map_set_timezone (self->priv->tzmap, zone); + + return FALSE; // Do normal action too +} + +static gboolean +entry_focus_out (GtkEntry * entry, + GdkEventFocus * event G_GNUC_UNUSED, + IndicatorDatetimePanel * self) +{ + // If the name left in the entry doesn't match the current timezone name, + // show an error icon. It's always an error for the user to manually type in + // a timezone. + CcTimezoneLocation * location = cc_timezone_map_get_location (self->priv->tzmap); + if (location == NULL) + return FALSE; + + gchar * zone; + g_object_get (location, "zone", &zone, NULL); + + gchar * name = get_current_zone_name (zone, self->priv->settings); + gboolean correct = (g_strcmp0 (gtk_entry_get_text (entry), name) == 0); + g_free (name); + g_free (zone); + + gtk_entry_set_icon_from_stock (entry, GTK_ENTRY_ICON_SECONDARY, + correct ? NULL : GTK_STOCK_DIALOG_ERROR); + gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY, + _("You need to choose a location to change the time zone.")); + gtk_entry_set_icon_activatable (entry, GTK_ENTRY_ICON_SECONDARY, FALSE); + return FALSE; +} + +static void +indicator_datetime_panel_init (IndicatorDatetimePanel * self) +{ + GError * error; + GSettings * conf; + + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + INDICATOR_DATETIME_TYPE_PANEL, + IndicatorDatetimePanelPrivate); + + self->priv->settings = conf = g_settings_new (SETTINGS_INTERFACE); + + self->priv->builder = gtk_builder_new (); + gtk_builder_set_translation_domain (self->priv->builder, GETTEXT_PACKAGE); + error = NULL; + gtk_builder_add_from_file (self->priv->builder, DATETIME_DIALOG_UI_FILE, &error); + if (error != NULL) { + /* We have to abort, we can't continue without the ui file */ + g_error ("Could not load ui file %s: %s", DATETIME_DIALOG_UI_FILE, error->message); + g_error_free (error); + return; + } + + + /* Add policykit button */ + GtkWidget * polkit_button = gtk_lock_button_new (NULL); + g_object_set (G_OBJECT (polkit_button), + "text-unlock", _("Unlock to change these settings"), + "text-lock", _("Lock to prevent further changes"), + NULL); + GtkWidget * alignment = gtk_alignment_new (0.0, 0.5, 0.0, 0.0); + gtk_container_add (GTK_CONTAINER (alignment), polkit_button); + gtk_box_pack_start (GTK_BOX (WIG ("timeDateBox")), alignment, FALSE, TRUE, 0); + + const gchar * polkit_name = "org.gnome.controlcenter.datetime.configure"; + polkit_permission_new (polkit_name, NULL, NULL, polkit_perm_ready, polkit_button); + + /* Add map */ + self->priv->tzmap = cc_timezone_map_new (); + gtk_container_add (GTK_CONTAINER (WIG ("mapBox")), GTK_WIDGET (self->priv->tzmap)); + /* Fufill the CC by Attribution license requirements for the Geonames lookup */ + cc_timezone_map_set_watermark (self->priv->tzmap, "Geonames.org"); + + /* And completion entry */ + self->priv->completion = cc_timezone_completion_new (); + cc_timezone_completion_watch_entry (self->priv->completion, GTK_ENTRY (WIG ("timezoneEntry"))); + g_signal_connect (self->priv->completion, "match-selected", G_CALLBACK (timezone_selected), self); + g_signal_connect (WIG ("timezoneEntry"), "focus-out-event", G_CALLBACK (entry_focus_out), self); + + /* Set up settings bindings */ + g_settings_bind (conf, SETTINGS_SHOW_CLOCK_S, WIG ("showClockCheck"), + "active", G_SETTINGS_BIND_DEFAULT); + g_settings_bind (conf, SETTINGS_SHOW_DAY_S, WIG ("showWeekdayCheck"), + "active", G_SETTINGS_BIND_DEFAULT); + g_settings_bind (conf, SETTINGS_SHOW_DATE_S, WIG ("showDateAndMonthCheck"), + "active", G_SETTINGS_BIND_DEFAULT); + g_settings_bind (conf, SETTINGS_SHOW_YEAR_S, WIG ("showYearCheck"), + "active", G_SETTINGS_BIND_DEFAULT); + g_settings_bind (conf, SETTINGS_SHOW_SECONDS_S, WIG ("showSecondsCheck"), + "active", G_SETTINGS_BIND_DEFAULT); + g_settings_bind_with_mapping (conf, SETTINGS_TIME_FORMAT_S, + WIG ("show12HourRadio"), "active", + G_SETTINGS_BIND_DEFAULT, + bind_hours_get, bind_hours_set, + GINT_TO_POINTER(TRUE), NULL); + g_settings_bind_with_mapping (conf, SETTINGS_TIME_FORMAT_S, + WIG ("show24HourRadio"), "active", + G_SETTINGS_BIND_DEFAULT, + bind_hours_get, bind_hours_set, + GINT_TO_POINTER(FALSE), NULL); + g_settings_bind (conf, SETTINGS_SHOW_CALENDAR_S, WIG ("showCalendarCheck"), + "active", G_SETTINGS_BIND_DEFAULT); + g_settings_bind (conf, SETTINGS_SHOW_WEEK_NUMBERS_S, WIG ("includeWeekNumbersCheck"), + "active", G_SETTINGS_BIND_DEFAULT); + g_settings_bind (conf, SETTINGS_SHOW_EVENTS_S, WIG ("showEventsCheck"), + "active", G_SETTINGS_BIND_DEFAULT); + g_settings_bind (conf, SETTINGS_SHOW_DETECTED_S, WIG ("showDetectedCheck"), + "active", G_SETTINGS_BIND_DEFAULT); + g_settings_bind (conf, SETTINGS_SHOW_LOCATIONS_S, WIG ("showLocationsCheck"), + "active", G_SETTINGS_BIND_DEFAULT); + + /* Set up sensitivities */ + add_widget_dependency (WIG ("showDateAndMonthCheck"), WIG ("showYearCheck")); + add_widget_dependency (WIG ("showCalendarCheck"), WIG ("calendarOptions")); + add_widget_dependency (WIG ("showClockCheck"), WIG ("clockOptions")); + add_widget_dependency (WIG ("showLocationsCheck"), WIG ("locationsButton")); + add_widget_dependency (WIG ("manualTimeRadio"), WIG ("manualOptions")); + add_polkit_dependency (polkit_button, WIG ("timeDateOptions")); + + /* Hacky proxy test for whether evolution-data-server is installed */ + gchar * evo_path = g_find_program_in_path ("evolution"); + gtk_widget_set_sensitive (WIG ("showEventsCheck"), (evo_path != NULL)); + g_free (evo_path); + + setup_time_spinners (self, WIG ("timeSpinner"), WIG ("dateSpinner")); + + GtkWidget * panel = WIG ("timeDatePanel"); + self->priv->auto_radio = WIG ("automaticTimeRadio"); + self->priv->tz_entry = WIG ("timezoneEntry"); + + g_signal_connect_swapped (WIG ("locationsButton"), "clicked", G_CALLBACK (show_locations), self); + + /* Grab proxy for settings daemon */ + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, NULL, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + NULL, (GAsyncReadyCallback)proxy_ready, self); + + /* Grab proxy for datetime service, to see if it's running. It would + actually be more ideal to see if the indicator module itself is running, + but that doesn't yet claim a name on the bus. Presumably the service + would have been started by any such indicator, so this will at least tell + us if there *was* a datetime module run this session. */ + self->priv->name_watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, + BUS_NAME, + G_BUS_NAME_WATCHER_FLAGS_NONE, + on_bus_name_appeared, + on_bus_name_vanished, + self, + NULL); + +#undef WIG + + gtk_widget_show_all (panel); + gtk_container_add (GTK_CONTAINER (self), panel); +} + +static void +indicator_datetime_panel_dispose (GObject * object) +{ + IndicatorDatetimePanel * self = (IndicatorDatetimePanel *) object; + IndicatorDatetimePanelPrivate * priv = self->priv; + + g_clear_object (&priv->builder); + g_clear_object (&priv->proxy); + g_clear_object (&priv->settings); + + if (priv->loc_dlg) { + gtk_widget_destroy (priv->loc_dlg); + priv->loc_dlg = NULL; + } + + if (priv->name_watch_id != 0) { + g_bus_unwatch_name (priv->name_watch_id); + priv->name_watch_id = 0; + } + + if (priv->save_time_id) { + g_source_remove (priv->save_time_id); + priv->save_time_id = 0; + } + + if (priv->completion) { + cc_timezone_completion_watch_entry (priv->completion, NULL); + g_clear_object (&priv->completion); + } + + if (priv->tz_entry) { + gtk_widget_destroy (priv->tz_entry); + priv->tz_entry = NULL; + } + + if (priv->time_spin) { + gtk_widget_destroy (priv->time_spin); + priv->time_spin = NULL; + } + + if (priv->date_spin) { + gtk_widget_destroy (priv->date_spin); + priv->date_spin = NULL; + } + + G_OBJECT_CLASS (indicator_datetime_panel_parent_class)->dispose (object); +} + +static void +indicator_datetime_panel_class_finalize (IndicatorDatetimePanelClass *klass G_GNUC_UNUSED) +{ +} + +static const char * +indicator_datetime_panel_get_help_uri (CcPanel *panel G_GNUC_UNUSED) +{ + return "help:ubuntu-help/clock"; +} + +static void +indicator_datetime_panel_class_init (IndicatorDatetimePanelClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + CcPanelClass *panel_class = CC_PANEL_CLASS (klass); + + g_type_class_add_private (klass, sizeof (IndicatorDatetimePanelPrivate)); + + panel_class->get_help_uri = indicator_datetime_panel_get_help_uri; + + gobject_class->dispose = indicator_datetime_panel_dispose; +} + +void +g_io_module_load (GIOModule *module) +{ + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + + indicator_datetime_panel_register_type (G_TYPE_MODULE (module)); + g_io_extension_point_implement (CC_SHELL_PANEL_EXTENSION_POINT, + INDICATOR_DATETIME_TYPE_PANEL, + "indicator-datetime", 0); +} + +void +g_io_module_unload (GIOModule *module G_GNUC_UNUSED) +{ +} |