1
/* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*-
3
A dialog for setting time and date preferences.
5
Copyright 2011 Canonical Ltd.
8
Ted Gould <ted@canonical.com>
9
Michael Terry <michael.terry@canonical.com>
11
This program is free software: you can redistribute it and/or modify it
12
under the terms of the GNU General Public License version 3, as published
13
by the Free Software Foundation.
15
This program is distributed in the hope that it will be useful, but
16
WITHOUT ANY WARRANTY; without even the implied warranties of
17
MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
18
PURPOSE. See the GNU General Public License for more details.
20
You should have received a copy of the GNU General Public License along
21
with this program. If not, see <http://www.gnu.org/licenses/>.
32
#include <glib/gi18n-lib.h>
34
#include <unique/unique.h>
35
#include <polkitgtk/polkitgtk.h>
37
#include "settings-shared.h"
39
#include "datetime-prefs-locations.h"
40
#include "timezone-completion.h"
41
#include "cc-timezone-map.h"
43
#define DATETIME_DIALOG_UI_FILE PKGDATADIR "/datetime-dialog.ui"
45
GDBusProxy * proxy = NULL;
46
GtkWidget * auto_radio = NULL;
47
GtkWidget * tz_entry = NULL;
48
CcTimezoneMap * tzmap = NULL;
50
/* Turns the boolean property into a string gsettings */
52
bind_hours_set (const GValue * value, const GVariantType * type, gpointer user_data)
54
const gchar * output = NULL;
55
gboolean is_12hour_button = (gboolean)GPOINTER_TO_INT(user_data);
57
if (g_value_get_boolean(value)) {
58
/* Only do anything if we're setting active = true */
59
output = is_12hour_button ? "12-hour" : "24-hour";
64
return g_variant_new_string (output);
67
/* Turns a string gsettings into a boolean property */
69
bind_hours_get (GValue * value, GVariant * variant, gpointer user_data)
71
const gchar * str = g_variant_get_string(variant, NULL);
72
gboolean output = FALSE;
73
gboolean is_12hour_button = (gboolean)GPOINTER_TO_INT(user_data);
75
if (g_strcmp0(str, "locale-default") == 0) {
76
output = (is_12hour_button == is_locale_12h ());
77
} else if (g_strcmp0(str, "12-hour") == 0) {
78
output = is_12hour_button;
79
} else if (g_strcmp0(str, "24-hour") == 0) {
80
output = !is_12hour_button;
85
g_value_set_boolean (value, output);
90
widget_dependency_cb (GtkWidget * parent, GParamSpec *pspec, GtkWidget * dependent)
92
gboolean active, sensitive;
93
g_object_get (G_OBJECT (parent),
95
"sensitive", &sensitive, NULL);
96
gtk_widget_set_sensitive (dependent, active && sensitive);
100
add_widget_dependency (GtkWidget * parent, GtkWidget * dependent)
102
g_signal_connect (parent, "notify::active", G_CALLBACK(widget_dependency_cb),
104
g_signal_connect (parent, "notify::sensitive", G_CALLBACK(widget_dependency_cb),
106
widget_dependency_cb (parent, NULL, dependent);
110
polkit_dependency_cb (GtkWidget * parent, GParamSpec *pspec, GtkWidget * dependent)
112
gboolean authorized, sensitive;
113
g_object_get (G_OBJECT (parent),
114
"is-authorized", &authorized,
115
"sensitive", &sensitive, NULL);
116
gtk_widget_set_sensitive (dependent, authorized && sensitive);
120
add_polkit_dependency (GtkWidget * parent, GtkWidget * dependent)
122
g_signal_connect (parent, "notify::is-authorized", G_CALLBACK(polkit_dependency_cb),
124
g_signal_connect (parent, "notify::sensitive", G_CALLBACK(polkit_dependency_cb),
126
polkit_dependency_cb (parent, NULL, dependent);
130
dbus_set_answered (GObject *object, GAsyncResult *res, gpointer command)
132
GError * error = NULL;
133
GVariant * answers = g_dbus_proxy_call_finish (proxy, res, &error);
136
g_warning("Could not set '%s' for SettingsDaemon: %s", (gchar *)command, error->message);
141
g_variant_unref (answers);
145
toggle_ntp (GtkWidget * radio, GParamSpec * pspec, gpointer user_data)
147
gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (radio));
149
g_dbus_proxy_call (proxy, "SetUsingNtp", g_variant_new ("(b)", active),
150
G_DBUS_CALL_FLAGS_NONE, -1, NULL, dbus_set_answered, "using_ntp");
154
ntp_query_answered (GObject *object, GAsyncResult *res, gpointer user_data)
156
GError * error = NULL;
157
GVariant * answers = g_dbus_proxy_call_finish (proxy, res, &error);
160
g_warning("Could not query DBus proxy for SettingsDaemon: %s", error->message);
165
gboolean can_use_ntp, is_using_ntp;
166
g_variant_get (answers, "(bb)", &can_use_ntp, &is_using_ntp);
168
gtk_widget_set_sensitive (GTK_WIDGET (auto_radio), can_use_ntp);
169
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (auto_radio), is_using_ntp);
171
g_signal_connect (auto_radio, "notify::active", G_CALLBACK (toggle_ntp), NULL);
173
g_variant_unref (answers);
177
sync_entry (const gchar * location)
180
split_settings_location (location, NULL, &name);
181
gtk_entry_set_text (GTK_ENTRY (tz_entry), name);
186
tz_changed (CcTimezoneMap * map, TzLocation * location)
188
if (location == NULL)
191
gchar * file = g_build_filename ("/usr/share/zoneinfo", location->zone, NULL);
192
g_dbus_proxy_call (proxy, "SetTimezone", g_variant_new ("(s)", file),
193
G_DBUS_CALL_FLAGS_NONE, -1, NULL, dbus_set_answered, "timezone");
196
sync_entry (location->zone);
200
tz_query_answered (GObject *object, GAsyncResult *res, gpointer user_data)
202
GError * error = NULL;
203
GVariant * answers = g_dbus_proxy_call_finish (proxy, res, &error);
206
g_warning("Could not query DBus proxy for SettingsDaemon: %s", error->message);
211
const gchar * timezone;
212
g_variant_get (answers, "(&s)", &timezone);
214
cc_timezone_map_set_timezone (tzmap, timezone);
216
sync_entry (timezone);
217
g_signal_connect (tzmap, "location-changed", G_CALLBACK (tz_changed), NULL);
219
g_variant_unref (answers);
222
void proxy_ready (GObject *object, GAsyncResult *res, gpointer user_data)
224
GError * error = NULL;
226
proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
229
g_critical("Could not grab DBus proxy for SettingsDaemon: %s", error->message);
234
/* And now, do initial proxy configuration */
235
g_dbus_proxy_call (proxy, "GetUsingNtp", NULL, G_DBUS_CALL_FLAGS_NONE, -1,
236
NULL, ntp_query_answered, auto_radio);
237
g_dbus_proxy_call (proxy, "GetTimezone", NULL, G_DBUS_CALL_FLAGS_NONE, -1,
238
NULL, tz_query_answered, NULL);
242
input_time_text (GtkWidget * spinner, gdouble *value, gpointer user_data)
244
gboolean is_time = (gboolean)GPOINTER_TO_INT (g_object_get_data (G_OBJECT (spinner), "is-time"));
245
const gchar * text = gtk_entry_get_text (GTK_ENTRY (spinner));
247
GDateTime * now = g_date_time_new_now_local ();
248
gint year, month, day, hour, minute, second;
249
year = g_date_time_get_year (now);
250
month = g_date_time_get_month (now);
251
day = g_date_time_get_day_of_month (now);
252
hour = g_date_time_get_hour (now);
253
minute = g_date_time_get_minute (now);
254
second = g_date_time_get_second (now);
255
g_date_time_unref (now);
257
/* Parse this string as if it were in the output format */
259
gboolean passed = TRUE, skip = FALSE;
261
gint hour_in, minute_in, second_in;
263
if (is_locale_12h ()) { // TODO: make this look-at/watch gsettings?
266
scanned = sscanf (text, "%u:%u:%u %50s", &hour_in, &minute_in, &second_in, ampm);
267
passed = (scanned == 4);
270
const char *pm_str = nl_langinfo (PM_STR);
271
if (g_ascii_strcasecmp (pm_str, ampm) == 0) {
276
scanned = sscanf (text, "%u:%u:%u", &hour_in, &minute_in, &second_in);
277
passed = (scanned == 3);
280
if (passed && (hour_in > 23 || minute_in > 59 || second_in > 59)) {
283
if (passed && hour == hour_in && minute == minute_in && second == second_in) {
284
skip = TRUE; // no change
292
gint year_in, month_in, day_in;
294
scanned = sscanf (text, "%u-%u-%u", &year_in, &month_in, &day_in);
296
if (scanned != 3 || year_in < 1 || year_in > 9999 ||
297
month_in < 1 || month_in > 12 || day_in < 1 || day_in > 31) {
300
if (passed && year == year_in && month == month_in && day == day_in) {
301
skip = TRUE; // no change
310
g_warning ("Could not understand %s", text);
318
GDateTime * datetime = g_date_time_new_local (year, month, day, hour, minute, second);
320
g_dbus_proxy_call (proxy, "SetTime", g_variant_new ("(x)", g_date_time_to_unix (datetime)),
321
G_DBUS_CALL_FLAGS_NONE, -1, NULL, dbus_set_answered, "time");
327
format_time_text (GtkWidget * spinner, gpointer user_data)
329
if (gtk_widget_has_focus (spinner)) {
330
/* Don't do anything if we have focus, user is likely editing us */
334
GDateTime * datetime = (GDateTime *)g_object_get_data (G_OBJECT (spinner), "datetime");
335
gboolean is_time = (gboolean)GPOINTER_TO_INT (g_object_get_data (G_OBJECT (spinner), "is-time"));
337
const gchar * format;
339
if (is_locale_12h ()) { // TODO: make this look-at/watch gsettings?
340
format = "%I:%M:%S %p";
349
gchar * formatted = g_date_time_format (datetime, format);
350
gtk_entry_set_text (GTK_ENTRY (spinner), formatted);
356
update_spinner (GtkWidget * spinner)
358
/* Add datetime object to spinner, which will hold the real time value, rather
359
then using the value of the spinner itself. */
360
GDateTime * datetime = g_date_time_new_now_local ();
361
g_object_set_data_full (G_OBJECT (spinner), "datetime", datetime, (GDestroyNotify)g_date_time_unref);
363
format_time_text (spinner, NULL);
369
setup_time_spinner (GtkWidget * spinner, GtkWidget * other, gboolean is_time)
371
/* Set up spinner to have reasonable behavior */
372
gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinner), FALSE);
373
g_signal_connect (spinner, "input", G_CALLBACK (input_time_text), other);
374
g_signal_connect (spinner, "output", G_CALLBACK (format_time_text), other);
375
g_object_set_data (G_OBJECT (spinner), "is-time", GINT_TO_POINTER (is_time));
377
/* 2 seconds is what the indicator itself uses */
378
guint time_id = g_timeout_add_seconds (2, (GSourceFunc)update_spinner, spinner);
379
g_signal_connect_swapped (spinner, "destroy", G_CALLBACK (g_source_remove), GINT_TO_POINTER (time_id));
381
update_spinner (spinner);
385
show_locations (GtkWidget * button, GtkWidget * dlg)
387
GtkWidget * locationsDlg = datetime_setup_locations_dialog (GTK_WINDOW (dlg), tzmap);
388
gtk_widget_show_all (locationsDlg);
392
timezone_selected (GtkEntryCompletion * widget, GtkTreeModel * model,
393
GtkTreeIter * iter, gpointer user_data)
396
const gchar * strval;
398
gtk_tree_model_get_value (model, iter, TIMEZONE_COMPLETION_ZONE, &value);
399
strval = g_value_get_string (&value);
401
if (strval != NULL && strval[0] != 0) {
402
cc_timezone_map_set_timezone (tzmap, strval);
405
GValue lon_value = {0}, lat_value = {0};
406
const gchar * strlon, * strlat;
407
gdouble lon = 0.0, lat = 0.0;
409
gtk_tree_model_get_value (model, iter, TIMEZONE_COMPLETION_LONGITUDE, &lon_value);
410
strlon = g_value_get_string (&lon_value);
411
if (strlon != NULL && strlon[0] != 0) {
412
lon = strtod(strlon, NULL);
415
gtk_tree_model_get_value (model, iter, TIMEZONE_COMPLETION_LATITUDE, &lat_value);
416
strlat = g_value_get_string (&lat_value);
417
if (strlat != NULL && strlat[0] != 0) {
418
lat = strtod(strlat, NULL);
421
cc_timezone_map_set_coords (tzmap, lon, lat);
424
g_value_unset (&value);
426
return FALSE; // Do normal action too
432
GError * error = NULL;
434
GtkBuilder * builder = gtk_builder_new ();
435
gtk_builder_add_from_file (builder, DATETIME_DIALOG_UI_FILE, &error);
437
/* We have to abort, we can't continue without the ui file */
438
g_error ("Could not load ui file %s: %s", DATETIME_DIALOG_UI_FILE, error->message);
439
g_error_free (error);
443
gtk_builder_set_translation_domain (builder, GETTEXT_PACKAGE);
445
GSettings * conf = g_settings_new (SETTINGS_INTERFACE);
447
#define WIG(name) GTK_WIDGET (gtk_builder_get_object (builder, name))
449
/* Add policykit button */
450
GtkWidget * polkit_button = polkit_lock_button_new ("org.gnome.settingsdaemon.datetimemechanism.configure");
451
polkit_lock_button_set_unlock_text (POLKIT_LOCK_BUTTON (polkit_button), _("Unlock to change these settings"));
452
polkit_lock_button_set_lock_text (POLKIT_LOCK_BUTTON (polkit_button), _("Lock to prevent further changes"));
453
gtk_box_pack_start (GTK_BOX (WIG ("timeDateBox")), polkit_button, FALSE, TRUE, 0);
456
tzmap = cc_timezone_map_new ();
457
gtk_container_add (GTK_CONTAINER (WIG ("mapBox")), GTK_WIDGET (tzmap));
458
/* Fufill the CC by Attribution license requirements for the Geonames lookup */
459
cc_timezone_map_set_watermark (tzmap, "Geonames.org");
461
/* And completion entry */
462
TimezoneCompletion * completion = timezone_completion_new ();
463
gtk_entry_set_completion (GTK_ENTRY (WIG ("timezoneEntry")),
464
GTK_ENTRY_COMPLETION (completion));
465
timezone_completion_watch_entry (completion, GTK_ENTRY (WIG ("timezoneEntry")));
466
g_signal_connect (completion, "match-selected", G_CALLBACK (timezone_selected), NULL);
468
/* Set up settings bindings */
469
g_settings_bind (conf, SETTINGS_SHOW_CLOCK_S, WIG ("showClockCheck"),
470
"active", G_SETTINGS_BIND_DEFAULT);
471
g_settings_bind (conf, SETTINGS_SHOW_DAY_S, WIG ("showWeekdayCheck"),
472
"active", G_SETTINGS_BIND_DEFAULT);
473
g_settings_bind (conf, SETTINGS_SHOW_DATE_S, WIG ("showDateTimeCheck"),
474
"active", G_SETTINGS_BIND_DEFAULT);
475
g_settings_bind (conf, SETTINGS_SHOW_SECONDS_S, WIG ("showSecondsCheck"),
476
"active", G_SETTINGS_BIND_DEFAULT);
477
g_settings_bind_with_mapping (conf, SETTINGS_TIME_FORMAT_S,
478
WIG ("show12HourRadio"), "active",
479
G_SETTINGS_BIND_DEFAULT,
480
bind_hours_get, bind_hours_set,
481
GINT_TO_POINTER(TRUE), NULL);
482
g_settings_bind_with_mapping (conf, SETTINGS_TIME_FORMAT_S,
483
WIG ("show24HourRadio"), "active",
484
G_SETTINGS_BIND_DEFAULT,
485
bind_hours_get, bind_hours_set,
486
GINT_TO_POINTER(FALSE), NULL);
487
g_settings_bind (conf, SETTINGS_SHOW_CALENDAR_S, WIG ("showCalendarCheck"),
488
"active", G_SETTINGS_BIND_DEFAULT);
489
g_settings_bind (conf, SETTINGS_SHOW_WEEK_NUMBERS_S, WIG ("includeWeekNumbersCheck"),
490
"active", G_SETTINGS_BIND_DEFAULT);
491
g_settings_bind (conf, SETTINGS_SHOW_EVENTS_S, WIG ("showEventsCheck"),
492
"active", G_SETTINGS_BIND_DEFAULT);
493
g_settings_bind (conf, SETTINGS_SHOW_LOCATIONS_S, WIG ("showLocationsCheck"),
494
"active", G_SETTINGS_BIND_DEFAULT);
496
/* Set up sensitivities */
497
add_widget_dependency (WIG ("showCalendarCheck"), WIG ("calendarOptions"));
498
add_widget_dependency (WIG ("showClockCheck"), WIG ("clockOptions"));
499
add_widget_dependency (WIG ("showLocationsCheck"), WIG ("locationsButton"));
500
add_widget_dependency (WIG ("manualTimeRadio"), WIG ("manualOptions"));
501
add_polkit_dependency (polkit_button, WIG ("timeDateOptions"));
503
/* Hacky proxy test for whether evolution-data-server is installed */
504
gchar * evo_path = g_find_program_in_path ("evolution");
505
gtk_widget_set_sensitive (WIG ("showEventsCheck"), (evo_path != NULL));
508
setup_time_spinner (WIG ("timeSpinner"), WIG ("dateSpinner"), TRUE);
509
setup_time_spinner (WIG ("dateSpinner"), WIG ("timeSpinner"), FALSE);
511
GtkWidget * dlg = WIG ("timeDateDialog");
512
auto_radio = WIG ("automaticTimeRadio");
513
tz_entry = WIG ("timezoneEntry");
515
g_signal_connect (WIG ("locationsButton"), "clicked", G_CALLBACK (show_locations), dlg);
517
/* Grab proxy for settings daemon */
518
g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, NULL,
519
"org.gnome.SettingsDaemon.DateTimeMechanism",
521
"org.gnome.SettingsDaemon.DateTimeMechanism",
522
NULL, proxy_ready, NULL);
526
g_object_unref (conf);
527
g_object_unref (builder);
532
static UniqueResponse
533
message_received (UniqueApp * app, gint command, UniqueMessageData *message_data,
534
guint time, gpointer user_data)
536
if (command == UNIQUE_ACTIVATE) {
537
gtk_window_present_with_time (GTK_WINDOW (user_data), time);
538
return UNIQUE_RESPONSE_OK;
540
return UNIQUE_RESPONSE_PASSTHROUGH;
544
main (int argc, char ** argv)
548
/* Setting up i18n and gettext. Apparently, we need
550
setlocale (LC_ALL, "");
551
bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
552
textdomain (GETTEXT_PACKAGE);
554
gtk_init (&argc, &argv);
556
UniqueApp * app = unique_app_new ("com.canonical.indicator.datetime.preferences", NULL);
558
if (unique_app_is_running (app)) {
559
unique_app_send_message (app, UNIQUE_ACTIVATE, NULL);
561
// We're first instance. Yay!
562
GtkWidget * dlg = create_dialog ();
564
g_signal_connect (app, "message-received", G_CALLBACK(message_received), dlg);
565
unique_app_watch_window (app, GTK_WINDOW (dlg));
567
gtk_widget_show_all (dlg);
568
g_signal_connect (dlg, "response", G_CALLBACK(gtk_main_quit), NULL);