]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-groups-widget.c
Merge branch 'gnome-3-8'
[empathy.git] / libempathy-gtk / empathy-groups-widget.c
1 /*
2  * Copyright (C) 2007-2010 Collabora Ltd.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *
18  * Authors: Xavier Claessens <xclaesse@gmail.com>
19  *          Philip Withnall <philip.withnall@collabora.co.uk>
20  */
21
22 #include "config.h"
23 #include "empathy-groups-widget.h"
24
25 #include <glib/gi18n-lib.h>
26
27 #include "empathy-utils.h"
28 #include "empathy-connection-aggregator.h"
29
30 /**
31  * SECTION:empathy-groups-widget
32  * @title:EmpathyGroupsWidget
33  * @short_description: A widget used to edit the groups of a #FolksGroupDetails
34  * @include: libempathy-gtk/empathy-groups-widget.h
35  *
36  * #EmpathyGroupsWidget is a widget which lists the groups of a
37  * #FolksGroupDetails (i.e. a #FolksPersona or a #FolksIndividual) and allows
38  * them to be added and removed.
39  */
40
41 /**
42  * EmpathyGroupsWidget:
43  * @parent: parent object
44  *
45  * Widget which displays and allows editing of the groups of a
46  * #FolksGroupDetails (i.e. a #FolksPersona or #FolksIndividual).
47  */
48
49 /* Delay before updating the widget when the id entry changed (seconds) */
50 #define ID_CHANGED_TIMEOUT 1
51
52 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyGroupsWidget)
53
54 typedef struct
55 {
56   /* The object we're actually changing the groups of */
57   FolksGroupDetails *group_details; /* owned */
58   GtkListStore *group_store; /* owned */
59
60   GtkWidget *add_group_entry; /* child widget */
61   GtkWidget *add_group_button; /* child widget */
62 } EmpathyGroupsWidgetPriv;
63
64 enum {
65   PROP_GROUP_DETAILS = 1,
66 };
67
68 enum {
69   COL_NAME,
70   COL_ENABLED,
71   COL_EDITABLE
72 };
73 #define NUM_COLUMNS COL_EDITABLE + 1
74
75 G_DEFINE_TYPE (EmpathyGroupsWidget, empathy_groups_widget, GTK_TYPE_BOX);
76
77 typedef struct
78 {
79   EmpathyGroupsWidget *widget;
80   const gchar *name;
81   gboolean found;
82   GtkTreeIter found_iter;
83 } FindNameData;
84
85 static gboolean
86 model_find_name_foreach (GtkTreeModel *model,
87     GtkTreePath *path,
88     GtkTreeIter *iter,
89     FindNameData *data)
90 {
91   gchar *name;
92
93   gtk_tree_model_get (model, iter,
94       COL_NAME, &name,
95       -1);
96
97   if (name != NULL && strcmp (data->name, name) == 0)
98     {
99       data->found = TRUE;
100       data->found_iter = *iter;
101
102       g_free (name);
103
104       return TRUE;
105     }
106
107   g_free (name);
108
109   return FALSE;
110 }
111
112 static gboolean
113 model_find_name (EmpathyGroupsWidget *self,
114     const gchar *name,
115     GtkTreeIter *iter)
116 {
117   EmpathyGroupsWidgetPriv *priv = GET_PRIV (self);
118   FindNameData data;
119
120   if (EMP_STR_EMPTY (name))
121     return FALSE;
122
123   data.widget = self;
124   data.name = name;
125   data.found = FALSE;
126
127   gtk_tree_model_foreach (GTK_TREE_MODEL (priv->group_store),
128       (GtkTreeModelForeachFunc) model_find_name_foreach, &data);
129
130   if (data.found == TRUE)
131     {
132       *iter = data.found_iter;
133       return TRUE;
134     }
135
136   return FALSE;
137 }
138
139 static void
140 populate_data (EmpathyGroupsWidget *self)
141 {
142   EmpathyGroupsWidgetPriv *priv = GET_PRIV (self);
143   EmpathyConnectionAggregator *aggregator;
144   GeeSet *member_groups;
145   GList *all_groups, *l;
146
147   /* Remove the old groups */
148   gtk_list_store_clear (priv->group_store);
149
150   /* FIXME: We have to get the whole group list from
151    * EmpathyConnectionAggregator, as libfolks hasn't grown API to get the whole
152    * group list yet. (bgo#627398) */
153   aggregator = empathy_connection_aggregator_dup_singleton ();
154   all_groups = empathy_connection_aggregator_get_all_groups (aggregator);
155   g_object_unref (aggregator);
156
157   /* Get the list of groups that this #FolksGroupDetails is currently in */
158   member_groups = folks_group_details_get_groups (priv->group_details);
159
160   for (l = all_groups; l != NULL; l = l->next)
161     {
162       const gchar *group_str = l->data;
163       gboolean enabled;
164
165       enabled = gee_collection_contains (GEE_COLLECTION (member_groups),
166           group_str);
167
168       gtk_list_store_insert_with_values (priv->group_store, NULL, -1,
169           COL_NAME, group_str,
170           COL_EDITABLE, TRUE,
171           COL_ENABLED, enabled,
172           -1);
173     }
174
175   g_list_free (all_groups);
176 }
177
178 static void
179 add_group_entry_changed_cb (GtkEditable *editable,
180     EmpathyGroupsWidget *self)
181 {
182   EmpathyGroupsWidgetPriv *priv = GET_PRIV (self);
183   GtkTreeIter iter;
184   const gchar *group;
185
186   group = gtk_entry_get_text (GTK_ENTRY (priv->add_group_entry));
187
188   if (model_find_name (self, group, &iter))
189     {
190       gtk_widget_set_sensitive (GTK_WIDGET (priv->add_group_button), FALSE);
191     }
192   else
193     {
194       gtk_widget_set_sensitive (GTK_WIDGET (priv->add_group_button),
195           !EMP_STR_EMPTY (group));
196     }
197 }
198
199 static void
200 add_group_entry_activate_cb (GtkEntry *entry,
201     EmpathyGroupsWidget  *self)
202 {
203   gtk_widget_activate (GTK_WIDGET (GET_PRIV (self)->add_group_button));
204 }
205
206 static void
207 change_group_cb (FolksGroupDetails *group_details,
208     GAsyncResult *async_result,
209     EmpathyGroupsWidget *self)
210 {
211   GError *error = NULL;
212
213   folks_group_details_change_group_finish (group_details, async_result, &error);
214
215   if (error != NULL)
216     {
217       g_warning ("Failed to change group: %s", error->message);
218       g_clear_error (&error);
219     }
220 }
221
222 static void
223 add_group_button_clicked_cb (GtkButton *button,
224    EmpathyGroupsWidget *self)
225 {
226   EmpathyGroupsWidgetPriv *priv = GET_PRIV (self);
227   const gchar *group;
228
229   group = gtk_entry_get_text (GTK_ENTRY (priv->add_group_entry));
230
231   gtk_list_store_insert_with_values (priv->group_store, NULL, -1,
232       COL_NAME, group,
233       COL_ENABLED, TRUE,
234       -1);
235
236   folks_group_details_change_group (priv->group_details, group, TRUE,
237       (GAsyncReadyCallback) change_group_cb, self);
238 }
239
240 static void
241 cell_toggled_cb (GtkCellRendererToggle *cell,
242     const gchar *path_string,
243     EmpathyGroupsWidget *self)
244 {
245   EmpathyGroupsWidgetPriv *priv = GET_PRIV (self);
246   GtkTreePath *path;
247   GtkTreeIter iter;
248   gboolean was_enabled;
249   gchar *group;
250
251   path = gtk_tree_path_new_from_string (path_string);
252
253   gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->group_store), &iter,
254       path);
255   gtk_tree_model_get (GTK_TREE_MODEL (priv->group_store), &iter,
256       COL_ENABLED, &was_enabled,
257       COL_NAME, &group,
258       -1);
259
260   gtk_list_store_set (priv->group_store, &iter,
261       COL_ENABLED, !was_enabled,
262       -1);
263
264   gtk_tree_path_free (path);
265
266   if (group != NULL)
267     {
268       folks_group_details_change_group (priv->group_details, group,
269           !was_enabled, (GAsyncReadyCallback) change_group_cb, self);
270       g_free (group);
271     }
272 }
273
274
275 static void
276 group_details_group_changed_cb (FolksGroupDetails *groups,
277     const gchar *group,
278     gboolean is_member,
279     EmpathyGroupsWidget *self)
280 {
281   EmpathyGroupsWidgetPriv *priv = GET_PRIV (self);
282   GtkTreeIter iter;
283
284   if (model_find_name (self, group, &iter) == TRUE)
285     {
286       gtk_list_store_set (priv->group_store, &iter,
287           COL_ENABLED, is_member,
288           -1);
289     }
290 }
291
292 static void
293 set_up (EmpathyGroupsWidget *self)
294 {
295   EmpathyGroupsWidgetPriv *priv;
296   GtkWidget *label, *alignment;
297   GtkBox *vbox, *hbox;
298   GtkTreeView *tree_view;
299   GtkTreeSelection *selection;
300   GtkTreeViewColumn *column;
301   GtkCellRenderer *renderer;
302   guint col_offset;
303   GtkScrolledWindow *scrolled_window;
304   gchar *markup;
305
306   priv = GET_PRIV (self);
307
308   /* Set up ourself */
309   gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
310       GTK_ORIENTATION_VERTICAL);
311   gtk_box_set_spacing (GTK_BOX (self), 6);
312
313   /* Create our child widgets */
314   label = gtk_label_new (NULL);
315   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
316
317   markup = g_strdup_printf ("<b>%s</b>", _("Groups"));
318   gtk_label_set_markup (GTK_LABEL (label), markup);
319   g_free (markup);
320
321   gtk_box_pack_start (GTK_BOX (self), label, FALSE, FALSE, 0);
322   gtk_widget_show (label);
323
324   alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
325   gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 12, 0);
326
327   vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 6));
328
329   label = gtk_label_new (_("Select the groups you want this contact to appear "
330       "in.  Note that you can select more than one group or no groups."));
331   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
332   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
333
334   gtk_box_pack_start (vbox, label, FALSE, FALSE, 0);
335   gtk_widget_show (label);
336
337   hbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12));
338
339   priv->add_group_entry = gtk_entry_new ();
340   g_signal_connect (priv->add_group_entry, "changed",
341       (GCallback) add_group_entry_changed_cb, self);
342   g_signal_connect (priv->add_group_entry, "activate",
343       (GCallback) add_group_entry_activate_cb, self);
344
345   gtk_box_pack_start (hbox, priv->add_group_entry, TRUE, TRUE, 0);
346   gtk_widget_show (priv->add_group_entry);
347
348   priv->add_group_button = gtk_button_new_with_mnemonic (_("_Add Group"));
349   gtk_widget_set_sensitive (priv->add_group_button, FALSE);
350   gtk_widget_set_receives_default (priv->add_group_button, TRUE);
351   g_signal_connect (priv->add_group_button, "clicked",
352       (GCallback) add_group_button_clicked_cb, self);
353
354   gtk_box_pack_start (hbox, priv->add_group_button, FALSE, FALSE, 0);
355   gtk_widget_show (priv->add_group_button);
356
357   gtk_box_pack_start (vbox, GTK_WIDGET (hbox), FALSE, FALSE, 0);
358   gtk_widget_show (GTK_WIDGET (hbox));
359
360   scrolled_window = GTK_SCROLLED_WINDOW (gtk_scrolled_window_new (NULL, NULL));
361   gtk_scrolled_window_set_policy (scrolled_window, GTK_POLICY_NEVER,
362       GTK_POLICY_AUTOMATIC);
363   gtk_scrolled_window_set_shadow_type (scrolled_window, GTK_SHADOW_IN);
364   gtk_widget_set_size_request (GTK_WIDGET (scrolled_window), -1, 100);
365
366   priv->group_store = gtk_list_store_new (NUM_COLUMNS,
367       G_TYPE_STRING,   /* name */
368       G_TYPE_BOOLEAN,  /* enabled */
369       G_TYPE_BOOLEAN); /* editable */
370
371   tree_view = GTK_TREE_VIEW (gtk_tree_view_new_with_model (
372       GTK_TREE_MODEL (priv->group_store)));
373   gtk_tree_view_set_headers_visible (tree_view, FALSE);
374   gtk_tree_view_set_enable_search (tree_view, FALSE);
375
376   selection = gtk_tree_view_get_selection (tree_view);
377   gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
378
379   renderer = gtk_cell_renderer_toggle_new ();
380   g_signal_connect (renderer, "toggled", (GCallback) cell_toggled_cb, self);
381
382   column = gtk_tree_view_column_new_with_attributes (
383       C_("verb in a column header displaying group names", "Select"), renderer,
384       "active", COL_ENABLED,
385       NULL);
386
387   gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
388   gtk_tree_view_column_set_fixed_width (column, 50);
389   gtk_tree_view_append_column (tree_view, column);
390
391   renderer = gtk_cell_renderer_text_new ();
392   col_offset = gtk_tree_view_insert_column_with_attributes (tree_view,
393       -1, _("Group"),
394       renderer,
395       "text", COL_NAME,
396       /* "editable", COL_EDITABLE, */
397       NULL);
398
399   column = gtk_tree_view_get_column (tree_view, col_offset - 1);
400   gtk_tree_view_column_set_sort_column_id (column, COL_NAME);
401   gtk_tree_view_column_set_resizable (column, FALSE);
402   gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
403
404   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->group_store),
405       COL_NAME, GTK_SORT_ASCENDING);
406
407   gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (tree_view));
408   gtk_widget_show (GTK_WIDGET (tree_view));
409
410   gtk_box_pack_start (vbox, GTK_WIDGET (scrolled_window), TRUE, TRUE, 0);
411   gtk_widget_show (GTK_WIDGET (scrolled_window));
412
413   gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (vbox));
414   gtk_widget_show (GTK_WIDGET (vbox));
415
416   gtk_box_pack_start (GTK_BOX (self), alignment, TRUE, TRUE, 0);
417   gtk_widget_show (alignment);
418 }
419
420 static void
421 empathy_groups_widget_init (EmpathyGroupsWidget *self)
422 {
423   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
424       EMPATHY_TYPE_GROUPS_WIDGET, EmpathyGroupsWidgetPriv);
425
426   set_up (self);
427 }
428
429 static void
430 get_property (GObject *object,
431     guint param_id,
432     GValue *value,
433     GParamSpec *pspec)
434 {
435   EmpathyGroupsWidgetPriv *priv;
436
437   priv = GET_PRIV (object);
438
439   switch (param_id)
440     {
441       case PROP_GROUP_DETAILS:
442         g_value_set_object (value, priv->group_details);
443         break;
444       default:
445         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
446         break;
447     }
448 }
449
450 static void
451 set_property (GObject *object,
452     guint param_id,
453     const GValue *value,
454     GParamSpec *pspec)
455 {
456   switch (param_id)
457     {
458       case PROP_GROUP_DETAILS:
459         empathy_groups_widget_set_group_details (EMPATHY_GROUPS_WIDGET (object),
460             g_value_get_object (value));
461         break;
462       default:
463         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
464         break;
465     }
466 }
467
468 static void
469 dispose (GObject *object)
470 {
471   EmpathyGroupsWidgetPriv *priv = GET_PRIV (object);
472
473   empathy_groups_widget_set_group_details (EMPATHY_GROUPS_WIDGET (object),
474       NULL);
475   tp_clear_object (&priv->group_store);
476
477   G_OBJECT_CLASS (empathy_groups_widget_parent_class)->dispose (object);
478 }
479
480 static void
481 empathy_groups_widget_class_init (EmpathyGroupsWidgetClass *klass)
482 {
483   GObjectClass *object_class = G_OBJECT_CLASS (klass);
484
485   object_class->get_property = get_property;
486   object_class->set_property = set_property;
487   object_class->dispose = dispose;
488
489   /**
490    * EmpathyGroupsWidget:group_details:
491    *
492    * The #FolksGroupDetails whose group membership is to be edited by the
493    * #EmpathyGroupsWidget.
494    */
495   g_object_class_install_property (object_class, PROP_GROUP_DETAILS,
496       g_param_spec_object ("group-details",
497           "Group Details",
498           "The #FolksGroupDetails whose groups are being edited.",
499           FOLKS_TYPE_GROUP_DETAILS,
500           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
501
502   g_type_class_add_private (object_class, sizeof (EmpathyGroupsWidgetPriv));
503 }
504
505 /**
506  * empathy_groups_widget_new:
507  * @group_details: a #FolksGroupDetails, or %NULL
508  *
509  * Creates a new #EmpathyGroupsWidget to edit the groups of the given
510  * @group_details.
511  *
512  * Return value: a new #EmpathyGroupsWidget
513  */
514 GtkWidget *
515 empathy_groups_widget_new (FolksGroupDetails *group_details)
516 {
517   g_return_val_if_fail (
518       group_details == NULL || FOLKS_IS_GROUP_DETAILS (group_details),
519       NULL);
520
521   return GTK_WIDGET (g_object_new (EMPATHY_TYPE_GROUPS_WIDGET,
522       "group-details", group_details,
523       NULL));
524 }
525
526 /**
527  * empathy_groups_widget_get_group_details:
528  * @self: an #EmpathyGroupsWidget
529  *
530  * Get the #FolksGroupDetails whose group membership is being edited by the
531  * #EmpathyGroupsWidget.
532  *
533  * Returns: the #FolksGroupDetails associated with @widget, or %NULL
534  */
535 FolksGroupDetails *
536 empathy_groups_widget_get_group_details (EmpathyGroupsWidget *self)
537 {
538   g_return_val_if_fail (EMPATHY_IS_GROUPS_WIDGET (self), NULL);
539
540   return GET_PRIV (self)->group_details;
541 }
542
543 /**
544  * empathy_groups_widget_set_group_details:
545  * @self: an #EmpathyGroupsWidget
546  * @group_details: the #FolksGroupDetails whose membership is to be edited, or
547  * %NULL
548  *
549  * Change the #FolksGroupDetails whose group membership is to be edited by the
550  * #EmpathyGroupsWidget.
551  */
552 void
553 empathy_groups_widget_set_group_details (EmpathyGroupsWidget *self,
554     FolksGroupDetails *group_details)
555 {
556   EmpathyGroupsWidgetPriv *priv;
557
558   g_return_if_fail (EMPATHY_IS_GROUPS_WIDGET (self));
559   g_return_if_fail (
560       group_details == NULL || FOLKS_IS_GROUP_DETAILS (group_details));
561
562   priv = GET_PRIV (self);
563
564   if (group_details == priv->group_details)
565     return;
566
567   if (priv->group_details != NULL)
568     {
569       g_signal_handlers_disconnect_by_func (priv->group_details,
570           group_details_group_changed_cb, self);
571     }
572
573   tp_clear_object (&priv->group_details);
574
575   if (group_details != NULL)
576     {
577       priv->group_details = g_object_ref (group_details);
578
579       g_signal_connect (priv->group_details, "group-changed",
580           (GCallback) group_details_group_changed_cb, self);
581
582       populate_data (self);
583     }
584
585   g_object_notify (G_OBJECT (self), "group-details");
586 }