2 * Copyright (C) 2010 Collabora Ltd.
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
9 * This program 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 * General Public License for more details.
14 * You should have received a copy of the GNU General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301 USA
19 * Authors: Philip Withnall <philip.withnall@collabora.co.uk>
26 #include <glib/gi18n-lib.h>
29 #include <telepathy-glib/util.h>
31 #include <folks/folks.h>
33 #include <libempathy/empathy-individual-manager.h>
34 #include <libempathy/empathy-utils.h>
36 #include "empathy-individual-linker.h"
37 #include "empathy-individual-store.h"
38 #include "empathy-individual-view.h"
39 #include "empathy-individual-widget.h"
40 #include "empathy-persona-store.h"
41 #include "empathy-persona-view.h"
44 * SECTION:empathy-individual-linker
45 * @title:EmpathyIndividualLinker
46 * @short_description: A widget used to link together #FolksIndividual<!-- -->s
47 * @include: libempathy-gtk/empathy-individual-linker.h
49 * #EmpathyIndividualLinker is a widget which allows selection of several
50 * #FolksIndividual<!-- -->s to link together to form a single new individual.
51 * The widget provides a preview of the linked individual.
55 * EmpathyIndividualLinker:
56 * @parent: parent object
58 * Widget which extends #GtkBin to provide a list of #FolksIndividual<!-- -->s
62 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualLinker)
65 EmpathyIndividualStore *individual_store; /* owned */
66 EmpathyIndividualView *individual_view; /* child widget */
67 GtkWidget *preview_widget; /* child widget */
68 EmpathyPersonaStore *persona_store; /* owned */
70 FolksIndividual *start_individual; /* owned, allow-none */
71 FolksIndividual *new_individual; /* owned, allow-none */
73 /* Stores the Individuals whose Personas have been added to the
75 /* unowned Individual (borrowed from EmpathyIndividualStore) -> bool */
76 GHashTable *changed_individuals;
77 } EmpathyIndividualLinkerPriv;
80 PROP_START_INDIVIDUAL = 1,
83 G_DEFINE_TYPE (EmpathyIndividualLinker, empathy_individual_linker,
87 contact_toggle_cell_data_func (GtkTreeViewColumn *tree_column,
88 GtkCellRenderer *cell,
89 GtkTreeModel *tree_model,
91 EmpathyIndividualLinker *self)
93 EmpathyIndividualLinkerPriv *priv;
94 FolksIndividual *individual;
95 gboolean is_group, individual_added;
97 priv = GET_PRIV (self);
99 gtk_tree_model_get (tree_model, iter,
100 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
101 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
104 individual_added = GPOINTER_TO_UINT (g_hash_table_lookup (
105 priv->changed_individuals, individual));
107 /* We don't want to show checkboxes next to the group rows.
108 * All checkboxes should be sensitive except the checkbox for the start
109 * individual, which should be permanently active and insensitive */
111 "visible", !is_group,
112 "sensitive", individual != priv->start_individual,
113 "activatable", individual != priv->start_individual,
114 "active", individual_added || individual == priv->start_individual,
117 tp_clear_object (&individual);
121 link_individual (EmpathyIndividualLinker *self,
122 FolksIndividual *individual)
124 EmpathyIndividualLinkerPriv *priv = GET_PRIV (self);
125 GList *new_persona_list;
127 /* Add the individual to the link */
128 g_hash_table_insert (priv->changed_individuals, individual,
129 GUINT_TO_POINTER (TRUE));
131 /* Add personas which are in @individual to priv->new_individual, appending
132 * them to the list of personas.
133 * This is rather slow. */
134 new_persona_list = g_list_copy (folks_individual_get_personas (
135 priv->new_individual));
136 new_persona_list = g_list_concat (new_persona_list,
137 g_list_copy (folks_individual_get_personas (individual)));
138 folks_individual_set_personas (priv->new_individual, new_persona_list);
139 g_list_free (new_persona_list);
143 unlink_individual (EmpathyIndividualLinker *self,
144 FolksIndividual *individual)
146 EmpathyIndividualLinkerPriv *priv = GET_PRIV (self);
147 GList *new_persona_list, *old_persona_list, *removing_personas, *l;
149 /* Remove the individual from the link */
150 g_hash_table_remove (priv->changed_individuals, individual);
152 /* Remove personas which are in @individual from priv->new_individual.
153 * This is rather slow. */
154 old_persona_list = folks_individual_get_personas (priv->new_individual);
155 removing_personas = folks_individual_get_personas (individual);
156 new_persona_list = NULL;
158 for (l = old_persona_list; l != NULL; l = l->next)
160 GList *removing = g_list_find (removing_personas, l->data);
162 if (removing == NULL)
163 new_persona_list = g_list_prepend (new_persona_list, l->data);
166 new_persona_list = g_list_reverse (new_persona_list);
167 folks_individual_set_personas (priv->new_individual, new_persona_list);
168 g_list_free (new_persona_list);
172 toggle_individual_row (EmpathyIndividualLinker *self,
175 EmpathyIndividualLinkerPriv *priv = GET_PRIV (self);
176 FolksIndividual *individual;
178 GtkTreeModel *tree_model;
179 gboolean individual_added;
181 tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->individual_view));
183 gtk_tree_model_get_iter (tree_model, &iter, path);
184 gtk_tree_model_get (tree_model, &iter,
185 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
188 individual_added = GPOINTER_TO_UINT (g_hash_table_lookup (
189 priv->changed_individuals, individual));
191 /* Toggle the Individual's linked status */
192 if (individual_added)
193 unlink_individual (self, individual);
195 link_individual (self, individual);
197 g_object_unref (individual);
201 row_activated_cb (EmpathyIndividualView *view,
203 GtkTreeViewColumn *column,
204 EmpathyIndividualLinker *self)
206 toggle_individual_row (self, path);
210 row_toggled_cb (GtkCellRendererToggle *cell_renderer,
212 EmpathyIndividualLinker *self)
214 GtkTreePath *tree_path = gtk_tree_path_new_from_string (path);
215 toggle_individual_row (self, tree_path);
216 gtk_tree_path_free (tree_path);
220 set_up (EmpathyIndividualLinker *self)
222 EmpathyIndividualLinkerPriv *priv;
223 EmpathyIndividualManager *individual_manager;
224 GtkCellRenderer *cell_renderer;
227 GtkWidget *label, *scrolled_window, *search_bar;
229 EmpathyPersonaView *persona_view;
231 GtkWidget *alignment;
233 priv = GET_PRIV (self);
235 top_vbox = gtk_vbox_new (FALSE, 6);
238 paned = GTK_PANED (gtk_hpaned_new ());
240 /* Left column heading */
241 alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
242 gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 0, 6);
243 gtk_widget_show (alignment);
245 vbox = GTK_BOX (gtk_vbox_new (FALSE, 6));
246 label = gtk_label_new (NULL);
247 tmp = g_strdup_printf ("<b>%s</b>", _("Select contacts to link"));
248 gtk_label_set_markup (GTK_LABEL (label), tmp);
250 gtk_box_pack_start (vbox, label, FALSE, TRUE, 0);
251 gtk_widget_show (label);
253 /* Individual selector */
254 individual_manager = empathy_individual_manager_dup_singleton ();
255 priv->individual_store = empathy_individual_store_new (individual_manager);
256 g_object_unref (individual_manager);
258 empathy_individual_store_set_show_protocols (priv->individual_store, FALSE);
260 priv->individual_view = empathy_individual_view_new (priv->individual_store,
261 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, EMPATHY_INDIVIDUAL_FEATURE_NONE);
262 empathy_individual_view_set_show_offline (priv->individual_view, TRUE);
264 g_signal_connect (priv->individual_view, "row-activated",
265 (GCallback) row_activated_cb, self);
267 /* Add a checkbox column to the selector */
268 cell_renderer = gtk_cell_renderer_toggle_new ();
269 g_signal_connect (cell_renderer, "toggled", (GCallback) row_toggled_cb, self);
270 gtk_tree_view_insert_column_with_data_func (
271 GTK_TREE_VIEW (priv->individual_view), 0, NULL, cell_renderer,
272 (GtkTreeCellDataFunc) contact_toggle_cell_data_func, self, NULL);
274 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
275 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
276 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
277 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
279 gtk_container_add (GTK_CONTAINER (scrolled_window),
280 GTK_WIDGET (priv->individual_view));
281 gtk_widget_show (GTK_WIDGET (priv->individual_view));
283 gtk_box_pack_start (vbox, scrolled_window, TRUE, TRUE, 0);
284 gtk_widget_show (scrolled_window);
287 search_bar = empathy_live_search_new (GTK_WIDGET (priv->individual_view));
288 empathy_individual_view_set_live_search (priv->individual_view,
289 EMPATHY_LIVE_SEARCH (search_bar));
291 gtk_box_pack_end (vbox, search_bar, FALSE, TRUE, 0);
293 gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (vbox));
294 gtk_paned_pack1 (paned, alignment, TRUE, FALSE);
295 gtk_widget_show (GTK_WIDGET (vbox));
297 /* Right column heading */
298 alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
299 gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 6, 0);
300 gtk_widget_show (alignment);
302 vbox = GTK_BOX (gtk_vbox_new (FALSE, 6));
303 label = gtk_label_new (NULL);
304 tmp = g_strdup_printf ("<b>%s</b>", _("New contact preview"));
305 gtk_label_set_markup (GTK_LABEL (label), tmp);
307 gtk_box_pack_start (vbox, label, FALSE, TRUE, 0);
308 gtk_widget_show (label);
310 /* New individual preview */
311 priv->preview_widget = empathy_individual_widget_new (priv->new_individual,
312 EMPATHY_INDIVIDUAL_WIDGET_SHOW_DETAILS);
313 gtk_box_pack_start (vbox, priv->preview_widget, FALSE, TRUE, 0);
314 gtk_widget_show (priv->preview_widget);
317 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
318 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
319 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
320 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
323 priv->persona_store = empathy_persona_store_new (priv->new_individual);
324 empathy_persona_store_set_show_protocols (priv->persona_store, TRUE);
325 persona_view = empathy_persona_view_new (priv->persona_store,
326 EMPATHY_PERSONA_VIEW_FEATURE_NONE);
327 empathy_persona_view_set_show_offline (persona_view, TRUE);
329 gtk_container_add (GTK_CONTAINER (scrolled_window),
330 GTK_WIDGET (persona_view));
331 gtk_widget_show (GTK_WIDGET (persona_view));
333 gtk_box_pack_start (vbox, scrolled_window, TRUE, TRUE, 0);
334 gtk_widget_show (scrolled_window);
336 gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (vbox));
337 gtk_paned_pack2 (paned, alignment, TRUE, FALSE);
338 gtk_widget_show (GTK_WIDGET (vbox));
340 gtk_widget_show (GTK_WIDGET (paned));
343 label = gtk_label_new (NULL);
344 tmp = g_strdup_printf ("<i>%s</i>",
345 _("Contacts selected in the list on the left will be linked together."));
346 gtk_label_set_markup (GTK_LABEL (label), tmp);
348 gtk_widget_show (label);
350 gtk_box_pack_start (GTK_BOX (top_vbox), GTK_WIDGET (paned), TRUE, TRUE, 0);
351 gtk_box_pack_start (GTK_BOX (top_vbox), label, FALSE, TRUE, 0);
353 /* Add the main vbox to the bin */
354 gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (top_vbox));
355 gtk_widget_show (GTK_WIDGET (top_vbox));
359 empathy_individual_linker_init (EmpathyIndividualLinker *self)
361 EmpathyIndividualLinkerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
362 EMPATHY_TYPE_INDIVIDUAL_LINKER, EmpathyIndividualLinkerPriv);
366 priv->changed_individuals = g_hash_table_new (NULL, NULL);
372 get_property (GObject *object,
377 EmpathyIndividualLinkerPriv *priv;
379 priv = GET_PRIV (object);
383 case PROP_START_INDIVIDUAL:
384 g_value_set_object (value, priv->start_individual);
387 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
393 set_property (GObject *object,
398 EmpathyIndividualLinkerPriv *priv;
400 priv = GET_PRIV (object);
404 case PROP_START_INDIVIDUAL:
405 empathy_individual_linker_set_start_individual (
406 EMPATHY_INDIVIDUAL_LINKER (object), g_value_get_object (value));
409 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
415 dispose (GObject *object)
417 EmpathyIndividualLinkerPriv *priv = GET_PRIV (object);
419 tp_clear_object (&priv->individual_store);
420 tp_clear_object (&priv->persona_store);
421 tp_clear_object (&priv->start_individual);
422 tp_clear_object (&priv->new_individual);
424 G_OBJECT_CLASS (empathy_individual_linker_parent_class)->dispose (object);
428 finalize (GObject *object)
430 EmpathyIndividualLinkerPriv *priv = GET_PRIV (object);
432 g_hash_table_destroy (priv->changed_individuals);
434 G_OBJECT_CLASS (empathy_individual_linker_parent_class)->finalize (object);
438 size_request (GtkWidget *widget,
439 GtkRequisition *requisition)
441 GtkBin *bin = GTK_BIN (widget);
445 gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
446 requisition->height =
447 gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
449 child = gtk_bin_get_child (bin);
451 if (child && gtk_widget_get_visible (child))
453 GtkRequisition child_requisition;
455 gtk_widget_size_request (child, &child_requisition);
457 requisition->width += child_requisition.width;
458 requisition->height += child_requisition.height;
463 size_allocate (GtkWidget *widget,
464 GtkAllocation *allocation)
466 GtkBin *bin = GTK_BIN (widget);
467 GtkAllocation child_allocation;
470 gtk_widget_set_allocation (widget, allocation);
472 child = gtk_bin_get_child (bin);
474 if (child && gtk_widget_get_visible (child))
476 child_allocation.x = allocation->x +
477 gtk_container_get_border_width (GTK_CONTAINER (widget));
478 child_allocation.y = allocation->y +
479 gtk_container_get_border_width (GTK_CONTAINER (widget));
480 child_allocation.width = MAX (allocation->width -
481 gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
482 child_allocation.height = MAX (allocation->height -
483 gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
485 gtk_widget_size_allocate (child, &child_allocation);
490 empathy_individual_linker_class_init (EmpathyIndividualLinkerClass *klass)
492 GObjectClass *object_class = G_OBJECT_CLASS (klass);
493 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
495 object_class->get_property = get_property;
496 object_class->set_property = set_property;
497 object_class->dispose = dispose;
498 object_class->finalize = finalize;
500 widget_class->size_request = size_request;
501 widget_class->size_allocate = size_allocate;
504 * EmpathyIndividualLinker:start-individual:
506 * The #FolksIndividual to link other individuals to. This individual is
507 * selected by default in the list of individuals, and cannot be unselected.
508 * This ensures that empathy_individual_linker_get_linked_personas() will
509 * always return at least one persona to link.
511 g_object_class_install_property (object_class, PROP_START_INDIVIDUAL,
512 g_param_spec_object ("start-individual",
514 "The #FolksIndividual to link other individuals to.",
515 FOLKS_TYPE_INDIVIDUAL,
516 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
518 g_type_class_add_private (object_class, sizeof (EmpathyIndividualLinkerPriv));
522 * empathy_individual_linker_new:
523 * @start_individual: (allow-none): the #FolksIndividual to link to, or %NULL
525 * Creates a new #EmpathyIndividualLinker.
527 * Return value: a new #EmpathyIndividualLinker
530 empathy_individual_linker_new (FolksIndividual *start_individual)
532 g_return_val_if_fail (start_individual == NULL ||
533 FOLKS_IS_INDIVIDUAL (start_individual), NULL);
535 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_LINKER,
536 "start-individual", start_individual,
541 * empathy_individual_linker_get_start_individual:
542 * @self: an #EmpathyIndividualLinker
544 * Get the value of #EmpathyIndividualLinker:start-individual.
546 * Return value: (transfer none): the start individual for linking, or %NULL
549 empathy_individual_linker_get_start_individual (EmpathyIndividualLinker *self)
551 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_LINKER (self), NULL);
553 return GET_PRIV (self)->start_individual;
557 * empathy_individual_linker_set_start_individual:
558 * @self: an #EmpathyIndividualLinker
559 * @individual: (allow-none): the start individual, or %NULL
561 * Set the value of #EmpathyIndividualLinker:start-individual to @individual.
564 empathy_individual_linker_set_start_individual (EmpathyIndividualLinker *self,
565 FolksIndividual *individual)
567 EmpathyIndividualLinkerPriv *priv;
569 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_LINKER (self));
570 g_return_if_fail (individual == NULL || FOLKS_IS_INDIVIDUAL (individual));
572 priv = GET_PRIV (self);
574 tp_clear_object (&priv->start_individual);
575 tp_clear_object (&priv->new_individual);
576 g_hash_table_remove_all (priv->changed_individuals);
578 if (individual != NULL)
580 priv->start_individual = g_object_ref (individual);
581 priv->new_individual = folks_individual_new (
582 folks_individual_get_personas (individual));
583 empathy_individual_view_set_store (priv->individual_view,
584 priv->individual_store);
588 priv->start_individual = NULL;
589 priv->new_individual = NULL;
591 /* We only display Individuals in the individual view if we have a
592 * new_individual to link them into */
593 empathy_individual_view_set_store (priv->individual_view, NULL);
596 empathy_individual_widget_set_individual (
597 EMPATHY_INDIVIDUAL_WIDGET (priv->preview_widget), priv->new_individual);
598 empathy_persona_store_set_individual (priv->persona_store,
599 priv->new_individual);
601 g_object_notify (G_OBJECT (self), "start-individual");
605 * empathy_individual_linker_get_linked_personas:
606 * @self: an #EmpathyIndividualLinker
608 * Return a list of the #FolksPersona<!-- -->s which comprise the linked
609 * individual currently displayed in the widget.
611 * The return value is guaranteed to contain at least one element.
613 * Return value: (transfer none) (element-type Folks.Persona): a list of
614 * #FolksPersona<!-- -->s to link together
617 empathy_individual_linker_get_linked_personas (EmpathyIndividualLinker *self)
619 EmpathyIndividualLinkerPriv *priv;
622 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_LINKER (self), NULL);
624 priv = GET_PRIV (self);
626 if (priv->new_individual == NULL)
629 personas = folks_individual_get_personas (priv->new_individual);
630 g_assert (personas != NULL);