]> git.0d.be Git - empathy.git/blobdiff - libempathy-gtk/empathy-individual-linker.c
use gtk_box_new() instead of gtk_[h,v]box_new()
[empathy.git] / libempathy-gtk / empathy-individual-linker.c
index 94c95cbdc08ca0ce6b805c84854ac6eea1a32c4e..d53633f7ace2c75fff6e87cff563cf1336765244 100644 (file)
@@ -66,6 +66,9 @@ typedef struct {
   EmpathyIndividualView *individual_view; /* child widget */
   GtkWidget *preview_widget; /* child widget */
   EmpathyPersonaStore *persona_store; /* owned */
+  GtkTreeViewColumn *toggle_column; /* child widget */
+  GtkCellRenderer *toggle_renderer; /* child widget */
+  GtkWidget *search_widget; /* child widget */
 
   FolksIndividual *start_individual; /* owned, allow-none */
   FolksIndividual *new_individual; /* owned, allow-none */
@@ -78,10 +81,11 @@ typedef struct {
 
 enum {
   PROP_START_INDIVIDUAL = 1,
+  PROP_HAS_CHANGED,
 };
 
 G_DEFINE_TYPE (EmpathyIndividualLinker, empathy_individual_linker,
-    GTK_TYPE_BIN);
+    GTK_TYPE_BOX);
 
 static void
 contact_toggle_cell_data_func (GtkTreeViewColumn *tree_column,
@@ -117,26 +121,61 @@ contact_toggle_cell_data_func (GtkTreeViewColumn *tree_column,
   tp_clear_object (&individual);
 }
 
+static void
+update_toggle_renderers (EmpathyIndividualLinker *self)
+{
+  EmpathyIndividualLinkerPriv *priv = GET_PRIV (self);
+
+  /* Re-setting the cell data func to the same function causes a refresh of the
+   * entire column, ensuring that each toggle button is correctly active or
+   * inactive. This is necessary because one Individual might appear multiple
+   * times in the list (in different groups), so toggling one instance of the
+   * Individual should toggle all of them. */
+  gtk_tree_view_column_set_cell_data_func (priv->toggle_column,
+      priv->toggle_renderer,
+      (GtkTreeCellDataFunc) contact_toggle_cell_data_func, self, NULL);
+}
+
 static void
 link_individual (EmpathyIndividualLinker *self,
     FolksIndividual *individual)
 {
   EmpathyIndividualLinkerPriv *priv = GET_PRIV (self);
-  GList *new_persona_list;
+  GeeSet *old_personas, *new_personas;
+  GeeHashSet *final_personas;
+  gboolean personas_changed;
 
   /* Add the individual to the link */
   g_hash_table_insert (priv->changed_individuals, individual,
       GUINT_TO_POINTER (TRUE));
 
-  /* Add personas which are in @individual to priv->new_individual, appending
-   * them to the list of personas.
-   * This is rather slow. */
-  new_persona_list = g_list_copy (folks_individual_get_personas (
-      priv->new_individual));
-  new_persona_list = g_list_concat (new_persona_list,
-      g_list_copy (folks_individual_get_personas (individual)));
-  folks_individual_set_personas (priv->new_individual, new_persona_list);
-  g_list_free (new_persona_list);
+  /* Add personas which are in @individual to priv->new_individual, adding them
+   * to the set of personas. */
+  old_personas = folks_individual_get_personas (individual);
+  new_personas = folks_individual_get_personas (priv->new_individual);
+  final_personas = gee_hash_set_new (FOLKS_TYPE_PERSONA, g_object_ref,
+      g_object_unref, g_direct_hash, g_direct_equal);
+  gee_collection_add_all (GEE_COLLECTION (final_personas),
+      GEE_COLLECTION (old_personas));
+  personas_changed = gee_collection_add_all (GEE_COLLECTION (final_personas),
+      GEE_COLLECTION (new_personas));
+
+  /* avoid updating all values in the Individual if the set of personas doesn't
+   * actually change */
+  if (personas_changed)
+    {
+      folks_individual_set_personas (priv->new_individual,
+          GEE_SET (final_personas));
+    }
+
+  g_clear_object (&final_personas);
+
+  /* Update the toggle renderers, so that if this Individual is listed in
+   * another group in the EmpathyIndividualView, the toggle button for that
+   * group is updated. */
+  update_toggle_renderers (self);
+
+  g_object_notify (G_OBJECT (self), "has-changed");
 }
 
 static void
@@ -144,28 +183,38 @@ unlink_individual (EmpathyIndividualLinker *self,
     FolksIndividual *individual)
 {
   EmpathyIndividualLinkerPriv *priv = GET_PRIV (self);
-  GList *new_persona_list, *old_persona_list, *removing_personas, *l;
+  GeeSet *removed_personas, *old_personas;
+  GeeHashSet *final_personas;
+  gboolean personas_changed;
 
   /* Remove the individual from the link */
   g_hash_table_remove (priv->changed_individuals, individual);
 
-  /* Remove personas which are in @individual from priv->new_individual.
-   * This is rather slow. */
-  old_persona_list = folks_individual_get_personas (priv->new_individual);
-  removing_personas = folks_individual_get_personas (individual);
-  new_persona_list = NULL;
+  /* Remove personas which are in @individual from priv->new_individual. */
+  old_personas = folks_individual_get_personas (priv->new_individual);
+  removed_personas = folks_individual_get_personas (individual);
 
-  for (l = old_persona_list; l != NULL; l = l->next)
-    {
-      GList *removing = g_list_find (removing_personas, l->data);
+  final_personas = gee_hash_set_new (FOLKS_TYPE_PERSONA, g_object_ref,
+      g_object_unref, g_direct_hash, g_direct_equal);
+  gee_collection_add_all (GEE_COLLECTION (final_personas),
+      GEE_COLLECTION (old_personas));
+  personas_changed = gee_collection_remove_all (GEE_COLLECTION (final_personas),
+      GEE_COLLECTION (removed_personas));
 
-      if (removing == NULL)
-        new_persona_list = g_list_prepend (new_persona_list, l->data);
+  if (personas_changed)
+    {
+      folks_individual_set_personas (priv->new_individual,
+          GEE_SET (final_personas));
     }
 
-  new_persona_list = g_list_reverse (new_persona_list);
-  folks_individual_set_personas (priv->new_individual, new_persona_list);
-  g_list_free (new_persona_list);
+  g_clear_object (&final_personas);
+
+  /* Update the toggle renderers, so that if this Individual is listed in
+   * another group in the EmpathyIndividualView, the toggle button for that
+   * group is updated. */
+  update_toggle_renderers (self);
+
+  g_object_notify (G_OBJECT (self), "has-changed");
 }
 
 static void
@@ -231,7 +280,7 @@ individual_view_drag_motion_cb (GtkWidget *widget,
 
   target = gtk_drag_dest_find_target (GTK_WIDGET (view), context, NULL);
 
-  if (target == gdk_atom_intern_static_string ("text/persona-id"))
+  if (target == gdk_atom_intern_static_string ("text/x-persona-id"))
     {
       GtkTreePath *path;
 
@@ -296,10 +345,9 @@ set_up (EmpathyIndividualLinker *self)
 {
   EmpathyIndividualLinkerPriv *priv;
   EmpathyIndividualManager *individual_manager;
-  GtkCellRenderer *cell_renderer;
   GtkWidget *top_vbox;
   GtkPaned *paned;
-  GtkWidget *label, *scrolled_window, *search_bar;
+  GtkWidget *label, *scrolled_window;
   GtkBox *vbox;
   EmpathyPersonaView *persona_view;
   gchar *tmp;
@@ -307,17 +355,18 @@ set_up (EmpathyIndividualLinker *self)
 
   priv = GET_PRIV (self);
 
-  top_vbox = gtk_vbox_new (FALSE, 6);
+  top_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
 
   /* Layout panes */
-  paned = GTK_PANED (gtk_hpaned_new ());
+
+  paned = GTK_PANED (gtk_paned_new (GTK_ORIENTATION_HORIZONTAL));
 
   /* Left column heading */
   alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
   gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 0, 6);
   gtk_widget_show (alignment);
 
-  vbox = GTK_BOX (gtk_vbox_new (FALSE, 6));
+  vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 6));
   label = gtk_label_new (NULL);
   tmp = g_strdup_printf ("<b>%s</b>", _("Select contacts to link"));
   gtk_label_set_markup (GTK_LABEL (label), tmp);
@@ -348,12 +397,20 @@ set_up (EmpathyIndividualLinker *self)
       (GCallback) individual_view_drag_persona_received_cb, self);
 
   /* Add a checkbox column to the selector */
-  cell_renderer = gtk_cell_renderer_toggle_new ();
-  g_signal_connect (cell_renderer, "toggled", (GCallback) row_toggled_cb, self);
-  gtk_tree_view_insert_column_with_data_func (
-      GTK_TREE_VIEW (priv->individual_view), 0, NULL, cell_renderer,
+  priv->toggle_renderer = gtk_cell_renderer_toggle_new ();
+  g_signal_connect (priv->toggle_renderer, "toggled",
+      (GCallback) row_toggled_cb, self);
+
+  priv->toggle_column = gtk_tree_view_column_new ();
+  gtk_tree_view_column_pack_start (priv->toggle_column, priv->toggle_renderer,
+      FALSE);
+  gtk_tree_view_column_set_cell_data_func (priv->toggle_column,
+      priv->toggle_renderer,
       (GtkTreeCellDataFunc) contact_toggle_cell_data_func, self, NULL);
 
+  gtk_tree_view_insert_column (GTK_TREE_VIEW (priv->individual_view),
+      priv->toggle_column, 0);
+
   scrolled_window = gtk_scrolled_window_new (NULL, NULL);
   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
       GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
@@ -367,11 +424,12 @@ set_up (EmpathyIndividualLinker *self)
   gtk_widget_show (scrolled_window);
 
   /* Live search */
-  search_bar = empathy_live_search_new (GTK_WIDGET (priv->individual_view));
+  priv->search_widget = empathy_live_search_new (
+      GTK_WIDGET (priv->individual_view));
   empathy_individual_view_set_live_search (priv->individual_view,
-      EMPATHY_LIVE_SEARCH (search_bar));
+      EMPATHY_LIVE_SEARCH (priv->search_widget));
 
-  gtk_box_pack_end (vbox, search_bar, FALSE, TRUE, 0);
+  gtk_box_pack_end (vbox, priv->search_widget, FALSE, TRUE, 0);
 
   gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (vbox));
   gtk_paned_pack1 (paned, alignment, TRUE, FALSE);
@@ -382,7 +440,7 @@ set_up (EmpathyIndividualLinker *self)
   gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 6, 0);
   gtk_widget_show (alignment);
 
