diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/datetime-prefs-locations.c | 168 | ||||
-rw-r--r-- | src/datetime-prefs-locations.h | 2 | ||||
-rw-r--r-- | src/datetime-prefs.c | 84 | ||||
-rw-r--r-- | src/datetime-service.c | 17 | ||||
-rw-r--r-- | src/indicator-datetime.c | 16 | ||||
-rw-r--r-- | src/settings-shared.h | 1 | ||||
-rw-r--r-- | src/timezone-completion.c | 214 | ||||
-rw-r--r-- | src/utils.c | 2 |
8 files changed, 390 insertions, 114 deletions
diff --git a/src/datetime-prefs-locations.c b/src/datetime-prefs-locations.c index 0c9a93c..2862022 100644 --- a/src/datetime-prefs-locations.c +++ b/src/datetime-prefs-locations.c @@ -35,6 +35,15 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #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); + static void handle_add (GtkWidget * button, GtkTreeView * tree) { @@ -83,37 +92,45 @@ handle_edit (GtkCellRendererText * renderer, gchar * path, gchar * new_text, { 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)) { - gtk_list_store_set (store, &iter, 0, new_text, -1); + const gchar * name; + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_NAME, &name, -1); + gboolean correct = g_strcmp0 (name, new_text) == 0; + + 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, gpointer user_data) + GtkTreeIter * iter, GtkWidget * dlg) { - GValue zone_value = {0}, name_value = {0}; const gchar * zone, * name; - gtk_tree_model_get_value (model, iter, TIMEZONE_COMPLETION_ZONE, &zone_value); - zone = g_value_get_string (&zone_value); - - gtk_tree_model_get_value (model, iter, TIMEZONE_COMPLETION_NAME, &name_value); - name = g_value_get_string (&name_value); + gtk_tree_model_get (model, iter, + TIMEZONE_COMPLETION_ZONE, &zone, + TIMEZONE_COMPLETION_NAME, &name, + -1); if (zone == NULL || zone[0] == 0) { - GValue lon_value = {0}, lat_value = {0}; const gchar * strlon, * strlat; gdouble lon = 0.0, lat = 0.0; - gtk_tree_model_get_value (model, iter, TIMEZONE_COMPLETION_LONGITUDE, &lon_value); - strlon = g_value_get_string (&lon_value); + gtk_tree_model_get (model, iter, + TIMEZONE_COMPLETION_LONGITUDE, &strlon, + TIMEZONE_COMPLETION_LATITUDE, &strlat, + -1); + if (strlon != NULL && strlon[0] != 0) { lon = strtod(strlon, NULL); } - gtk_tree_model_get_value (model, iter, TIMEZONE_COMPLETION_LATITUDE, &lat_value); - strlat = g_value_get_string (&lat_value); if (strlat != NULL && strlat[0] != 0) { lat = strtod(strlat, NULL); } @@ -125,11 +142,14 @@ timezone_selected (GtkEntryCompletion * widget, GtkTreeModel * model, 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, 0, name, 2, zone, -1); + gtk_list_store_set (store, store_iter, + COL_VISIBLE_NAME, name, + COL_ICON, NULL, + COL_NAME, name, + COL_ZONE, zone, -1); } - g_value_unset (&name_value); - g_value_unset (&zone_value); + update_times (dlg); return FALSE; // Do normal action too } @@ -140,7 +160,6 @@ handle_edit_started (GtkCellRendererText * renderer, GtkCellEditable * editable, { if (GTK_IS_ENTRY (editable)) { GtkEntry *entry = GTK_ENTRY (editable); - gtk_entry_set_completion (entry, GTK_ENTRY_COMPLETION (completion)); timezone_completion_watch_entry (completion, entry); GtkListStore * store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (completion), "store")); @@ -152,9 +171,10 @@ handle_edit_started (GtkCellRendererText * renderer, GtkCellEditable * editable, } static gboolean -update_times (TimezoneCompletion * completion) +update_times (GtkWidget * dlg) { /* For each entry, check zone in column 2 and set column 1 to it's time */ + TimezoneCompletion * completion = 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")); @@ -164,16 +184,16 @@ update_times (TimezoneCompletion * completion) return TRUE; } + g_signal_handlers_block_by_func (store, save_when_idle, dlg); + GDateTime * now = g_date_time_new_now_local (); GtkTreeIter iter; if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) { do { - GValue zone_value = {0}; const gchar * strzone; - gtk_tree_model_get_value (GTK_TREE_MODEL (store), &iter, 2, &zone_value); - strzone = g_value_get_string (&zone_value); + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_ZONE, &strzone, -1); if (strzone != NULL && strzone[0] != 0) { GTimeZone * tz = g_time_zone_new (strzone); @@ -181,19 +201,20 @@ update_times (TimezoneCompletion * completion) gchar * format = generate_format_string_at_time (now_tz); gchar * time_str = g_date_time_format (now_tz, format); - gtk_list_store_set (store, &iter, 1, time_str, -1); + gtk_list_store_set (store, &iter, COL_TIME, time_str, -1); g_free (time_str); g_free (format); g_date_time_unref (now_tz); g_time_zone_unref (tz); } - - g_value_unset (&zone_value); } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)); } g_date_time_unref (now); + + g_signal_handlers_unblock_by_func (store, save_when_idle, dlg); + return TRUE; } @@ -211,7 +232,11 @@ fill_from_settings (GObject * store, GSettings * conf) split_settings_location (*striter, &zone, &name); gtk_list_store_append (GTK_LIST_STORE (store), &iter); - gtk_list_store_set (GTK_LIST_STORE (store), &iter, 0, name, 2, zone, -1); + 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); @@ -221,45 +246,78 @@ fill_from_settings (GObject * store, GSettings * conf) } static void -dialog_closed (GtkWidget * dlg, GObject * store) +save_to_settings (GObject * store, GSettings * conf) { - /* Cleanup a tad */ - guint time_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dlg), "time-id")); - g_source_remove (time_id); - - /* Now save to settings */ + gboolean empty = TRUE; GVariantBuilder builder; g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); GtkTreeIter iter; if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) { do { - GValue zone_value = {0}, name_value = {0}; const gchar * strzone, * strname; - gtk_tree_model_get_value (GTK_TREE_MODEL (store), &iter, 0, &name_value); - gtk_tree_model_get_value (GTK_TREE_MODEL (store), &iter, 2, &zone_value); - - strzone = g_value_get_string (&zone_value); - strname = g_value_get_string (&name_value); + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, + COL_NAME, &strname, + COL_ZONE, &strzone, + -1); if (strzone != NULL && strzone[0] != 0 && strname != NULL && strname[0] != 0) { gchar * settings_string = g_strdup_printf("%s %s", strzone, strname); g_variant_builder_add (&builder, "s", settings_string); g_free (settings_string); + empty = FALSE; } - - g_value_unset (&zone_value); - g_value_unset (&name_value); } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)); } - GVariant * locations = g_variant_builder_end (&builder); + if (empty) { + /* Empty list */ + g_variant_builder_clear (&builder); + g_settings_set_strv (conf, SETTINGS_LOCATIONS_S, NULL); + } + else { + GVariant * locations = g_variant_builder_end (&builder); + g_settings_set_strv (conf, SETTINGS_LOCATIONS_S, g_variant_get_strv (locations, NULL)); + g_variant_unref (locations); + } +} +static gboolean +save_now (GtkWidget *dlg) +{ GSettings * conf = G_SETTINGS (g_object_get_data (G_OBJECT (dlg), "conf")); - g_settings_set_strv (conf, SETTINGS_LOCATIONS_S, g_variant_get_strv (locations, NULL)); + 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_variant_unref (locations); + 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) +{ + /* 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 @@ -270,7 +328,7 @@ selection_changed (GtkTreeSelection * selection, GtkWidget * remove_button) } GtkWidget * -datetime_setup_locations_dialog (GtkWindow * parent, CcTimezoneMap * map) +datetime_setup_locations_dialog (CcTimezoneMap * map) { GError * error = NULL; GtkBuilder * builder = gtk_builder_new (); @@ -296,7 +354,7 @@ datetime_setup_locations_dialog (GtkWindow * parent, CcTimezoneMap * map) TimezoneCompletion * completion = 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), NULL); + 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); @@ -304,15 +362,19 @@ datetime_setup_locations_dialog (GtkWindow * parent, CcTimezoneMap * map) 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", 0, NULL); + "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); + cell = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, _("Time"), cell, - "text", 1, NULL); + "text", COL_TIME, NULL); GtkTreeSelection * selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); @@ -323,16 +385,18 @@ datetime_setup_locations_dialog (GtkWindow * parent, CcTimezoneMap * map) g_signal_connect (WIG ("removeButton"), "clicked", G_CALLBACK (handle_remove), tree); fill_from_settings (store, conf); - - guint time_id = g_timeout_add_seconds (2, (GSourceFunc)update_times, completion); - update_times (completion); + g_signal_connect_swapped (store, "row-deleted", G_CALLBACK (save_when_idle), dlg); + g_signal_connect_swapped (store, "row-inserted", G_CALLBACK (save_when_idle), dlg); + g_signal_connect_swapped (store, "row-changed", G_CALLBACK (save_when_idle), dlg); + g_signal_connect_swapped (store, "rows-reordered", G_CALLBACK (save_when_idle), 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_object_set_data (G_OBJECT (dlg), "time-id", GINT_TO_POINTER(time_id)); g_signal_connect (dlg, "destroy", G_CALLBACK (dialog_closed), store); - gtk_window_set_transient_for (GTK_WINDOW (dlg), parent); + 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 diff --git a/src/datetime-prefs-locations.h b/src/datetime-prefs-locations.h index 1760567..e312894 100644 --- a/src/datetime-prefs-locations.h +++ b/src/datetime-prefs-locations.h @@ -28,7 +28,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. G_BEGIN_DECLS -GtkWidget * datetime_setup_locations_dialog (GtkWindow * parent, CcTimezoneMap * map); +GtkWidget * datetime_setup_locations_dialog (CcTimezoneMap * map); G_END_DECLS diff --git a/src/datetime-prefs.c b/src/datetime-prefs.c index fbc88f2..b1335ca 100644 --- a/src/datetime-prefs.c +++ b/src/datetime-prefs.c @@ -53,6 +53,7 @@ GtkWidget * date_spin = NULL; guint save_time_id = 0; gboolean user_edited_time = FALSE; gboolean changing_time = FALSE; +GtkWidget * loc_dlg = NULL; /* Turns the boolean property into a string gsettings */ static GVariant * @@ -183,10 +184,30 @@ ntp_query_answered (GObject *object, GAsyncResult *res, gpointer user_data) static void sync_entry (const gchar * location) { - gchar * name; - split_settings_location (location, NULL, &name); - gtk_entry_set_text (GTK_ENTRY (tz_entry), name); - g_free (name); + gchar * new_zone, * new_name; + gchar * old_zone, * old_name; + + split_settings_location (location, &new_zone, &new_name); + + GSettings * conf = g_settings_new (SETTINGS_INTERFACE); + gchar * tz_name = g_settings_get_string (conf, SETTINGS_TIMEZONE_NAME_S); + split_settings_location (tz_name, &old_zone, &old_name); + g_free (tz_name); + g_object_unref (conf); + + // new_name is always just a sanitized version of a timezone. + // old_name is potentially a saved "pretty" version of a timezone name from + // geonames. So we prefer to use it if available and the zones match. + + if (g_strcmp0 (old_zone, new_zone) == 0) + gtk_entry_set_text (GTK_ENTRY (tz_entry), old_name); + else + gtk_entry_set_text (GTK_ENTRY (tz_entry), new_name); + + g_free (new_zone); + g_free (old_zone); + g_free (new_name); + g_free (old_name); } static void @@ -495,46 +516,65 @@ setup_time_spinners (GtkWidget * time, GtkWidget * date) } static void +hide_locations () +{ + if (loc_dlg != NULL) + gtk_widget_destroy (loc_dlg); +} + +static void show_locations (GtkWidget * button, GtkWidget * dlg) { - GtkWidget * locationsDlg = datetime_setup_locations_dialog (GTK_WINDOW (dlg), tzmap); - gtk_widget_show_all (locationsDlg); + if (loc_dlg == NULL) { + loc_dlg = datetime_setup_locations_dialog (tzmap); + gtk_window_set_transient_for (GTK_WINDOW (loc_dlg), GTK_WINDOW (dlg)); + g_signal_connect (loc_dlg, "destroy", G_CALLBACK (gtk_widget_destroyed), &loc_dlg); + g_signal_connect (dlg, "focus-in-event", G_CALLBACK (hide_locations), NULL); + gtk_widget_show_all (loc_dlg); + } + else { + gtk_window_present_with_time (GTK_WINDOW (loc_dlg), gtk_get_current_event_time ()); + } } static gboolean timezone_selected (GtkEntryCompletion * widget, GtkTreeModel * model, GtkTreeIter * iter, gpointer user_data) { - GValue value = {0}; - const gchar * strval; + const gchar * name, * zone; - gtk_tree_model_get_value (model, iter, TIMEZONE_COMPLETION_ZONE, &value); - strval = g_value_get_string (&value); + gtk_tree_model_get (model, iter, + TIMEZONE_COMPLETION_NAME, &name, + TIMEZONE_COMPLETION_ZONE, &zone, + -1); - if (strval != NULL && strval[0] != 0) { - cc_timezone_map_set_timezone (tzmap, strval); - } - else { - GValue lon_value = {0}, lat_value = {0}; + if (zone == NULL || zone[0] == 0) { const gchar * strlon, * strlat; gdouble lon = 0.0, lat = 0.0; - gtk_tree_model_get_value (model, iter, TIMEZONE_COMPLETION_LONGITUDE, &lon_value); - strlon = g_value_get_string (&lon_value); + gtk_tree_model_get (model, iter, + TIMEZONE_COMPLETION_LONGITUDE, &strlon, + TIMEZONE_COMPLETION_LATITUDE, &strlat, + -1); + if (strlon != NULL && strlon[0] != 0) { lon = strtod(strlon, NULL); } - gtk_tree_model_get_value (model, iter, TIMEZONE_COMPLETION_LATITUDE, &lat_value); - strlat = g_value_get_string (&lat_value); if (strlat != NULL && strlat[0] != 0) { lat = strtod(strlat, NULL); } - cc_timezone_map_set_coords (tzmap, lon, lat); + zone = cc_timezone_map_get_timezone_at_coords (tzmap, lon, lat); } - g_value_unset (&value); + GSettings * conf = g_settings_new (SETTINGS_INTERFACE); + gchar * tz_name = g_strdup_printf ("%s %s", zone, name); + g_settings_set_string (conf, SETTINGS_TIMEZONE_NAME_S, tz_name); + g_free (tz_name); + g_object_unref (conf); + + cc_timezone_map_set_timezone (tzmap, zone); return FALSE; // Do normal action too } @@ -604,8 +644,6 @@ create_dialog (void) /* And completion entry */ TimezoneCompletion * completion = timezone_completion_new (); - gtk_entry_set_completion (GTK_ENTRY (WIG ("timezoneEntry")), - GTK_ENTRY_COMPLETION (completion)); timezone_completion_watch_entry (completion, GTK_ENTRY (WIG ("timezoneEntry"))); g_signal_connect (completion, "match-selected", G_CALLBACK (timezone_selected), NULL); diff --git a/src/datetime-service.c b/src/datetime-service.c index 65df77e..905128b 100644 --- a/src/datetime-service.c +++ b/src/datetime-service.c @@ -364,12 +364,13 @@ static gboolean check_for_calendar (gpointer user_data) { g_return_val_if_fail (calendar != NULL, FALSE); - + // Always enable the calendar even if it does nothing + dbusmenu_menuitem_property_set_bool(calendar, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE); + dbusmenu_menuitem_property_set_bool(calendar, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE); + gchar *evo = g_find_program_in_path("evolution"); if (evo != NULL) { g_debug("Found the calendar application: %s", evo); - dbusmenu_menuitem_property_set_bool(calendar, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE); - dbusmenu_menuitem_property_set_bool(calendar, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE); dbusmenu_menuitem_property_set_bool(date, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE); g_signal_connect (G_OBJECT(date), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, @@ -437,11 +438,11 @@ update_timezone_menu_items(gpointer user_data) { gboolean show = g_settings_get_boolean (conf, SETTINGS_SHOW_LOCATIONS_S); - if (len > 0) { - dbusmenu_menuitem_property_set_bool (locations_separator, DBUSMENU_MENUITEM_PROP_VISIBLE, show); - dbusmenu_menuitem_property_set_bool (current_location, DBUSMENU_MENUITEM_PROP_VISIBLE, show); - dbusmenu_menuitem_property_set_bool (current_location, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE); - } else { + dbusmenu_menuitem_property_set_bool (locations_separator, DBUSMENU_MENUITEM_PROP_VISIBLE, show); + dbusmenu_menuitem_property_set_bool (current_location, DBUSMENU_MENUITEM_PROP_VISIBLE, show); + dbusmenu_menuitem_property_set_bool (current_location, DBUSMENU_MENUITEM_PROP_ENABLED, TRUE); + + if (len == 0) { g_debug("No locations configured (Empty List)"); return FALSE; } diff --git a/src/indicator-datetime.c b/src/indicator-datetime.c index 1cdcd3f..aed498d 100644 --- a/src/indicator-datetime.c +++ b/src/indicator-datetime.c @@ -525,7 +525,7 @@ set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec } break; } - case PROP_SHOW_SECONDS: + 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) { @@ -534,7 +534,8 @@ set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec } } break; - case PROP_SHOW_DAY: + } + 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) { @@ -542,7 +543,8 @@ set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec } } break; - case PROP_SHOW_DATE: + } + 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) { @@ -550,6 +552,7 @@ set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec } } break; + } case PROP_CUSTOM_TIME_FORMAT: { const gchar * newstr = g_value_get_string(value); if (g_strcmp0(newstr, self->priv->custom_string) != 0) { @@ -585,11 +588,12 @@ set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec gtk_widget_set_visible (GTK_WIDGET (self->priv->ido_calendar), self->priv->show_calendar); } break; - } - default: + } + default: { G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); return; } + } if (!update) { return; @@ -813,7 +817,7 @@ setup_timer (IndicatorDatetime * self, GDateTime * datetime) if (self->priv->show_seconds || (self->priv->time_mode == SETTINGS_TIME_CUSTOM && self->priv->custom_show_seconds)) { - self->priv->timer = g_timeout_add_full(G_PRIORITY_HIGH, 865, timer_func, self, NULL); + self->priv->timer = g_timeout_add_full(G_PRIORITY_HIGH, 999, timer_func, self, NULL); } else { if (datetime == NULL) { datetime = g_date_time_new_now_local(); diff --git a/src/settings-shared.h b/src/settings-shared.h index cdbb063..1ad4799 100644 --- a/src/settings-shared.h +++ b/src/settings-shared.h @@ -34,6 +34,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #define SETTINGS_SHOW_EVENTS_S "show-events" #define SETTINGS_SHOW_LOCATIONS_S "show-locations" #define SETTINGS_LOCATIONS_S "locations" +#define SETTINGS_TIMEZONE_NAME_S "timezone-name" enum { SETTINGS_TIME_LOCALE = 0, diff --git a/src/timezone-completion.c b/src/timezone-completion.c index aaf6bdc..ad04d0c 100644 --- a/src/timezone-completion.c +++ b/src/timezone-completion.c @@ -24,6 +24,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include <json-glib/json-glib.h> #include <gdk/gdk.h> +#include <gdk/gdkkeysyms.h> #include <glib/gi18n.h> #include "timezone-completion.h" #include "tz.h" @@ -41,6 +42,7 @@ struct _TimezoneCompletionPrivate GtkEntry * entry; guint queued_request; guint changed_id; + guint keypress_id; GCancellable * cancel; gchar * request_text; GHashTable * request_table; @@ -79,12 +81,48 @@ save_and_use_model (TimezoneCompletion * completion, GtkTreeModel * model) gtk_entry_completion_set_match_func (GTK_ENTRY_COMPLETION (completion), match_func, NULL, NULL); gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (completion), model); - gtk_entry_completion_complete (GTK_ENTRY_COMPLETION (completion)); - /* By this time, the changed signal has come and gone. We didn't give a - model to use, so no popup appeared for user. Poke the entry again to show - popup in 300ms. */ - g_signal_emit_by_name (priv->entry, "changed"); + if (priv->entry != NULL) { + gtk_entry_completion_complete (GTK_ENTRY_COMPLETION (completion)); + + /* By this time, the changed signal has come and gone. We didn't give a + model to use, so no popup appeared for user. Poke the entry again to show + popup in 300ms. */ + g_signal_emit_by_name (priv->entry, "changed"); + } +} + +static gint +sort_zone (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, + gpointer user_data) +{ + /* Anything that has text as a prefix goes first, in mostly sorted order. + Then everything else goes after, in mostly sorted order. */ + const gchar *casefolded_text = (const gchar *)user_data; + + const gchar *namea = NULL, *nameb = NULL; + gtk_tree_model_get (model, a, TIMEZONE_COMPLETION_NAME, &namea, -1); + gtk_tree_model_get (model, b, TIMEZONE_COMPLETION_NAME, &nameb, -1); + + gchar *casefolded_namea = NULL, *casefolded_nameb = NULL; + casefolded_namea = g_utf8_casefold (namea, -1); + casefolded_nameb = g_utf8_casefold (nameb, -1); + + gboolean amatches = FALSE, bmatches = FALSE; + amatches = strncmp (casefolded_text, casefolded_namea, strlen(casefolded_text)) == 0; + bmatches = strncmp (casefolded_text, casefolded_nameb, strlen(casefolded_text)) == 0; + + gint rv; + if (amatches && !bmatches) + rv = -1; + else if (bmatches && !amatches) + rv = 1; + else + rv = g_utf8_collate (casefolded_namea, casefolded_nameb); + + g_free (casefolded_namea); + g_free (casefolded_nameb); + return rv; } static void @@ -99,14 +137,15 @@ json_parse_ready (GObject *object, GAsyncResult *res, gpointer user_data) json_parser_load_from_stream_finish (JSON_PARSER (object), res, &error); - if (priv->cancel && (error == NULL || error->code != G_IO_ERROR_CANCELLED)) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && priv->cancel) { g_cancellable_reset (priv->cancel); } if (error != NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + save_and_use_model (completion, priv->initial_model); g_warning ("Could not parse geoname JSON data: %s", error->message); g_error_free (error); - save_and_use_model (completion, priv->initial_model); return; } @@ -179,6 +218,13 @@ json_parse_ready (GObject *object, GAsyncResult *res, gpointer user_data) TIMEZONE_COMPLETION_LONGITUDE, longitude, TIMEZONE_COMPLETION_LATITUDE, latitude, -1); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store), + TIMEZONE_COMPLETION_NAME, sort_zone, + g_utf8_casefold(priv->request_text, -1), + g_free); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), + TIMEZONE_COMPLETION_NAME, + GTK_SORT_ASCENDING); } prev_name = name; @@ -189,6 +235,20 @@ json_parse_ready (GObject *object, GAsyncResult *res, gpointer user_data) json_reader_end_element (reader); } + if (strlen (priv->request_text) < 4) { + gchar * lower_text = g_ascii_strdown (priv->request_text, -1); + if (g_strcmp0 (lower_text, "ut") == 0 || + g_strcmp0 (lower_text, "utc") == 0) { + GtkTreeIter iter; + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + TIMEZONE_COMPLETION_ZONE, "UTC", + TIMEZONE_COMPLETION_NAME, "UTC", + -1); + } + g_free (lower_text); + } + save_and_use_model (completion, GTK_TREE_MODEL (store)); g_object_unref (G_OBJECT (reader)); } @@ -203,14 +263,15 @@ geonames_data_ready (GObject *object, GAsyncResult *res, gpointer user_data) stream = g_file_read_finish (G_FILE (object), res, &error); - if (priv->cancel && (error == NULL || error->code != G_IO_ERROR_CANCELLED)) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && priv->cancel) { g_cancellable_reset (priv->cancel); } if (error != NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + save_and_use_model (completion, priv->initial_model); g_warning ("Could not connect to geoname lookup server: %s", error->message); g_error_free (error); - save_and_use_model (completion, priv->initial_model); return; } @@ -257,6 +318,7 @@ entry_changed (GtkEntry * entry, TimezoneCompletion * completion) if (priv->queued_request) { g_source_remove (priv->queued_request); + priv->queued_request = 0; } /* See if we've already got this one */ @@ -272,6 +334,94 @@ entry_changed (GtkEntry * entry, TimezoneCompletion * completion) gtk_entry_completion_complete (GTK_ENTRY_COMPLETION (completion)); } +static GtkWidget * +get_descendent (GtkWidget * parent, GType type) +{ + if (g_type_is_a (G_OBJECT_TYPE (parent), type)) + return parent; + + if (GTK_IS_CONTAINER (parent)) { + GList * children = gtk_container_get_children (GTK_CONTAINER (parent)); + GList * iter; + for (iter = children; iter; iter = iter->next) { + GtkWidget * found = get_descendent (GTK_WIDGET (iter->data), type); + if (found) { + g_list_free (children); + return found; + } + } + g_list_free (children); + } + + return NULL; +} + +/** + * The popup window and its GtkTreeView are private to our parent completion + * object. We can't get access to discover if there is a highlighted item or + * even if the window is showing right now. So this is a super hack to find + * it by looking through our toplevel's window group and finding a window with + * a GtkTreeView that points at our model. There should be only one ever, so + * we'll use the first one we find. + */ +static GtkTreeView * +find_popup_treeview (GtkWidget * widget, GtkTreeModel * model) +{ + GtkWidget * toplevel = gtk_widget_get_toplevel (widget); + if (!GTK_IS_WINDOW (toplevel)) + return NULL; + + GtkWindowGroup * group = gtk_window_get_group (GTK_WINDOW (toplevel)); + GList * windows = gtk_window_group_list_windows (group); + GList * iter; + for (iter = windows; iter; iter = iter->next) { + if (iter->data == toplevel) + continue; // Skip our own window, we don't have it + GtkWidget * view = get_descendent (GTK_WIDGET (iter->data), GTK_TYPE_TREE_VIEW); + if (view != NULL) { + GtkTreeModel * tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); + if (GTK_IS_TREE_MODEL_FILTER (tree_model)) + tree_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (tree_model)); + if (tree_model == model) { + g_list_free (windows); + return GTK_TREE_VIEW (view); + } + } + } + g_list_free (windows); + + return NULL; +} + +static gboolean +entry_keypress (GtkEntry * entry, GdkEventKey *event, TimezoneCompletion * completion) +{ + if (event->keyval == GDK_ISO_Enter || + event->keyval == GDK_KP_Enter || + event->keyval == GDK_Return) { + /* Make sure that user has a selection to choose, otherwise ignore */ + GtkTreeModel * model = gtk_entry_completion_get_model (GTK_ENTRY_COMPLETION (completion)); + GtkTreeView * view = find_popup_treeview (GTK_WIDGET (entry), model); + if (view == NULL) { + // Just beep if popup hasn't appeared yet. + gtk_widget_error_bell (GTK_WIDGET (entry)); + return TRUE; + } + + GtkTreeSelection * sel = gtk_tree_view_get_selection (view); + GtkTreeModel * sel_model = NULL; + if (!gtk_tree_selection_get_selected (sel, &sel_model, NULL)) { + // No selection, we should help them out and select first item in list + GtkTreeIter iter; + if (gtk_tree_model_get_iter_first (sel_model, &iter)) + gtk_tree_selection_select_iter (sel, &iter); + // And fall through to normal handler code + } + } + + return FALSE; +} + void timezone_completion_watch_entry (TimezoneCompletion * completion, GtkEntry * entry) { @@ -282,15 +432,22 @@ timezone_completion_watch_entry (TimezoneCompletion * completion, GtkEntry * ent priv->queued_request = 0; } if (priv->entry) { - g_source_remove (priv->changed_id); + g_signal_handler_disconnect (priv->entry, priv->changed_id); + g_signal_handler_disconnect (priv->entry, priv->keypress_id); g_object_remove_weak_pointer (G_OBJECT (priv->entry), (gpointer *)&priv->entry); + gtk_entry_set_completion (priv->entry, NULL); } guint id = g_signal_connect (entry, "changed", G_CALLBACK (entry_changed), completion); priv->changed_id = id; + id = g_signal_connect (entry, "key-press-event", G_CALLBACK (entry_keypress), completion); + priv->keypress_id = id; + priv->entry = entry; g_object_add_weak_pointer (G_OBJECT (entry), (gpointer *)&priv->entry); + + gtk_entry_set_completion (entry, GTK_ENTRY_COMPLETION (completion)); } static GtkListStore * @@ -332,6 +489,13 @@ get_initial_model (void) g_free (name); } + GtkTreeIter iter; + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + TIMEZONE_COMPLETION_ZONE, "UTC", + TIMEZONE_COMPLETION_NAME, "UTC", + -1); + tz_db_free (db); return store; } @@ -340,29 +504,24 @@ static void data_func (GtkCellLayout *cell_layout, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data) { - GValue name_val = {0}, admin1_val = {0}, country_val = {0}; const gchar * name, * admin1, * country; - gtk_tree_model_get_value (GTK_TREE_MODEL (tree_model), iter, TIMEZONE_COMPLETION_NAME, &name_val); - gtk_tree_model_get_value (GTK_TREE_MODEL (tree_model), iter, TIMEZONE_COMPLETION_ADMIN1, &admin1_val); - gtk_tree_model_get_value (GTK_TREE_MODEL (tree_model), iter, TIMEZONE_COMPLETION_COUNTRY, &country_val); - - name = g_value_get_string (&name_val); - admin1 = g_value_get_string (&admin1_val); - country = g_value_get_string (&country_val); + gtk_tree_model_get (GTK_TREE_MODEL (tree_model), iter, + TIMEZONE_COMPLETION_NAME, &name, + TIMEZONE_COMPLETION_ADMIN1, &admin1, + TIMEZONE_COMPLETION_COUNTRY, &country, + -1); gchar * user_name; - if (admin1 == NULL || admin1[0] == 0) { + if (country == NULL || country[0] == 0) { + user_name = g_strdup (name); + } else if (admin1 == NULL || admin1[0] == 0) { user_name = g_strdup_printf ("%s <small>(%s)</small>", name, country); } else { user_name = g_strdup_printf ("%s <small>(%s, %s)</small>", name, admin1, country); } g_object_set (G_OBJECT (cell), "markup", user_name, NULL); - - g_value_unset (&name_val); - g_value_unset (&admin1_val); - g_value_unset (&country_val); } static void @@ -410,10 +569,17 @@ timezone_completion_dispose (GObject * object) TimezoneCompletionPrivate * priv = TIMEZONE_COMPLETION_GET_PRIVATE (completion); if (priv->changed_id) { - g_source_remove (priv->changed_id); + if (priv->entry) + g_signal_handler_disconnect (priv->entry, priv->changed_id); priv->changed_id = 0; } + if (priv->keypress_id) { + if (priv->entry) + g_signal_handler_disconnect (priv->entry, priv->keypress_id); + priv->keypress_id = 0; + } + if (priv->entry != NULL) { g_object_remove_weak_pointer (G_OBJECT (priv->entry), (gpointer *)&priv->entry); } diff --git a/src/utils.c b/src/utils.c index 537495b..d8851aa 100644 --- a/src/utils.c +++ b/src/utils.c @@ -233,6 +233,8 @@ generate_format_string_at_time (GDateTime * time) g_date_time_unref(future_bound); } + g_date_time_unref (now); + return generate_format_string_full(show_day, show_date); } |