/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2007 Ray Strode * Copyright (C) 2007 William Jon McCann * Copyright (C) 2008 Red Hat, Inc. * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "gdm-chooser-widget.h" #include "gdm-scrollable-widget.h" #include "gdm-cell-renderer-timer.h" #include "gdm-timer.h" #define GDM_CHOOSER_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_CHOOSER_WIDGET, GdmChooserWidgetPrivate)) #ifndef GDM_CHOOSER_WIDGET_DEFAULT_ICON_SIZE #define GDM_CHOOSER_WIDGET_DEFAULT_ICON_SIZE 64 #endif typedef enum { GDM_CHOOSER_WIDGET_STATE_GROWN = 0, GDM_CHOOSER_WIDGET_STATE_GROWING, GDM_CHOOSER_WIDGET_STATE_SHRINKING, GDM_CHOOSER_WIDGET_STATE_SHRUNK, } GdmChooserWidgetState; struct GdmChooserWidgetPrivate { GtkWidget *frame; GtkWidget *frame_alignment; GtkWidget *scrollable_widget; GtkWidget *items_view; GtkListStore *list_store; GtkTreeModelFilter *model_filter; GtkTreeModelSort *model_sorter; GdkPixbuf *is_in_use_pixbuf; /* row for the list_store model */ GtkTreeRowReference *active_row; GtkTreeRowReference *separator_row; GHashTable *rows_with_timers; GtkTreeViewColumn *status_column; GtkTreeViewColumn *image_column; char *inactive_text; char *active_text; char *in_use_message; gint number_of_normal_rows; gint number_of_separated_rows; gint number_of_rows_with_status; gint number_of_rows_with_images; gint number_of_active_timers; guint update_idle_id; guint timer_animation_timeout_id; guint32 should_hide_inactive_items : 1; guint32 emit_activated_after_resize_animation : 1; GdmChooserWidgetPosition separator_position; GdmChooserWidgetState state; double active_row_normalized_position; }; enum { PROP_0, PROP_INACTIVE_TEXT, PROP_ACTIVE_TEXT, PROP_LIST_VISIBLE }; enum { ACTIVATED = 0, DEACTIVATED, LOADED, NUMBER_OF_SIGNALS }; static guint signals[NUMBER_OF_SIGNALS]; static void gdm_chooser_widget_class_init (GdmChooserWidgetClass *klass); static void gdm_chooser_widget_init (GdmChooserWidget *chooser_widget); static void gdm_chooser_widget_finalize (GObject *object); static void update_timer_from_time (GdmChooserWidget *widget, GtkTreeRowReference *row, double now); G_DEFINE_TYPE (GdmChooserWidget, gdm_chooser_widget, GTK_TYPE_ALIGNMENT) enum { CHOOSER_IMAGE_COLUMN = 0, CHOOSER_NAME_COLUMN, CHOOSER_COMMENT_COLUMN, CHOOSER_PRIORITY_COLUMN, CHOOSER_ITEM_IS_IN_USE_COLUMN, CHOOSER_ITEM_IS_SEPARATED_COLUMN, CHOOSER_ITEM_IS_VISIBLE_COLUMN, CHOOSER_TIMER_START_TIME_COLUMN, CHOOSER_TIMER_DURATION_COLUMN, CHOOSER_TIMER_VALUE_COLUMN, CHOOSER_ID_COLUMN, NUMBER_OF_CHOOSER_COLUMNS }; static gboolean find_item (GdmChooserWidget *widget, const char *id, GtkTreeIter *iter) { GtkTreeModel *model; gboolean found_item; g_assert (GDM_IS_CHOOSER_WIDGET (widget)); g_assert (id != NULL); found_item = FALSE; model = GTK_TREE_MODEL (widget->priv->list_store); if (!gtk_tree_model_get_iter_first (model, iter)) { return FALSE; } do { char *item_id; gtk_tree_model_get (model, iter, CHOOSER_ID_COLUMN, &item_id, -1); g_assert (item_id != NULL); if (strcmp (id, item_id) == 0) { found_item = TRUE; } g_free (item_id); } while (!found_item && gtk_tree_model_iter_next (model, iter)); return found_item; } typedef struct { GdmChooserWidget *widget; GdmChooserUpdateForeachFunc func; gpointer user_data; } UpdateForeachData; static gboolean foreach_item (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, UpdateForeachData *data) { GdkPixbuf *image; char *name; char *comment; gboolean in_use; gboolean is_separate; gboolean res; char *id; gulong priority; gtk_tree_model_get (model, iter, CHOOSER_ID_COLUMN, &id, CHOOSER_IMAGE_COLUMN, &image, CHOOSER_NAME_COLUMN, &name, CHOOSER_COMMENT_COLUMN, &comment, CHOOSER_PRIORITY_COLUMN, &priority, CHOOSER_ITEM_IS_IN_USE_COLUMN, &in_use, CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate, -1); res = data->func (data->widget, (const char *)id, &image, &name, &comment, &priority, &in_use, &is_separate, data->user_data); if (res) { gtk_list_store_set (GTK_LIST_STORE (model), iter, CHOOSER_ID_COLUMN, id, CHOOSER_IMAGE_COLUMN, image, CHOOSER_NAME_COLUMN, name, CHOOSER_COMMENT_COLUMN, comment, CHOOSER_PRIORITY_COLUMN, priority, CHOOSER_ITEM_IS_IN_USE_COLUMN, in_use, CHOOSER_ITEM_IS_SEPARATED_COLUMN, is_separate, -1); } g_free (name); g_free (comment); if (image != NULL) { g_object_unref (image); } return FALSE; } void gdm_chooser_widget_update_foreach_item (GdmChooserWidget *widget, GdmChooserUpdateForeachFunc func, gpointer user_data) { UpdateForeachData fdata; fdata.widget = widget; fdata.func = func; fdata.user_data = user_data; gtk_tree_model_foreach (GTK_TREE_MODEL (widget->priv->list_store), (GtkTreeModelForeachFunc) foreach_item, &fdata); } static void translate_list_path_to_view_path (GdmChooserWidget *widget, GtkTreePath **path) { GtkTreePath *filtered_path; GtkTreePath *sorted_path; /* the child model is the source for the filter */ filtered_path = gtk_tree_model_filter_convert_child_path_to_path (widget->priv->model_filter, *path); sorted_path = gtk_tree_model_sort_convert_child_path_to_path (widget->priv->model_sorter, filtered_path); gtk_tree_path_free (filtered_path); gtk_tree_path_free (*path); *path = sorted_path; } static void translate_view_path_to_list_path (GdmChooserWidget *widget, GtkTreePath **path) { GtkTreePath *filtered_path; GtkTreePath *list_path; /* the child model is the source for the filter */ filtered_path = gtk_tree_model_sort_convert_path_to_child_path (widget->priv->model_sorter, *path); list_path = gtk_tree_model_filter_convert_path_to_child_path (widget->priv->model_filter, filtered_path); gtk_tree_path_free (filtered_path); gtk_tree_path_free (*path); *path = list_path; } static GtkTreePath * get_list_path_to_active_row (GdmChooserWidget *widget) { GtkTreePath *path; if (widget->priv->active_row == NULL) { return NULL; } path = gtk_tree_row_reference_get_path (widget->priv->active_row); if (path == NULL) { return NULL; } return path; } static GtkTreePath * get_view_path_to_active_row (GdmChooserWidget *widget) { GtkTreePath *path; path = get_list_path_to_active_row (widget); if (path == NULL) { return NULL; } translate_list_path_to_view_path (widget, &path); return path; } static char * get_active_item_id (GdmChooserWidget *widget, GtkTreeIter *iter) { char *item_id; GtkTreeModel *model; GtkTreePath *path; g_return_val_if_fail (GDM_IS_CHOOSER_WIDGET (widget), NULL); model = GTK_TREE_MODEL (widget->priv->list_store); item_id = NULL; if (widget->priv->active_row == NULL) { return NULL; } path = get_list_path_to_active_row (widget); if (path == NULL) { return NULL; } if (gtk_tree_model_get_iter (model, iter, path)) { gtk_tree_model_get (model, iter, CHOOSER_ID_COLUMN, &item_id, -1); } gtk_tree_path_free (path); return item_id; } char * gdm_chooser_widget_get_active_item (GdmChooserWidget *widget) { GtkTreeIter iter; return get_active_item_id (widget, &iter); } static void get_selected_list_path (GdmChooserWidget *widget, GtkTreePath **pathp) { GtkTreeSelection *selection; GtkTreeModel *sort_model; GtkTreeIter sorted_iter; GtkTreePath *path; path = NULL; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view)); if (gtk_tree_selection_get_selected (selection, &sort_model, &sorted_iter)) { g_assert (sort_model == GTK_TREE_MODEL (widget->priv->model_sorter)); path = gtk_tree_model_get_path (sort_model, &sorted_iter); translate_view_path_to_list_path (widget, &path); } else { g_debug ("GdmChooserWidget: no rows selected"); } *pathp = path; } char * gdm_chooser_widget_get_selected_item (GdmChooserWidget *widget) { GtkTreeIter iter; GtkTreeModel *model; GtkTreePath *path; char *id; id = NULL; get_selected_list_path (widget, &path); if (path == NULL) { return NULL; } model = GTK_TREE_MODEL (widget->priv->list_store); if (gtk_tree_model_get_iter (model, &iter, path)) { gtk_tree_model_get (model, &iter, CHOOSER_ID_COLUMN, &id, -1); } gtk_tree_path_free (path); return id; } void gdm_chooser_widget_set_selected_item (GdmChooserWidget *widget, const char *id) { GtkTreeIter iter; GtkTreeSelection *selection; GtkTreeModel *model; g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); g_debug ("GdmChooserWidget: setting selected item '%s'", id ? id : "(null)"); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view)); model = GTK_TREE_MODEL (widget->priv->list_store); if (find_item (widget, id, &iter)) { GtkTreePath *path; path = gtk_tree_model_get_path (model, &iter); translate_list_path_to_view_path (widget, &path); gtk_tree_selection_select_path (selection, path); gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget->priv->items_view), path, NULL, TRUE, 0.5, 0.0); gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view), path, NULL, FALSE); gtk_tree_path_free (path); } else { gtk_tree_selection_unselect_all (selection); } } static void activate_from_item_id (GdmChooserWidget *widget, const char *item_id) { GtkTreeModel *model; GtkTreePath *path; GtkTreeIter iter; char *path_str; model = GTK_TREE_MODEL (widget->priv->list_store); path = NULL; if (find_item (widget, item_id, &iter)) { path = gtk_tree_model_get_path (model, &iter); path_str = gtk_tree_path_to_string (path); g_debug ("GdmChooserWidget: got list path '%s'", path_str); g_free (path_str); translate_list_path_to_view_path (widget, &path); path_str = gtk_tree_path_to_string (path); g_debug ("GdmChooserWidget: translated to view path '%s'", path_str); g_free (path_str); } if (path == NULL) { g_debug ("GdmChooserWidget: unable to activate - path for item '%s' not found", item_id); return; } gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget->priv->items_view), path, NULL, TRUE, 0.5, 0.0); gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view), path, NULL, FALSE); gtk_tree_view_row_activated (GTK_TREE_VIEW (widget->priv->items_view), path, NULL); gtk_tree_path_free (path); } static void set_frame_text (GdmChooserWidget *widget, const char *text) { GtkWidget *label; label = gtk_frame_get_label_widget (GTK_FRAME (widget->priv->frame)); if (text == NULL && label != NULL) { gtk_frame_set_label_widget (GTK_FRAME (widget->priv->frame), NULL); gtk_alignment_set_padding (GTK_ALIGNMENT (widget->priv->frame_alignment), 0, 0, 0, 0); } else if (text != NULL && label == NULL) { label = gtk_label_new (""); gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget->priv->items_view); gtk_widget_show (label); gtk_frame_set_label_widget (GTK_FRAME (widget->priv->frame), label); gtk_alignment_set_padding (GTK_ALIGNMENT (widget->priv->frame_alignment), 0, 0, 0, 0); } if (label != NULL && text != NULL) { char *markup; markup = g_strdup_printf ("%s", text); gtk_label_set_markup_with_mnemonic (GTK_LABEL (label), markup); g_free (markup); } } static void on_shrink_animation_step (GdmScrollableWidget *scrollable_widget, double progress, int *new_height, GdmChooserWidget *widget) { GtkTreePath *active_row_path; const double final_alignment = 0.5; double row_alignment; active_row_path = get_view_path_to_active_row (widget); row_alignment = widget->priv->active_row_normalized_position + progress * (final_alignment - widget->priv->active_row_normalized_position); gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget->priv->items_view), active_row_path, NULL, TRUE, row_alignment, 0.0); gtk_tree_path_free (active_row_path); } static void update_separator_visibility (GdmChooserWidget *widget) { GtkTreePath *separator_path; GtkTreeIter iter; gboolean is_visible; separator_path = gtk_tree_row_reference_get_path (widget->priv->separator_row); if (separator_path == NULL) { return; } gtk_tree_model_get_iter (GTK_TREE_MODEL (widget->priv->list_store), &iter, separator_path); if (widget->priv->number_of_normal_rows > 0 && widget->priv->number_of_separated_rows > 0 && widget->priv->state != GDM_CHOOSER_WIDGET_STATE_SHRUNK) { is_visible = TRUE; } else { is_visible = FALSE; } gtk_list_store_set (widget->priv->list_store, &iter, CHOOSER_ITEM_IS_VISIBLE_COLUMN, is_visible, -1); } static void update_chooser_visibility (GdmChooserWidget *widget) { if (gdm_chooser_widget_get_number_of_items (widget) > 0) { gtk_widget_show (widget->priv->frame); } else { gtk_widget_hide (widget->priv->frame); } g_object_notify (G_OBJECT (widget), "list-visible"); } static void set_inactive_items_visible (GdmChooserWidget *widget, gboolean should_show) { GtkTreeModel *model; char *active_item_id; GtkTreeIter active_item_iter; GtkTreeIter iter; model = GTK_TREE_MODEL (widget->priv->list_store); if (!gtk_tree_model_get_iter_first (model, &iter)) { return; } active_item_id = get_active_item_id (widget, &active_item_iter); do { gboolean is_active; is_active = FALSE; if (active_item_id != NULL) { char *id; gtk_tree_model_get (model, &iter, CHOOSER_ID_COLUMN, &id, -1); if (strcmp (active_item_id, id) == 0) { is_active = TRUE; g_free (active_item_id); active_item_id = NULL; } g_free (id); } if (!is_active) { gtk_list_store_set (widget->priv->list_store, &iter, CHOOSER_ITEM_IS_VISIBLE_COLUMN, should_show, -1); } else { gtk_list_store_set (widget->priv->list_store, &iter, CHOOSER_ITEM_IS_VISIBLE_COLUMN, TRUE, -1); } } while (gtk_tree_model_iter_next (model, &iter)); g_free (active_item_id); update_separator_visibility (widget); } static void on_shrink_animation_complete (GdmScrollableWidget *scrollable_widget, GdmChooserWidget *widget) { g_assert (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_SHRINKING); g_debug ("GdmChooserWidget: shrink complete"); widget->priv->active_row_normalized_position = 0.5; set_inactive_items_visible (GDM_CHOOSER_WIDGET (widget), FALSE); gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), FALSE); widget->priv->state = GDM_CHOOSER_WIDGET_STATE_SHRUNK; update_separator_visibility (widget); if (widget->priv->emit_activated_after_resize_animation) { g_signal_emit (widget, signals[ACTIVATED], 0); widget->priv->emit_activated_after_resize_animation = FALSE; } } static int get_height_of_row_at_path (GdmChooserWidget *widget, GtkTreePath *path) { GdkRectangle area; gtk_tree_view_get_background_area (GTK_TREE_VIEW (widget->priv->items_view), path, NULL, &area); return area.height; } static double get_normalized_position_of_row_at_path (GdmChooserWidget *widget, GtkTreePath *path) { GdkRectangle area_of_row_at_path; GdkRectangle area_of_visible_rows; gtk_tree_view_get_background_area (GTK_TREE_VIEW (widget->priv->items_view), path, NULL, &area_of_row_at_path); gtk_tree_view_convert_tree_to_widget_coords (GTK_TREE_VIEW (widget->priv->items_view), area_of_visible_rows.x, area_of_visible_rows.y, &area_of_visible_rows.x, &area_of_visible_rows.y); return CLAMP (((double) area_of_row_at_path.y) / widget->priv->items_view->allocation.height, 0.0, 1.0); } static void start_shrink_animation (GdmChooserWidget *widget) { GtkTreePath *active_row_path; int active_row_height; int number_of_visible_rows; g_assert (widget->priv->active_row != NULL); number_of_visible_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (widget->priv->model_sorter), NULL); if (number_of_visible_rows <= 1) { on_shrink_animation_complete (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget), widget); return; } active_row_path = get_view_path_to_active_row (widget); active_row_height = get_height_of_row_at_path (widget, active_row_path); widget->priv->active_row_normalized_position = get_normalized_position_of_row_at_path (widget, active_row_path); gtk_tree_path_free (active_row_path); gdm_scrollable_widget_slide_to_height (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget), active_row_height, (GdmScrollableWidgetSlideStepFunc) on_shrink_animation_step, widget, (GdmScrollableWidgetSlideDoneFunc) on_shrink_animation_complete, widget); } static char * get_first_item (GdmChooserWidget *widget) { GtkTreeModel *model; GtkTreeIter iter; char *id; model = GTK_TREE_MODEL (widget->priv->list_store); if (!gtk_tree_model_get_iter_first (model, &iter)) { g_assert_not_reached (); } gtk_tree_model_get (model, &iter, CHOOSER_ID_COLUMN, &id, -1); return id; } static gboolean activate_if_one_item (GdmChooserWidget *widget) { char *id; g_debug ("GdmChooserWidget: attempting to activate single item"); if (gdm_chooser_widget_get_number_of_items (widget) != 1) { g_debug ("GdmChooserWidget: unable to activate single item - has %d items", gdm_chooser_widget_get_number_of_items (widget)); return FALSE; } id = get_first_item (widget); if (id != NULL) { gdm_chooser_widget_set_active_item (widget, id); g_free (id); } return FALSE; } static void _grab_focus (GtkWidget *widget) { GtkWidget *foc_widget; foc_widget = GDM_CHOOSER_WIDGET (widget)->priv->items_view; g_debug ("GdmChooserWidget: grabbing focus"); if (! GTK_WIDGET_REALIZED (foc_widget)) { g_debug ("GdmChooserWidget: not grabbing focus - not realized"); return; } if (GTK_WIDGET_HAS_FOCUS (foc_widget)) { g_debug ("GdmChooserWidget: not grabbing focus - already has it"); return; } gtk_widget_grab_focus (foc_widget); } static void on_grow_animation_complete (GdmScrollableWidget *scrollable_widget, GdmChooserWidget *widget) { g_assert (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_GROWING); widget->priv->state = GDM_CHOOSER_WIDGET_STATE_GROWN; gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), TRUE); _grab_focus (GTK_WIDGET (widget)); } static int get_number_of_on_screen_rows (GdmChooserWidget *widget) { GtkTreePath *start_path; GtkTreePath *end_path; int *start_index; int *end_index; int number_of_rows; if (!gtk_tree_view_get_visible_range (GTK_TREE_VIEW (widget->priv->items_view), &start_path, &end_path)) { return 0; } start_index = gtk_tree_path_get_indices (start_path); end_index = gtk_tree_path_get_indices (end_path); number_of_rows = *end_index - *start_index + 1; gtk_tree_path_free (start_path); gtk_tree_path_free (end_path); return number_of_rows; } static void on_grow_animation_step (GdmScrollableWidget *scrollable_widget, double progress, int *new_height, GdmChooserWidget *widget) { int number_of_visible_rows; int number_of_on_screen_rows; number_of_visible_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (widget->priv->model_sorter), NULL); number_of_on_screen_rows = get_number_of_on_screen_rows (widget); *new_height = GTK_BIN (scrollable_widget)->child->requisition.height; } static void start_grow_animation (GdmChooserWidget *widget) { set_inactive_items_visible (widget, TRUE); gdm_scrollable_widget_slide_to_height (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget), GTK_BIN (widget->priv->scrollable_widget)->child->requisition.height, (GdmScrollableWidgetSlideStepFunc) on_grow_animation_step, widget, (GdmScrollableWidgetSlideDoneFunc) on_grow_animation_complete, widget); } static void skip_resize_animation (GdmChooserWidget *widget) { if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_SHRINKING) { set_inactive_items_visible (GDM_CHOOSER_WIDGET (widget), FALSE); gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), FALSE); widget->priv->state = GDM_CHOOSER_WIDGET_STATE_SHRUNK; } else if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_GROWING) { set_inactive_items_visible (GDM_CHOOSER_WIDGET (widget), TRUE); gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), TRUE); widget->priv->state = GDM_CHOOSER_WIDGET_STATE_GROWN; _grab_focus (GTK_WIDGET (widget)); } } static void gdm_chooser_widget_grow (GdmChooserWidget *widget) { if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_SHRINKING) { gdm_scrollable_widget_stop_sliding (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget)); } gtk_alignment_set (GTK_ALIGNMENT (widget->priv->frame_alignment), 0.0, 0.0, 1.0, 1.0); set_frame_text (widget, widget->priv->inactive_text); widget->priv->state = GDM_CHOOSER_WIDGET_STATE_GROWING; if (GTK_WIDGET_VISIBLE (widget)) { start_grow_animation (widget); } else { skip_resize_animation (widget); } } static void move_cursor_to_top (GdmChooserWidget *widget) { GtkTreeModel *model; GtkTreePath *path; GtkTreeIter iter; model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->items_view)); path = gtk_tree_path_new_first (); if (gtk_tree_model_get_iter (model, &iter, path)) { gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view), path, NULL, FALSE); } gtk_tree_path_free (path); } static gboolean clear_selection (GdmChooserWidget *widget) { GtkTreeSelection *selection; GtkWidget *window; g_debug ("GdmChooserWidget: clearing selection"); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view)); gtk_tree_selection_unselect_all (selection); window = gtk_widget_get_ancestor (GTK_WIDGET (widget), GTK_TYPE_WINDOW); if (window != NULL) { gtk_window_set_focus (GTK_WINDOW (window), NULL); } return FALSE; } static void gdm_chooser_widget_shrink (GdmChooserWidget *widget) { g_assert (widget->priv->should_hide_inactive_items == TRUE); if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_GROWING) { gdm_scrollable_widget_stop_sliding (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget)); } set_frame_text (widget, widget->priv->active_text); gtk_alignment_set (GTK_ALIGNMENT (widget->priv->frame_alignment), 0.0, 0.0, 1.0, 0.0); clear_selection (widget); widget->priv->state = GDM_CHOOSER_WIDGET_STATE_SHRINKING; if (GTK_WIDGET_VISIBLE (widget)) { start_shrink_animation (widget); } else { skip_resize_animation (widget); } } static void activate_from_row (GdmChooserWidget *widget, GtkTreeRowReference *row) { g_assert (row != NULL); g_assert (gtk_tree_row_reference_valid (row)); if (widget->priv->active_row != NULL) { gtk_tree_row_reference_free (widget->priv->active_row); widget->priv->active_row = NULL; } widget->priv->active_row = gtk_tree_row_reference_copy (row); if (widget->priv->should_hide_inactive_items) { g_debug ("GdmChooserWidget: will emit activated after resize"); widget->priv->emit_activated_after_resize_animation = TRUE; gdm_chooser_widget_shrink (widget); } else { g_debug ("GdmChooserWidget: emitting activated"); g_signal_emit (widget, signals[ACTIVATED], 0); } } static void deactivate (GdmChooserWidget *widget) { GtkTreePath *path; if (widget->priv->active_row == NULL) { return; } path = get_view_path_to_active_row (widget); gtk_tree_row_reference_free (widget->priv->active_row); widget->priv->active_row = NULL; gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view), path, NULL, FALSE); gtk_tree_path_free (path); if (widget->priv->state != GDM_CHOOSER_WIDGET_STATE_GROWN) { gdm_chooser_widget_grow (widget); } g_signal_emit (widget, signals[DEACTIVATED], 0); } void gdm_chooser_widget_activate_selected_item (GdmChooserWidget *widget) { GtkTreeRowReference *row; gboolean is_already_active; GtkTreePath *path; GtkTreeModel *model; row = NULL; model = GTK_TREE_MODEL (widget->priv->list_store); is_already_active = FALSE; path = NULL; get_selected_list_path (widget, &path); if (path == NULL) { g_debug ("GdmChooserWidget: no row selected"); return; } if (widget->priv->active_row != NULL) { GtkTreePath *active_path; active_path = gtk_tree_row_reference_get_path (widget->priv->active_row); if (gtk_tree_path_compare (path, active_path) == 0) { is_already_active = TRUE; } gtk_tree_path_free (active_path); } g_assert (path != NULL); row = gtk_tree_row_reference_new (model, path); gtk_tree_path_free (path); if (!is_already_active) { activate_from_row (widget, row); } else { g_debug ("GdmChooserWidget: row is already active"); } gtk_tree_row_reference_free (row); } void gdm_chooser_widget_set_active_item (GdmChooserWidget *widget, const char *id) { g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); g_debug ("GdmChooserWidget: setting active item '%s'", id ? id : "(null)"); if (id != NULL) { activate_from_item_id (widget, id); } else { deactivate (widget); } } static void gdm_chooser_widget_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GdmChooserWidget *self; self = GDM_CHOOSER_WIDGET (object); switch (prop_id) { case PROP_INACTIVE_TEXT: g_free (self->priv->inactive_text); self->priv->inactive_text = g_value_dup_string (value); if (self->priv->active_row == NULL) { set_frame_text (self, self->priv->inactive_text); } break; case PROP_ACTIVE_TEXT: g_free (self->priv->active_text); self->priv->active_text = g_value_dup_string (value); if (self->priv->active_row != NULL) { set_frame_text (self, self->priv->active_text); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gdm_chooser_widget_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GdmChooserWidget *self; self = GDM_CHOOSER_WIDGET (object); switch (prop_id) { case PROP_INACTIVE_TEXT: g_value_set_string (value, self->priv->inactive_text); break; case PROP_ACTIVE_TEXT: g_value_set_string (value, self->priv->active_text); break; case PROP_LIST_VISIBLE: g_value_set_boolean (value, GTK_WIDGET_VISIBLE (self->priv->scrollable_widget)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gdm_chooser_widget_dispose (GObject *object) { GdmChooserWidget *widget; widget = GDM_CHOOSER_WIDGET (object); if (widget->priv->separator_row != NULL) { gtk_tree_row_reference_free (widget->priv->separator_row); widget->priv->separator_row = NULL; } if (widget->priv->active_row != NULL) { gtk_tree_row_reference_free (widget->priv->active_row); widget->priv->active_row = NULL; } if (widget->priv->inactive_text != NULL) { g_free (widget->priv->inactive_text); widget->priv->inactive_text = NULL; } if (widget->priv->active_text != NULL) { g_free (widget->priv->active_text); widget->priv->active_text = NULL; } if (widget->priv->in_use_message != NULL) { g_free (widget->priv->in_use_message); widget->priv->in_use_message = NULL; } G_OBJECT_CLASS (gdm_chooser_widget_parent_class)->dispose (object); } static void gdm_chooser_widget_hide (GtkWidget *widget) { skip_resize_animation (GDM_CHOOSER_WIDGET (widget)); GTK_WIDGET_CLASS (gdm_chooser_widget_parent_class)->hide (widget); } static void gdm_chooser_widget_show (GtkWidget *widget) { skip_resize_animation (GDM_CHOOSER_WIDGET (widget)); GTK_WIDGET_CLASS (gdm_chooser_widget_parent_class)->show (widget); } static void gdm_chooser_widget_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GdmChooserWidget *chooser_widget; GTK_WIDGET_CLASS (gdm_chooser_widget_parent_class)->size_allocate (widget, allocation); chooser_widget = GDM_CHOOSER_WIDGET (widget); } static gboolean gdm_chooser_widget_focus (GtkWidget *widget, GtkDirectionType direction) { /* Since we only have one focusable child (the tree view), * no matter which direction we're going the rules are the * same: * * 1) if it's aready got focus, return FALSE to surrender * that focus. * 2) if it doesn't already have focus, then grab it */ if (GTK_CONTAINER (widget)->focus_child != NULL) { g_debug ("GdmChooserWidget: not focusing - focus child not null"); return FALSE; } _grab_focus (widget); return TRUE; } static gboolean gdm_chooser_widget_focus_in_event (GtkWidget *widget, GdkEventFocus *focus_event) { /* We don't ever want the chooser widget itself to have focus. * Focus should always go to the tree view. */ _grab_focus (widget); return FALSE; } static void gdm_chooser_widget_class_init (GdmChooserWidgetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->get_property = gdm_chooser_widget_get_property; object_class->set_property = gdm_chooser_widget_set_property; object_class->dispose = gdm_chooser_widget_dispose; object_class->finalize = gdm_chooser_widget_finalize; widget_class->size_allocate = gdm_chooser_widget_size_allocate; widget_class->hide = gdm_chooser_widget_hide; widget_class->show = gdm_chooser_widget_show; widget_class->focus = gdm_chooser_widget_focus; widget_class->focus_in_event = gdm_chooser_widget_focus_in_event; signals [LOADED] = g_signal_new ("loaded", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GdmChooserWidgetClass, loaded), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals [ACTIVATED] = g_signal_new ("activated", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GdmChooserWidgetClass, activated), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals [DEACTIVATED] = g_signal_new ("deactivated", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GdmChooserWidgetClass, deactivated), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_object_class_install_property (object_class, PROP_INACTIVE_TEXT, g_param_spec_string ("inactive-text", _("Inactive Text"), _("The text to use in the label if the " "user hasn't picked an item yet"), NULL, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT))); g_object_class_install_property (object_class, PROP_ACTIVE_TEXT, g_param_spec_string ("active-text", _("Active Text"), _("The text to use in the label if the " "user has picked an item"), NULL, (G_PARAM_READWRITE | G_PARAM_CONSTRUCT))); g_object_class_install_property (object_class, PROP_LIST_VISIBLE, g_param_spec_boolean ("list-visible", _("List Visible"), _("Whether the chooser list is visible"), TRUE, G_PARAM_READABLE)); g_type_class_add_private (klass, sizeof (GdmChooserWidgetPrivate)); } static void on_row_activated (GtkTreeView *tree_view, GtkTreePath *tree_path, GtkTreeViewColumn *tree_column, GdmChooserWidget *widget) { char *path_str; path_str = gtk_tree_path_to_string (tree_path); g_debug ("GdmChooserWidget: row activated '%s'", path_str ? path_str : "(null)"); g_free (path_str); gdm_chooser_widget_activate_selected_item (widget); } static gboolean path_is_separator (GdmChooserWidget *widget, GtkTreeModel *model, GtkTreePath *path) { GtkTreePath *separator_path; GtkTreePath *translated_path; gboolean is_separator; separator_path = gtk_tree_row_reference_get_path (widget->priv->separator_row); if (separator_path == NULL) { return FALSE; } if (model == GTK_TREE_MODEL (widget->priv->model_sorter)) { GtkTreePath *filtered_path; filtered_path = gtk_tree_model_sort_convert_path_to_child_path (widget->priv->model_sorter, path); translated_path = gtk_tree_model_filter_convert_path_to_child_path (widget->priv->model_filter, filtered_path); gtk_tree_path_free (filtered_path); } else if (model == GTK_TREE_MODEL (widget->priv->model_filter)) { translated_path = gtk_tree_model_filter_convert_path_to_child_path (widget->priv->model_filter, path); } else { g_assert (model == GTK_TREE_MODEL (widget->priv->list_store)); translated_path = gtk_tree_path_copy (path); } if (gtk_tree_path_compare (separator_path, translated_path) == 0) { is_separator = TRUE; } else { is_separator = FALSE; } gtk_tree_path_free (translated_path); return is_separator; } static int compare_item (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data) { GdmChooserWidget *widget; char *name_a; char *name_b; char *text_a; char *text_b; gulong prio_a; gulong prio_b; gboolean is_separate_a; gboolean is_separate_b; int result; int direction; GtkTreeIter *separator_iter; char *id; PangoAttrList *attrs; g_assert (GDM_IS_CHOOSER_WIDGET (data)); widget = GDM_CHOOSER_WIDGET (data); separator_iter = NULL; if (widget->priv->separator_row != NULL) { GtkTreePath *path_a; GtkTreePath *path_b; path_a = gtk_tree_model_get_path (model, a); path_b = gtk_tree_model_get_path (model, b); if (path_is_separator (widget, model, path_a)) { separator_iter = a; } else if (path_is_separator (widget, model, path_b)) { separator_iter = b; } gtk_tree_path_free (path_a); gtk_tree_path_free (path_b); } name_a = NULL; is_separate_a = FALSE; if (separator_iter != a) { gtk_tree_model_get (model, a, CHOOSER_NAME_COLUMN, &name_a, CHOOSER_PRIORITY_COLUMN, &prio_a, CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate_a, -1); } name_b = NULL; is_separate_b = FALSE; if (separator_iter != b) { gtk_tree_model_get (model, b, CHOOSER_NAME_COLUMN, &name_b, CHOOSER_ID_COLUMN, &id, CHOOSER_PRIORITY_COLUMN, &prio_b, CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate_b, -1); } if (widget->priv->separator_position == GDM_CHOOSER_WIDGET_POSITION_TOP) { direction = -1; } else { direction = 1; } if (separator_iter == b) { result = is_separate_a? 1 : -1; result *= direction; } else if (separator_iter == a) { result = is_separate_b? -1 : 1; result *= direction; } else if (is_separate_b == is_separate_a) { if (prio_a == prio_b) { pango_parse_markup (name_a, -1, 0, &attrs, &text_a, NULL, NULL); pango_parse_markup (name_b, -1, 0, &attrs, &text_b, NULL, NULL); if (text_a && text_b) result = g_utf8_collate (text_a, text_b); else result = g_utf8_collate (name_a, name_b); g_free (text_a); g_free (text_b); } else if (prio_a > prio_b) { result = -1; } else { result = 1; } } else { result = is_separate_a - is_separate_b; result *= direction; } g_free (name_a); g_free (name_b); return result; } static void name_cell_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, GdmChooserWidget *widget) { gboolean is_in_use; char *name; char *markup; name = NULL; gtk_tree_model_get (model, iter, CHOOSER_ITEM_IS_IN_USE_COLUMN, &is_in_use, CHOOSER_NAME_COLUMN, &name, -1); if (is_in_use) { markup = g_strdup_printf ("%s\n" "%s", name ? name : "(null)", widget->priv->in_use_message); } else { markup = g_strdup_printf ("%s", name ? name : "(null)"); } g_free (name); g_object_set (cell, "markup", markup, NULL); g_free (markup); } static void check_cell_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, GdmChooserWidget *widget) { gboolean is_in_use; GdkPixbuf *pixbuf; gtk_tree_model_get (model, iter, CHOOSER_ITEM_IS_IN_USE_COLUMN, &is_in_use, -1); if (is_in_use) { pixbuf = widget->priv->is_in_use_pixbuf; } else { pixbuf = NULL; } g_object_set (cell, "pixbuf", pixbuf, NULL); } static GdkPixbuf * get_is_in_use_pixbuf (GdmChooserWidget *widget) { GtkIconTheme *theme; GdkPixbuf *pixbuf; theme = gtk_icon_theme_get_default (); pixbuf = gtk_icon_theme_load_icon (theme, "emblem-default", GDM_CHOOSER_WIDGET_DEFAULT_ICON_SIZE / 3, 0, NULL); return pixbuf; } static gboolean separator_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { GdmChooserWidget *widget; GtkTreePath *path; gboolean is_separator; g_assert (GDM_IS_CHOOSER_WIDGET (data)); widget = GDM_CHOOSER_WIDGET (data); g_assert (widget->priv->separator_row != NULL); path = gtk_tree_model_get_path (model, iter); is_separator = path_is_separator (widget, model, path); gtk_tree_path_free (path); return is_separator; } static void add_separator (GdmChooserWidget *widget) { GtkTreeIter iter; GtkTreeModel *model; GtkTreePath *path; g_assert (widget->priv->separator_row == NULL); model = GTK_TREE_MODEL (widget->priv->list_store); gtk_list_store_insert_with_values (widget->priv->list_store, &iter, 0, CHOOSER_ID_COLUMN, "-", -1); path = gtk_tree_model_get_path (model, &iter); widget->priv->separator_row = gtk_tree_row_reference_new (model, path); gtk_tree_path_free (path); } static gboolean update_column_visibility (GdmChooserWidget *widget) { if (widget->priv->number_of_rows_with_images > 0) { gtk_tree_view_column_set_visible (widget->priv->image_column, TRUE); } else { gtk_tree_view_column_set_visible (widget->priv->image_column, FALSE); } if (widget->priv->number_of_rows_with_status > 0) { gtk_tree_view_column_set_visible (widget->priv->status_column, TRUE); } else { gtk_tree_view_column_set_visible (widget->priv->status_column, FALSE); } return FALSE; } static void clear_canceled_visibility_update (GdmChooserWidget *widget) { widget->priv->update_idle_id = 0; } static void queue_column_visibility_update (GdmChooserWidget *widget) { if (widget->priv->update_idle_id == 0) { widget->priv->update_idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, (GSourceFunc) update_column_visibility, widget, (GDestroyNotify) clear_canceled_visibility_update); } } static void on_row_changed (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, GdmChooserWidget *widget) { queue_column_visibility_update (widget); } static void add_frame (GdmChooserWidget *widget) { widget->priv->frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (widget->priv->frame), GTK_SHADOW_NONE); gtk_widget_show (widget->priv->frame); gtk_container_add (GTK_CONTAINER (widget), widget->priv->frame); widget->priv->frame_alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); gtk_widget_show (widget->priv->frame_alignment); gtk_container_add (GTK_CONTAINER (widget->priv->frame), widget->priv->frame_alignment); } static gboolean on_button_release (GtkTreeView *items_view, GdkEventButton *event, GdmChooserWidget *widget) { GtkTreeModel *model; GtkTreeIter iter; GtkTreeSelection *selection; if (!widget->priv->should_hide_inactive_items) { return FALSE; } selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view)); if (gtk_tree_selection_get_selected (selection, &model, &iter)) { GtkTreePath *path; path = gtk_tree_model_get_path (model, &iter); gtk_tree_view_row_activated (GTK_TREE_VIEW (items_view), path, NULL); gtk_tree_path_free (path); } return FALSE; } static gboolean search_equal_func (GtkTreeModel *model, int column, const char *key, GtkTreeIter *iter, GdmChooserWidget *widget) { char *id; char *name; char *key_folded; gboolean ret; if (key == NULL) { return FALSE; } ret = TRUE; id = NULL; name = NULL; key_folded = g_utf8_casefold (key, -1); gtk_tree_model_get (model, iter, CHOOSER_ID_COLUMN, &id, CHOOSER_NAME_COLUMN, &name, -1); if (name != NULL) { char *name_folded; name_folded = g_utf8_casefold (name, -1); ret = !g_str_has_prefix (name_folded, key_folded); g_free (name_folded); if (!ret) { goto out; } } if (id != NULL) { char *id_folded; id_folded = g_utf8_casefold (id, -1); ret = !g_str_has_prefix (id_folded, key_folded); g_free (id_folded); if (!ret) { goto out; } } out: g_free (id); g_free (name); g_free (key_folded); return ret; } static void search_position_func (GtkTreeView *tree_view, GtkWidget *search_dialog, gpointer user_data) { /* Move it outside the region viewable by * the user. * FIXME: This is pretty inelegant. * * It might be nicer to make a GdmOffscreenBin * widget that we pack into the chooser widget below * the frame but gets redirected offscreen. * * Then we would add a GtkEntry to the bin and set * that entry as the search entry for the tree view * instead of using a search position func. */ gtk_window_move (GTK_WINDOW (search_dialog), -24000, -24000); } static void on_selection_changed (GtkTreeSelection *selection, GdmChooserWidget *widget) { GtkTreePath *path; get_selected_list_path (widget, &path); if (path != NULL) { char *path_str; path_str = gtk_tree_path_to_string (path); g_debug ("GdmChooserWidget: selection change to list path '%s'", path_str); g_free (path_str); } else { g_debug ("GdmChooserWidget: selection cleared"); } } static void gdm_chooser_widget_init (GdmChooserWidget *widget) { GtkTreeViewColumn *column; GtkTreeSelection *selection; GtkCellRenderer *renderer; widget->priv = GDM_CHOOSER_WIDGET_GET_PRIVATE (widget); /* Even though, we're a container and also don't ever take * focus for ourselve, we set CAN_FOCUS so that gtk_widget_grab_focus * works on us. We then override grab_focus requests to * be redirected our internal tree view */ GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS); gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 0, 0, 0); add_frame (widget); widget->priv->scrollable_widget = gdm_scrollable_widget_new (); gtk_widget_show (widget->priv->scrollable_widget); gtk_container_add (GTK_CONTAINER (widget->priv->frame_alignment), widget->priv->scrollable_widget); widget->priv->items_view = gtk_tree_view_new (); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (widget->priv->items_view), FALSE); g_signal_connect (widget->priv->items_view, "row-activated", G_CALLBACK (on_row_activated), widget); gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (widget->priv->items_view), (GtkTreeViewSearchEqualFunc)search_equal_func, widget, NULL); gtk_tree_view_set_search_position_func (GTK_TREE_VIEW (widget->priv->items_view), (GtkTreeViewSearchPositionFunc)search_position_func, widget, NULL); /* hack to make single-click activate work */ g_signal_connect_after (widget->priv->items_view, "button-release-event", G_CALLBACK (on_button_release), widget); gtk_widget_show (widget->priv->items_view); gtk_container_add (GTK_CONTAINER (widget->priv->scrollable_widget), widget->priv->items_view); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view)); gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); g_signal_connect (selection, "changed", G_CALLBACK (on_selection_changed), widget); g_assert (NUMBER_OF_CHOOSER_COLUMNS == 11); widget->priv->list_store = gtk_list_store_new (NUMBER_OF_CHOOSER_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_ULONG, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_STRING); widget->priv->model_filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (widget->priv->list_store), NULL)); gtk_tree_model_filter_set_visible_column (widget->priv->model_filter, CHOOSER_ITEM_IS_VISIBLE_COLUMN); g_signal_connect (G_OBJECT (widget->priv->model_filter), "row-changed", G_CALLBACK (on_row_changed), widget); widget->priv->model_sorter = GTK_TREE_MODEL_SORT (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (widget->priv->model_filter))); gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (widget->priv->model_sorter), CHOOSER_ID_COLUMN, compare_item, widget, NULL); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (widget->priv->model_sorter), CHOOSER_ID_COLUMN, GTK_SORT_ASCENDING); gtk_tree_view_set_model (GTK_TREE_VIEW (widget->priv->items_view), GTK_TREE_MODEL (widget->priv->model_sorter)); gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (widget->priv->items_view), separator_func, widget, NULL); /* IMAGE COLUMN */ renderer = gtk_cell_renderer_pixbuf_new (); column = gtk_tree_view_column_new (); gtk_tree_view_column_pack_start (column, renderer, FALSE); gtk_tree_view_append_column (GTK_TREE_VIEW (widget->priv->items_view), column); widget->priv->image_column = column; gtk_tree_view_column_set_attributes (column, renderer, "pixbuf", CHOOSER_IMAGE_COLUMN, NULL); g_object_set (renderer, "xalign", 1.0, NULL); /* NAME COLUMN */ renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new (); gtk_tree_view_column_pack_start (column, renderer, FALSE); gtk_tree_view_append_column (GTK_TREE_VIEW (widget->priv->items_view), column); gtk_tree_view_column_set_cell_data_func (column, renderer, (GtkTreeCellDataFunc) name_cell_data_func, widget, NULL); gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (widget->priv->items_view), CHOOSER_COMMENT_COLUMN); /* STATUS COLUMN */ renderer = gtk_cell_renderer_pixbuf_new (); column = gtk_tree_view_column_new (); gtk_tree_view_column_pack_start (column, renderer, FALSE); gtk_tree_view_append_column (GTK_TREE_VIEW (widget->priv->items_view), column); widget->priv->status_column = column; gtk_tree_view_column_set_cell_data_func (column, renderer, (GtkTreeCellDataFunc) check_cell_data_func, widget, NULL); widget->priv->is_in_use_pixbuf = get_is_in_use_pixbuf (widget); renderer = gdm_cell_renderer_timer_new (); gtk_tree_view_column_pack_start (column, renderer, FALSE); gtk_tree_view_column_add_attribute (column, renderer, "value", CHOOSER_TIMER_VALUE_COLUMN); widget->priv->rows_with_timers = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) gtk_tree_row_reference_free); add_separator (widget); queue_column_visibility_update (widget); } static void gdm_chooser_widget_finalize (GObject *object) { GdmChooserWidget *widget; g_return_if_fail (object != NULL); g_return_if_fail (GDM_IS_CHOOSER_WIDGET (object)); widget = GDM_CHOOSER_WIDGET (object); g_return_if_fail (widget->priv != NULL); g_hash_table_destroy (widget->priv->rows_with_timers); widget->priv->rows_with_timers = NULL; G_OBJECT_CLASS (gdm_chooser_widget_parent_class)->finalize (object); } GtkWidget * gdm_chooser_widget_new (const char *inactive_text, const char *active_text) { GObject *object; object = g_object_new (GDM_TYPE_CHOOSER_WIDGET, "inactive-text", inactive_text, "active-text", active_text, NULL); return GTK_WIDGET (object); } void gdm_chooser_widget_update_item (GdmChooserWidget *widget, const char *id, GdkPixbuf *new_image, const char *new_name, const char *new_comment, gulong new_priority, gboolean new_in_use, gboolean new_is_separate) { GtkTreeModel *model; GtkTreeIter iter; GdkPixbuf *image; gboolean is_separate; gboolean in_use; g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); model = GTK_TREE_MODEL (widget->priv->list_store); if (!find_item (widget, id, &iter)) { g_critical ("Tried to remove non-existing item from chooser"); return; } is_separate = FALSE; gtk_tree_model_get (model, &iter, CHOOSER_IMAGE_COLUMN, &image, CHOOSER_ITEM_IS_IN_USE_COLUMN, &in_use, CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate, -1); if (image != new_image) { if (image == NULL && new_image != NULL) { widget->priv->number_of_rows_with_images++; } else if (image != NULL && new_image == NULL) { widget->priv->number_of_rows_with_images--; } queue_column_visibility_update (widget); } if (image != NULL) { g_object_unref (image); } if (in_use != new_in_use) { if (new_in_use) { widget->priv->number_of_rows_with_status++; } else { widget->priv->number_of_rows_with_status--; } queue_column_visibility_update (widget); } if (is_separate != new_is_separate) { if (new_is_separate) { widget->priv->number_of_separated_rows++; widget->priv->number_of_normal_rows--; } else { widget->priv->number_of_separated_rows--; widget->priv->number_of_normal_rows++; } update_separator_visibility (widget); } gtk_list_store_set (widget->priv->list_store, &iter, CHOOSER_IMAGE_COLUMN, new_image, CHOOSER_NAME_COLUMN, new_name, CHOOSER_COMMENT_COLUMN, new_comment, CHOOSER_PRIORITY_COLUMN, new_priority, CHOOSER_ITEM_IS_IN_USE_COLUMN, new_in_use, CHOOSER_ITEM_IS_SEPARATED_COLUMN, new_is_separate, -1); } void gdm_chooser_widget_add_item (GdmChooserWidget *widget, const char *id, GdkPixbuf *image, const char *name, const char *comment, gulong priority, gboolean in_use, gboolean keep_separate) { gboolean is_visible; g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); if (keep_separate) { widget->priv->number_of_separated_rows++; } else { widget->priv->number_of_normal_rows++; } update_separator_visibility (widget); if (in_use) { widget->priv->number_of_rows_with_status++; queue_column_visibility_update (widget); } if (image != NULL) { widget->priv->number_of_rows_with_images++; queue_column_visibility_update (widget); } is_visible = widget->priv->active_row == NULL; gtk_list_store_insert_with_values (widget->priv->list_store, NULL, 0, CHOOSER_IMAGE_COLUMN, image, CHOOSER_NAME_COLUMN, name, CHOOSER_COMMENT_COLUMN, comment, CHOOSER_PRIORITY_COLUMN, priority, CHOOSER_ITEM_IS_IN_USE_COLUMN, in_use, CHOOSER_ITEM_IS_SEPARATED_COLUMN, keep_separate, CHOOSER_ITEM_IS_VISIBLE_COLUMN, is_visible, CHOOSER_ID_COLUMN, id, -1); move_cursor_to_top (widget); update_chooser_visibility (widget); } void gdm_chooser_widget_remove_item (GdmChooserWidget *widget, const char *id) { GtkTreeModel *model; GtkTreeIter iter; GdkPixbuf *image; gboolean is_separate; gboolean is_in_use; g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); model = GTK_TREE_MODEL (widget->priv->list_store); if (!find_item (widget, id, &iter)) { g_critical ("Tried to remove non-existing item from chooser"); return; } is_separate = FALSE; gtk_tree_model_get (model, &iter, CHOOSER_IMAGE_COLUMN, &image, CHOOSER_ITEM_IS_IN_USE_COLUMN, &is_in_use, CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate, -1); if (image != NULL) { widget->priv->number_of_rows_with_images--; g_object_unref (image); } if (is_in_use) { widget->priv->number_of_rows_with_status--; queue_column_visibility_update (widget); } if (is_separate) { widget->priv->number_of_separated_rows--; } else { widget->priv->number_of_normal_rows--; } update_separator_visibility (widget); gtk_list_store_remove (widget->priv->list_store, &iter); move_cursor_to_top (widget); update_chooser_visibility (widget); } gboolean gdm_chooser_widget_lookup_item (GdmChooserWidget *widget, const char *id, GdkPixbuf **image, char **name, char **comment, gulong *priority, gboolean *is_in_use, gboolean *is_separate) { GtkTreeIter iter; char *active_item_id; g_return_val_if_fail (GDM_IS_CHOOSER_WIDGET (widget), FALSE); g_return_val_if_fail (id != NULL, FALSE); active_item_id = get_active_item_id (widget, &iter); if (active_item_id == NULL || strcmp (active_item_id, id) != 0) { g_free (active_item_id); if (!find_item (widget, id, &iter)) { return FALSE; } } g_free (active_item_id); if (image != NULL) { gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, CHOOSER_IMAGE_COLUMN, image, -1); } if (name != NULL) { gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, CHOOSER_NAME_COLUMN, name, -1); } if (priority != NULL) { gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, CHOOSER_PRIORITY_COLUMN, priority, -1); } if (is_in_use != NULL) { gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, CHOOSER_ITEM_IS_IN_USE_COLUMN, is_in_use, -1); } if (is_separate != NULL) { gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, CHOOSER_ITEM_IS_SEPARATED_COLUMN, is_separate, -1); } return TRUE; } void gdm_chooser_widget_set_item_in_use (GdmChooserWidget *widget, const char *id, gboolean is_in_use) { GtkTreeIter iter; gboolean was_in_use; g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); if (!find_item (widget, id, &iter)) { return; } gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, CHOOSER_ITEM_IS_IN_USE_COLUMN, &was_in_use, -1); if (was_in_use != is_in_use) { if (is_in_use) { widget->priv->number_of_rows_with_status++; } else { widget->priv->number_of_rows_with_status--; } queue_column_visibility_update (widget); gtk_list_store_set (widget->priv->list_store, &iter, CHOOSER_ITEM_IS_IN_USE_COLUMN, is_in_use, -1); } } void gdm_chooser_widget_set_item_priority (GdmChooserWidget *widget, const char *id, gulong priority) { GtkTreeIter iter; gulong was_priority; g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); if (!find_item (widget, id, &iter)) { return; } gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter, CHOOSER_PRIORITY_COLUMN, &was_priority, -1); if (was_priority != priority) { gtk_list_store_set (widget->priv->list_store, &iter, CHOOSER_PRIORITY_COLUMN, priority, -1); gtk_tree_model_filter_refilter (widget->priv->model_filter); } } static double get_current_time (void) { const double microseconds_per_second = 1000000.0; double timestamp; GTimeVal now; g_get_current_time (&now); timestamp = ((microseconds_per_second * now.tv_sec) + now.tv_usec) / microseconds_per_second; return timestamp; } static gboolean on_timer_timeout (GdmChooserWidget *widget) { GHashTableIter iter; GSList *list; GSList *tmp; gpointer key; gpointer value; double now; list = NULL; g_hash_table_iter_init (&iter, widget->priv->rows_with_timers); while (g_hash_table_iter_next (&iter, &key, &value)) { list = g_slist_prepend (list, value); } now = get_current_time (); for (tmp = list; tmp != NULL; tmp = tmp->next) { GtkTreeRowReference *row; row = (GtkTreeRowReference *) tmp->data; update_timer_from_time (widget, row, now); } g_slist_free (list); return TRUE; } static void start_timer (GdmChooserWidget *widget, GtkTreeRowReference *row, double duration) { GtkTreeModel *model; GtkTreePath *path; GtkTreeIter iter; model = GTK_TREE_MODEL (widget->priv->list_store); path = gtk_tree_row_reference_get_path (row); gtk_tree_model_get_iter (model, &iter, path); gtk_tree_path_free (path); gtk_list_store_set (widget->priv->list_store, &iter, CHOOSER_TIMER_START_TIME_COLUMN, get_current_time (), -1); gtk_list_store_set (widget->priv->list_store, &iter, CHOOSER_TIMER_DURATION_COLUMN, duration, -1); gtk_list_store_set (widget->priv->list_store, &iter, CHOOSER_TIMER_VALUE_COLUMN, 0.0, -1); widget->priv->number_of_active_timers++; if (widget->priv->timer_animation_timeout_id == 0) { g_assert (g_hash_table_size (widget->priv->rows_with_timers) == 1); widget->priv->timer_animation_timeout_id = g_timeout_add (1000 / 20, (GSourceFunc) on_timer_timeout, widget); } } static void stop_timer (GdmChooserWidget *widget, GtkTreeRowReference *row) { GtkTreeModel *model; GtkTreePath *path; GtkTreeIter iter; model = GTK_TREE_MODEL (widget->priv->list_store); path = gtk_tree_row_reference_get_path (row); gtk_tree_model_get_iter (model, &iter, path); gtk_tree_path_free (path); gtk_list_store_set (widget->priv->list_store, &iter, CHOOSER_TIMER_START_TIME_COLUMN, 0.0, -1); gtk_list_store_set (widget->priv->list_store, &iter, CHOOSER_TIMER_DURATION_COLUMN, 0.0, -1); gtk_list_store_set (widget->priv->list_store, &iter, CHOOSER_TIMER_VALUE_COLUMN, 0.0, -1); widget->priv->number_of_active_timers--; if (widget->priv->number_of_active_timers == 0) { g_source_remove (widget->priv->timer_animation_timeout_id); widget->priv->timer_animation_timeout_id = 0; } } static void update_timer_from_time (GdmChooserWidget *widget, GtkTreeRowReference *row, double now) { GtkTreeModel *model; GtkTreePath *path; GtkTreeIter iter; double start_time; double duration; double elapsed_ratio; model = GTK_TREE_MODEL (widget->priv->list_store); path = gtk_tree_row_reference_get_path (row); gtk_tree_model_get_iter (model, &iter, path); gtk_tree_path_free (path); gtk_tree_model_get (model, &iter, CHOOSER_TIMER_START_TIME_COLUMN, &start_time, CHOOSER_TIMER_DURATION_COLUMN, &duration, -1); if (duration > G_MINDOUBLE) { elapsed_ratio = (now - start_time) / duration; } else { elapsed_ratio = 0.0; } gtk_list_store_set (widget->priv->list_store, &iter, CHOOSER_TIMER_VALUE_COLUMN, elapsed_ratio, -1); if (elapsed_ratio > .999) { char *id; stop_timer (widget, row); gtk_tree_model_get (model, &iter, CHOOSER_ID_COLUMN, &id, -1); g_hash_table_remove (widget->priv->rows_with_timers, id); g_free (id); widget->priv->number_of_rows_with_status--; queue_column_visibility_update (widget); } } void gdm_chooser_widget_set_item_timer (GdmChooserWidget *widget, const char *id, gulong timeout) { GtkTreeModel *model; GtkTreeRowReference *row; g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); model = GTK_TREE_MODEL (widget->priv->list_store); row = g_hash_table_lookup (widget->priv->rows_with_timers, id); g_assert (row == NULL || gtk_tree_row_reference_valid (row)); if (row != NULL) { stop_timer (widget, row); } if (timeout == 0) { if (row == NULL) { g_warning ("could not find item with ID '%s' to " "remove timer", id); return; } g_hash_table_remove (widget->priv->rows_with_timers, id); gtk_tree_model_filter_refilter (widget->priv->model_filter); return; } if (row == NULL) { GtkTreeIter iter; GtkTreePath *path; if (!find_item (widget, id, &iter)) { g_warning ("could not find item with ID '%s' to " "add timer", id); return; } path = gtk_tree_model_get_path (model, &iter); row = gtk_tree_row_reference_new (model, path); g_hash_table_insert (widget->priv->rows_with_timers, g_strdup (id), row); widget->priv->number_of_rows_with_status++; queue_column_visibility_update (widget); } start_timer (widget, row, timeout / 1000.0); } void gdm_chooser_widget_set_in_use_message (GdmChooserWidget *widget, const char *message) { g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); g_free (widget->priv->in_use_message); widget->priv->in_use_message = g_strdup (message); } void gdm_chooser_widget_set_separator_position (GdmChooserWidget *widget, GdmChooserWidgetPosition position) { g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget)); if (widget->priv->separator_position != position) { widget->priv->separator_position = position; } gtk_tree_model_filter_refilter (widget->priv->model_filter); } void gdm_chooser_widget_set_hide_inactive_items (GdmChooserWidget *widget, gboolean should_hide) { widget->priv->should_hide_inactive_items = should_hide; if (should_hide && (widget->priv->state != GDM_CHOOSER_WIDGET_STATE_SHRUNK || widget->priv->state != GDM_CHOOSER_WIDGET_STATE_SHRINKING) && widget->priv->active_row != NULL) { gdm_chooser_widget_shrink (widget); } else if (!should_hide && (widget->priv->state != GDM_CHOOSER_WIDGET_STATE_GROWN || widget->priv->state != GDM_CHOOSER_WIDGET_STATE_GROWING)) { gdm_chooser_widget_grow (widget); } } int gdm_chooser_widget_get_number_of_items (GdmChooserWidget *widget) { return widget->priv->number_of_normal_rows + widget->priv->number_of_separated_rows; } void gdm_chooser_widget_activate_if_one_item (GdmChooserWidget *widget) { activate_if_one_item (widget); } void gdm_chooser_widget_propagate_pending_key_events (GdmChooserWidget *widget) { if (!gdm_scrollable_widget_has_queued_key_events (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget))) { return; } gdm_scrollable_widget_replay_queued_key_events (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget)); } void gdm_chooser_widget_loaded (GdmChooserWidget *widget) { gdm_chooser_widget_grow (widget); g_signal_emit (widget, signals[LOADED], 0); }