-  vbox = GTK_BOX (gtk_vbox_new (FALSE, 6));
+  vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 6));
   label = gtk_label_new (NULL);
   tmp = g_strdup_printf ("<b>%s</b>", _("New contact preview"));
   gtk_label_set_markup (GTK_LABEL (label), tmp);
@@ -437,7 +495,7 @@ set_up (EmpathyIndividualLinker *self)
   gtk_box_pack_start (GTK_BOX (top_vbox), label, FALSE, TRUE, 0);
 
   /* Add the main vbox to the bin */
-  gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (top_vbox));
+  gtk_box_pack_start (GTK_BOX (self), GTK_WIDGET (top_vbox), TRUE, TRUE, 0);
   gtk_widget_show (GTK_WIDGET (top_vbox));
 }
 
@@ -469,6 +527,10 @@ get_property (GObject *object,
       case PROP_START_INDIVIDUAL:
         g_value_set_object (value, priv->start_individual);
         break;
+      case PROP_HAS_CHANGED:
+        g_value_set_boolean (value, empathy_individual_linker_get_has_changed (
+            EMPATHY_INDIVIDUAL_LINKER (object)));
+        break;
       default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
         break;
@@ -481,10 +543,6 @@ set_property (GObject *object,
     const GValue *value,
     GParamSpec *pspec)
 {
-  EmpathyIndividualLinkerPriv *priv;
-
-  priv = GET_PRIV (object);
-
   switch (param_id)
     {
       case PROP_START_INDIVIDUAL:
@@ -515,77 +573,21 @@ finalize (GObject *object)
 {
   EmpathyIndividualLinkerPriv *priv = GET_PRIV (object);
 
-  g_hash_table_destroy (priv->changed_individuals);
+  g_hash_table_unref (priv->changed_individuals);
 
   G_OBJECT_CLASS (empathy_individual_linker_parent_class)->finalize (object);
 }
 
-static void
-size_request (GtkWidget *widget,
-    GtkRequisition *requisition)
-{
-  GtkBin *bin = GTK_BIN (widget);
-  GtkWidget *child;
-
-  requisition->width =
-      gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
-  requisition->height =
-      gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
-
-  child = gtk_bin_get_child (bin);
-
-  if (child && gtk_widget_get_visible (child))
-    {
-      GtkRequisition child_requisition;
-
-      gtk_widget_size_request (child, &child_requisition);
-
-      requisition->width += child_requisition.width;
-      requisition->height += child_requisition.height;
-    }
-}
-
-static void
-size_allocate (GtkWidget *widget,
-    GtkAllocation *allocation)
-{
-  GtkBin *bin = GTK_BIN (widget);
-  GtkAllocation child_allocation;
-  GtkWidget *child;
-
-  gtk_widget_set_allocation (widget, allocation);
-
-  child = gtk_bin_get_child (bin);
-
-  if (child && gtk_widget_get_visible (child))
-    {
-      child_allocation.x = allocation->x +
-          gtk_container_get_border_width (GTK_CONTAINER (widget));
-      child_allocation.y = allocation->y +
-          gtk_container_get_border_width (GTK_CONTAINER (widget));
-      child_allocation.width = MAX (allocation->width -
-          gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
-      child_allocation.height = MAX (allocation->height -
-          gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
-
-      gtk_widget_size_allocate (child, &child_allocation);
-    }
-}
-
 static void
 empathy_individual_linker_class_init (EmpathyIndividualLinkerClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
   object_class->get_property = get_property;
   object_class->set_property = set_property;
   object_class->dispose = dispose;
   object_class->finalize = finalize;
 
-  widget_class->size_request = size_request;
-  widget_class->size_allocate = size_allocate;
-
   /**
    * EmpathyIndividualLinker:start-individual:
    *
@@ -601,6 +603,24 @@ empathy_individual_linker_class_init (EmpathyIndividualLinkerClass *klass)
           FOLKS_TYPE_INDIVIDUAL,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
+  /**
+   * EmpathyIndividualLinker:has-changed:
+   *
+   * Whether #FolksIndividual<!-- -->s have been added to or removed from
+   * the linked individual currently displayed in the widget.
+   *
+   * This will be %FALSE after the widget is initialised, and set to %TRUE when
+   * an individual is checked in the individual view on the left of the widget.
+   * If the individual is later unchecked, this will be reset to %FALSE, etc.
+   */
+  g_object_class_install_property (object_class, PROP_HAS_CHANGED,
+      g_param_spec_boolean ("has-changed",
+          "Changed?",
+          "Whether individuals have been added to or removed from the linked "
+          "individual currently displayed in the widget.",
+          FALSE,
+          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
   g_type_class_add_private (object_class, sizeof (EmpathyIndividualLinkerPriv));
 }
 
@@ -684,7 +704,10 @@ empathy_individual_linker_set_start_individual (EmpathyIndividualLinker *self,
   empathy_persona_store_set_individual (priv->persona_store,
       priv->new_individual);
 
+  g_object_freeze_notify (G_OBJECT (self));
   g_object_notify (G_OBJECT (self), "start-individual");
+  g_object_notify (G_OBJECT (self), "has-changed");
+  g_object_thaw_notify (G_OBJECT (self));
 }
 
 /**
@@ -696,14 +719,14 @@ empathy_individual_linker_set_start_individual (EmpathyIndividualLinker *self,
  *
  * The return value is guaranteed to contain at least one element.
  *
- * Return value: (transfer none) (element-type Folks.Persona): a list of
+ * Return value: (transfer none) (element-type Folks.Persona): a set of
  * #FolksPersona<!-- -->s to link together
  */
-GList *
+GeeSet *
 empathy_individual_linker_get_linked_personas (EmpathyIndividualLinker *self)
 {
   EmpathyIndividualLinkerPriv *priv;
-  GList *personas;
+  GeeSet *personas;
 
   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_LINKER (self), NULL);
 
@@ -716,3 +739,39 @@ empathy_individual_linker_get_linked_personas (EmpathyIndividualLinker *self)
   g_assert (personas != NULL);
   return personas;
 }
+
+/**
+ * empathy_individual_linker_get_has_changed:
+ * @self: an #EmpathyIndividualLinker
+ *
+ * Return whether #FolksIndividual<!-- -->s have been added to or removed from
+ * the linked individual currently displayed in the widget.
+ *
+ * This will be %FALSE after the widget is initialised, and set to %TRUE when
+ * an individual is checked in the individual view on the left of the widget.
+ * If the individual is later unchecked, this will be reset to %FALSE, etc.
+ *
+ * Return value: %TRUE if the linked individual has been changed, %FALSE
+ * otherwise
+ */
+gboolean
+empathy_individual_linker_get_has_changed (EmpathyIndividualLinker *self)
+{
+  EmpathyIndividualLinkerPriv *priv;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_LINKER (self), FALSE);
+
+  priv = GET_PRIV (self);
+
+  return (g_hash_table_size (priv->changed_individuals) > 0) ? TRUE : FALSE;
+}
+
+void
+empathy_individual_linker_set_search_text (EmpathyIndividualLinker *self,
+    const gchar *search_text)
+{
+  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_LINKER (self));
+
+  empathy_live_search_set_text (
+      EMPATHY_LIVE_SEARCH (GET_PRIV (self)->search_widget), search_text);
+}