diff options
Diffstat (limited to 'src/indicator-datetime.c')
-rw-r--r-- | src/indicator-datetime.c | 488 |
1 files changed, 458 insertions, 30 deletions
diff --git a/src/indicator-datetime.c b/src/indicator-datetime.c index 13d22e3..7034fb8 100644 --- a/src/indicator-datetime.c +++ b/src/indicator-datetime.c @@ -129,6 +129,12 @@ static void indicator_datetime_dispose (GObject *object); static void indicator_datetime_finalize (GObject *object); static GtkLabel * get_label (IndicatorObject * io); static GtkMenu * get_menu (IndicatorObject * io); +static GVariant * bind_enum_set (const GValue * value, const GVariantType * type, gpointer user_data); +static gboolean bind_enum_get (GValue * value, GVariant * variant, gpointer user_data); +static gchar * generate_format_string (IndicatorDatetime * self); +static struct tm * update_label (IndicatorDatetime * io); +static void guess_label_size (IndicatorDatetime * self); +static void setup_timer (IndicatorDatetime * self, struct tm * ltime); /* Indicator Module Config */ INDICATOR_SET_VERSION @@ -219,11 +225,14 @@ indicator_datetime_init (IndicatorDatetime *self) self->priv->settings = g_settings_new(SETTINGS_INTERFACE); if (self->priv->settings != NULL) { - g_settings_bind(self->priv->settings, + g_settings_bind_with_mapping(self->priv->settings, SETTINGS_TIME_FORMAT_S, self, PROP_TIME_FORMAT_S, - G_SETTINGS_BIND_DEFAULT); + G_SETTINGS_BIND_DEFAULT, + bind_enum_get, + bind_enum_set, + NULL, NULL); /* Userdata and destroy func */ g_settings_bind(self->priv->settings, SETTINGS_SHOW_SECONDS_S, self, @@ -311,15 +320,162 @@ indicator_datetime_finalize (GObject *object) return; } +/* Turns the int value into a string GVariant */ +static GVariant * +bind_enum_set (const GValue * value, const GVariantType * type, gpointer user_data) +{ + switch (g_value_get_int(value)) { + case SETTINGS_TIME_LOCALE: + return g_variant_new_string("locale-default"); + case SETTINGS_TIME_12_HOUR: + return g_variant_new_string("12-hour"); + case SETTINGS_TIME_24_HOUR: + return g_variant_new_string("24-hour"); + case SETTINGS_TIME_CUSTOM: + return g_variant_new_string("custom"); + default: + return NULL; + } +} + +/* Turns a string GVariant into an int value */ +static gboolean +bind_enum_get (GValue * value, GVariant * variant, gpointer user_data) +{ + const gchar * str = g_variant_get_string(variant, NULL); + gint output = 0; + + if (g_strcmp0(str, "locale-default") == 0) { + output = SETTINGS_TIME_LOCALE; + } else if (g_strcmp0(str, "12-hour") == 0) { + output = SETTINGS_TIME_12_HOUR; + } else if (g_strcmp0(str, "24-hour") == 0) { + output = SETTINGS_TIME_24_HOUR; + } else if (g_strcmp0(str, "custom") == 0) { + output = SETTINGS_TIME_CUSTOM; + } else { + return FALSE; + } + + g_value_set_int(value, output); + return TRUE; +} + +/* Sets a property on the object */ static void set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { + IndicatorDatetime * self = INDICATOR_DATETIME(object); + gboolean update = FALSE; + + switch(prop_id) { + case PROP_TIME_FORMAT: { + gint newval = g_value_get_int(value); + if (newval != self->priv->time_mode) { + update = TRUE; + self->priv->time_mode = newval; + } + break; + } + case PROP_SHOW_SECONDS: + if (g_value_get_boolean(value) != self->priv->show_seconds) { + self->priv->show_seconds = !self->priv->show_seconds; + if (self->priv->time_mode != SETTINGS_TIME_CUSTOM) { + update = TRUE; + setup_timer(self, NULL); + } + } + break; + case PROP_SHOW_DAY: + if (g_value_get_boolean(value) != self->priv->show_day) { + self->priv->show_day = !self->priv->show_day; + if (self->priv->time_mode != SETTINGS_TIME_CUSTOM) { + update = TRUE; + } + } + break; + case PROP_SHOW_DATE: + if (g_value_get_boolean(value) != self->priv->show_date) { + self->priv->show_date = !self->priv->show_date; + if (self->priv->time_mode != SETTINGS_TIME_CUSTOM) { + update = TRUE; + } + } + break; + case PROP_CUSTOM_TIME_FORMAT: { + const gchar * newstr = g_value_get_string(value); + if (g_strcmp0(newstr, self->priv->custom_string) != 0) { + if (self->priv->custom_string != NULL) { + g_free(self->priv->custom_string); + self->priv->custom_string = NULL; + } + self->priv->custom_string = g_strdup(newstr); + if (self->priv->time_mode == SETTINGS_TIME_CUSTOM) { + update = TRUE; + setup_timer(self, NULL); + } + } + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + return; + } + + if (!update) { + return; + } + + /* Get the new format string */ + gchar * newformat = generate_format_string(self); + + /* check to ensure the format really changed */ + if (g_strcmp0(self->priv->time_string, newformat) == 0) { + g_free(newformat); + return; + } + + /* Okay now process the change */ + if (self->priv->time_string != NULL) { + g_free(self->priv->time_string); + self->priv->time_string = NULL; + } + self->priv->time_string = newformat; + + /* And update everything */ + update_label(self); + guess_label_size(self); + return; } +/* Gets a property from the object */ static void get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { + IndicatorDatetime * self = INDICATOR_DATETIME(object); + + switch(prop_id) { + case PROP_TIME_FORMAT: + g_value_set_int(value, self->priv->time_mode); + break; + case PROP_SHOW_SECONDS: + g_value_set_boolean(value, self->priv->show_seconds); + break; + case PROP_SHOW_DAY: + g_value_set_boolean(value, self->priv->show_day); + break; + case PROP_SHOW_DATE: + g_value_set_boolean(value, self->priv->show_date); + break; + case PROP_CUSTOM_TIME_FORMAT: + g_value_set_string(value, self->priv->custom_string); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + return; + } + return; } @@ -346,12 +502,12 @@ idle_measure (gpointer data) } /* Updates the label to be the current time. */ -static void +static struct tm * update_label (IndicatorDatetime * io) { IndicatorDatetime * self = INDICATOR_DATETIME(io); - if (self->priv->label == NULL) return; + if (self->priv->label == NULL) return NULL; gchar longstr[128]; time_t t; @@ -362,7 +518,7 @@ update_label (IndicatorDatetime * io) if (ltime == NULL) { g_debug("Error getting local time"); gtk_label_set_label(self->priv->label, _("Error getting time")); - return; + return NULL; } strftime(longstr, 128, self->priv->time_string, ltime); @@ -375,24 +531,43 @@ update_label (IndicatorDatetime * io) self->priv->idle_measure = g_idle_add(idle_measure, io); } - return; + return ltime; } /* Runs every minute and updates the time */ gboolean -minute_timer_func (gpointer user_data) +timer_func (gpointer user_data) { IndicatorDatetime * self = INDICATOR_DATETIME(user_data); + self->priv->timer = 0; + struct tm * ltime = update_label(self); + setup_timer(self, ltime); + return FALSE; +} - if (self->priv->label != NULL) { - update_label(self); - return TRUE; - } else { +/* Configure the timer to run the next time through */ +static void +setup_timer (IndicatorDatetime * self, struct tm * ltime) +{ + if (self->priv->timer != 0) { + g_source_remove(self->priv->timer); self->priv->timer = 0; - return FALSE; } + + if (self->priv->show_seconds) { + self->priv->timer = g_timeout_add_seconds(1, timer_func, self); + } else { + if (ltime == NULL) { + time_t t; + t = time(NULL); + ltime = localtime(&t); + } - return FALSE; + /* Plus 2 so we're just after the minute, don't want to be early. */ + self->priv->timer = g_timeout_add_seconds(60 - ltime->tm_sec + 2, timer_func, self); + } + + return; } /* Does a quick meausre of how big the string is in @@ -410,33 +585,203 @@ measure_string (GtkStyle * style, PangoContext * context, const gchar * string) return width; } +/* Format for the table of strftime() modifiers to what + we need to check when determining the length */ +typedef struct _strftime_type_t strftime_type_t; +struct _strftime_type_t { + char character; + gint mask; +}; + +enum { + STRFTIME_MASK_NONE = 0, /* Hours or minutes as we always test those */ + STRFTIME_MASK_SECONDS = 1 << 0, /* Seconds count */ + STRFTIME_MASK_AMPM = 1 << 1, /* AM/PM counts */ + STRFTIME_MASK_WEEK = 1 << 2, /* Day of the week maters (Sat, Sun, etc.) */ + STRFTIME_MASK_DAY = 1 << 3, /* Day of the month counts (Feb 1st) */ + STRFTIME_MASK_MONTH = 1 << 4, /* Which month matters */ + STRFTIME_MASK_YEAR = 1 << 5, /* Which year matters */ + /* Last entry, combines all previous */ + STRFTIME_MASK_ALL = (STRFTIME_MASK_SECONDS | STRFTIME_MASK_AMPM | STRFTIME_MASK_WEEK | STRFTIME_MASK_DAY | STRFTIME_MASK_MONTH | STRFTIME_MASK_YEAR) +}; + +/* A table taken from the man page of strftime to what the different + characters can effect. These are worst case in that we need to + test the length based on all these things to ensure that we have + a reasonable string lenght measurement. */ +const static strftime_type_t strftime_type[] = { + {'a', STRFTIME_MASK_WEEK}, + {'A', STRFTIME_MASK_WEEK}, + {'b', STRFTIME_MASK_MONTH}, + {'B', STRFTIME_MASK_MONTH}, + {'c', STRFTIME_MASK_ALL}, /* We don't know, so we have to assume all */ + {'C', STRFTIME_MASK_YEAR}, + {'d', STRFTIME_MASK_MONTH}, + {'D', STRFTIME_MASK_MONTH | STRFTIME_MASK_YEAR | STRFTIME_MASK_DAY}, + {'e', STRFTIME_MASK_DAY}, + {'F', STRFTIME_MASK_MONTH | STRFTIME_MASK_YEAR | STRFTIME_MASK_DAY}, + {'G', STRFTIME_MASK_YEAR}, + {'g', STRFTIME_MASK_YEAR}, + {'h', STRFTIME_MASK_MONTH}, + {'j', STRFTIME_MASK_DAY}, + {'m', STRFTIME_MASK_MONTH}, + {'p', STRFTIME_MASK_AMPM}, + {'P', STRFTIME_MASK_AMPM}, + {'r', STRFTIME_MASK_AMPM}, + {'s', STRFTIME_MASK_SECONDS}, + {'S', STRFTIME_MASK_SECONDS}, + {'T', STRFTIME_MASK_SECONDS}, + {'u', STRFTIME_MASK_WEEK}, + {'U', STRFTIME_MASK_DAY | STRFTIME_MASK_MONTH}, + {'V', STRFTIME_MASK_DAY | STRFTIME_MASK_MONTH}, + {'w', STRFTIME_MASK_DAY}, + {'W', STRFTIME_MASK_DAY | STRFTIME_MASK_MONTH}, + {'x', STRFTIME_MASK_YEAR | STRFTIME_MASK_MONTH | STRFTIME_MASK_DAY | STRFTIME_MASK_WEEK}, + {'X', STRFTIME_MASK_SECONDS}, + {'y', STRFTIME_MASK_YEAR}, + {'Y', STRFTIME_MASK_YEAR}, + /* Last one */ + {0, 0} +}; + #define FAT_NUMBER 8 +/* Looks through the characters in the format string to + ensure that we can figure out which of the things we + need to check in determining the length. */ +static gint +generate_strftime_bitmask (IndicatorDatetime * self) +{ + gint retval = 0; + glong strlength = g_utf8_strlen(self->priv->time_string, -1); + gint i; + g_debug("Evaluating bitmask for '%s'", self->priv->time_string); + + for (i = 0; i < strlength; i++) { + if (self->priv->time_string[i] == '%' && i + 1 < strlength) { + gchar evalchar = self->priv->time_string[i + 1]; + + /* If we're using alternate formats we need to skip those characters */ + if (evalchar == 'E' || evalchar == 'O') { + if (i + 2 < strlength) { + evalchar = self->priv->time_string[i + 2]; + } else { + continue; + } + } + + /* Let's look at that character in the table */ + int j; + for (j = 0; strftime_type[j].character != 0; j++) { + if (strftime_type[j].character == evalchar) { + retval |= strftime_type[j].mask; + break; + } + } + } + } + + return retval; +} + +/* Build an array up of all the time values that we want to check + for length to ensure we're in a good place */ +static void +build_timeval_array (GArray * timevals, gint mask) +{ + struct tm mytm = {0}; + + /* Sun 12/28/8888 00:00 */ + mytm.tm_hour = 0; + mytm.tm_mday = 28; + mytm.tm_mon = 11; + mytm.tm_year = 8888 - 1900; + mytm.tm_wday = 0; + mytm.tm_yday = 363; + g_array_append_val(timevals, mytm); + + if (mask & STRFTIME_MASK_AMPM) { + /* Sun 12/28/8888 12:00 */ + mytm.tm_hour = 12; + g_array_append_val(timevals, mytm); + } + + /* NOTE: Ignoring year 8888 should handle it */ + + if (mask & STRFTIME_MASK_MONTH) { + gint oldlen = timevals->len; + gint i, j; + for (i = 0; i < oldlen; i++) { + for (j = 0; j < 11; j++) { + struct tm localval = g_array_index(timevals, struct tm, i); + localval.tm_mon = j; + /* Not sure if I need to adjust yday & wday, hope not */ + g_array_append_val(timevals, localval); + } + } + } + + /* Doing these together as it seems like just slightly more + coverage on the numerical days, but worth it. */ + if (mask & (STRFTIME_MASK_WEEK | STRFTIME_MASK_DAY)) { + gint oldlen = timevals->len; + gint i, j; + for (i = 0; i < oldlen; i++) { + for (j = 22; j < 28; j++) { + struct tm localval = g_array_index(timevals, struct tm, i); + + gint diff = 28 - j; + + localval.tm_mday = j; + localval.tm_wday = localval.tm_wday - diff; + if (localval.tm_wday < 0) { + localval.tm_wday += 7; + } + localval.tm_yday = localval.tm_yday - diff; + + g_array_append_val(timevals, localval); + } + } + } + + return; +} + /* Try to get a good guess at what a maximum width of the entire string would be. */ static void guess_label_size (IndicatorDatetime * self) { + /* This is during startup. */ + if (self->priv->label == NULL) return; + GtkStyle * style = gtk_widget_get_style(GTK_WIDGET(self->priv->label)); PangoContext * context = gtk_widget_get_pango_context(GTK_WIDGET(self->priv->label)); + gint * max_width = &(self->priv->max_width); + gint posibilitymask = generate_strftime_bitmask(self); + + /* Build the array of possibilities that we want to test */ + GArray * timevals = g_array_new(FALSE, TRUE, sizeof(struct tm)); + build_timeval_array(timevals, posibilitymask); + + g_debug("Checking against %d posible times", timevals->len); + gint check_time; + for (check_time = 0; check_time < timevals->len; check_time++) { + gchar longstr[128]; + strftime(longstr, 128, self->priv->time_string, &(g_array_index(timevals, struct tm, check_time))); + + gchar * utf8 = g_locale_to_utf8(longstr, -1, NULL, NULL, NULL); + gint length = measure_string(style, context, utf8); + g_free(utf8); + + if (length > *max_width) { + *max_width = length; + } + } - /* TRANSLATORS: This string is used for measuring the size of - the font used for showing the time and is not shown to the - user anywhere. */ - gchar * am_str = g_strdup_printf(_("%d%d:%d%d AM"), FAT_NUMBER, FAT_NUMBER, FAT_NUMBER, FAT_NUMBER); - gint am_width = measure_string(style, context, am_str); - g_free(am_str); - - /* TRANSLATORS: This string is used for measuring the size of - the font used for showing the time and is not shown to the - user anywhere. */ - gchar * pm_str = g_strdup_printf(_("%d%d:%d%d PM"), FAT_NUMBER, FAT_NUMBER, FAT_NUMBER, FAT_NUMBER); - gint pm_width = measure_string(style, context, pm_str); - g_free(pm_str); - - self->priv->max_width = MAX(am_width, pm_width); - gtk_widget_set_size_request(GTK_WIDGET(self->priv->label), self->priv->max_width, -1); + g_array_free(timevals, TRUE); + gtk_widget_set_size_request(GTK_WIDGET(self->priv->label), self->priv->max_width, -1); g_debug("Guessing max time width: %d", self->priv->max_width); return; @@ -454,6 +799,89 @@ style_changed (GtkWidget * widget, GtkStyle * oldstyle, gpointer data) return; } +/* Tries to figure out what our format string should be. Lots + of translator comments in here. */ +static gchar * +generate_format_string (IndicatorDatetime * self) +{ + if (self->priv->time_mode == SETTINGS_TIME_CUSTOM) { + return g_strdup(self->priv->custom_string); + } + + gboolean twelvehour = TRUE; + + if (self->priv->time_mode == SETTINGS_TIME_LOCALE) { + /* TRANSLATORS: This string is used to determine the default + clock style for your locale. If it is the string '12' then + the default will be a 12-hour clock using AM/PM string. If + it is '24' then it will be a 24-hour clock. Users may over + ride this setting so it's still important to translate the + other strings no matter how this is set. */ + const gchar * locale_default = _("12"); + + if (g_strcmp0(locale_default, "24") == 0) { + twelvehour = FALSE; + } + } else if (self->priv->time_mode == SETTINGS_TIME_24_HOUR) { + twelvehour = FALSE; + } + + const gchar * time_string = NULL; + if (twelvehour) { + if (self->priv->show_seconds) { + /* TRANSLATORS: A format string for the strftime function for + a clock showing 12-hour time with seconds. */ + time_string = _("%l:%M:%S %p"); + } else { + /* TRANSLATORS: A format string for the strftime function for + a clock showing 12-hour time. */ + time_string = _(DEFAULT_TIME_12_FORMAT); + } + } else { + if (self->priv->show_seconds) { + /* TRANSLATORS: A format string for the strftime function for + a clock showing 24-hour time with seconds. */ + time_string = _("%H:%M:%S"); + } else { + /* TRANSLATORS: A format string for the strftime function for + a clock showing 24-hour time. */ + time_string = _(DEFAULT_TIME_24_FORMAT); + } + } + + /* Checkpoint, let's not fail */ + g_return_val_if_fail(time_string != NULL, g_strdup(DEFAULT_TIME_FORMAT)); + + /* If there's no date or day let's just leave now and + not worry about the rest of this code */ + if (!self->priv->show_date && !self->priv->show_day) { + return g_strdup(time_string); + } + + const gchar * date_string = NULL; + if (self->priv->show_date && self->priv->show_day) { + /* TRANSLATORS: This is a format string passed to strftime to represent + the day of the week, the month and the day of the month. */ + date_string = _("%a %b %e"); + } else if (self->priv->show_date) { + /* TRANSLATORS: This is a format string passed to strftime to represent + the month and the day of the month. */ + date_string = _("%b %e"); + } else if (self->priv->show_day) { + /* TRANSLATORS: This is a format string passed to strftime to represent + the day of the week. */ + date_string = _("%a"); + } + + /* Check point, we should have a date string */ + g_return_val_if_fail(date_string != NULL, g_strdup(time_string)); + + /* TRANSLATORS: This is a format string passed to strftime to combine the + date and the time. The value of "%s, %s" would result in a string like + this in US English 12-hour time: 'Fri Jul 16, 11:50 AM' */ + return g_strdup_printf(_("%s, %s"), date_string, time_string); +} + /* Grabs the label. Creates it if it doesn't exist already */ static GtkLabel * @@ -472,7 +900,7 @@ get_label (IndicatorObject * io) } if (self->priv->timer == 0) { - self->priv->timer = g_timeout_add_seconds(60, minute_timer_func, self); + setup_timer(self, NULL); } return self->priv->label; |