source: proiecte/PPPP/gdm/gui/simple-greeter/gdm-chooser-widget.c @ 134

Last change on this file since 134 was 134, checked in by (none), 14 years ago

gdm sources with the modifications for webcam

File size: 86.5 KB
Line 
1/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2007 Ray Strode <rstrode@redhat.com>
4 * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
5 * Copyright (C) 2008 Red Hat, Inc.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 *
21 */
22
23#include "config.h"
24
25#include <stdlib.h>
26#include <stdio.h>
27#include <unistd.h>
28#include <string.h>
29#include <errno.h>
30#include <dirent.h>
31#include <sys/stat.h>
32#include <syslog.h>
33
34#include <glib.h>
35#include <glib/gi18n.h>
36#include <glib/gstdio.h>
37#include <gtk/gtk.h>
38
39#include "gdm-chooser-widget.h"
40#include "gdm-scrollable-widget.h"
41#include "gdm-cell-renderer-timer.h"
42#include "gdm-timer.h"
43
44#define GDM_CHOOSER_WIDGET_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_CHOOSER_WIDGET, GdmChooserWidgetPrivate))
45
46#ifndef GDM_CHOOSER_WIDGET_DEFAULT_ICON_SIZE
47#define GDM_CHOOSER_WIDGET_DEFAULT_ICON_SIZE 64
48#endif
49
50typedef enum {
51        GDM_CHOOSER_WIDGET_STATE_GROWN = 0,
52        GDM_CHOOSER_WIDGET_STATE_GROWING,
53        GDM_CHOOSER_WIDGET_STATE_SHRINKING,
54        GDM_CHOOSER_WIDGET_STATE_SHRUNK,
55} GdmChooserWidgetState;
56
57struct GdmChooserWidgetPrivate
58{
59        GtkWidget                *frame;
60        GtkWidget                *frame_alignment;
61        GtkWidget                *scrollable_widget;
62
63        GtkWidget                *items_view;
64        GtkListStore             *list_store;
65
66        GtkTreeModelFilter       *model_filter;
67        GtkTreeModelSort         *model_sorter;
68
69        GdkPixbuf                *is_in_use_pixbuf;
70
71        /* row for the list_store model */
72        GtkTreeRowReference      *active_row;
73        GtkTreeRowReference      *separator_row;
74
75        GHashTable               *rows_with_timers;
76
77        GtkTreeViewColumn        *status_column;
78        GtkTreeViewColumn        *image_column;
79
80        char                     *inactive_text;
81        char                     *active_text;
82        char                     *in_use_message;
83
84        gint                     number_of_normal_rows;
85        gint                     number_of_separated_rows;
86        gint                     number_of_rows_with_status;
87        gint                     number_of_rows_with_images;
88        gint                     number_of_active_timers;
89
90        guint                    update_idle_id;
91        guint                    timer_animation_timeout_id;
92
93        guint32                  should_hide_inactive_items : 1;
94        guint32                  emit_activated_after_resize_animation : 1;
95        guint32                  was_fully_grown : 1;
96
97        GdmChooserWidgetPosition separator_position;
98        GdmChooserWidgetState    state;
99
100        double                   active_row_normalized_position;
101        int                      height_when_grown;
102};
103
104enum {
105        PROP_0,
106        PROP_INACTIVE_TEXT,
107        PROP_ACTIVE_TEXT,
108        PROP_LIST_VISIBLE
109};
110
111enum {
112        ACTIVATED = 0,
113        DEACTIVATED,
114        LOADED,
115        NUMBER_OF_SIGNALS
116};
117
118static guint    signals[NUMBER_OF_SIGNALS];
119
120static void     gdm_chooser_widget_class_init  (GdmChooserWidgetClass *klass);
121static void     gdm_chooser_widget_init        (GdmChooserWidget      *chooser_widget);
122static void     gdm_chooser_widget_finalize    (GObject               *object);
123
124static void     update_timer_from_time         (GdmChooserWidget    *widget,
125                                                GtkTreeRowReference *row,
126                                                double               now);
127
128G_DEFINE_TYPE (GdmChooserWidget, gdm_chooser_widget, GTK_TYPE_ALIGNMENT)
129
130enum {
131        CHOOSER_IMAGE_COLUMN = 0,
132        CHOOSER_NAME_COLUMN,
133        CHOOSER_COMMENT_COLUMN,
134        CHOOSER_PRIORITY_COLUMN,
135        CHOOSER_ITEM_IS_IN_USE_COLUMN,
136        CHOOSER_ITEM_IS_SEPARATED_COLUMN,
137        CHOOSER_ITEM_IS_VISIBLE_COLUMN,
138        CHOOSER_TIMER_START_TIME_COLUMN,
139        CHOOSER_TIMER_DURATION_COLUMN,
140        CHOOSER_TIMER_VALUE_COLUMN,
141        CHOOSER_ID_COLUMN,
142        NUMBER_OF_CHOOSER_COLUMNS
143};
144
145static gboolean
146find_item (GdmChooserWidget *widget,
147           const char       *id,
148           GtkTreeIter      *iter)
149{
150        GtkTreeModel *model;
151        gboolean      found_item;
152
153        g_assert (GDM_IS_CHOOSER_WIDGET (widget));
154        g_assert (id != NULL);
155
156        found_item = FALSE;
157        model = GTK_TREE_MODEL (widget->priv->list_store);
158
159        if (!gtk_tree_model_get_iter_first (model, iter)) {
160                return FALSE;
161        }
162
163        do {
164                char *item_id;
165
166                gtk_tree_model_get (model,
167                                    iter,
168                                    CHOOSER_ID_COLUMN,
169                                    &item_id,
170                                    -1);
171
172                g_assert (item_id != NULL);
173
174                if (strcmp (id, item_id) == 0) {
175                        found_item = TRUE;
176                }
177                g_free (item_id);
178
179        } while (!found_item && gtk_tree_model_iter_next (model, iter));
180
181        return found_item;
182}
183
184typedef struct {
185        GdmChooserWidget           *widget;
186        GdmChooserUpdateForeachFunc func;
187        gpointer                    user_data;
188} UpdateForeachData;
189
190static gboolean
191foreach_item (GtkTreeModel      *model,
192              GtkTreePath       *path,
193              GtkTreeIter       *iter,
194              UpdateForeachData *data)
195{
196        GdkPixbuf *image;
197        char      *name;
198        char      *comment;
199        gboolean   in_use;
200        gboolean   is_separate;
201        gboolean   res;
202        char      *id;
203        gulong     priority;
204
205        gtk_tree_model_get (model,
206                            iter,
207                            CHOOSER_ID_COLUMN, &id,
208                            CHOOSER_IMAGE_COLUMN, &image,
209                            CHOOSER_NAME_COLUMN, &name,
210                            CHOOSER_COMMENT_COLUMN, &comment,
211                            CHOOSER_PRIORITY_COLUMN, &priority,
212                            CHOOSER_ITEM_IS_IN_USE_COLUMN, &in_use,
213                            CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate,
214                            -1);
215        res = data->func (data->widget,
216                          (const char *)id,
217                          &image,
218                          &name,
219                          &comment,
220                          &priority,
221                          &in_use,
222                          &is_separate,
223                          data->user_data);
224        if (res) {
225                gtk_list_store_set (GTK_LIST_STORE (model),
226                                    iter,
227                                    CHOOSER_ID_COLUMN, id,
228                                    CHOOSER_IMAGE_COLUMN, image,
229                                    CHOOSER_NAME_COLUMN, name,
230                                    CHOOSER_COMMENT_COLUMN, comment,
231                                    CHOOSER_PRIORITY_COLUMN, priority,
232                                    CHOOSER_ITEM_IS_IN_USE_COLUMN, in_use,
233                                    CHOOSER_ITEM_IS_SEPARATED_COLUMN, is_separate,
234                                    -1);
235        }
236
237        g_free (name);
238        g_free (comment);
239        if (image != NULL) {
240                g_object_unref (image);
241        }
242
243        return FALSE;
244}
245
246void
247gdm_chooser_widget_update_foreach_item (GdmChooserWidget           *widget,
248                                        GdmChooserUpdateForeachFunc func,
249                                        gpointer                    user_data)
250{
251        UpdateForeachData fdata;
252
253        fdata.widget = widget;
254        fdata.func = func;
255        fdata.user_data = user_data;
256        gtk_tree_model_foreach (GTK_TREE_MODEL (widget->priv->list_store),
257                                (GtkTreeModelForeachFunc) foreach_item,
258                                &fdata);
259}
260
261static void
262translate_list_path_to_view_path (GdmChooserWidget  *widget,
263                                  GtkTreePath      **path)
264{
265        GtkTreePath *filtered_path;
266        GtkTreePath *sorted_path;
267
268        /* the child model is the source for the filter */
269        filtered_path = gtk_tree_model_filter_convert_child_path_to_path (widget->priv->model_filter,
270                                                                          *path);
271        sorted_path = gtk_tree_model_sort_convert_child_path_to_path (widget->priv->model_sorter,
272                                                                      filtered_path);
273        gtk_tree_path_free (filtered_path);
274
275        gtk_tree_path_free (*path);
276        *path = sorted_path;
277}
278
279
280static void
281translate_view_path_to_list_path (GdmChooserWidget  *widget,
282                                  GtkTreePath      **path)
283{
284        GtkTreePath *filtered_path;
285        GtkTreePath *list_path;
286
287        /* the child model is the source for the filter */
288        filtered_path = gtk_tree_model_sort_convert_path_to_child_path (widget->priv->model_sorter,
289                                                                        *path);
290
291        list_path = gtk_tree_model_filter_convert_path_to_child_path (widget->priv->model_filter,
292                                                                      filtered_path);
293        gtk_tree_path_free (filtered_path);
294
295        gtk_tree_path_free (*path);
296        *path = list_path;
297}
298
299static GtkTreePath *
300get_list_path_to_active_row (GdmChooserWidget *widget)
301{
302        GtkTreePath *path;
303
304        if (widget->priv->active_row == NULL) {
305                return NULL;
306        }
307
308        path = gtk_tree_row_reference_get_path (widget->priv->active_row);
309        if (path == NULL) {
310                return NULL;
311        }
312
313        return path;
314}
315
316static GtkTreePath *
317get_view_path_to_active_row (GdmChooserWidget *widget)
318{
319        GtkTreePath *path;
320
321        path = get_list_path_to_active_row (widget);
322        if (path == NULL) {
323                return NULL;
324        }
325
326        translate_list_path_to_view_path (widget, &path);
327
328        return path;
329}
330
331static char *
332get_active_item_id (GdmChooserWidget *widget,
333                    GtkTreeIter      *iter)
334{
335        char         *item_id;
336        GtkTreeModel *model;
337        GtkTreePath  *path;
338
339        g_return_val_if_fail (GDM_IS_CHOOSER_WIDGET (widget), NULL);
340
341        model = GTK_TREE_MODEL (widget->priv->list_store);
342        item_id = NULL;
343
344        if (widget->priv->active_row == NULL) {
345                return NULL;
346        }
347
348        path = get_list_path_to_active_row (widget);
349        if (path == NULL) {
350                return NULL;
351        }
352
353        if (gtk_tree_model_get_iter (model, iter, path)) {
354                gtk_tree_model_get (model,
355                                    iter,
356                                    CHOOSER_ID_COLUMN,
357                                    &item_id,
358                                    -1);
359        }
360        gtk_tree_path_free (path);
361
362        return item_id;
363}
364
365char *
366gdm_chooser_widget_get_active_item (GdmChooserWidget *widget)
367{
368        GtkTreeIter iter;
369
370        return get_active_item_id (widget, &iter);
371}
372
373static void
374get_selected_list_path (GdmChooserWidget *widget,
375                        GtkTreePath     **pathp)
376{
377        GtkTreeSelection    *selection;
378        GtkTreeModel        *sort_model;
379        GtkTreeIter          sorted_iter;
380        GtkTreePath         *path;
381
382        path = NULL;
383
384        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view));
385        if (gtk_tree_selection_get_selected (selection, &sort_model, &sorted_iter)) {
386
387                g_assert (sort_model == GTK_TREE_MODEL (widget->priv->model_sorter));
388
389                path = gtk_tree_model_get_path (sort_model, &sorted_iter);
390
391                translate_view_path_to_list_path (widget, &path);
392        } else {
393                g_debug ("GdmChooserWidget: no rows selected");
394        }
395
396        *pathp = path;
397}
398
399char *
400gdm_chooser_widget_get_selected_item (GdmChooserWidget *widget)
401{
402        GtkTreeIter   iter;
403        GtkTreeModel *model;
404        GtkTreePath  *path;
405        char         *id;
406
407        id = NULL;
408
409        get_selected_list_path (widget, &path);
410
411        if (path == NULL) {
412                return NULL;
413        }
414
415        model = GTK_TREE_MODEL (widget->priv->list_store);
416
417        if (gtk_tree_model_get_iter (model, &iter, path)) {
418                gtk_tree_model_get (model,
419                                    &iter,
420                                    CHOOSER_ID_COLUMN,
421                                    &id,
422                                    -1);
423        }
424
425        gtk_tree_path_free (path);
426
427        return id;
428}
429
430void
431gdm_chooser_widget_set_selected_item (GdmChooserWidget *widget,
432                                      const char       *id)
433{
434        GtkTreeIter       iter;
435        GtkTreeSelection *selection;
436        GtkTreeModel     *model;
437
438        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
439
440        g_debug ("GdmChooserWidget: setting selected item '%s'",
441                 id ? id : "(null)");
442
443        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view));
444
445        model = GTK_TREE_MODEL (widget->priv->list_store);
446
447        if (find_item (widget, id, &iter)) {
448                GtkTreePath  *path;
449
450                path = gtk_tree_model_get_path (model, &iter);
451                translate_list_path_to_view_path (widget, &path);
452
453                gtk_tree_selection_select_path (selection, path);
454
455                gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget->priv->items_view),
456                                              path,
457                                              NULL,
458                                              TRUE,
459                                              0.5,
460                                              0.0);
461
462                gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view),
463                                          path,
464                                          NULL,
465                                          FALSE);
466                gtk_tree_path_free (path);
467        } else {
468                gtk_tree_selection_unselect_all (selection);
469        }
470}
471
472static void
473activate_from_item_id (GdmChooserWidget *widget,
474                       const char       *item_id)
475{
476        GtkTreeModel *model;
477        GtkTreePath  *path;
478        GtkTreeIter   iter;
479        char         *path_str;
480
481        model = GTK_TREE_MODEL (widget->priv->list_store);
482        path = NULL;
483
484        if (find_item (widget, item_id, &iter)) {
485                path = gtk_tree_model_get_path (model, &iter);
486
487                path_str = gtk_tree_path_to_string (path);
488                g_debug ("GdmChooserWidget: got list path '%s'", path_str);
489                g_free (path_str);
490
491                translate_list_path_to_view_path (widget, &path);
492
493                path_str = gtk_tree_path_to_string (path);
494                g_debug ("GdmChooserWidget: translated to view path '%s'", path_str);
495                g_free (path_str);
496        }
497
498        if (path == NULL) {
499                g_debug ("GdmChooserWidget: unable to activate - path for item '%s' not found", item_id);
500                return;
501        }
502
503        gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget->priv->items_view),
504                                      path,
505                                      NULL,
506                                      TRUE,
507                                      0.5,
508                                      0.0);
509
510        gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view),
511                                  path,
512                                  NULL,
513                                  FALSE);
514
515        gtk_tree_view_row_activated (GTK_TREE_VIEW (widget->priv->items_view),
516                                     path,
517                                     NULL);
518        gtk_tree_path_free (path);
519}
520
521static void
522set_frame_text (GdmChooserWidget *widget,
523                const char       *text)
524{
525        GtkWidget *label;
526
527        label = gtk_frame_get_label_widget (GTK_FRAME (widget->priv->frame));
528
529        if (text == NULL && label != NULL) {
530                gtk_frame_set_label_widget (GTK_FRAME (widget->priv->frame),
531                                            NULL);
532                gtk_alignment_set_padding (GTK_ALIGNMENT (widget->priv->frame_alignment),
533                                           0, 0, 0, 0);
534        } else if (text != NULL && label == NULL) {
535                label = gtk_label_new ("");
536                gtk_label_set_mnemonic_widget (GTK_LABEL (label),
537                                               widget->priv->items_view);
538                gtk_widget_show (label);
539                gtk_frame_set_label_widget (GTK_FRAME (widget->priv->frame),
540                                            label);
541                gtk_alignment_set_padding (GTK_ALIGNMENT (widget->priv->frame_alignment),
542                                           0, 0, 0, 0);
543        }
544
545        if (label != NULL && text != NULL) {
546                char *markup;
547                markup = g_strdup_printf ("%s", text);
548                gtk_label_set_markup_with_mnemonic (GTK_LABEL (label), markup);
549                g_free (markup);
550        }
551}
552
553static void
554on_shrink_animation_step (GdmScrollableWidget *scrollable_widget,
555                          double               progress,
556                          GdmChooserWidget    *widget)
557{
558        GtkTreePath   *active_row_path;
559        const double   final_alignment = 0.5;
560        double         row_alignment;
561
562        active_row_path = get_view_path_to_active_row (widget);
563        row_alignment = widget->priv->active_row_normalized_position + progress * (final_alignment - widget->priv->active_row_normalized_position);
564
565        gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget->priv->items_view),
566                                     active_row_path, NULL, TRUE, row_alignment, 0.0);
567        gtk_tree_path_free (active_row_path);
568}
569
570static void
571update_separator_visibility (GdmChooserWidget *widget)
572{
573        GtkTreePath *separator_path;
574        GtkTreeIter  iter;
575        gboolean     is_visible;
576
577        separator_path = gtk_tree_row_reference_get_path (widget->priv->separator_row);
578
579        if (separator_path == NULL) {
580                return;
581        }
582
583        gtk_tree_model_get_iter (GTK_TREE_MODEL (widget->priv->list_store),
584                                 &iter, separator_path);
585
586        if (widget->priv->number_of_normal_rows > 0 &&
587            widget->priv->number_of_separated_rows > 0 &&
588            widget->priv->state != GDM_CHOOSER_WIDGET_STATE_SHRUNK) {
589                is_visible = TRUE;
590        } else {
591                is_visible = FALSE;
592        }
593
594        gtk_list_store_set (widget->priv->list_store,
595                            &iter,
596                            CHOOSER_ITEM_IS_VISIBLE_COLUMN, is_visible,
597                            -1);
598}
599
600static void
601update_chooser_visibility (GdmChooserWidget *widget)
602{
603        if (gdm_chooser_widget_get_number_of_items (widget) > 0) {
604                gtk_widget_show (widget->priv->scrollable_widget);
605        } else {
606                gtk_widget_hide (widget->priv->scrollable_widget);
607        }
608        g_object_notify (G_OBJECT (widget), "list-visible");
609}
610
611static void
612set_inactive_items_visible (GdmChooserWidget *widget,
613                            gboolean          should_show)
614{
615        GtkTreeModel *model;
616        char         *active_item_id;
617        GtkTreeIter   active_item_iter;
618        GtkTreeIter   iter;
619
620        model = GTK_TREE_MODEL (widget->priv->list_store);
621
622        if (!gtk_tree_model_get_iter_first (model, &iter)) {
623                return;
624        }
625
626        active_item_id = get_active_item_id (widget, &active_item_iter);
627
628        do {
629                gboolean is_active;
630
631                is_active = FALSE;
632                if (active_item_id != NULL) {
633                        char *id;
634
635                        gtk_tree_model_get (model, &iter,
636                                            CHOOSER_ID_COLUMN, &id, -1);
637
638                        if (strcmp (active_item_id, id) == 0) {
639                                is_active = TRUE;
640                                g_free (active_item_id);
641                                active_item_id = NULL;
642                        }
643                        g_free (id);
644                }
645
646                if (!is_active) {
647                        gtk_list_store_set (widget->priv->list_store,
648                                            &iter,
649                                            CHOOSER_ITEM_IS_VISIBLE_COLUMN, should_show,
650                                            -1);
651                } else {
652                        gtk_list_store_set (widget->priv->list_store,
653                                            &iter,
654                                            CHOOSER_ITEM_IS_VISIBLE_COLUMN, TRUE,
655                                            -1);
656                }
657        } while (gtk_tree_model_iter_next (model, &iter));
658
659        g_free (active_item_id);
660
661        update_separator_visibility (widget);
662}
663
664static void
665on_shrink_animation_complete (GdmScrollableWidget *scrollable_widget,
666                              GdmChooserWidget    *widget)
667{
668        g_assert (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_SHRINKING);
669
670        g_debug ("GdmChooserWidget: shrink complete");
671
672        widget->priv->active_row_normalized_position = 0.5;
673        set_inactive_items_visible (GDM_CHOOSER_WIDGET (widget), FALSE);
674        gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), FALSE);
675        widget->priv->state = GDM_CHOOSER_WIDGET_STATE_SHRUNK;
676
677        update_separator_visibility (widget);
678
679        if (widget->priv->emit_activated_after_resize_animation) {
680                g_signal_emit (widget, signals[ACTIVATED], 0);
681                widget->priv->emit_activated_after_resize_animation = FALSE;
682        }
683}
684
685static int
686get_height_of_row_at_path (GdmChooserWidget *widget,
687                           GtkTreePath      *path)
688{
689        GdkRectangle area;
690
691        gtk_tree_view_get_background_area (GTK_TREE_VIEW (widget->priv->items_view),
692                                           path, NULL, &area);
693
694        return area.height;
695}
696
697static double
698get_normalized_position_of_row_at_path (GdmChooserWidget *widget,
699                                        GtkTreePath      *path)
700{
701        GdkRectangle area_of_row_at_path;
702        GdkRectangle area_of_visible_rows;
703
704        gtk_tree_view_get_background_area (GTK_TREE_VIEW (widget->priv->items_view),
705                                           path, NULL, &area_of_row_at_path);
706
707        gtk_tree_view_convert_tree_to_widget_coords (GTK_TREE_VIEW (widget->priv->items_view),
708                                                     area_of_visible_rows.x,
709                                                     area_of_visible_rows.y,
710                                                     &area_of_visible_rows.x,
711                                                     &area_of_visible_rows.y);
712        return CLAMP (((double) area_of_row_at_path.y) / widget->priv->items_view->allocation.height, 0.0, 1.0);
713}
714
715static void
716start_shrink_animation (GdmChooserWidget *widget)
717{
718       GtkTreePath *active_row_path;
719       int          active_row_height;
720       int          number_of_visible_rows;
721
722       g_assert (widget->priv->active_row != NULL);
723
724       number_of_visible_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (widget->priv->model_sorter), NULL);
725
726       if (number_of_visible_rows <= 1) {
727               on_shrink_animation_complete (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget),
728                                             widget);
729               return;
730       }
731
732       active_row_path = get_view_path_to_active_row (widget);
733       active_row_height = get_height_of_row_at_path (widget, active_row_path);
734       widget->priv->active_row_normalized_position = get_normalized_position_of_row_at_path (widget, active_row_path);
735       gtk_tree_path_free (active_row_path);
736
737       gdm_scrollable_widget_slide_to_height (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget),
738                                              active_row_height,
739                                              (GdmScrollableWidgetSlideStepFunc)
740                                              on_shrink_animation_step, widget,
741                                              (GdmScrollableWidgetSlideDoneFunc)
742                                              on_shrink_animation_complete, widget);
743}
744
745static char *
746get_first_item (GdmChooserWidget *widget)
747{
748        GtkTreeModel *model;
749        GtkTreeIter   iter;
750        char         *id;
751
752        model = GTK_TREE_MODEL (widget->priv->list_store);
753
754        if (!gtk_tree_model_get_iter_first (model, &iter)) {
755                g_assert_not_reached ();
756        }
757
758        gtk_tree_model_get (model, &iter,
759                            CHOOSER_ID_COLUMN, &id, -1);
760        return id;
761}
762
763static gboolean
764activate_if_one_item (GdmChooserWidget *widget)
765{
766        char *id;
767
768        g_debug ("GdmChooserWidget: attempting to activate single item");
769
770        if (gdm_chooser_widget_get_number_of_items (widget) != 1) {
771                g_debug ("GdmChooserWidget: unable to activate single item - has %d items", gdm_chooser_widget_get_number_of_items (widget));
772                return FALSE;
773        }
774
775        id = get_first_item (widget);
776        if (id != NULL) {
777                gdm_chooser_widget_set_active_item (widget, id);
778                g_free (id);
779        }
780
781        return FALSE;
782}
783
784static void
785_grab_focus (GtkWidget *widget)
786{
787        GtkWidget *foc_widget;
788
789        foc_widget = GDM_CHOOSER_WIDGET (widget)->priv->items_view;
790        g_debug ("GdmChooserWidget: grabbing focus");
791        if (! GTK_WIDGET_REALIZED (foc_widget)) {
792                g_debug ("GdmChooserWidget: not grabbing focus - not realized");
793                return;
794        }
795
796        if (GTK_WIDGET_HAS_FOCUS (foc_widget)) {
797                g_debug ("GdmChooserWidget: not grabbing focus - already has it");
798                return;
799        }
800
801        gtk_widget_grab_focus (foc_widget);
802}
803
804static void
805on_grow_animation_complete (GdmScrollableWidget *scrollable_widget,
806                            GdmChooserWidget    *widget)
807{
808        g_assert (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_GROWING);
809        widget->priv->state = GDM_CHOOSER_WIDGET_STATE_GROWN;
810        widget->priv->was_fully_grown = TRUE;
811        gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), TRUE);
812
813        _grab_focus (GTK_WIDGET (widget));
814}
815
816static int
817get_height_of_screen (GdmChooserWidget *widget)
818{
819        GdkScreen    *screen;
820        GdkRectangle  area;
821        int           monitor;
822
823        screen = gtk_widget_get_screen (GTK_WIDGET (widget));
824
825        monitor = gdk_screen_get_monitor_at_window (screen,
826                                                    gdk_screen_get_root_window (screen));
827        gdk_screen_get_monitor_geometry (screen, monitor, &area);
828
829        return area.height;
830}
831
832static int
833get_number_of_on_screen_rows (GdmChooserWidget *widget)
834{
835        GtkTreePath *start_path;
836        GtkTreePath *end_path;
837        int         *start_index;
838        int         *end_index;
839        int          number_of_rows;
840
841        if (!gtk_tree_view_get_visible_range (GTK_TREE_VIEW (widget->priv->items_view),
842                                              &start_path, &end_path)) {
843                return 0;
844        }
845
846        start_index = gtk_tree_path_get_indices (start_path);
847        end_index = gtk_tree_path_get_indices (end_path);
848
849        number_of_rows = *end_index - *start_index + 1;
850
851        gtk_tree_path_free (start_path);
852        gtk_tree_path_free (end_path);
853
854        return number_of_rows;
855}
856
857static void
858on_grow_animation_step (GdmScrollableWidget *scrollable_widget,
859                        double               progress,
860                        GdmChooserWidget    *widget)
861{
862        int number_of_visible_rows;
863        int number_of_on_screen_rows;
864
865        number_of_visible_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (widget->priv->model_sorter), NULL);
866        number_of_on_screen_rows = get_number_of_on_screen_rows (widget);
867
868        if (number_of_on_screen_rows >= number_of_visible_rows) {
869                gdm_scrollable_widget_stop_sliding (scrollable_widget);
870                return;
871        }
872}
873
874static void
875start_grow_animation (GdmChooserWidget *widget)
876{
877        int number_of_visible_rows;
878        int number_of_rows;
879        int height;
880
881        number_of_visible_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (widget->priv->model_sorter), NULL);
882        number_of_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (widget->priv->list_store), NULL);
883
884        if (number_of_visible_rows >= number_of_rows) {
885               on_grow_animation_complete (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget), widget);
886               return;
887        }
888
889        set_inactive_items_visible (widget, TRUE);
890
891        if (widget->priv->was_fully_grown) {
892                height = widget->priv->height_when_grown;
893        } else {
894                height = get_height_of_screen (widget);
895        }
896        gdm_scrollable_widget_slide_to_height (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget),
897                                               widget->priv->height_when_grown,
898                                               (GdmScrollableWidgetSlideStepFunc)
899                                               on_grow_animation_step, widget,
900                                               (GdmScrollableWidgetSlideDoneFunc)
901                                               on_grow_animation_complete, widget);
902}
903
904static void
905skip_resize_animation (GdmChooserWidget *widget)
906{
907        if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_SHRINKING) {
908                set_inactive_items_visible (GDM_CHOOSER_WIDGET (widget), FALSE);
909                gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), FALSE);
910                widget->priv->state = GDM_CHOOSER_WIDGET_STATE_SHRUNK;
911        } else if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_GROWING) {
912                set_inactive_items_visible (GDM_CHOOSER_WIDGET (widget), TRUE);
913                gtk_tree_view_set_enable_search (GTK_TREE_VIEW (widget->priv->items_view), TRUE);
914                widget->priv->state = GDM_CHOOSER_WIDGET_STATE_GROWN;
915                widget->priv->was_fully_grown = FALSE;
916                _grab_focus (GTK_WIDGET (widget));
917        }
918}
919
920static void
921gdm_chooser_widget_grow (GdmChooserWidget *widget)
922{
923        if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_SHRINKING) {
924                gdm_scrollable_widget_stop_sliding (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget));
925                widget->priv->was_fully_grown = FALSE;
926        }
927
928        gtk_alignment_set (GTK_ALIGNMENT (widget->priv->frame_alignment),
929                           0.0, 0.0, 1.0, 1.0);
930
931        set_frame_text (widget, widget->priv->inactive_text);
932
933        widget->priv->state = GDM_CHOOSER_WIDGET_STATE_GROWING;
934
935        if (GTK_WIDGET_VISIBLE (widget)) {
936                start_grow_animation (widget);
937        } else {
938                skip_resize_animation (widget);
939        }
940}
941
942static void
943move_cursor_to_top (GdmChooserWidget *widget)
944{
945        GtkTreeModel *model;
946        GtkTreePath  *path;
947        GtkTreeIter   iter;
948
949        model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->items_view));
950        path = gtk_tree_path_new_first ();
951        if (gtk_tree_model_get_iter (model, &iter, path)) {
952                gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view),
953                                          path,
954                                          NULL,
955                                          FALSE);
956        }
957        gtk_tree_path_free (path);
958}
959
960static gboolean
961clear_selection (GdmChooserWidget *widget)
962{
963        GtkTreeSelection *selection;
964        GtkWidget        *window;
965
966        g_debug ("GdmChooserWidget: clearing selection");
967
968        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view));
969        gtk_tree_selection_unselect_all (selection);
970
971        window = gtk_widget_get_ancestor (GTK_WIDGET (widget), GTK_TYPE_WINDOW);
972
973        if (window != NULL) {
974                gtk_window_set_focus (GTK_WINDOW (window), NULL);
975        }
976
977        return FALSE;
978}
979
980static void
981gdm_chooser_widget_shrink (GdmChooserWidget *widget)
982{
983        g_assert (widget->priv->should_hide_inactive_items == TRUE);
984
985        if (widget->priv->state == GDM_CHOOSER_WIDGET_STATE_GROWING) {
986                gdm_scrollable_widget_stop_sliding (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget));
987        }
988
989        set_frame_text (widget, widget->priv->active_text);
990
991        gtk_alignment_set (GTK_ALIGNMENT (widget->priv->frame_alignment),
992                           0.0, 0.0, 1.0, 0.0);
993
994        clear_selection (widget);
995
996        widget->priv->state = GDM_CHOOSER_WIDGET_STATE_SHRINKING;
997
998        if (GTK_WIDGET_VISIBLE (widget)) {
999                start_shrink_animation (widget);
1000        } else {
1001                skip_resize_animation (widget);
1002        }
1003}
1004
1005static void
1006activate_from_row (GdmChooserWidget    *widget,
1007                   GtkTreeRowReference *row)
1008{
1009        g_assert (row != NULL);
1010        g_assert (gtk_tree_row_reference_valid (row));
1011
1012        if (widget->priv->active_row != NULL) {
1013                gtk_tree_row_reference_free (widget->priv->active_row);
1014                widget->priv->active_row = NULL;
1015        }
1016
1017        widget->priv->active_row = gtk_tree_row_reference_copy (row);
1018
1019        if (widget->priv->should_hide_inactive_items) {
1020                g_debug ("GdmChooserWidget: will emit activated after resize");
1021                widget->priv->emit_activated_after_resize_animation = TRUE;
1022                gdm_chooser_widget_shrink (widget);
1023        } else {
1024                g_debug ("GdmChooserWidget: emitting activated");
1025                g_signal_emit (widget, signals[ACTIVATED], 0);
1026        }
1027}
1028
1029static void
1030deactivate (GdmChooserWidget *widget)
1031{
1032        GtkTreePath *path;
1033
1034        if (widget->priv->active_row == NULL) {
1035                return;
1036        }
1037
1038        path = get_view_path_to_active_row (widget);
1039
1040        gtk_tree_row_reference_free (widget->priv->active_row);
1041        widget->priv->active_row = NULL;
1042
1043        gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget->priv->items_view),
1044                                  path, NULL, FALSE);
1045        gtk_tree_path_free (path);
1046
1047        if (widget->priv->state != GDM_CHOOSER_WIDGET_STATE_GROWN) {
1048                gdm_chooser_widget_grow (widget);
1049        }
1050
1051        g_signal_emit (widget, signals[DEACTIVATED], 0);
1052}
1053
1054void
1055gdm_chooser_widget_activate_selected_item (GdmChooserWidget *widget)
1056{
1057        GtkTreeRowReference *row;
1058        gboolean             is_already_active;
1059        GtkTreePath         *path;
1060        GtkTreeModel        *model;
1061
1062        row = NULL;
1063        model = GTK_TREE_MODEL (widget->priv->list_store);
1064        is_already_active = FALSE;
1065
1066        path = NULL;
1067
1068        get_selected_list_path (widget, &path);
1069        if (path == NULL) {
1070                g_debug ("GdmChooserWidget: no row selected");
1071                return;
1072        }
1073
1074        if (widget->priv->active_row != NULL) {
1075                GtkTreePath *active_path;
1076
1077                active_path = gtk_tree_row_reference_get_path (widget->priv->active_row);
1078
1079                if (gtk_tree_path_compare (path, active_path) == 0) {
1080                        is_already_active = TRUE;
1081                }
1082                gtk_tree_path_free (active_path);
1083        }
1084        g_assert (path != NULL);
1085        row = gtk_tree_row_reference_new (model, path);
1086        gtk_tree_path_free (path);
1087
1088        if (!is_already_active) {
1089                activate_from_row (widget, row);
1090        } else {
1091                g_debug ("GdmChooserWidget: row is already active");
1092        }
1093        gtk_tree_row_reference_free (row);
1094}
1095
1096void
1097gdm_chooser_widget_set_active_item (GdmChooserWidget *widget,
1098                                    const char       *id)
1099{
1100        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
1101
1102        g_debug ("GdmChooserWidget: setting active item '%s'",
1103                 id ? id : "(null)");
1104
1105        if (id != NULL) {
1106                activate_from_item_id (widget, id);
1107        } else {
1108                deactivate (widget);
1109        }
1110}
1111
1112static void
1113gdm_chooser_widget_set_property (GObject        *object,
1114                                 guint           prop_id,
1115                                 const GValue   *value,
1116                                 GParamSpec     *pspec)
1117{
1118        GdmChooserWidget *self;
1119
1120        self = GDM_CHOOSER_WIDGET (object);
1121
1122        switch (prop_id) {
1123
1124        case PROP_INACTIVE_TEXT:
1125                g_free (self->priv->inactive_text);
1126                self->priv->inactive_text = g_value_dup_string (value);
1127
1128                if (self->priv->active_row == NULL) {
1129                        set_frame_text (self, self->priv->inactive_text);
1130                }
1131                break;
1132
1133        case PROP_ACTIVE_TEXT:
1134                g_free (self->priv->active_text);
1135                self->priv->active_text = g_value_dup_string (value);
1136
1137                if (self->priv->active_row != NULL) {
1138                        set_frame_text (self, self->priv->active_text);
1139                }
1140                break;
1141
1142        default:
1143                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1144                break;
1145        }
1146}
1147
1148static void
1149gdm_chooser_widget_get_property (GObject        *object,
1150                                 guint           prop_id,
1151                                 GValue         *value,
1152                                 GParamSpec     *pspec)
1153{
1154        GdmChooserWidget *self;
1155
1156        self = GDM_CHOOSER_WIDGET (object);
1157
1158        switch (prop_id) {
1159        case PROP_INACTIVE_TEXT:
1160                g_value_set_string (value, self->priv->inactive_text);
1161                break;
1162
1163        case PROP_ACTIVE_TEXT:
1164                g_value_set_string (value, self->priv->active_text);
1165                break;
1166        case PROP_LIST_VISIBLE:
1167                g_value_set_boolean (value, GTK_WIDGET_VISIBLE (self->priv->scrollable_widget));
1168                break;
1169        default:
1170                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1171                break;
1172        }
1173}
1174
1175static void
1176gdm_chooser_widget_dispose (GObject *object)
1177{
1178        GdmChooserWidget *widget;
1179
1180        widget = GDM_CHOOSER_WIDGET (object);
1181
1182        if (widget->priv->separator_row != NULL) {
1183                gtk_tree_row_reference_free (widget->priv->separator_row);
1184                widget->priv->separator_row = NULL;
1185        }
1186
1187        if (widget->priv->active_row != NULL) {
1188                gtk_tree_row_reference_free (widget->priv->active_row);
1189                widget->priv->active_row = NULL;
1190        }
1191
1192        if (widget->priv->inactive_text != NULL) {
1193                g_free (widget->priv->inactive_text);
1194                widget->priv->inactive_text = NULL;
1195        }
1196
1197        if (widget->priv->active_text != NULL) {
1198                g_free (widget->priv->active_text);
1199                widget->priv->active_text = NULL;
1200        }
1201
1202        if (widget->priv->in_use_message != NULL) {
1203                g_free (widget->priv->in_use_message);
1204                widget->priv->in_use_message = NULL;
1205        }
1206
1207        G_OBJECT_CLASS (gdm_chooser_widget_parent_class)->dispose (object);
1208}
1209
1210static void
1211gdm_chooser_widget_hide (GtkWidget *widget)
1212{
1213        skip_resize_animation (GDM_CHOOSER_WIDGET (widget));
1214        GTK_WIDGET_CLASS (gdm_chooser_widget_parent_class)->hide (widget);
1215}
1216
1217static void
1218gdm_chooser_widget_show (GtkWidget *widget)
1219{
1220        skip_resize_animation (GDM_CHOOSER_WIDGET (widget));
1221
1222        GTK_WIDGET_CLASS (gdm_chooser_widget_parent_class)->show (widget);
1223}
1224
1225static void
1226gdm_chooser_widget_size_allocate (GtkWidget     *widget,
1227                                  GtkAllocation *allocation)
1228{
1229        GdmChooserWidget *chooser_widget;
1230
1231        GTK_WIDGET_CLASS (gdm_chooser_widget_parent_class)->size_allocate (widget, allocation);
1232
1233        chooser_widget = GDM_CHOOSER_WIDGET (widget);
1234
1235        if (chooser_widget->priv->state == GDM_CHOOSER_WIDGET_STATE_GROWN) {
1236                if (chooser_widget->priv->was_fully_grown) {
1237                        chooser_widget->priv->height_when_grown = allocation->height;
1238                }
1239        }
1240}
1241
1242static gboolean
1243gdm_chooser_widget_focus (GtkWidget        *widget,
1244                          GtkDirectionType  direction)
1245{
1246        /* Since we only have one focusable child (the tree view),
1247         * no matter which direction we're going the rules are the
1248         * same:
1249         *
1250         *    1) if it's aready got focus, return FALSE to surrender
1251         *    that focus.
1252         *    2) if it doesn't already have focus, then grab it
1253         */
1254        if (GTK_CONTAINER (widget)->focus_child != NULL) {
1255                g_debug ("GdmChooserWidget: not focusing - focus child not null");
1256                return FALSE;
1257        }
1258
1259        _grab_focus (widget);
1260
1261        return TRUE;
1262}
1263
1264static gboolean
1265gdm_chooser_widget_focus_in_event (GtkWidget     *widget,
1266                                   GdkEventFocus *focus_event)
1267{
1268        /* We don't ever want the chooser widget itself to have focus.
1269         * Focus should always go to the tree view.
1270         */
1271        _grab_focus (widget);
1272
1273        return FALSE;
1274}
1275
1276static void
1277gdm_chooser_widget_class_init (GdmChooserWidgetClass *klass)
1278{
1279        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
1280        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1281
1282        object_class->get_property = gdm_chooser_widget_get_property;
1283        object_class->set_property = gdm_chooser_widget_set_property;
1284        object_class->dispose = gdm_chooser_widget_dispose;
1285        object_class->finalize = gdm_chooser_widget_finalize;
1286        widget_class->size_allocate = gdm_chooser_widget_size_allocate;
1287        widget_class->hide = gdm_chooser_widget_hide;
1288        widget_class->show = gdm_chooser_widget_show;
1289        widget_class->focus = gdm_chooser_widget_focus;
1290        widget_class->focus_in_event = gdm_chooser_widget_focus_in_event;
1291
1292        signals [LOADED] = g_signal_new ("loaded",
1293                                         G_TYPE_FROM_CLASS (object_class),
1294                                         G_SIGNAL_RUN_LAST,
1295                                         G_STRUCT_OFFSET (GdmChooserWidgetClass, loaded),
1296                                         NULL,
1297                                         NULL,
1298                                         g_cclosure_marshal_VOID__VOID,
1299                                         G_TYPE_NONE,
1300                                         0);
1301
1302        signals [ACTIVATED] = g_signal_new ("activated",
1303                                            G_TYPE_FROM_CLASS (object_class),
1304                                            G_SIGNAL_RUN_LAST,
1305                                            G_STRUCT_OFFSET (GdmChooserWidgetClass, activated),
1306                                            NULL,
1307                                            NULL,
1308                                            g_cclosure_marshal_VOID__VOID,
1309                                            G_TYPE_NONE,
1310                                            0);
1311
1312        signals [DEACTIVATED] = g_signal_new ("deactivated",
1313                                              G_TYPE_FROM_CLASS (object_class),
1314                                              G_SIGNAL_RUN_LAST,
1315                                              G_STRUCT_OFFSET (GdmChooserWidgetClass, deactivated),
1316                                              NULL,
1317                                              NULL,
1318                                              g_cclosure_marshal_VOID__VOID,
1319                                              G_TYPE_NONE,
1320                                              0);
1321
1322        g_object_class_install_property (object_class,
1323                                         PROP_INACTIVE_TEXT,
1324                                         g_param_spec_string ("inactive-text",
1325                                                              _("Inactive Text"),
1326                                                              _("The text to use in the label if the "
1327                                                                "user hasn't picked an item yet"),
1328                                                              NULL,
1329                                                              (G_PARAM_READWRITE |
1330                                                               G_PARAM_CONSTRUCT)));
1331        g_object_class_install_property (object_class,
1332                                         PROP_ACTIVE_TEXT,
1333                                         g_param_spec_string ("active-text",
1334                                                              _("Active Text"),
1335                                                              _("The text to use in the label if the "
1336                                                                "user has picked an item"),
1337                                                              NULL,
1338                                                              (G_PARAM_READWRITE |
1339                                                               G_PARAM_CONSTRUCT)));
1340
1341        g_object_class_install_property (object_class,
1342                                         PROP_LIST_VISIBLE,
1343                                         g_param_spec_boolean ("list-visible",
1344                                                              _("List Visible"),
1345                                                              _("Whether or not the chooser list is visible"),
1346                                                              TRUE,
1347                                                              G_PARAM_READABLE));
1348
1349        g_type_class_add_private (klass, sizeof (GdmChooserWidgetPrivate));
1350}
1351
1352static void
1353on_row_activated (GtkTreeView          *tree_view,
1354                  GtkTreePath          *tree_path,
1355                  GtkTreeViewColumn    *tree_column,
1356                  GdmChooserWidget     *widget)
1357{
1358        char *path_str;
1359
1360        path_str = gtk_tree_path_to_string (tree_path);
1361        g_debug ("GdmChooserWidget: row activated '%s'", path_str ? path_str : "(null)");
1362        g_free (path_str);
1363        gdm_chooser_widget_activate_selected_item (widget);
1364}
1365
1366static gboolean
1367path_is_separator (GdmChooserWidget *widget,
1368                   GtkTreeModel     *model,
1369                   GtkTreePath      *path)
1370{
1371        GtkTreePath      *separator_path;
1372        GtkTreePath      *translated_path;
1373        gboolean          is_separator;
1374
1375        separator_path = gtk_tree_row_reference_get_path (widget->priv->separator_row);
1376
1377        if (separator_path == NULL) {
1378                return FALSE;
1379        }
1380
1381        if (model == GTK_TREE_MODEL (widget->priv->model_sorter)) {
1382                GtkTreePath *filtered_path;
1383
1384                filtered_path = gtk_tree_model_sort_convert_path_to_child_path (widget->priv->model_sorter, path);
1385
1386                translated_path = gtk_tree_model_filter_convert_path_to_child_path (widget->priv->model_filter, filtered_path);
1387                gtk_tree_path_free (filtered_path);
1388        } else if (model == GTK_TREE_MODEL (widget->priv->model_filter)) {
1389                translated_path = gtk_tree_model_filter_convert_path_to_child_path (widget->priv->model_filter, path);
1390        } else {
1391                g_assert (model == GTK_TREE_MODEL (widget->priv->list_store));
1392                translated_path = gtk_tree_path_copy (path);
1393        }
1394
1395        if (gtk_tree_path_compare (separator_path, translated_path) == 0) {
1396                is_separator = TRUE;
1397        } else {
1398                is_separator = FALSE;
1399        }
1400        gtk_tree_path_free (translated_path);
1401
1402        return is_separator;
1403}
1404
1405static int
1406compare_item  (GtkTreeModel *model,
1407               GtkTreeIter  *a,
1408               GtkTreeIter  *b,
1409               gpointer      data)
1410{
1411        GdmChooserWidget *widget;
1412        char             *name_a;
1413        char             *name_b;
1414        char             *text_a;
1415        char             *text_b;
1416        gulong            prio_a;
1417        gulong            prio_b;
1418        gboolean          is_separate_a;
1419        gboolean          is_separate_b;
1420        int               result;
1421        int               direction;
1422        GtkTreeIter      *separator_iter;
1423        char             *id;
1424        PangoAttrList    *attrs;
1425
1426        g_assert (GDM_IS_CHOOSER_WIDGET (data));
1427
1428        widget = GDM_CHOOSER_WIDGET (data);
1429
1430        separator_iter = NULL;
1431        if (widget->priv->separator_row != NULL) {
1432
1433                GtkTreePath      *path_a;
1434                GtkTreePath      *path_b;
1435
1436                path_a = gtk_tree_model_get_path (model, a);
1437                path_b = gtk_tree_model_get_path (model, b);
1438
1439                if (path_is_separator (widget, model, path_a)) {
1440                        separator_iter = a;
1441                } else if (path_is_separator (widget, model, path_b)) {
1442                        separator_iter = b;
1443                }
1444
1445                gtk_tree_path_free (path_a);
1446                gtk_tree_path_free (path_b);
1447        }
1448
1449        name_a = NULL;
1450        is_separate_a = FALSE;
1451        if (separator_iter != a) {
1452                gtk_tree_model_get (model, a,
1453                                    CHOOSER_NAME_COLUMN, &name_a,
1454                                    CHOOSER_PRIORITY_COLUMN, &prio_a,
1455                                    CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate_a,
1456                                    -1);
1457        }
1458
1459        name_b = NULL;
1460        is_separate_b = FALSE;
1461        if (separator_iter != b) {
1462                gtk_tree_model_get (model, b,
1463                                    CHOOSER_NAME_COLUMN, &name_b,
1464                                    CHOOSER_ID_COLUMN, &id,
1465                                    CHOOSER_PRIORITY_COLUMN, &prio_b,
1466                                    CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate_b,
1467                                    -1);
1468        }
1469
1470        if (widget->priv->separator_position == GDM_CHOOSER_WIDGET_POSITION_TOP) {
1471                direction = -1;
1472        } else {
1473                direction = 1;
1474        }
1475
1476        if (separator_iter == b) {
1477                result = is_separate_a? 1 : -1;
1478                result *= direction;
1479        } else if (separator_iter == a) {
1480                result = is_separate_b? -1 : 1;
1481                result *= direction;
1482        } else if (is_separate_b == is_separate_a) {
1483                if (prio_a == prio_b) {
1484                        pango_parse_markup (name_a, -1, 0, &attrs, &text_a, NULL, NULL);
1485                        pango_parse_markup (name_b, -1, 0, &attrs, &text_b, NULL, NULL);
1486                        if (text_a && text_b)
1487                            result = g_utf8_collate (text_a, text_b);
1488                        else
1489                            result = g_utf8_collate (name_a, name_b);
1490                        g_free (text_a);
1491                        g_free (text_b);
1492                } else if (prio_a > prio_b) {
1493                        result = -1;
1494                } else {
1495                        result = 1;
1496                }
1497        } else {
1498                result = is_separate_a - is_separate_b;
1499                result *= direction;
1500        }
1501
1502        g_free (name_a);
1503        g_free (name_b);
1504
1505        return result;
1506}
1507
1508static void
1509name_cell_data_func (GtkTreeViewColumn  *tree_column,
1510                     GtkCellRenderer    *cell,
1511                     GtkTreeModel       *model,
1512                     GtkTreeIter        *iter,
1513                     GdmChooserWidget   *widget)
1514{
1515        gboolean is_in_use;
1516        char    *name;
1517        char    *markup;
1518
1519        name = NULL;
1520        gtk_tree_model_get (model,
1521                            iter,
1522                            CHOOSER_ITEM_IS_IN_USE_COLUMN, &is_in_use,
1523                            CHOOSER_NAME_COLUMN, &name,
1524                            -1);
1525
1526        if (is_in_use) {
1527                markup = g_strdup_printf ("<b>%s</b>\n"
1528                                          "<i><span size=\"x-small\">%s</span></i>",
1529                                          name ? name : "(null)", widget->priv->in_use_message);
1530        } else {
1531                markup = g_strdup_printf ("%s", name ? name : "(null)");
1532        }
1533        g_free (name);
1534
1535        g_object_set (cell, "markup", markup, NULL);
1536        g_free (markup);
1537}
1538
1539static void
1540check_cell_data_func (GtkTreeViewColumn    *tree_column,
1541                      GtkCellRenderer      *cell,
1542                      GtkTreeModel         *model,
1543                      GtkTreeIter          *iter,
1544                      GdmChooserWidget     *widget)
1545{
1546        gboolean   is_in_use;
1547        GdkPixbuf *pixbuf;
1548
1549        gtk_tree_model_get (model,
1550                            iter,
1551                            CHOOSER_ITEM_IS_IN_USE_COLUMN, &is_in_use,
1552                            -1);
1553
1554        if (is_in_use) {
1555                pixbuf = widget->priv->is_in_use_pixbuf;
1556        } else {
1557                pixbuf = NULL;
1558        }
1559
1560        g_object_set (cell, "pixbuf", pixbuf, NULL);
1561}
1562
1563static GdkPixbuf *
1564get_is_in_use_pixbuf (GdmChooserWidget *widget)
1565{
1566        GtkIconTheme *theme;
1567        GdkPixbuf    *pixbuf;
1568
1569        theme = gtk_icon_theme_get_default ();
1570        pixbuf = gtk_icon_theme_load_icon (theme,
1571                                           "emblem-default",
1572                                           GDM_CHOOSER_WIDGET_DEFAULT_ICON_SIZE / 3,
1573                                           0,
1574                                           NULL);
1575
1576        return pixbuf;
1577}
1578
1579static gboolean
1580separator_func (GtkTreeModel *model,
1581                GtkTreeIter  *iter,
1582                gpointer      data)
1583{
1584        GdmChooserWidget *widget;
1585        GtkTreePath      *path;
1586        gboolean          is_separator;
1587
1588        g_assert (GDM_IS_CHOOSER_WIDGET (data));
1589
1590        widget = GDM_CHOOSER_WIDGET (data);
1591
1592        g_assert (widget->priv->separator_row != NULL);
1593
1594        path = gtk_tree_model_get_path (model, iter);
1595
1596        is_separator = path_is_separator (widget, model, path);
1597
1598        gtk_tree_path_free (path);
1599
1600        return is_separator;
1601}
1602
1603static void
1604add_separator (GdmChooserWidget *widget)
1605{
1606        GtkTreeIter   iter;
1607        GtkTreeModel *model;
1608        GtkTreePath  *path;
1609
1610        g_assert (widget->priv->separator_row == NULL);
1611
1612        model = GTK_TREE_MODEL (widget->priv->list_store);
1613
1614        gtk_list_store_insert_with_values (widget->priv->list_store,
1615                                           &iter, 0,
1616                                           CHOOSER_ID_COLUMN, "-", -1);
1617        path = gtk_tree_model_get_path (model, &iter);
1618        widget->priv->separator_row = gtk_tree_row_reference_new (model, path);
1619        gtk_tree_path_free (path);
1620}
1621
1622static gboolean
1623update_column_visibility (GdmChooserWidget *widget)
1624{
1625        if (widget->priv->number_of_rows_with_images > 0) {
1626                gtk_tree_view_column_set_visible (widget->priv->image_column,
1627                                                  TRUE);
1628        } else {
1629                gtk_tree_view_column_set_visible (widget->priv->image_column,
1630                                                  FALSE);
1631        }
1632        if (widget->priv->number_of_rows_with_status > 0) {
1633                gtk_tree_view_column_set_visible (widget->priv->status_column,
1634                                                  TRUE);
1635        } else {
1636                gtk_tree_view_column_set_visible (widget->priv->status_column,
1637                                                  FALSE);
1638        }
1639
1640        return FALSE;
1641}
1642
1643static void
1644clear_canceled_visibility_update (GdmChooserWidget *widget)
1645{
1646        widget->priv->update_idle_id = 0;
1647}
1648
1649static void
1650queue_column_visibility_update (GdmChooserWidget *widget)
1651{
1652        if (widget->priv->update_idle_id == 0) {
1653                widget->priv->update_idle_id =
1654                        g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
1655                                         (GSourceFunc)
1656                                         update_column_visibility, widget,
1657                                         (GDestroyNotify)
1658                                         clear_canceled_visibility_update);
1659        }
1660}
1661
1662static void
1663on_row_changed (GtkTreeModel     *model,
1664                GtkTreePath      *path,
1665                GtkTreeIter      *iter,
1666                GdmChooserWidget *widget)
1667{
1668        queue_column_visibility_update (widget);
1669}
1670
1671static void
1672add_frame (GdmChooserWidget *widget)
1673{
1674        widget->priv->frame = gtk_frame_new (NULL);
1675        gtk_frame_set_shadow_type (GTK_FRAME (widget->priv->frame),
1676                                   GTK_SHADOW_NONE);
1677        gtk_widget_show (widget->priv->frame);
1678        gtk_container_add (GTK_CONTAINER (widget), widget->priv->frame);
1679
1680        widget->priv->frame_alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
1681        gtk_widget_show (widget->priv->frame_alignment);
1682        gtk_container_add (GTK_CONTAINER (widget->priv->frame),
1683                           widget->priv->frame_alignment);
1684}
1685
1686static gboolean
1687on_button_release (GtkTreeView      *items_view,
1688                   GdkEventButton   *event,
1689                   GdmChooserWidget *widget)
1690{
1691        GtkTreeModel     *model;
1692        GtkTreeIter       iter;
1693        GtkTreeSelection *selection;
1694
1695        if (!widget->priv->should_hide_inactive_items) {
1696                return FALSE;
1697        }
1698
1699        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view));
1700        if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
1701                GtkTreePath *path;
1702
1703                path = gtk_tree_model_get_path (model, &iter);
1704                gtk_tree_view_row_activated (GTK_TREE_VIEW (items_view),
1705                                             path, NULL);
1706                gtk_tree_path_free (path);
1707        }
1708
1709        return FALSE;
1710}
1711
1712static gboolean
1713search_equal_func (GtkTreeModel     *model,
1714                   int               column,
1715                   const char       *key,
1716                   GtkTreeIter      *iter,
1717                   GdmChooserWidget *widget)
1718{
1719        char       *id;
1720        char       *name;
1721        char       *key_folded;
1722        gboolean    ret;
1723
1724        if (key == NULL) {
1725                return FALSE;
1726        }
1727
1728        ret = TRUE;
1729        id = NULL;
1730        name = NULL;
1731
1732        key_folded = g_utf8_casefold (key, -1);
1733
1734        gtk_tree_model_get (model,
1735                            iter,
1736                            CHOOSER_ID_COLUMN, &id,
1737                            CHOOSER_NAME_COLUMN, &name,
1738                            -1);
1739        if (name != NULL) {
1740                char *name_folded;
1741
1742                name_folded = g_utf8_casefold (name, -1);
1743                ret = !g_str_has_prefix (name_folded, key_folded);
1744                g_free (name_folded);
1745
1746                if (!ret) {
1747                        goto out;
1748                }
1749        }
1750
1751        if (id != NULL) {
1752                char *id_folded;
1753
1754
1755                id_folded = g_utf8_casefold (id, -1);
1756                ret = !g_str_has_prefix (id_folded, key_folded);
1757                g_free (id_folded);
1758
1759                if (!ret) {
1760                        goto out;
1761                }
1762        }
1763 out:
1764        g_free (id);
1765        g_free (name);
1766        g_free (key_folded);
1767
1768        return ret;
1769}
1770
1771static void
1772on_selection_changed (GtkTreeSelection *selection,
1773                      GdmChooserWidget *widget)
1774{
1775        GtkTreePath *path;
1776
1777        get_selected_list_path (widget, &path);
1778        if (path != NULL) {
1779                char *path_str;
1780                path_str = gtk_tree_path_to_string (path);
1781                g_debug ("GdmChooserWidget: selection change to list path '%s'", path_str);
1782                g_free (path_str);
1783        } else {
1784                g_debug ("GdmChooserWidget: selection cleared");
1785        }
1786}
1787
1788static void
1789gdm_chooser_widget_init (GdmChooserWidget *widget)
1790{
1791        GtkTreeViewColumn *column;
1792        GtkTreeSelection  *selection;
1793        GtkCellRenderer   *renderer;
1794
1795        widget->priv = GDM_CHOOSER_WIDGET_GET_PRIVATE (widget);
1796
1797        /* Even though, we're a container and also don't ever take
1798         * focus for ourselve, we set CAN_FOCUS so that gtk_widget_grab_focus
1799         * works on us.  We then override grab_focus requests to
1800         * be redirected our internal tree view
1801         */
1802        GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS);
1803
1804        widget->priv->height_when_grown = get_height_of_screen (widget);
1805
1806        gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 0, 0, 0, 0);
1807
1808        add_frame (widget);
1809
1810        widget->priv->scrollable_widget = gdm_scrollable_widget_new ();
1811        gtk_widget_show (widget->priv->scrollable_widget);
1812        gtk_container_add (GTK_CONTAINER (widget->priv->frame_alignment),
1813                           widget->priv->scrollable_widget);
1814
1815        widget->priv->items_view = gtk_tree_view_new ();
1816        gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (widget->priv->items_view),
1817                                           FALSE);
1818        g_signal_connect (widget->priv->items_view,
1819                          "row-activated",
1820                          G_CALLBACK (on_row_activated),
1821                          widget);
1822
1823        gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (widget->priv->items_view),
1824                                             (GtkTreeViewSearchEqualFunc)search_equal_func,
1825                                             widget,
1826                                             NULL);
1827
1828        /* hack to make single-click activate work
1829         */
1830        g_signal_connect_after (widget->priv->items_view,
1831                               "button-release-event",
1832                               G_CALLBACK (on_button_release),
1833                               widget);
1834
1835        gtk_widget_show (widget->priv->items_view);
1836        gtk_container_add (GTK_CONTAINER (widget->priv->scrollable_widget),
1837                           widget->priv->items_view);
1838
1839        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->items_view));
1840        gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
1841
1842        g_signal_connect (selection, "changed", G_CALLBACK (on_selection_changed), widget);
1843
1844        g_assert (NUMBER_OF_CHOOSER_COLUMNS == 11);
1845        widget->priv->list_store = gtk_list_store_new (NUMBER_OF_CHOOSER_COLUMNS,
1846                                                       GDK_TYPE_PIXBUF,
1847                                                       G_TYPE_STRING,
1848                                                       G_TYPE_STRING,
1849                                                       G_TYPE_ULONG,
1850                                                       G_TYPE_BOOLEAN,
1851                                                       G_TYPE_BOOLEAN,
1852                                                       G_TYPE_BOOLEAN,
1853                                                       G_TYPE_DOUBLE,
1854                                                       G_TYPE_DOUBLE,
1855                                                       G_TYPE_DOUBLE,
1856                                                       G_TYPE_STRING);
1857
1858        widget->priv->model_filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (widget->priv->list_store), NULL));
1859
1860        gtk_tree_model_filter_set_visible_column (widget->priv->model_filter,
1861                                                  CHOOSER_ITEM_IS_VISIBLE_COLUMN);
1862        g_signal_connect (G_OBJECT (widget->priv->model_filter), "row-changed",
1863                          G_CALLBACK (on_row_changed), widget);
1864
1865        widget->priv->model_sorter = GTK_TREE_MODEL_SORT (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (widget->priv->model_filter)));
1866
1867        gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (widget->priv->model_sorter),
1868                                         CHOOSER_ID_COLUMN,
1869                                         compare_item,
1870                                         widget, NULL);
1871
1872        gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (widget->priv->model_sorter),
1873                                              CHOOSER_ID_COLUMN,
1874                                              GTK_SORT_ASCENDING);
1875        gtk_tree_view_set_model (GTK_TREE_VIEW (widget->priv->items_view),
1876                                 GTK_TREE_MODEL (widget->priv->model_sorter));
1877        gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (widget->priv->items_view),
1878                                              separator_func,
1879                                              widget, NULL);
1880
1881        /* IMAGE COLUMN */
1882        renderer = gtk_cell_renderer_pixbuf_new ();
1883        column = gtk_tree_view_column_new ();
1884        gtk_tree_view_column_pack_start (column, renderer, FALSE);
1885        gtk_tree_view_append_column (GTK_TREE_VIEW (widget->priv->items_view), column);
1886        widget->priv->image_column = column;
1887
1888        gtk_tree_view_column_set_attributes (column,
1889                                             renderer,
1890                                             "pixbuf", CHOOSER_IMAGE_COLUMN,
1891                                             NULL);
1892
1893        g_object_set (renderer,
1894                      "xalign", 1.0,
1895                      NULL);
1896
1897        /* NAME COLUMN */
1898        renderer = gtk_cell_renderer_text_new ();
1899        column = gtk_tree_view_column_new ();
1900        gtk_tree_view_column_pack_start (column, renderer, FALSE);
1901        gtk_tree_view_append_column (GTK_TREE_VIEW (widget->priv->items_view), column);
1902        gtk_tree_view_column_set_cell_data_func (column,
1903                                                 renderer,
1904                                                 (GtkTreeCellDataFunc) name_cell_data_func,
1905                                                 widget,
1906                                                 NULL);
1907
1908        gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (widget->priv->items_view),
1909                                          CHOOSER_COMMENT_COLUMN);
1910
1911        /* STATUS COLUMN */
1912        renderer = gtk_cell_renderer_pixbuf_new ();
1913        column = gtk_tree_view_column_new ();
1914        gtk_tree_view_column_pack_start (column, renderer, FALSE);
1915        gtk_tree_view_append_column (GTK_TREE_VIEW (widget->priv->items_view), column);
1916        widget->priv->status_column = column;
1917
1918        gtk_tree_view_column_set_cell_data_func (column,
1919                                                 renderer,
1920                                                 (GtkTreeCellDataFunc) check_cell_data_func,
1921                                                 widget,
1922                                                 NULL);
1923        widget->priv->is_in_use_pixbuf = get_is_in_use_pixbuf (widget);
1924
1925        renderer = gdm_cell_renderer_timer_new ();
1926        gtk_tree_view_column_pack_start (column, renderer, FALSE);
1927        gtk_tree_view_column_add_attribute (column, renderer, "value",
1928                                            CHOOSER_TIMER_VALUE_COLUMN);
1929
1930        widget->priv->rows_with_timers =
1931            g_hash_table_new_full (g_str_hash,
1932                                   g_str_equal,
1933                                  (GDestroyNotify) g_free,
1934                                  (GDestroyNotify)
1935                                  gtk_tree_row_reference_free);
1936
1937        add_separator (widget);
1938
1939        queue_column_visibility_update (widget);
1940        gdm_chooser_widget_grow (widget);
1941}
1942
1943static void
1944gdm_chooser_widget_finalize (GObject *object)
1945{
1946        GdmChooserWidget *widget;
1947
1948        g_return_if_fail (object != NULL);
1949        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (object));
1950
1951        widget = GDM_CHOOSER_WIDGET (object);
1952
1953        g_return_if_fail (widget->priv != NULL);
1954
1955        g_hash_table_destroy (widget->priv->rows_with_timers);
1956        widget->priv->rows_with_timers = NULL;
1957
1958        G_OBJECT_CLASS (gdm_chooser_widget_parent_class)->finalize (object);
1959}
1960
1961GtkWidget *
1962gdm_chooser_widget_new (const char *inactive_text,
1963                        const char *active_text)
1964{
1965        GObject *object;
1966
1967        object = g_object_new (GDM_TYPE_CHOOSER_WIDGET,
1968                               "inactive-text", inactive_text,
1969                               "active-text", active_text, NULL);
1970
1971        return GTK_WIDGET (object);
1972}
1973
1974void
1975gdm_chooser_widget_update_item (GdmChooserWidget *widget,
1976                                const char       *id,
1977                                GdkPixbuf        *new_image,
1978                                const char       *new_name,
1979                                const char       *new_comment,
1980                                gulong            new_priority,
1981                                gboolean          new_in_use,
1982                                gboolean          new_is_separate)
1983{
1984        GtkTreeModel *model;
1985        GtkTreeIter   iter;
1986        GdkPixbuf    *image;
1987        gboolean      is_separate;
1988        gboolean      in_use;
1989
1990        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
1991
1992        model = GTK_TREE_MODEL (widget->priv->list_store);
1993
1994        if (!find_item (widget, id, &iter)) {
1995                g_critical ("Tried to remove non-existing item from chooser");
1996                return;
1997        }
1998
1999        is_separate = FALSE;
2000        gtk_tree_model_get (model, &iter,
2001                            CHOOSER_IMAGE_COLUMN, &image,
2002                            CHOOSER_ITEM_IS_IN_USE_COLUMN, &in_use,
2003                            CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate,
2004                            -1);
2005
2006        if (image != new_image) {
2007                if (image == NULL && new_image != NULL) {
2008                        widget->priv->number_of_rows_with_images++;
2009                } else if (image != NULL && new_image == NULL) {
2010                        widget->priv->number_of_rows_with_images--;
2011                }
2012                queue_column_visibility_update (widget);
2013        }
2014        if (image != NULL) {
2015                g_object_unref (image);
2016        }
2017
2018        if (in_use != new_in_use) {
2019                if (new_in_use) {
2020                        widget->priv->number_of_rows_with_status++;
2021                } else {
2022                        widget->priv->number_of_rows_with_status--;
2023                }
2024                queue_column_visibility_update (widget);
2025        }
2026
2027        if (is_separate != new_is_separate) {
2028                if (new_is_separate) {
2029                        widget->priv->number_of_separated_rows++;
2030                        widget->priv->number_of_normal_rows--;
2031                } else {
2032                        widget->priv->number_of_separated_rows--;
2033                        widget->priv->number_of_normal_rows++;
2034                }
2035                update_separator_visibility (widget);
2036        }
2037
2038        gtk_list_store_set (widget->priv->list_store,
2039                            &iter,
2040                            CHOOSER_IMAGE_COLUMN, new_image,
2041                            CHOOSER_NAME_COLUMN, new_name,
2042                            CHOOSER_COMMENT_COLUMN, new_comment,
2043                            CHOOSER_PRIORITY_COLUMN, new_priority,
2044                            CHOOSER_ITEM_IS_IN_USE_COLUMN, new_in_use,
2045                            CHOOSER_ITEM_IS_SEPARATED_COLUMN, new_is_separate,
2046                            -1);
2047}
2048
2049void
2050gdm_chooser_widget_add_item (GdmChooserWidget *widget,
2051                             const char       *id,
2052                             GdkPixbuf        *image,
2053                             const char       *name,
2054                             const char       *comment,
2055                             gulong            priority,
2056                             gboolean          in_use,
2057                             gboolean          keep_separate)
2058{
2059        gboolean is_visible;
2060
2061        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
2062
2063        if (keep_separate) {
2064                widget->priv->number_of_separated_rows++;
2065        } else {
2066                widget->priv->number_of_normal_rows++;
2067        }
2068        update_separator_visibility (widget);
2069
2070        if (in_use) {
2071                widget->priv->number_of_rows_with_status++;
2072                queue_column_visibility_update (widget);
2073        }
2074
2075        if (image != NULL) {
2076                widget->priv->number_of_rows_with_images++;
2077                queue_column_visibility_update (widget);
2078        }
2079
2080        is_visible = widget->priv->active_row == NULL;
2081
2082        gtk_list_store_insert_with_values (widget->priv->list_store,
2083                                           NULL, 0,
2084                                           CHOOSER_IMAGE_COLUMN, image,
2085                                           CHOOSER_NAME_COLUMN, name,
2086                                           CHOOSER_COMMENT_COLUMN, comment,
2087                                           CHOOSER_PRIORITY_COLUMN, priority,
2088                                           CHOOSER_ITEM_IS_IN_USE_COLUMN, in_use,
2089                                           CHOOSER_ITEM_IS_SEPARATED_COLUMN, keep_separate,
2090                                           CHOOSER_ITEM_IS_VISIBLE_COLUMN, is_visible,
2091                                           CHOOSER_ID_COLUMN, id,
2092                                           -1);
2093
2094        move_cursor_to_top (widget);
2095        update_chooser_visibility (widget);
2096}
2097
2098void
2099gdm_chooser_widget_remove_item (GdmChooserWidget *widget,
2100                                const char       *id)
2101{
2102        GtkTreeModel *model;
2103        GtkTreeIter   iter;
2104        GdkPixbuf    *image;
2105        gboolean      is_separate;
2106        gboolean      is_in_use;
2107
2108        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
2109
2110        model = GTK_TREE_MODEL (widget->priv->list_store);
2111
2112        if (!find_item (widget, id, &iter)) {
2113                g_critical ("Tried to remove non-existing item from chooser");
2114                return;
2115        }
2116
2117        is_separate = FALSE;
2118        gtk_tree_model_get (model, &iter,
2119                            CHOOSER_IMAGE_COLUMN, &image,
2120                            CHOOSER_ITEM_IS_IN_USE_COLUMN, &is_in_use,
2121                            CHOOSER_ITEM_IS_SEPARATED_COLUMN, &is_separate,
2122                            -1);
2123
2124        if (image != NULL) {
2125                widget->priv->number_of_rows_with_images--;
2126                g_object_unref (image);
2127        }
2128
2129        if (is_in_use) {
2130                widget->priv->number_of_rows_with_status--;
2131                queue_column_visibility_update (widget);
2132        }
2133
2134        if (is_separate) {
2135                widget->priv->number_of_separated_rows--;
2136        } else {
2137                widget->priv->number_of_normal_rows--;
2138        }
2139        update_separator_visibility (widget);
2140
2141        gtk_list_store_remove (widget->priv->list_store, &iter);
2142
2143        move_cursor_to_top (widget);
2144        update_chooser_visibility (widget);
2145}
2146
2147gboolean
2148gdm_chooser_widget_lookup_item (GdmChooserWidget *widget,
2149                                const char       *id,
2150                                GdkPixbuf       **image,
2151                                char            **name,
2152                                char            **comment,
2153                                gulong           *priority,
2154                                gboolean         *is_in_use,
2155                                gboolean         *is_separate)
2156{
2157        GtkTreeIter   iter;
2158        char         *active_item_id;
2159
2160        g_return_val_if_fail (GDM_IS_CHOOSER_WIDGET (widget), FALSE);
2161        g_return_val_if_fail (id != NULL, FALSE);
2162
2163        active_item_id = get_active_item_id (widget, &iter);
2164
2165        if (active_item_id == NULL || strcmp (active_item_id, id) != 0) {
2166                g_free (active_item_id);
2167
2168                if (!find_item (widget, id, &iter)) {
2169                        return FALSE;
2170                }
2171        }
2172        g_free (active_item_id);
2173
2174        gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter,
2175                            CHOOSER_IMAGE_COLUMN, image,
2176                            CHOOSER_NAME_COLUMN, name,
2177                            CHOOSER_PRIORITY_COLUMN, priority,
2178                            CHOOSER_ITEM_IS_IN_USE_COLUMN, is_in_use,
2179                            CHOOSER_ITEM_IS_SEPARATED_COLUMN, is_separate,
2180                            -1);
2181
2182        return TRUE;
2183}
2184
2185void
2186gdm_chooser_widget_set_item_in_use (GdmChooserWidget *widget,
2187                                    const char       *id,
2188                                    gboolean          is_in_use)
2189{
2190        GtkTreeIter   iter;
2191        gboolean      was_in_use;
2192
2193        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
2194
2195        if (!find_item (widget, id, &iter)) {
2196                return;
2197        }
2198
2199        gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter,
2200                            CHOOSER_ITEM_IS_IN_USE_COLUMN, &was_in_use,
2201                            -1);
2202
2203        if (was_in_use != is_in_use) {
2204
2205                if (is_in_use) {
2206                        widget->priv->number_of_rows_with_status++;
2207                } else {
2208                        widget->priv->number_of_rows_with_status--;
2209                }
2210                queue_column_visibility_update (widget);
2211
2212                gtk_list_store_set (widget->priv->list_store,
2213                                    &iter,
2214                                    CHOOSER_ITEM_IS_IN_USE_COLUMN, is_in_use,
2215                                    -1);
2216
2217        }
2218}
2219
2220void
2221gdm_chooser_widget_set_item_priority (GdmChooserWidget *widget,
2222                                      const char       *id,
2223                                      gulong            priority)
2224{
2225        GtkTreeIter   iter;
2226        gulong        was_priority;
2227
2228        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
2229
2230        if (!find_item (widget, id, &iter)) {
2231                return;
2232        }
2233
2234        gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->list_store), &iter,
2235                            CHOOSER_PRIORITY_COLUMN, &was_priority,
2236                            -1);
2237
2238        if (was_priority != priority) {
2239
2240                gtk_list_store_set (widget->priv->list_store,
2241                                    &iter,
2242                                    CHOOSER_PRIORITY_COLUMN, priority,
2243                                    -1);
2244                gtk_tree_model_filter_refilter (widget->priv->model_filter);
2245        }
2246}
2247
2248static double
2249get_current_time (void)
2250{
2251  const double microseconds_per_second = 1000000.0;
2252  double       timestamp;
2253  GTimeVal     now;
2254
2255  g_get_current_time (&now);
2256
2257  timestamp = ((microseconds_per_second * now.tv_sec) + now.tv_usec) /
2258               microseconds_per_second;
2259
2260  return timestamp;
2261}
2262
2263static gboolean
2264on_timer_timeout (GdmChooserWidget *widget)
2265{
2266        GHashTableIter  iter;
2267        GSList         *list;
2268        GSList         *tmp;
2269        gpointer        key;
2270        gpointer        value;
2271        double          now;
2272
2273        list = NULL;
2274        g_hash_table_iter_init (&iter, widget->priv->rows_with_timers);
2275        while (g_hash_table_iter_next (&iter, &key, &value)) {
2276                list = g_slist_prepend (list, value);
2277        }
2278
2279        now = get_current_time ();
2280        for (tmp = list; tmp != NULL; tmp = tmp->next) {
2281                GtkTreeRowReference *row;
2282
2283                row = (GtkTreeRowReference *) tmp->data;
2284
2285                update_timer_from_time (widget, row, now);
2286        }
2287        g_slist_free (list);
2288
2289        return TRUE;
2290}
2291
2292static void
2293start_timer (GdmChooserWidget    *widget,
2294             GtkTreeRowReference *row,
2295             double               duration)
2296{
2297        GtkTreeModel *model;
2298        GtkTreePath  *path;
2299        GtkTreeIter   iter;
2300
2301        model = GTK_TREE_MODEL (widget->priv->list_store);
2302
2303        path = gtk_tree_row_reference_get_path (row);
2304        gtk_tree_model_get_iter (model, &iter, path);
2305        gtk_tree_path_free (path);
2306
2307        gtk_list_store_set (widget->priv->list_store, &iter,
2308                            CHOOSER_TIMER_START_TIME_COLUMN,
2309                            get_current_time (), -1);
2310        gtk_list_store_set (widget->priv->list_store, &iter,
2311                            CHOOSER_TIMER_DURATION_COLUMN,
2312                            duration, -1);
2313        gtk_list_store_set (widget->priv->list_store, &iter,
2314                            CHOOSER_TIMER_VALUE_COLUMN, 0.0, -1);
2315
2316        widget->priv->number_of_active_timers++;
2317        if (widget->priv->timer_animation_timeout_id == 0) {
2318                g_assert (g_hash_table_size (widget->priv->rows_with_timers) == 1);
2319
2320                widget->priv->timer_animation_timeout_id =
2321                    g_timeout_add (1000 / 20,
2322                                   (GSourceFunc) on_timer_timeout,
2323                                   widget);
2324
2325        }
2326}
2327
2328static void
2329stop_timer (GdmChooserWidget    *widget,
2330            GtkTreeRowReference *row)
2331{
2332        GtkTreeModel *model;
2333        GtkTreePath  *path;
2334        GtkTreeIter   iter;
2335
2336        model = GTK_TREE_MODEL (widget->priv->list_store);
2337
2338        path = gtk_tree_row_reference_get_path (row);
2339        gtk_tree_model_get_iter (model, &iter, path);
2340        gtk_tree_path_free (path);
2341
2342        gtk_list_store_set (widget->priv->list_store, &iter,
2343                            CHOOSER_TIMER_START_TIME_COLUMN,
2344                            0.0, -1);
2345        gtk_list_store_set (widget->priv->list_store, &iter,
2346                            CHOOSER_TIMER_DURATION_COLUMN,
2347                            0.0, -1);
2348        gtk_list_store_set (widget->priv->list_store, &iter,
2349                            CHOOSER_TIMER_VALUE_COLUMN, 0.0, -1);
2350
2351        widget->priv->number_of_active_timers--;
2352        if (widget->priv->number_of_active_timers == 0) {
2353                g_source_remove (widget->priv->timer_animation_timeout_id);
2354                widget->priv->timer_animation_timeout_id = 0;
2355        }
2356}
2357
2358static void
2359update_timer_from_time (GdmChooserWidget    *widget,
2360                        GtkTreeRowReference *row,
2361                        double               now)
2362{
2363        GtkTreeModel *model;
2364        GtkTreePath  *path;
2365        GtkTreeIter   iter;
2366        double        start_time;
2367        double        duration;
2368        double        elapsed_ratio;
2369
2370        model = GTK_TREE_MODEL (widget->priv->list_store);
2371
2372        path = gtk_tree_row_reference_get_path (row);
2373        gtk_tree_model_get_iter (model, &iter, path);
2374        gtk_tree_path_free (path);
2375
2376        gtk_tree_model_get (model, &iter,
2377                            CHOOSER_TIMER_START_TIME_COLUMN, &start_time,
2378                            CHOOSER_TIMER_DURATION_COLUMN, &duration, -1);
2379
2380        if (duration > G_MINDOUBLE) {
2381                elapsed_ratio = (now - start_time) / duration;
2382        } else {
2383                elapsed_ratio = 0.0;
2384        }
2385
2386        gtk_list_store_set (widget->priv->list_store, &iter,
2387                            CHOOSER_TIMER_VALUE_COLUMN,
2388                            elapsed_ratio, -1);
2389
2390        if (elapsed_ratio > .999) {
2391                char *id;
2392
2393                stop_timer (widget, row);
2394
2395                gtk_tree_model_get (model, &iter,
2396                                    CHOOSER_ID_COLUMN, &id, -1);
2397                g_hash_table_remove (widget->priv->rows_with_timers,
2398                                     id);
2399                g_free (id);
2400
2401                widget->priv->number_of_rows_with_status--;
2402                queue_column_visibility_update (widget);
2403        }
2404}
2405
2406void
2407gdm_chooser_widget_set_item_timer (GdmChooserWidget *widget,
2408                                   const char       *id,
2409                                   gulong            timeout)
2410{
2411        GtkTreeModel        *model;
2412        GtkTreeRowReference *row;
2413
2414        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
2415
2416        model = GTK_TREE_MODEL (widget->priv->list_store);
2417
2418        row = g_hash_table_lookup (widget->priv->rows_with_timers,
2419                                   id);
2420
2421        g_assert (row == NULL || gtk_tree_row_reference_valid (row));
2422
2423        if (row != NULL) {
2424                stop_timer (widget, row);
2425        }
2426
2427        if (timeout == 0) {
2428                if (row == NULL) {
2429                        g_warning ("could not find item with id '%s' to "
2430                                   "remove timer", id);
2431                        return;
2432                }
2433
2434                g_hash_table_remove (widget->priv->rows_with_timers,
2435                                     id);
2436                gtk_tree_model_filter_refilter (widget->priv->model_filter);
2437                return;
2438        }
2439
2440        if (row == NULL) {
2441                GtkTreeIter  iter;
2442                GtkTreePath *path;
2443
2444                if (!find_item (widget, id, &iter)) {
2445                        g_warning ("could not find item with id '%s' to "
2446                                   "add timer", id);
2447                        return;
2448                }
2449
2450                path = gtk_tree_model_get_path (model, &iter);
2451                row = gtk_tree_row_reference_new (model, path);
2452
2453                g_hash_table_insert (widget->priv->rows_with_timers,
2454                                     g_strdup (id), row);
2455
2456                widget->priv->number_of_rows_with_status++;
2457                queue_column_visibility_update (widget);
2458        }
2459
2460        start_timer (widget, row, timeout / 1000.0);
2461}
2462
2463void
2464gdm_chooser_widget_set_in_use_message (GdmChooserWidget *widget,
2465                                       const char       *message)
2466{
2467        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
2468
2469        g_free (widget->priv->in_use_message);
2470        widget->priv->in_use_message = g_strdup (message);
2471}
2472
2473void
2474gdm_chooser_widget_set_separator_position (GdmChooserWidget         *widget,
2475                                           GdmChooserWidgetPosition  position)
2476{
2477        g_return_if_fail (GDM_IS_CHOOSER_WIDGET (widget));
2478
2479        if (widget->priv->separator_position != position) {
2480                widget->priv->separator_position = position;
2481        }
2482
2483        gtk_tree_model_filter_refilter (widget->priv->model_filter);
2484}
2485
2486void
2487gdm_chooser_widget_set_hide_inactive_items (GdmChooserWidget  *widget,
2488                                            gboolean           should_hide)
2489{
2490        widget->priv->should_hide_inactive_items = should_hide;
2491
2492        if (should_hide &&
2493            (widget->priv->state != GDM_CHOOSER_WIDGET_STATE_SHRUNK
2494             || widget->priv->state != GDM_CHOOSER_WIDGET_STATE_SHRINKING) &&
2495            widget->priv->active_row != NULL) {
2496                gdm_chooser_widget_shrink (widget);
2497        } else if (!should_hide &&
2498              (widget->priv->state != GDM_CHOOSER_WIDGET_STATE_GROWN
2499               || widget->priv->state != GDM_CHOOSER_WIDGET_STATE_GROWING)) {
2500                gdm_chooser_widget_grow (widget);
2501        }
2502}
2503
2504int
2505gdm_chooser_widget_get_number_of_items (GdmChooserWidget *widget)
2506{
2507        return widget->priv->number_of_normal_rows +
2508               widget->priv->number_of_separated_rows;
2509}
2510
2511void
2512gdm_chooser_widget_activate_if_one_item (GdmChooserWidget *widget)
2513{
2514        activate_if_one_item (widget);
2515}
2516
2517void
2518gdm_chooser_widget_propagate_pending_key_events (GdmChooserWidget *widget)
2519{
2520        if (!gdm_scrollable_widget_has_queued_key_events (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget))) {
2521                return;
2522        }
2523
2524        gdm_scrollable_widget_replay_queued_key_events (GDM_SCROLLABLE_WIDGET (widget->priv->scrollable_widget));
2525}
2526
2527void
2528gdm_chooser_widget_loaded (GdmChooserWidget *widget)
2529{
2530        g_signal_emit (widget, signals[LOADED], 0);
2531}
Note: See TracBrowser for help on using the repository browser.