aboutsummaryrefslogtreecommitdiff
path: root/panel
diff options
context:
space:
mode:
Diffstat (limited to 'panel')
-rw-r--r--panel/CMakeLists.txt25
-rw-r--r--panel/datetime-prefs-locations.c683
-rw-r--r--panel/datetime-prefs-locations.h35
-rw-r--r--panel/datetime-prefs.c863
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)
+{
+}