#include "empathy-individual-linker.h"
#include "empathy-individual-store.h"
+#include "empathy-individual-store-manager.h"
#include "empathy-individual-view.h"
#include "empathy-individual-widget.h"
#include "empathy-persona-store.h"
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 */
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,
g_object_set (cell,
"visible", !is_group,
"sensitive", individual != priv->start_individual,
+ "activatable", individual != priv->start_individual,
"active", individual_added || individual == priv->start_individual,
NULL);
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
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
GtkTreeModel *tree_model;
gboolean individual_added;
- tree_model = GTK_TREE_MODEL (priv->individual_store);
+ tree_model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->individual_view));
gtk_tree_model_get_iter (tree_model, &iter, path);
gtk_tree_model_get (tree_model, &iter,
EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
-1);
+ if (individual == NULL)
+ return;
+
individual_added = GPOINTER_TO_UINT (g_hash_table_lookup (
priv->changed_individuals, individual));
g_object_unref (individual);
}
+static void
+row_activated_cb (EmpathyIndividualView *view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ EmpathyIndividualLinker *self)
+{
+ toggle_individual_row (self, path);
+}
+
static void
row_toggled_cb (GtkCellRendererToggle *cell_renderer,
const gchar *path,
gtk_tree_path_free (tree_path);
}
-static void
-search_bar_activate_cb (EmpathyLiveSearch *search_bar,
- EmpathyIndividualLinker *self)
+static gboolean
+individual_view_drag_motion_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time_)
{
- EmpathyIndividualLinkerPriv *priv = GET_PRIV (self);
- GtkTreeSelection *selection;
- GList *rows, *l;
+ EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (widget);
+ GdkAtom target;
- /* Toggle the status of the selected individuals */
- selection = gtk_tree_view_get_selection (
- GTK_TREE_VIEW (priv->individual_view));
- rows = gtk_tree_selection_get_selected_rows (selection, NULL);
+ target = gtk_drag_dest_find_target (GTK_WIDGET (view), context, NULL);
- for (l = rows; l != NULL; l = l->next)
+ if (target == gdk_atom_intern_static_string ("text/x-persona-id"))
{
- GtkTreePath *path = (GtkTreePath *) l->data;
- toggle_individual_row (self, path);
+ GtkTreePath *path;
+
+ /* FIXME: It doesn't make sense for us to highlight a specific row or
+ * position to drop a Persona in, so just highlight the entire widget.
+ * Since I can't find a way to do this, just highlight the first possible
+ * position in the tree. */
+ gdk_drag_status (context, gdk_drag_context_get_suggested_action (context),
+ time_);
+
+ path = gtk_tree_path_new_first ();
+ gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (view), path,
+ GTK_TREE_VIEW_DROP_BEFORE);
gtk_tree_path_free (path);
+
+ return TRUE;
}
- g_list_free (rows);
+ /* Unknown or unhandled drag target */
+ gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
+ gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (view), NULL, 0);
+
+ return FALSE;
+}
+
+static gboolean
+individual_view_drag_persona_received_cb (EmpathyIndividualView *view,
+ GdkDragAction action,
+ FolksPersona *persona,
+ FolksIndividual *individual,
+ EmpathyIndividualLinker *self)
+{
+ EmpathyIndividualLinkerPriv *priv = GET_PRIV (self);
+
+ /* A Persona has been dragged onto the EmpathyIndividualView (from the
+ * EmpathyPersonaView), so we try to remove the Individual which contains
+ * the Persona from the link. */
+ if (individual != priv->start_individual)
+ {
+ unlink_individual (self, individual);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+persona_view_drag_individual_received_cb (EmpathyPersonaView *view,
+ GdkDragAction action,
+ FolksIndividual *individual,
+ EmpathyIndividualLinker *self)
+{
+ /* An Individual has been dragged onto the EmpathyPersonaView (from the
+ * EmpathyIndividualView), so we try to add the Individual to the link. */
+ link_individual (self, individual);
+
+ return TRUE;
}
static void
{
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;
+ GtkWidget *alignment;
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 */
- vbox = GTK_BOX (gtk_vbox_new (FALSE, 6));
+ 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_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);
/* Individual selector */
individual_manager = empathy_individual_manager_dup_singleton ();
- priv->individual_store = empathy_individual_store_new (individual_manager);
+ priv->individual_store = EMPATHY_INDIVIDUAL_STORE (
+ empathy_individual_store_manager_new (individual_manager));
g_object_unref (individual_manager);
empathy_individual_store_set_show_protocols (priv->individual_store, FALSE);
priv->individual_view = empathy_individual_view_new (priv->individual_store,
- EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, EMPATHY_INDIVIDUAL_FEATURE_NONE);
+ EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG |
+ EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP |
+ EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP,
+ EMPATHY_INDIVIDUAL_FEATURE_NONE);
empathy_individual_view_set_show_offline (priv->individual_view, TRUE);
+ empathy_individual_view_set_show_untrusted (priv->individual_view, FALSE);
+
+ g_signal_connect (priv->individual_view, "row-activated",
+ (GCallback) row_activated_cb, self);
+ g_signal_connect (priv->individual_view, "drag-motion",
+ (GCallback) individual_view_drag_motion_cb, self);
+ g_signal_connect (priv->individual_view, "drag-persona-received",
+ (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);
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));
- g_signal_connect (search_bar, "activate", (GCallback) search_bar_activate_cb,
- self);
+ 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_paned_pack1 (paned, GTK_WIDGET (vbox), TRUE, FALSE);
+ gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (vbox));
+ gtk_paned_pack1 (paned, alignment, TRUE, FALSE);
gtk_widget_show (GTK_WIDGET (vbox));
/* Right column heading */
- vbox = GTK_BOX (gtk_vbox_new (FALSE, 6));
+ alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 6, 0);
+ gtk_widget_show (alignment);
+
+ 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);
priv->persona_store = empathy_persona_store_new (priv->new_individual);
empathy_persona_store_set_show_protocols (priv->persona_store, TRUE);
- persona_view = empathy_persona_view_new (priv->persona_store);
+ persona_view = empathy_persona_view_new (priv->persona_store,
+ EMPATHY_PERSONA_VIEW_FEATURE_ALL);
empathy_persona_view_set_show_offline (persona_view, TRUE);
+ g_signal_connect (persona_view, "drag-individual-received",
+ (GCallback) persona_view_drag_individual_received_cb, self);
+
gtk_container_add (GTK_CONTAINER (scrolled_window),
GTK_WIDGET (persona_view));
gtk_widget_show (GTK_WIDGET (persona_view));
gtk_box_pack_start (vbox, scrolled_window, TRUE, TRUE, 0);
gtk_widget_show (scrolled_window);
- gtk_paned_pack2 (paned, GTK_WIDGET (vbox), TRUE, FALSE);
+ gtk_container_add (GTK_CONTAINER (alignment), GTK_WIDGET (vbox));
+ gtk_paned_pack2 (paned, alignment, TRUE, FALSE);
gtk_widget_show (GTK_WIDGET (vbox));
gtk_widget_show (GTK_WIDGET (paned));
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));
}
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;
const GValue *value,
GParamSpec *pspec)
{
- EmpathyIndividualLinkerPriv *priv;
-
- priv = GET_PRIV (object);
-
switch (param_id)
{
case PROP_START_INDIVIDUAL:
{
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:
*
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));
}
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));
}
/**
*
* 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);
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);
+}