]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-linker.c
Add ax_config_dir from audacity
[empathy.git] / libempathy-gtk / empathy-individual-linker.c
1 /*
2  * Copyright (C) 2010 Collabora Ltd.
3  *
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.
8  *
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.
13  *
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
18  *
19  * Authors: Philip Withnall <philip.withnall@collabora.co.uk>
20  */
21
22 #include "config.h"
23
24 #include <string.h>
25
26 #include <glib/gi18n-lib.h>
27 #include <gtk/gtk.h>
28
29 #include <telepathy-glib/util.h>
30
31 #include <folks/folks.h>
32
33 #include <libempathy/empathy-individual-manager.h>
34 #include <libempathy/empathy-utils.h>
35
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"
42
43 /**
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
48  *
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.
52  */
53
54 /**
55  * EmpathyIndividualLinker:
56  * @parent: parent object
57  *
58  * Widget which extends #GtkBin to provide a list of #FolksIndividual<!-- -->s
59  * to link together.
60  */
61
62 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualLinker)
63
64 typedef struct {
65   EmpathyIndividualStore *individual_store; /* owned */
66   EmpathyIndividualView *individual_view; /* child widget */
67   GtkWidget *preview_widget; /* child widget */
68   EmpathyPersonaStore *persona_store; /* owned */
69   GtkTreeViewColumn *toggle_column; /* child widget */
70   GtkCellRenderer *toggle_renderer; /* child widget */
71   GtkWidget *search_widget; /* child widget */
72
73   FolksIndividual *start_individual; /* owned, allow-none */
74   FolksIndividual *new_individual; /* owned, allow-none */
75
76   /* Stores the Individuals whose Personas have been added to the
77    * new_individual */
78   /* unowned Individual (borrowed from EmpathyIndividualStore) -> bool */
79   GHashTable *changed_individuals;
80 } EmpathyIndividualLinkerPriv;
81
82 enum {
83   PROP_START_INDIVIDUAL = 1,
84   PROP_HAS_CHANGED,
85 };
86
87 G_DEFINE_TYPE (EmpathyIndividualLinker, empathy_individual_linker,
88     GTK_TYPE_BIN);
89
90 static void
91 contact_toggle_cell_data_func (GtkTreeViewColumn *tree_column,
92     GtkCellRenderer *cell,
93     GtkTreeModel *tree_model,
94     GtkTreeIter *iter,
95     EmpathyIndividualLinker *self)
96 {
97   EmpathyIndividualLinkerPriv *priv;
98   FolksIndividual *individual;
99   gboolean is_group, individual_added;
100
101   priv = GET_PRIV (self);
102
103   gtk_tree_model_get (tree_model, iter,
104       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
105       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
106       -1);
107
108   individual_added = GPOINTER_TO_UINT (g_hash_table_lookup (
109       priv->changed_individuals, individual));
110
111   /* We don't want to show checkboxes next to the group rows.
112    * All checkboxes should be sensitive except the checkbox for the start
113    * individual, which should be permanently active and insensitive */
114   g_object_set (cell,
115       "visible", !is_group,
116       "sensitive", individual != priv->start_individual,
117       "activatable", individual != priv->start_individual,
118       "active", individual_added || individual == priv->start_individual,
119       NULL);
120
121   tp_clear_object (&individual);
122 }
123
124 static void
125 update_toggle_renderers (EmpathyIndividualLinker *self)
126 {
127   EmpathyIndividualLinkerPriv *priv = GET_PRIV (self);
128
129   /* Re-setting the cell data func to the same function causes a refresh of the
130    * entire column, ensuring that each toggle button is correctly active or
131    * inactive. This is necessary because one Individual might appear multiple
132    * times in the list (in different groups), so toggling one instance of the
133    * Individual should toggle all of them. */
134   gtk_tree_view_column_set_cell_data_func (priv->toggle_column,
135       priv->toggle_renderer,
136       (GtkTreeCellDataFunc) contact_toggle_cell_data_func, self, NULL);
137 }
138
139 static void
140 link_individual (EmpathyIndividualLinker *self,
141     FolksIndividual *individual)
142 {
143   EmpathyIndividualLinkerPriv *priv = GET_PRIV (self);
144   GeeSet *old_personas, *new_personas;
145   GeeHashSet *final_personas;
146   gboolean personas_changed;
147
148   /* Add the individual to the link */
149   g_hash_table_insert (priv->changed_individuals, individual,
150       GUINT_TO_POINTER (TRUE));
151
152   /* Add personas which are in @individual to priv->new_individual, adding them
153    * to the set of personas. */
154   old_personas = folks_individual_get_personas (individual);
155   new_personas = folks_individual_get_personas (priv->new_individual);
156   final_personas = gee_hash_set_new (FOLKS_TYPE_PERSONA, g_object_ref,
157       g_object_unref, g_direct_hash, g_direct_equal);
158   gee_collection_add_all (GEE_COLLECTION (final_personas),
159       GEE_COLLECTION (old_personas));
160   personas_changed = gee_collection_add_all (GEE_COLLECTION (final_personas),
161       GEE_COLLECTION (new_personas));
162
163   /* avoid updating all values in the Individual if the set of personas doesn't
164    * actually change */
165   if (personas_changed)
166     {
167       folks_individual_set_personas (priv->new_individual,
168           GEE_SET (final_personas));
169     }
170
171   g_clear_object (&final_personas);
172
173   /* Update the toggle renderers, so that if this Individual is listed in
174    * another group in the EmpathyIndividualView, the toggle button for that
175    * group is updated. */
176   update_toggle_renderers (self);
177
178   g_object_notify (G_OBJECT (self), "has-changed");
179 }
180
181 static void
182 unlink_individual (EmpathyIndividualLinker *self,
183     FolksIndividual *individual)
184 {
185   EmpathyIndividualLinkerPriv *priv = GET_PRIV (self);
186   GeeSet *removed_personas, *old_personas;
187   GeeHashSet *final_personas;
188   gboolean personas_changed;
189
190   /* Remove the individual from the link */
191   g_hash_table_remove (priv->changed_individuals, individual);
192
193   /* Remove personas which are in @individual from priv->new_individual. */
194   old_personas = folks_individual_get_personas (priv->new_individual);
195   removed_personas = folks_individual_get_personas (individual);
196
197   final_personas = gee_hash_set_new (FOLKS_TYPE_PERSONA, g_object_ref,
198       g_object_unref, g_direct_hash, g_direct_equal);
199   gee_collection_add_all (GEE_COLLECTION (final_personas),
200       GEE_COLLECTION (old_personas));
201   personas_changed = gee_collection_remove_all (GEE_COLLECTION (final_personas),
202       GEE_COLLECTION (removed_personas));
203
204   if (personas_changed)
205     {
206       folks_individual_set_personas (priv->new_individual,
207           GEE_SET (final_personas));
208     }
209
210   g_clear_object (&final_personas);
211
212   /* Update the toggle renderers, so that if this Individual is listed in
213    * another group in the EmpathyIndividualView, the toggle button for that
214    * group is updated. */
215   update_toggle_renderers (self);
216
217   g_object_notify (G_OBJECT (self), "has-changed");
218 }
219
220 static void
221 toggle_individual_row (EmpathyIndividualLinker *self,
222     GtkTreePath *path)
223 {
224   EmpathyIndividualLinkerPriv *priv = GET_PRIV (self);
225   FolksIndividual *individual;
226   GtkTreeIter iter;
227   GtkTreeModel *tree_model;
228   gboolean individual_added;
229
230   tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->individual_view));
231
232   gtk_tree_model_get_iter (tree_model, &iter, path);
233   gtk_tree_model_get (tree_model, &iter,
234       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
235       -1);
236
237   if (individual == NULL)
238     return;
239
240   individual_added = GPOINTER_TO_UINT (g_hash_table_lookup (
241       priv->changed_individuals, individual));
242
243   /* Toggle the Individual's linked status */
244   if (individual_added)
245     unlink_individual (self, individual);
246   else
247     link_individual (self, individual);
248
249   g_object_unref (individual);
250 }
251
252 static void
253 row_activated_cb (EmpathyIndividualView *view,
254     GtkTreePath *path,
255     GtkTreeViewColumn *column,
256     EmpathyIndividualLinker *self)
257 {
258   toggle_individual_row (self, path);
259 }
260
261 static void
262 row_toggled_cb (GtkCellRendererToggle *cell_renderer,
263     const gchar *path,
264     EmpathyIndividualLinker *self)
265 {
266   GtkTreePath *tree_path = gtk_tree_path_new_from_string (path);
267   toggle_individual_row (self, tree_path);
268   gtk_tree_path_free (tree_path);
269 }
270
271 static gboolean
272 individual_view_drag_motion_cb (GtkWidget *widget,
273     GdkDragContext *context,
274     gint x,
275     gint y,
276     guint time_)
277 {
278   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (widget);
279   GdkAtom target;
280
281   target = gtk_drag_dest_find_target (GTK_WIDGET (view), context, NULL);
282
283   if (target == gdk_atom_intern_static_string ("text/persona-id"))
284     {
285       GtkTreePath *path;
286
287       /* FIXME: It doesn't make sense for us to highlight a specific row or
288        * position to drop a Persona in, so just highlight the entire widget.
289        * Since I can't find a way to do this, just highlight the first possible
290        * position in the tree. */
291       gdk_drag_status (context, gdk_drag_context_get_suggested_action (context),
292           time_);
293
294       path = gtk_tree_path_new_first ();
295       gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (view), path,
296           GTK_TREE_VIEW_DROP_BEFORE);
297       gtk_tree_path_free (path);
298
299       return TRUE;
300     }
301
302   /* Unknown or unhandled drag target */
303   gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
304   gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (view), NULL, 0);
305
306   return FALSE;
307 }
308
309 static gboolean
310 individual_view_drag_persona_received_cb (EmpathyIndividualView *view,
311     GdkDragAction action,
312     FolksPersona *persona,
313     FolksIndividual *individual,
314     EmpathyIndividualLinker *self)
315 {
316   EmpathyIndividualLinkerPriv *priv = GET_PRIV (self);
317
318   /* A Persona has been dragged onto the EmpathyIndividualView (from the
319    * EmpathyPersonaView), so we try to remove the Individual which contains
320    * the Persona from the link. */
321   if (individual != priv->start_individual)
322     {
323       unlink_individual (self, individual);
324       return TRUE;
325     }
326
327   return FALSE;
328 }
329
330 static gboolean
331 persona_view_drag_individual_received_cb (EmpathyPersonaView *view,
332     GdkDragAction action,
333     FolksIndividual *individual,
334     EmpathyIndividualLinker *self)
335 {
336   /* An Individual has been dragged onto the EmpathyPersonaView (from the
337    * EmpathyIndividualView), so we try to add the Individual to the link. */
338   link_individual (self, individual);
339
340   return TRUE;
341 }
342
343 static void
344 set_up (EmpathyIndividualLinker *self)
345 {
346   EmpathyIndividualLinkerPriv *priv;
347   EmpathyIndividualManager *individual_manager;
348   GtkWidget *top_vbox;
349   GtkPaned *paned;
350   GtkWidget *label, *scrolled_window;
351   GtkBox *vbox;
352   EmpathyPersonaView *persona_view;
353   gchar *tmp;
354   GtkWidget *alignment;
355
356   priv = GET_PRIV (self);
357
358   top_vbox = gtk_vbox_new (FALSE, 6);
359
360   /* Layout panes */
361   paned = GTK_PANED (gtk_hpaned_new ());
362
363   /* Left column heading */
364   alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
365   gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 0, 6);
366   gtk_widget_show (alignment);
367
368   vbox = GTK_BOX (gtk_vbox_new (FALSE, 6));
369   label = gtk_label_new (NULL);
370   tmp = g_strdup_printf ("<b>%s</b>", _("Select contacts to link"));
371   gtk_label_set_markup (GTK_LABEL (label), tmp);
372   g_free (tmp);
373   gtk_box_pack_start (vbox, label, FALSE, TRUE, 0);
374   gtk_widget_show (label);
375
376   /* Individual selector */
377   individual_manager = empathy_individual_manager_dup_singleton ();
378   priv->individual_store = empathy_individual_store_new (individual_manager);
379   g_object_unref (individual_manager);
380
381   empathy_individual_store_set_show_protocols (priv->individual_store, FALSE);
382
383   priv->individual_view = empathy_individual_view_new (priv->individual_store,
384       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG |
385       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP |
386       EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP,
387       EMPATHY_INDIVIDUAL_FEATURE_NONE);
388   empathy_individual_view_set_show_offline (priv->individual_view, TRUE);
389   empathy_individual_view_set_show_untrusted (priv->individual_view, FALSE);
390
391   g_signal_connect (priv->individual_view, "row-activated",
392       (GCallback) row_activated_cb, self);
393   g_signal_connect (priv->individual_view, "drag-motion",
394       (GCallback) individual_view_drag_motion_cb, self);
395   g_signal_connect (priv->individual_view, "drag-persona-received",
396       (GCallback) individual_view_drag_persona_received_cb, self);
397
398   /* Add a checkbox column to the selector */
399   priv->toggle_renderer = gtk_cell_renderer_toggle_new ();
400   g_signal_connect (priv->toggle_renderer, "toggled",
401       (GCallback) row_toggled_cb, self);
402
403   priv->toggle_column = gtk_tree_view_column_new ();
404   gtk_tree_view_column_pack_start (priv->toggle_column, priv->toggle_renderer,
405       FALSE);
406   gtk_tree_view_column_set_cell_data_func (priv->toggle_column,
407       priv->toggle_renderer,
408       (GtkTreeCellDataFunc) contact_toggle_cell_data_func, self, NULL);
409
410   gtk_tree_view_insert_column (GTK_TREE_VIEW (priv->individual_view),
411       priv->toggle_column, 0);
412
413   scrolled_window = gtk_scrolled_window_new (NULL, NULL);
414   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
415       GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
416   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
417       GTK_SHADOW_IN);
418   gtk_container_add (GTK_CONTAINER (scrolled_window),
419       GTK_WIDGET (priv->individual_view));
420   gtk_widget_show (GTK_WIDGET (priv->individual_view));
421
422   gtk_box_pack_start (vbox, scrolled_window, TRUE, TRUE, 0);
423   gtk_widget_show (scrolled_window);
424
425   /* Live search */
426   priv->search_widget = empathy_live_search_new (
427       GTK_WIDGET (priv->individual_view));
428   empathy_individual_view_set_live_search (priv->individual_view,
429       EMPATHY_LIVE_SEARCH (priv->search_widget));
430
431   gtk_box_pack_end (vbox, priv->search_widget, FALSE, TRUE, 0);
432
433   gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (vbox));
434   gtk_paned_pack1 (paned, alignment, TRUE, FALSE);
435   gtk_widget_show (GTK_WIDGET (vbox));
436
437   /* Right column heading */
438   alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
439   gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 6, 0);
440   gtk_widget_show (alignment);
441
442   vbox = GTK_BOX (gtk_vbox_new (FALSE, 6));
443   label = gtk_label_new (NULL);
444   tmp = g_strdup_printf ("<b>%s</b>", _("New contact preview"));
445   gtk_label_set_markup (GTK_LABEL (label), tmp);
446   g_free (tmp);
447   gtk_box_pack_start (vbox, label, FALSE, TRUE, 0);
448   gtk_widget_show (label);
449
450   /* New individual preview */
451   priv->preview_widget = empathy_individual_widget_new (priv->new_individual,
452       EMPATHY_INDIVIDUAL_WIDGET_SHOW_DETAILS);
453   gtk_box_pack_start (vbox, priv->preview_widget, FALSE, TRUE, 0);
454   gtk_widget_show (priv->preview_widget);
455
456   /* Persona list */
457   scrolled_window = gtk_scrolled_window_new (NULL, NULL);
458   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
459       GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
460   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
461       GTK_SHADOW_IN);
462
463   priv->persona_store = empathy_persona_store_new (priv->new_individual);
464   empathy_persona_store_set_show_protocols (priv->persona_store, TRUE);
465   persona_view = empathy_persona_view_new (priv->persona_store,
466       EMPATHY_PERSONA_VIEW_FEATURE_ALL);
467   empathy_persona_view_set_show_offline (persona_view, TRUE);
468
469   g_signal_connect (persona_view, "drag-individual-received",
470       (GCallback) persona_view_drag_individual_received_cb, self);
471
472   gtk_container_add (GTK_CONTAINER (scrolled_window),
473       GTK_WIDGET (persona_view));
474   gtk_widget_show (GTK_WIDGET (persona_view));
475
476   gtk_box_pack_start (vbox, scrolled_window, TRUE, TRUE, 0);
477   gtk_widget_show (scrolled_window);
478
479   gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (vbox));
480   gtk_paned_pack2 (paned, alignment, TRUE, FALSE);
481   gtk_widget_show (GTK_WIDGET (vbox));
482
483   gtk_widget_show (GTK_WIDGET (paned));
484
485   /* Footer label */
486   label = gtk_label_new (NULL);
487   tmp = g_strdup_printf ("<i>%s</i>",
488       _("Contacts selected in the list on the left will be linked together."));
489   gtk_label_set_markup (GTK_LABEL (label), tmp);
490   g_free (tmp);
491   gtk_widget_show (label);
492
493   gtk_box_pack_start (GTK_BOX (top_vbox), GTK_WIDGET (paned), TRUE, TRUE, 0);
494   gtk_box_pack_start (GTK_BOX (top_vbox), label, FALSE, TRUE, 0);
495
496   /* Add the main vbox to the bin */
497   gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (top_vbox));
498   gtk_widget_show (GTK_WIDGET (top_vbox));
499 }
500
501 static void
502 empathy_individual_linker_init (EmpathyIndividualLinker *self)
503 {
504   EmpathyIndividualLinkerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
505       EMPATHY_TYPE_INDIVIDUAL_LINKER, EmpathyIndividualLinkerPriv);
506
507   self->priv = priv;
508
509   priv->changed_individuals = g_hash_table_new (NULL, NULL);
510
511   set_up (self);
512 }
513
514 static void
515 get_property (GObject *object,
516     guint param_id,
517     GValue *value,
518     GParamSpec *pspec)
519 {
520   EmpathyIndividualLinkerPriv *priv;
521
522   priv = GET_PRIV (object);
523
524   switch (param_id)
525     {
526       case PROP_START_INDIVIDUAL:
527         g_value_set_object (value, priv->start_individual);
528         break;
529       case PROP_HAS_CHANGED:
530         g_value_set_boolean (value, empathy_individual_linker_get_has_changed (
531             EMPATHY_INDIVIDUAL_LINKER (object)));
532         break;
533       default:
534         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
535         break;
536     }
537 }
538
539 static void
540 set_property (GObject *object,
541     guint param_id,
542     const GValue *value,
543     GParamSpec *pspec)
544 {
545   switch (param_id)
546     {
547       case PROP_START_INDIVIDUAL:
548         empathy_individual_linker_set_start_individual (
549             EMPATHY_INDIVIDUAL_LINKER (object), g_value_get_object (value));
550         break;
551       default:
552         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
553         break;
554     }
555 }
556
557 static void
558 dispose (GObject *object)
559 {
560   EmpathyIndividualLinkerPriv *priv = GET_PRIV (object);
561
562   tp_clear_object (&priv->individual_store);
563   tp_clear_object (&priv->persona_store);
564   tp_clear_object (&priv->start_individual);
565   tp_clear_object (&priv->new_individual);
566
567   G_OBJECT_CLASS (empathy_individual_linker_parent_class)->dispose (object);
568 }
569
570 static void
571 finalize (GObject *object)
572 {
573   EmpathyIndividualLinkerPriv *priv = GET_PRIV (object);
574
575   g_hash_table_destroy (priv->changed_individuals);
576
577   G_OBJECT_CLASS (empathy_individual_linker_parent_class)->finalize (object);
578 }
579
580 static void
581 size_allocate (GtkWidget *widget,
582     GtkAllocation *allocation)
583 {
584   GtkBin *bin = GTK_BIN (widget);
585   GtkAllocation child_allocation;
586   GtkWidget *child;
587
588   gtk_widget_set_allocation (widget, allocation);
589
590   child = gtk_bin_get_child (bin);
591
592   if (child && gtk_widget_get_visible (child))
593     {
594       child_allocation.x = allocation->x +
595           gtk_container_get_border_width (GTK_CONTAINER (widget));
596       child_allocation.y = allocation->y +
597           gtk_container_get_border_width (GTK_CONTAINER (widget));
598       child_allocation.width = MAX (allocation->width -
599           gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
600       child_allocation.height = MAX (allocation->height -
601           gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
602
603       gtk_widget_size_allocate (child, &child_allocation);
604     }
605 }
606
607 static void
608 empathy_individual_linker_class_init (EmpathyIndividualLinkerClass *klass)
609 {
610   GObjectClass *object_class = G_OBJECT_CLASS (klass);
611   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
612
613   object_class->get_property = get_property;
614   object_class->set_property = set_property;
615   object_class->dispose = dispose;
616   object_class->finalize = finalize;
617
618   widget_class->size_allocate = size_allocate;
619
620   /**
621    * EmpathyIndividualLinker:start-individual:
622    *
623    * The #FolksIndividual to link other individuals to. This individual is
624    * selected by default in the list of individuals, and cannot be unselected.
625    * This ensures that empathy_individual_linker_get_linked_personas() will
626    * always return at least one persona to link.
627    */
628   g_object_class_install_property (object_class, PROP_START_INDIVIDUAL,
629       g_param_spec_object ("start-individual",
630           "Start Individual",
631           "The #FolksIndividual to link other individuals to.",
632           FOLKS_TYPE_INDIVIDUAL,
633           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
634
635   /**
636    * EmpathyIndividualLinker:has-changed:
637    *
638    * Whether #FolksIndividual<!-- -->s have been added to or removed from
639    * the linked individual currently displayed in the widget.
640    *
641    * This will be %FALSE after the widget is initialised, and set to %TRUE when
642    * an individual is checked in the individual view on the left of the widget.
643    * If the individual is later unchecked, this will be reset to %FALSE, etc.
644    */
645   g_object_class_install_property (object_class, PROP_HAS_CHANGED,
646       g_param_spec_boolean ("has-changed",
647           "Changed?",
648           "Whether individuals have been added to or removed from the linked "
649           "individual currently displayed in the widget.",
650           FALSE,
651           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
652
653   g_type_class_add_private (object_class, sizeof (EmpathyIndividualLinkerPriv));
654 }
655
656 /**
657  * empathy_individual_linker_new:
658  * @start_individual: (allow-none): the #FolksIndividual to link to, or %NULL
659  *
660  * Creates a new #EmpathyIndividualLinker.
661  *
662  * Return value: a new #EmpathyIndividualLinker
663  */
664 GtkWidget *
665 empathy_individual_linker_new (FolksIndividual *start_individual)
666 {
667   g_return_val_if_fail (start_individual == NULL ||
668       FOLKS_IS_INDIVIDUAL (start_individual), NULL);
669
670   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_LINKER,
671       "start-individual", start_individual,
672       NULL);
673 }
674
675 /**
676  * empathy_individual_linker_get_start_individual:
677  * @self: an #EmpathyIndividualLinker
678  *
679  * Get the value of #EmpathyIndividualLinker:start-individual.
680  *
681  * Return value: (transfer none): the start individual for linking, or %NULL
682  */
683 FolksIndividual *
684 empathy_individual_linker_get_start_individual (EmpathyIndividualLinker *self)
685 {
686   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_LINKER (self), NULL);
687
688   return GET_PRIV (self)->start_individual;
689 }
690
691 /**
692  * empathy_individual_linker_set_start_individual:
693  * @self: an #EmpathyIndividualLinker
694  * @individual: (allow-none): the start individual, or %NULL
695  *
696  * Set the value of #EmpathyIndividualLinker:start-individual to @individual.
697  */
698 void
699 empathy_individual_linker_set_start_individual (EmpathyIndividualLinker *self,
700     FolksIndividual *individual)
701 {
702   EmpathyIndividualLinkerPriv *priv;
703
704   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_LINKER (self));
705   g_return_if_fail (individual == NULL || FOLKS_IS_INDIVIDUAL (individual));
706
707   priv = GET_PRIV (self);
708
709   tp_clear_object (&priv->start_individual);
710   tp_clear_object (&priv->new_individual);
711   g_hash_table_remove_all (priv->changed_individuals);
712
713   if (individual != NULL)
714     {
715       priv->start_individual = g_object_ref (individual);
716       priv->new_individual = folks_individual_new (
717           folks_individual_get_personas (individual));
718       empathy_individual_view_set_store (priv->individual_view,
719           priv->individual_store);
720     }
721   else
722     {
723       priv->start_individual = NULL;
724       priv->new_individual = NULL;
725
726       /* We only display Individuals in the individual view if we have a
727        * new_individual to link them into */
728       empathy_individual_view_set_store (priv->individual_view, NULL);
729     }
730
731   empathy_individual_widget_set_individual (
732       EMPATHY_INDIVIDUAL_WIDGET (priv->preview_widget), priv->new_individual);
733   empathy_persona_store_set_individual (priv->persona_store,
734       priv->new_individual);
735
736   g_object_freeze_notify (G_OBJECT (self));
737   g_object_notify (G_OBJECT (self), "start-individual");
738   g_object_notify (G_OBJECT (self), "has-changed");
739   g_object_thaw_notify (G_OBJECT (self));
740 }
741
742 /**
743  * empathy_individual_linker_get_linked_personas:
744  * @self: an #EmpathyIndividualLinker
745  *
746  * Return a list of the #FolksPersona<!-- -->s which comprise the linked
747  * individual currently displayed in the widget.
748  *
749  * The return value is guaranteed to contain at least one element.
750  *
751  * Return value: (transfer none) (element-type Folks.Persona): a set of
752  * #FolksPersona<!-- -->s to link together
753  */
754 GeeSet *
755 empathy_individual_linker_get_linked_personas (EmpathyIndividualLinker *self)
756 {
757   EmpathyIndividualLinkerPriv *priv;
758   GeeSet *personas;
759
760   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_LINKER (self), NULL);
761
762   priv = GET_PRIV (self);
763
764   if (priv->new_individual == NULL)
765     return NULL;
766
767   personas = folks_individual_get_personas (priv->new_individual);
768   g_assert (personas != NULL);
769   return personas;
770 }
771
772 /**
773  * empathy_individual_linker_get_has_changed:
774  * @self: an #EmpathyIndividualLinker
775  *
776  * Return whether #FolksIndividual<!-- -->s have been added to or removed from
777  * the linked individual currently displayed in the widget.
778  *
779  * This will be %FALSE after the widget is initialised, and set to %TRUE when
780  * an individual is checked in the individual view on the left of the widget.
781  * If the individual is later unchecked, this will be reset to %FALSE, etc.
782  *
783  * Return value: %TRUE if the linked individual has been changed, %FALSE
784  * otherwise
785  */
786 gboolean
787 empathy_individual_linker_get_has_changed (EmpathyIndividualLinker *self)
788 {
789   EmpathyIndividualLinkerPriv *priv;
790
791   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_LINKER (self), FALSE);
792
793   priv = GET_PRIV (self);
794
795   return (g_hash_table_size (priv->changed_individuals) > 0) ? TRUE : FALSE;
796 }
797
798 void
799 empathy_individual_linker_set_search_text (EmpathyIndividualLinker *self,
800     const gchar *search_text)
801 {
802   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_LINKER (self));
803
804   empathy_live_search_set_text (
805       EMPATHY_LIVE_SEARCH (GET_PRIV (self)->search_widget), search_text);
806 }