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_toggled_cb (GtkCellRendererToggle *cell_renderer,
203 EmpathyIndividualLinker *self)
205 GtkTreePath *tree_path = gtk_tree_path_new_from_string (path);
206 toggle_individual_row (self, tree_path);
207 gtk_tree_path_free (tree_path);
211 search_bar_activate_cb (EmpathyLiveSearch *search_bar,
212 EmpathyIndividualLinker *self)
214 EmpathyIndividualLinkerPriv *priv = GET_PRIV (self);
215 GtkTreeSelection *selection;
218 /* Toggle the status of the selected individuals */
219 selection = gtk_tree_view_get_selection (
220 GTK_TREE_VIEW (priv->individual_view));
221 rows = gtk_tree_selection_get_selected_rows (selection, NULL);
223 for (l = rows; l != NULL; l = l->next)
225 GtkTreePath *path = (GtkTreePath *) l->data;
226 toggle_individual_row (self, path);
227 gtk_tree_path_free (path);
234 set_up (EmpathyIndividualLinker *self)
236 EmpathyIndividualLinkerPriv *priv;
237 EmpathyIndividualManager *individual_manager;
238 GtkCellRenderer *cell_renderer;
241 GtkWidget *label, *scrolled_window, *search_bar;
243 EmpathyPersonaView *persona_view;
245 GtkWidget *alignment;
247 priv = GET_PRIV (self);
249 top_vbox = gtk_vbox_new (FALSE, 6);
252 paned = GTK_PANED (gtk_hpaned_new ());
254 /* Left column heading */
255 alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
256 gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 0, 6);
257 gtk_widget_show (alignment);
259 vbox = GTK_BOX (gtk_vbox_new (FALSE, 6));
260 label = gtk_label_new (NULL);
261 tmp = g_strdup_printf ("<b>%s</b>", _("Select contacts to link"));
262 gtk_label_set_markup (GTK_LABEL (label), tmp);
264 gtk_box_pack_start (vbox, label, FALSE, TRUE, 0);
265 gtk_widget_show (label);
267 /* Individual selector */
268 individual_manager = empathy_individual_manager_dup_singleton ();
269 priv->individual_store = empathy_individual_store_new (individual_manager);
270 g_object_unref (individual_manager);
272 empathy_individual_store_set_show_protocols (priv->individual_store, FALSE);
274 priv->individual_view = empathy_individual_view_new (priv->individual_store,
275 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, EMPATHY_INDIVIDUAL_FEATURE_NONE);
276 empathy_individual_view_set_show_offline (priv->individual_view, TRUE);
278 /* Add a checkbox column to the selector */
279 cell_renderer = gtk_cell_renderer_toggle_new ();
280 g_signal_connect (cell_renderer, "toggled", (GCallback) row_toggled_cb, self);
281 gtk_tree_view_insert_column_with_data_func (
282 GTK_TREE_VIEW (priv->individual_view), 0, NULL, cell_renderer,
283 (GtkTreeCellDataFunc) contact_toggle_cell_data_func, self, NULL);
285 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
286 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
287 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
288 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
290 gtk_container_add (GTK_CONTAINER (scrolled_window),
291 GTK_WIDGET (priv->individual_view));
292 gtk_widget_show (GTK_WIDGET (priv->individual_view));
294 gtk_box_pack_start (vbox, scrolled_window, TRUE, TRUE, 0);
295 gtk_widget_show (scrolled_window);
298 search_bar = empathy_live_search_new (GTK_WIDGET (priv->individual_view));
299 empathy_individual_view_set_live_search (priv->individual_view,
300 EMPATHY_LIVE_SEARCH (search_bar));
301 g_signal_connect (search_bar, "activate", (GCallback) search_bar_activate_cb,
304 gtk_box_pack_end (vbox, search_bar, FALSE, TRUE, 0);
306 gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (vbox));
307 gtk_paned_pack1 (paned, alignment, TRUE, FALSE);
308 gtk_widget_show (GTK_WIDGET (vbox));
310 /* Right column heading */
311 alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
312 gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 6, 0);
313 gtk_widget_show (alignment);
315 vbox = GTK_BOX (gtk_vbox_new (FALSE, 6));
316 label = gtk_label_new (NULL);
317 tmp = g_strdup_printf ("<b>%s</b>", _("New contact preview"));
318 gtk_label_set_markup (GTK_LABEL (label), tmp);
320 gtk_box_pack_start (vbox, label, FALSE, TRUE, 0);
321 gtk_widget_show (label);
323 /* New individual preview */
324 priv->preview_widget = empathy_individual_widget_new (priv->new_individual,
325 EMPATHY_INDIVIDUAL_WIDGET_SHOW_DETAILS);
326 gtk_box_pack_start (vbox, priv->preview_widget, FALSE, TRUE, 0);
327 gtk_widget_show (priv->preview_widget);
330 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
331 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
332 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
333 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
336 priv->persona_store = empathy_persona_store_new (priv->new_individual);
337 empathy_persona_store_set_show_protocols (priv->persona_store, TRUE);
338 persona_view = empathy_persona_view_new (priv->persona_store);
339 empathy_persona_view_set_show_offline (persona_view, TRUE);
341 gtk_container_add (GTK_CONTAINER (scrolled_window),
342 GTK_WIDGET (persona_view));
343 gtk_widget_show (GTK_WIDGET (persona_view));
345 gtk_box_pack_start (vbox, scrolled_window, TRUE, TRUE, 0);
346 gtk_widget_show (scrolled_window);
348 gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (vbox));
349 gtk_paned_pack2 (paned, alignment, TRUE, FALSE);
350 gtk_widget_show (GTK_WIDGET (vbox));
352 gtk_widget_show (GTK_WIDGET (paned));
355 label = gtk_label_new (NULL);
356 tmp = g_strdup_printf ("<i>%s</i>",
357 _("Contacts selected in the list on the left will be linked together."));
358 gtk_label_set_markup (GTK_LABEL (label), tmp);
360 gtk_widget_show (label);
362 gtk_box_pack_start (GTK_BOX (top_vbox), GTK_WIDGET (paned), TRUE, TRUE, 0);
363 gtk_box_pack_start (GTK_BOX (top_vbox), label, FALSE, TRUE, 0);
365 /* Add the main vbox to the bin */
366 gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (top_vbox));
367 gtk_widget_show (GTK_WIDGET (top_vbox));
371 empathy_individual_linker_init (EmpathyIndividualLinker *self)
373 EmpathyIndividualLinkerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
374 EMPATHY_TYPE_INDIVIDUAL_LINKER, EmpathyIndividualLinkerPriv);
378 priv->changed_individuals = g_hash_table_new (NULL, NULL);
384 get_property (GObject *object,
389 EmpathyIndividualLinkerPriv *priv;
391 priv = GET_PRIV (object);
395 case PROP_START_INDIVIDUAL:
396 g_value_set_object (value, priv->start_individual);
399 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
405 set_property (GObject *object,
410 EmpathyIndividualLinkerPriv *priv;
412 priv = GET_PRIV (object);
416 case PROP_START_INDIVIDUAL:
417 empathy_individual_linker_set_start_individual (
418 EMPATHY_INDIVIDUAL_LINKER (object), g_value_get_object (value));
421 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
427 dispose (GObject *object)
429 EmpathyIndividualLinkerPriv *priv = GET_PRIV (object);
431 tp_clear_object (&priv->individual_store);
432 tp_clear_object (&priv->persona_store);
433 tp_clear_object (&priv->start_individual);
434 tp_clear_object (&priv->new_individual);
436 G_OBJECT_CLASS (empathy_individual_linker_parent_class)->dispose (object);
440 finalize (GObject *object)
442 EmpathyIndividualLinkerPriv *priv = GET_PRIV (object);
444 g_hash_table_destroy (priv->changed_individuals);
446 G_OBJECT_CLASS (empathy_individual_linker_parent_class)->finalize (object);
450 size_request (GtkWidget *widget,
451 GtkRequisition *requisition)
453 GtkBin *bin = GTK_BIN (widget);
457 gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
458 requisition->height =
459 gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
461 child = gtk_bin_get_child (bin);
463 if (child && gtk_widget_get_visible (child))
465 GtkRequisition child_requisition;
467 gtk_widget_size_request (child, &child_requisition);
469 requisition->width += child_requisition.width;
470 requisition->height += child_requisition.height;
475 size_allocate (GtkWidget *widget,
476 GtkAllocation *allocation)
478 GtkBin *bin = GTK_BIN (widget);
479 GtkAllocation child_allocation;
482 gtk_widget_set_allocation (widget, allocation);
484 child = gtk_bin_get_child (bin);
486 if (child && gtk_widget_get_visible (child))
488 child_allocation.x = allocation->x +
489 gtk_container_get_border_width (GTK_CONTAINER (widget));
490 child_allocation.y = allocation->y +
491 gtk_container_get_border_width (GTK_CONTAINER (widget));
492 child_allocation.width = MAX (allocation->width -
493 gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
494 child_allocation.height = MAX (allocation->height -
495 gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
497 gtk_widget_size_allocate (child, &child_allocation);
502 empathy_individual_linker_class_init (EmpathyIndividualLinkerClass *klass)
504 GObjectClass *object_class = G_OBJECT_CLASS (klass);
505 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
507 object_class->get_property = get_property;
508 object_class->set_property = set_property;
509 object_class->dispose = dispose;
510 object_class->finalize = finalize;
512 widget_class->size_request = size_request;
513 widget_class->size_allocate = size_allocate;
516 * EmpathyIndividualLinker:start-individual:
518 * The #FolksIndividual to link other individuals to. This individual is
519 * selected by default in the list of individuals, and cannot be unselected.
520 * This ensures that empathy_individual_linker_get_linked_personas() will
521 * always return at least one persona to link.
523 g_object_class_install_property (object_class, PROP_START_INDIVIDUAL,
524 g_param_spec_object ("start-individual",
526 "The #FolksIndividual to link other individuals to.",
527 FOLKS_TYPE_INDIVIDUAL,
528 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
530 g_type_class_add_private (object_class, sizeof (EmpathyIndividualLinkerPriv));
534 * empathy_individual_linker_new:
535 * @start_individual: (allow-none): the #FolksIndividual to link to, or %NULL
537 * Creates a new #EmpathyIndividualLinker.
539 * Return value: a new #EmpathyIndividualLinker
542 empathy_individual_linker_new (FolksIndividual *start_individual)
544 g_return_val_if_fail (start_individual == NULL ||
545 FOLKS_IS_INDIVIDUAL (start_individual), NULL);
547 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_LINKER,
548 "start-individual", start_individual,
553 * empathy_individual_linker_get_start_individual:
554 * @self: an #EmpathyIndividualLinker
556 * Get the value of #EmpathyIndividualLinker:start-individual.
558 * Return value: (transfer none): the start individual for linking, or %NULL
561 empathy_individual_linker_get_start_individual (EmpathyIndividualLinker *self)
563 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_LINKER (self), NULL);
565 return GET_PRIV (self)->start_individual;
569 * empathy_individual_linker_set_start_individual:
570 * @self: an #EmpathyIndividualLinker
571 * @individual: (allow-none): the start individual, or %NULL
573 * Set the value of #EmpathyIndividualLinker:start-individual to @individual.
576 empathy_individual_linker_set_start_individual (EmpathyIndividualLinker *self,
577 FolksIndividual *individual)
579 EmpathyIndividualLinkerPriv *priv;
581 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_LINKER (self));
582 g_return_if_fail (individual == NULL || FOLKS_IS_INDIVIDUAL (individual));
584 priv = GET_PRIV (self);
586 tp_clear_object (&priv->start_individual);
587 tp_clear_object (&priv->new_individual);
588 g_hash_table_remove_all (priv->changed_individuals);
590 if (individual != NULL)
592 priv->start_individual = g_object_ref (individual);
593 priv->new_individual = folks_individual_new (
594 folks_individual_get_personas (individual));
595 empathy_individual_view_set_store (priv->individual_view,
596 priv->individual_store);
600 priv->start_individual = NULL;
601 priv->new_individual = NULL;
603 /* We only display Individuals in the individual view if we have a
604 * new_individual to link them into */
605 empathy_individual_view_set_store (priv->individual_view, NULL);
608 empathy_individual_widget_set_individual (
609 EMPATHY_INDIVIDUAL_WIDGET (priv->preview_widget), priv->new_individual);
610 empathy_persona_store_set_individual (priv->persona_store,
611 priv->new_individual);
613 g_object_notify (G_OBJECT (self), "start-individual");
617 * empathy_individual_linker_get_linked_personas:
618 * @self: an #EmpathyIndividualLinker
620 * Return a list of the #FolksPersona<!-- -->s which comprise the linked
621 * individual currently displayed in the widget.
623 * The return value is guaranteed to contain at least one element.
625 * Return value: (transfer none) (element-type Folks.Persona): a list of
626 * #FolksPersona<!-- -->s to link together
629 empathy_individual_linker_get_linked_personas (EmpathyIndividualLinker *self)
631 EmpathyIndividualLinkerPriv *priv;
634 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_LINKER (self), NULL);
636 priv = GET_PRIV (self);
638 if (priv->new_individual == NULL)
641 personas = folks_individual_get_personas (priv->new_individual);
642 g_assert (personas != NULL);