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