/* time-admin
* Copyright (C) 2018 zhuyaliang https://github.com/zhuyaliang/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY 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 .
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "time-zone.h"
#include "time-map.h"
#include "time-tool.h"
#include
#define MATE_DESKTOP_USE_UNSTABLE_API
#include
#include
#include
#include
#include
#include
#define DEFAULT_TZ "Europe/London"
#define BACKFILE "/usr/share/mate-time-admin/map/backward"
static void LocationChanged(TimezoneMap *map,
TzLocation *location,TimeAdmin *ta);
enum {
CITY_COL_CITY_HUMAN_READABLE,
CITY_COL_ZONE,
CITY_NUM_COLS
};
static gchar *tz_data_file_get (void)
{
gchar *file;
file = g_strdup (TZ_DATA_FILE);
return file;
}
static float convert_pos (gchar *pos, int digits)
{
gchar whole[10];
gchar *fraction;
gint i;
float t1, t2;
if (!pos || strlen(pos) < 4 || digits > 9) return 0.0;
for (i = 0; i < digits + 1; i++) whole[i] = pos[i];
whole[i] = '\0';
fraction = pos + digits + 1;
t1 = g_strtod (whole, NULL);
t2 = g_strtod (fraction, NULL);
if (t1 >= 0.0) return t1 + t2/pow (10.0, strlen(fraction));
else return t1 - t2/pow (10.0, strlen(fraction));
}
static int compare_country_names (const void *a, const void *b)
{
const TzLocation *tza = * (TzLocation **) a;
const TzLocation *tzb = * (TzLocation **) b;
return strcmp (tza->zone, tzb->zone);
}
static void sort_locations_by_country (GPtrArray *locations)
{
qsort (locations->pdata, locations->len, sizeof (gpointer),
compare_country_names);
}
static void load_backward_tz (TzDB *tz_db)
{
FILE *fp;
char buf[128] = { 0 };
tz_db->backward = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
fp = fopen(BACKFILE,"r");
if(fp == NULL)
{
g_error("%s does not exist\r\n",BACKFILE);
}
while(fgets(buf,128,fp))
{
g_auto(GStrv) items = NULL;
guint j;
char *real, *alias;
if (g_ascii_strncasecmp (buf, "Link\t", 5) != 0)
continue;
items = g_strsplit (buf, "\t", -1);
real = NULL;
alias = NULL;
for (j = 1; items[j] != NULL; j++)
{
if (items[j][0] == '\0')
continue;
if (real == NULL)
{
real = items[j];
continue;
}
alias = items[j];
break;
}
if (real == NULL || alias == NULL)
g_warning ("Could not parse line: %s", buf);
/* We don't need more than one name for it */
if (g_str_equal (real, "Etc/UTC") ||
g_str_equal (real, "Etc/UCT"))
real = "Etc/GMT";
g_hash_table_insert (tz_db->backward, g_strdup (alias), g_strdup (real));
}
fclose(fp);
}
TzDB *tz_load_db (void)
{
g_autofree gchar *tz_data_file = NULL;
TzDB *tz_db;
FILE *tzfile;
char buf[4096];
tz_data_file = tz_data_file_get ();
if (!tz_data_file)
{
g_warning ("Could not get the TimeZone data file name");
return NULL;
}
tzfile = fopen (tz_data_file, "r");
if (!tzfile)
{
g_warning ("Could not open *%s*\n", tz_data_file);
return NULL;
}
tz_db = g_new0 (TzDB, 1);
tz_db->locations = g_ptr_array_new ();
while (fgets (buf, sizeof(buf), tzfile))
{
g_auto(GStrv) tmpstrarr = NULL;
g_autofree gchar *latstr = NULL;
g_autofree gchar *lngstr = NULL;
gchar *p;
TzLocation *loc;
if (*buf == '#') continue;
g_strchomp(buf);
tmpstrarr = g_strsplit(buf,"\t", 6);
latstr = g_strdup (tmpstrarr[1]);
p = latstr + 1;
while (*p != '-' && *p != '+') p++;
lngstr = g_strdup (p);
*p = '\0';
loc = g_new0 (TzLocation, 1);
loc->country = g_strdup (tmpstrarr[0]);
loc->zone = g_strdup (tmpstrarr[2]);
loc->latitude = convert_pos (latstr, 2);
loc->longitude = convert_pos (lngstr, 3);
#ifdef __sun
if (tmpstrarr[3] && *tmpstrarr[3] == '-' && tmpstrarr[4])
loc->comment = g_strdup (tmpstrarr[4]);
if (tmpstrarr[3] && *tmpstrarr[3] != '-' && !islower(loc->zone)) {
TzLocation *locgrp;
locgrp = g_new0 (TzLocation, 1);
locgrp->country = g_strdup (tmpstrarr[0]);
locgrp->zone = g_strdup (tmpstrarr[3]);
locgrp->latitude = convert_pos (latstr, 2);
locgrp->longitude = convert_pos (lngstr, 3);
locgrp->comment = (tmpstrarr[4]) ? g_strdup (tmpstrarr[4]) : NULL;
g_ptr_array_add (tz_db->locations, (gpointer) locgrp);
}
#else
loc->comment = (tmpstrarr[3]) ? g_strdup(tmpstrarr[3]) : NULL;
#endif
g_ptr_array_add (tz_db->locations, (gpointer) loc);
}
fclose (tzfile);
/* now sort by country */
sort_locations_by_country (tz_db->locations);
/* Load up the hashtable of backward links */
load_backward_tz (tz_db);
return tz_db;
}
static GtkWidget *GetTimeZoneMap(TimeAdmin *ta)
{
GtkWidget *map;
g_autoptr(GtkEntryCompletion) completion = NULL;
map = (GtkWidget *) timezone_map_new ();
g_signal_connect (map,
"location-changed",
G_CALLBACK (LocationChanged),
ta);
completion = gtk_entry_completion_new ();
gtk_entry_set_completion (GTK_ENTRY (ta->TimezoneEntry), completion);
gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (ta->CityListStore));
gtk_entry_completion_set_text_column (completion, CITY_COL_CITY_HUMAN_READABLE);
return map;
}
static char *
translated_city_name (TzLocation *loc)
{
g_autofree gchar *zone_translated = NULL;
g_auto(GStrv) split_translated = NULL;
g_autofree gchar *country = NULL;
gchar *name;
gint length;
zone_translated = g_strdup (_(loc->zone));
g_strdelimit (zone_translated, "_", ' ');
split_translated = g_regex_split_simple ("[\\x{2044}\\x{2215}\\x{29f8}\\x{ff0f}/]",
zone_translated,
0, 0);
length = g_strv_length (split_translated);
country = mate_get_country_from_code (loc->country, NULL);
name = g_strdup_printf (C_("timezone loc", "%s, %s"),
split_translated[length-1],
country);
return name;
}
static void
update_timezone (TimezoneMap *map)
{
g_autofree gchar *bubble_text = NULL;
g_autoptr(GDateTime) current_date = NULL;
g_autoptr(GTimeZone) timezone = NULL;
g_autofree gchar *city_country = NULL;
g_autofree gchar *utc_label = NULL;
g_autofree gchar *time_label = NULL;
g_autofree gchar *tz_desc = NULL;
TzLocation *current_location;
GDateTime *date;
current_location = timezone_map_get_location (TIMEZONEMAP (map));
city_country = translated_city_name (current_location);
timezone = g_time_zone_new (current_location->zone);
current_date = g_date_time_new_now_local ();
date = g_date_time_to_timezone (current_date, timezone);
utc_label = g_date_time_format (date, _("UTC%:::z"));
tz_desc = g_strdup_printf ( "%s (%s)",
g_date_time_get_timezone_abbreviation (date),
utc_label);
time_label = g_date_time_format (date, _("%R"));
bubble_text = g_strdup_printf ("%s\n"
"%s\n"
"%s",
tz_desc,
city_country,
time_label);
timezone_map_set_bubble_text (TIMEZONEMAP (map), bubble_text);
g_date_time_unref(date);
}
static void LocationChanged(TimezoneMap *map,
TzLocation *location,
TimeAdmin *ta)
{
update_timezone (map);
}
static void
get_initial_timezone (TimeAdmin *ta)
{
const gchar *timezone;
timezone = GetTimeZone(ta);
if (timezone == NULL ||
!timezone_map_set_timezone (TIMEZONEMAP (ta->map), timezone))
{
g_warning ("Timezone '%s' is unhandled,setting %s as default", timezone ? timezone : "(null)", DEFAULT_TZ);
timezone_map_set_timezone (TIMEZONEMAP (ta->map), DEFAULT_TZ);
}
update_timezone (TIMEZONEMAP(ta->map));
}
static void LoadCities (TzLocation *loc,
GtkListStore *CityStore)
{
g_autofree gchar *human_readable = NULL;
human_readable = translated_city_name (loc);
gtk_list_store_insert_with_values (CityStore,
NULL,
0,
CITY_COL_CITY_HUMAN_READABLE,
human_readable,
CITY_COL_ZONE,
loc->zone,
-1);
}
static void CreateCityList(TimeAdmin *ta)
{
g_autoptr(TzDB) db = NULL;
ta->CityListStore = gtk_list_store_new (CITY_NUM_COLS,G_TYPE_STRING,G_TYPE_STRING);
db = tz_load_db ();
g_ptr_array_foreach (db->locations, (GFunc) LoadCities, ta->CityListStore);
}
static GtkWidget *CreateZoneFrame(TimeAdmin *ta)
{
GtkWidget *TimeZoneFrame;
TimeZoneFrame = gtk_frame_new (_("Time Zone"));
gtk_widget_set_size_request(TimeZoneFrame,300,200);
gtk_frame_set_shadow_type(GTK_FRAME(TimeZoneFrame),GTK_SHADOW_NONE);
return TimeZoneFrame;
}
static GtkWidget *CreateZoneScrolled(TimeAdmin *ta)
{
GtkWidget *Scrolled;
Scrolled = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (Scrolled),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (Scrolled),
GTK_SHADOW_IN);
return Scrolled;
}
static void CreateZoneEntry(TimeAdmin *ta)
{
GtkWidget *hbox;
ta->TimezoneEntry = gtk_search_entry_new ();
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
gtk_widget_set_halign (hbox, GTK_ALIGN_CENTER);
gtk_box_pack_start (GTK_BOX (hbox), ta->TimezoneEntry, FALSE, FALSE, 0);
ta->SearchBar = gtk_search_bar_new ();
gtk_search_bar_connect_entry (GTK_SEARCH_BAR (ta->SearchBar),
GTK_ENTRY (ta->TimezoneEntry));
gtk_search_bar_set_show_close_button (GTK_SEARCH_BAR (ta->SearchBar),FALSE);
gtk_container_add (GTK_CONTAINER (ta->SearchBar), hbox);
gtk_search_bar_set_search_mode(GTK_SEARCH_BAR(ta->SearchBar),TRUE);
}
static gboolean CityChanged(GtkEntryCompletion *completion,
GtkTreeModel *model,
GtkTreeIter *iter,
TimeAdmin *self)
{
GtkWidget *entry;
g_autofree gchar *zone = NULL;
gtk_tree_model_get (model,
iter,
CITY_COL_ZONE,
&zone,
-1);
timezone_map_set_timezone (TIMEZONEMAP (self->map), zone);
entry = gtk_entry_completion_get_entry (completion);
gtk_entry_set_text (GTK_ENTRY (entry), "");
return TRUE;
}
static void ChoooseTimezoneDone (GtkWidget *widget,
TimeAdmin *ta)
{
TimezoneMap *map;
g_autofree gchar *ZoneCity = NULL;
map = TIMEZONEMAP(ta->map);
SetTimeZone(ta->proxy,map->location->zone);
ZoneCity = translated_city_name(map->location);
gtk_button_set_label((GTK_BUTTON(ta->TimeZoneButton)),ZoneCity);
gtk_widget_hide_on_delete(GTK_WIDGET(ta->dialog));
}
static void ChoooseTimezoneClose(GtkWidget *widget,
TimeAdmin *ta)
{
gtk_widget_hide_on_delete(GTK_WIDGET(ta->dialog));
}
void SetupTimezoneDialog(TimeAdmin *ta)
{
GtkWidget *Vbox, *TimeZoneFrame, *Scrolled, *image;
ta->dialog = gtk_dialog_new_with_buttons (_("Time Zone Selection"),
NULL,
GTK_DIALOG_DESTROY_WITH_PARENT,
NULL,
NULL);
gtk_window_set_default_size (GTK_WINDOW (ta->dialog), 730, 520);
ta->TZclose = gtk_button_new_with_mnemonic (_("_Close"));
image = gtk_image_new_from_icon_name ("window-close", GTK_ICON_SIZE_BUTTON);
gtk_button_set_image (GTK_BUTTON (ta->TZclose), image);
gtk_button_set_use_underline (GTK_BUTTON (ta->TZclose), TRUE);
gtk_style_context_add_class (gtk_widget_get_style_context (ta->TZclose), "text-button");
gtk_widget_set_can_default (ta->TZclose, TRUE);
gtk_dialog_add_action_widget (GTK_DIALOG (ta->dialog), ta->TZclose, GTK_RESPONSE_CANCEL);
ta->TZconfire = gtk_button_new_with_mnemonic (_("Con_firm"));
image = gtk_image_new_from_icon_name ("emblem-default", GTK_ICON_SIZE_BUTTON);
gtk_button_set_image (GTK_BUTTON (ta->TZconfire), image);
gtk_button_set_use_underline (GTK_BUTTON (ta->TZconfire), TRUE);
gtk_style_context_add_class (gtk_widget_get_style_context (ta->TZconfire), "text-button");
gtk_widget_set_can_default (ta->TZconfire, TRUE);
gtk_dialog_add_action_widget (GTK_DIALOG (ta->dialog), ta->TZconfire, GTK_RESPONSE_OK);
g_signal_connect (ta->TZconfire,
"clicked",
G_CALLBACK (ChoooseTimezoneDone),
ta);
g_signal_connect (ta->TZclose,
"clicked",
G_CALLBACK (ChoooseTimezoneClose),
ta);
Vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_style_context_add_class (gtk_widget_get_style_context (Vbox), "linked");
TimeZoneFrame = CreateZoneFrame(ta);
Scrolled = CreateZoneScrolled(ta);
gtk_container_add (GTK_CONTAINER (TimeZoneFrame), Scrolled);
CreateCityList(ta);
CreateZoneEntry(ta);
gtk_box_pack_start (GTK_BOX (Vbox), ta->SearchBar,FALSE,FALSE, 0);
ta->map = GetTimeZoneMap(ta);
gtk_widget_show (ta->map);
gtk_container_add (GTK_CONTAINER (Scrolled),ta->map);
gtk_box_pack_start(GTK_BOX(Vbox),TimeZoneFrame,TRUE,TRUE,10);
get_initial_timezone(ta);
g_signal_connect(gtk_entry_get_completion (GTK_ENTRY (ta->TimezoneEntry)),
"match-selected",
G_CALLBACK (CityChanged),
ta);
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (ta->dialog))),
Vbox,
TRUE,
TRUE, 8);
}
void tz_info_free (TzInfo *tzinfo)
{
g_return_if_fail (tzinfo != NULL);
if (tzinfo->tzname_normal) g_free (tzinfo->tzname_normal);
if (tzinfo->tzname_daylight) g_free (tzinfo->tzname_daylight);
g_free (tzinfo);
}
struct {
const char *orig;
const char *dest;
} aliases[] = {
{ "Asia/Istanbul", "Europe/Istanbul" }, /* Istanbul is in both Europe and Asia */
{ "Europe/Nicosia", "Asia/Nicosia" }, /* Ditto */
{ "EET", "Europe/Istanbul" }, /* Same tz as the 2 above */
{ "HST", "Pacific/Honolulu" },
{ "WET", "Europe/Brussels" }, /* Other name for the mainland Europe tz */
{ "CET", "Europe/Brussels" }, /* ditto */
{ "MET", "Europe/Brussels" },
{ "Etc/Zulu", "Etc/GMT" },
{ "Etc/UTC", "Etc/GMT" },
{ "GMT", "Etc/GMT" },
{ "Greenwich", "Etc/GMT" },
{ "Etc/UCT", "Etc/GMT" },
{ "Etc/GMT0", "Etc/GMT" },
{ "Etc/GMT+0", "Etc/GMT" },
{ "Etc/GMT-0", "Etc/GMT" },
{ "Etc/Universal", "Etc/GMT" },
{ "PST8PDT", "America/Los_Angeles" }, /* Other name for the Atlantic tz */
{ "EST", "America/New_York" }, /* Other name for the Eastern tz */
{ "EST5EDT", "America/New_York" }, /* ditto */
{ "CST6CDT", "America/Chicago" }, /* Other name for the Central tz */
{ "MST", "America/Denver" }, /* Other name for the mountain tz */
{ "MST7MDT", "America/Denver" }, /* ditto */
};
static gboolean
compare_timezones (const char *a,
const char *b)
{
if (g_str_equal (a, b))
return TRUE;
if (strchr (b, '/') == NULL) {
g_autofree gchar *prefixed = NULL;
prefixed = g_strdup_printf ("/%s", b);
if (g_str_has_suffix (a, prefixed))
return TRUE;
}
return FALSE;
}
char *tz_info_get_clean_name (TzDB *tz_db,
const char *tz)
{
char *ret;
const char *timezone;
guint i;
gboolean replaced;
/* Remove useless prefixes */
if (g_str_has_prefix (tz, "right/"))
tz = tz + strlen ("right/");
else if (g_str_has_prefix (tz, "posix/"))
tz = tz + strlen ("posix/");
/* Here start the crazies */
replaced = FALSE;
for (i = 0; i < G_N_ELEMENTS (aliases); i++) {
if (compare_timezones (tz, aliases[i].orig)) {
replaced = TRUE;
timezone = aliases[i].dest;
break;
}
}
/* Try again! */
if (!replaced) {
/* Ignore crazy solar times from the '80s */
if (g_str_has_prefix (tz, "Asia/Riyadh") ||
g_str_has_prefix (tz, "Mideast/Riyadh")) {
timezone = "Asia/Riyadh";
replaced = TRUE;
}
}
if (!replaced)
timezone = tz;
ret = g_hash_table_lookup (tz_db->backward, timezone);
if (ret == NULL)
return g_strdup (timezone);
return g_strdup (ret);
}
TzInfo *tz_info_from_location (TzLocation *loc)
{
TzInfo *tzinfo;
time_t curtime;
struct tm *curzone;
g_autofree gchar *tz_env_value = NULL;
g_return_val_if_fail (loc != NULL, NULL);
g_return_val_if_fail (loc->zone != NULL, NULL);
tz_env_value = g_strdup (getenv ("TZ"));
setenv ("TZ", loc->zone, 1);
#if 0
tzset ();
#endif
tzinfo = g_new0 (TzInfo, 1);
curtime = time (NULL);
curzone = localtime (&curtime);
#ifndef __sun
tzinfo->tzname_normal = g_strdup (curzone->tm_zone);
if (curzone->tm_isdst)
tzinfo->tzname_daylight =
g_strdup (&curzone->tm_zone[curzone->tm_isdst]);
else
tzinfo->tzname_daylight = NULL;
tzinfo->utc_offset = curzone->tm_gmtoff;
#else
tzinfo->tzname_normal = NULL;
tzinfo->tzname_daylight = NULL;
tzinfo->utc_offset = 0;
#endif
tzinfo->daylight = curzone->tm_isdst;
if (tz_env_value)
setenv ("TZ", tz_env_value, 1);
else
unsetenv ("TZ");
return tzinfo;
}
glong tz_location_get_utc_offset (TzLocation *loc)
{
g_autoptr(TzInfo) tz_info = NULL;
glong offset;
tz_info = tz_info_from_location (loc);
offset = tz_info->utc_offset;
return offset;
}
void RunTimeZoneDialog (GtkButton *button,
gpointer data)
{
TimeAdmin *ta = (TimeAdmin *)data;
gtk_widget_show_all(GTK_WIDGET(ta->dialog));
}
static void
tz_location_free (TzLocation *loc, gpointer data)
{
g_free (loc->country);
g_free (loc->zone);
g_free (loc->comment);
g_free (loc);
}
void TimeZoneDateBaseFree (TzDB *db)
{
g_ptr_array_foreach (db->locations, (GFunc) tz_location_free, NULL);
g_ptr_array_free (db->locations, TRUE);
g_hash_table_destroy (db->backward);
g_free (db);
}
GPtrArray *tz_get_locations (TzDB *db)
{
return db->locations;
}