]> git.0d.be Git - empathy.git/blobdiff - libempathy-gtk/empathy-persona-view.c
individual_view_drag_end: remove the auto scroll
[empathy.git] / libempathy-gtk / empathy-persona-view.c
index 384f080389bd23f4a13e6c449722bb29ec42f698..7df94a630ea1871b692315b54ab200162d05b806 100644 (file)
@@ -37,6 +37,7 @@
 #include <folks/folks.h>
 #include <folks/folks-telepathy.h>
 
+#include <libempathy/empathy-individual-manager.h>
 #include <libempathy/empathy-utils.h>
 
 #include "empathy-persona-view.h"
@@ -44,6 +45,8 @@
 #include "empathy-images.h"
 #include "empathy-cell-renderer-text.h"
 #include "empathy-cell-renderer-activatable.h"
+#include "empathy-gtk-enum-types.h"
+#include "empathy-ui-utils.h"
 
 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
 #include <libempathy/empathy-debug.h>
@@ -69,6 +72,7 @@ typedef struct
   GtkTreeModelFilter *filter;
   GtkWidget *tooltip_widget;
   gboolean show_offline;
+  EmpathyPersonaViewFeatureFlags features;
 } EmpathyPersonaViewPriv;
 
 enum
@@ -76,8 +80,42 @@ enum
   PROP_0,
   PROP_MODEL,
   PROP_SHOW_OFFLINE,
+  PROP_FEATURES,
 };
 
+typedef enum
+{
+  DND_DRAG_TYPE_UNKNOWN = -1,
+  DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
+  DND_DRAG_TYPE_PERSONA_ID,
+  DND_DRAG_TYPE_STRING,
+} DndDragType;
+
+#define DRAG_TYPE(T,I) \
+  { (gchar *) T, 0, I }
+
+static const GtkTargetEntry drag_types_dest[] = {
+  DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
+  DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
+  DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
+};
+
+static const GtkTargetEntry drag_types_source[] = {
+  DRAG_TYPE ("text/x-persona-id", DND_DRAG_TYPE_PERSONA_ID),
+};
+
+#undef DRAG_TYPE
+
+static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
+
+enum
+{
+  DRAG_INDIVIDUAL_RECEIVED,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
 G_DEFINE_TYPE (EmpathyPersonaView, empathy_persona_view, GTK_TYPE_TREE_VIEW);
 
 static gboolean
@@ -135,6 +173,7 @@ query_tooltip_cb (EmpathyPersonaView *self,
 {
   EmpathyPersonaViewPriv *priv = GET_PRIV (self);
   FolksPersona *persona;
+  TpContact *tp_contact;
   EmpathyContact *contact;
   GtkTreeModel *model;
   GtkTreeIter iter;
@@ -162,8 +201,14 @@ query_tooltip_cb (EmpathyPersonaView *self,
   if (persona == NULL)
     goto OUT;
 
-  contact = empathy_contact_new (tpf_persona_get_contact (
-      TPF_PERSONA (persona)));
+  tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
+  if (tp_contact == NULL)
+    {
+      g_clear_object (&persona);
+      goto OUT;
+    }
+
+  contact = empathy_contact_dup_from_tp_contact (tp_contact);
 
   if (priv->tooltip_widget == NULL)
     {
@@ -198,29 +243,28 @@ cell_set_background (EmpathyPersonaView *self,
     GtkCellRenderer *cell,
     gboolean is_active)
 {
-  GdkColor  color;
-  GtkStyle *style;
-
-  style = gtk_widget_get_style (GTK_WIDGET (self));
-
   if (is_active)
     {
-      color = style->bg[GTK_STATE_SELECTED];
+      GdkRGBA color;
+      GtkStyleContext *style;
+
+      style = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+      gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
+          &color);
 
       /* Here we take the current theme colour and add it to
        * the colour for white and average the two. This
        * gives a colour which is inline with the theme but
        * slightly whiter.
        */
-      color.red = (color.red + (style->white).red) / 2;
-      color.green = (color.green + (style->white).green) / 2;
-      color.blue = (color.blue + (style->white).blue) / 2;
+      empathy_make_color_whiter (&color);
 
-      g_object_set (cell, "cell-background-gdk", &color, NULL);
+      g_object_set (cell, "cell-background-rgba", &color, NULL);
     }
   else
     {
-      g_object_set (cell, "cell-background-gdk", NULL, NULL);
+      g_object_set (cell, "cell-background-rgba", NULL, NULL);
     }
 }
 
@@ -311,6 +355,188 @@ text_cell_data_func (GtkTreeViewColumn *tree_column,
   cell_set_background (self, cell, is_active);
 }
 
+static gboolean
+individual_drag_received (EmpathyPersonaView *self,
+    GdkDragContext *context,
+    GtkSelectionData *selection)
+{
+  EmpathyIndividualManager *manager = NULL;
+  FolksIndividual *individual;
+  const gchar *individual_id;
+  gboolean success = FALSE;
+
+  individual_id = (const gchar *) gtk_selection_data_get_data (selection);
+  manager = empathy_individual_manager_dup_singleton ();
+  individual = empathy_individual_manager_lookup_member (manager,
+      individual_id);
+
+  if (individual == NULL)
+    {
+      DEBUG ("Failed to find drag event individual with ID '%s'",
+          individual_id);
+      g_object_unref (manager);
+      return FALSE;
+    }
+
+  /* Emit a signal notifying of the drag. */
+  g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
+      gdk_drag_context_get_selected_action (context), individual, &success);
+
+  g_object_unref (manager);
+
+  return success;
+}
+
+static void
+drag_data_received (GtkWidget *widget,
+    GdkDragContext *context,
+    gint x,
+    gint y,
+    GtkSelectionData *selection,
+    guint info,
+    guint time_)
+{
+  EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (widget);
+  gboolean success = TRUE;
+
+  if (info == DND_DRAG_TYPE_INDIVIDUAL_ID || info == DND_DRAG_TYPE_STRING)
+    success = individual_drag_received (self, context, selection);
+
+  gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
+}
+
+static gboolean
+drag_motion (GtkWidget *widget,
+    GdkDragContext *context,
+    gint x,
+    gint y,
+    guint time_)
+{
+  EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (widget);
+  GdkAtom target;
+  guint i;
+  DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
+
+  target = gtk_drag_dest_find_target (GTK_WIDGET (self), context, NULL);
+
+  /* Determine the DndDragType of the data */
+  for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
+    {
+      if (target == drag_atoms_dest[i])
+        {
+          drag_type = drag_types_dest[i].info;
+          break;
+        }
+    }
+
+  if (drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID)
+    {
+      GtkTreePath *path;
+
+      /* FIXME: It doesn't make sense for us to highlight a specific row or
+       * position to drop an Individual 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 (self), path,
+          GTK_TREE_VIEW_DROP_BEFORE);
+      gtk_tree_path_free (path);
+
+      return TRUE;
+    }
+
+  /* Unknown or unhandled drag target */
+  gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
+  gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (self), NULL, 0);
+
+  return FALSE;
+}
+
+static void
+drag_data_get (GtkWidget *widget,
+    GdkDragContext *context,
+    GtkSelectionData *selection,
+    guint info,
+    guint time_)
+{
+  EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (widget);
+  FolksPersona *persona;
+  const gchar *persona_uid;
+
+  if (info != DND_DRAG_TYPE_PERSONA_ID)
+    return;
+
+  persona = empathy_persona_view_dup_selected (self);
+  if (persona == NULL)
+    return;
+
+  persona_uid = folks_persona_get_uid (persona);
+  gtk_selection_data_set (selection,
+      gdk_atom_intern ("text/x-persona-id", FALSE), 8,
+      (guchar *) persona_uid, strlen (persona_uid) + 1);
+
+  g_object_unref (persona);
+}
+
+static gboolean
+drag_drop (GtkWidget *widget,
+    GdkDragContext *drag_context,
+    gint x,
+    gint y,
+    guint time_)
+{
+  return FALSE;
+}
+
+static void
+set_features (EmpathyPersonaView *self,
+    EmpathyPersonaViewFeatureFlags features)
+{
+  EmpathyPersonaViewPriv *priv = GET_PRIV (self);
+
+  priv->features = features;
+
+  /* Setting reorderable is a hack that gets us row previews as drag icons
+     for free.  We override all the drag handlers.  It's tricky to get the
+     position of the drag icon right in drag_begin.  GtkTreeView has special
+     voodoo for it, so we let it do the voodoo that he do (but only if dragging
+     is enabled). */
+  gtk_tree_view_set_reorderable (GTK_TREE_VIEW (self),
+      (features & EMPATHY_PERSONA_VIEW_FEATURE_PERSONA_DRAG));
+
+  /* Update DnD source/dest */
+  if (features & EMPATHY_PERSONA_VIEW_FEATURE_PERSONA_DRAG)
+    {
+      gtk_drag_source_set (GTK_WIDGET (self),
+          GDK_BUTTON1_MASK,
+          drag_types_source,
+          G_N_ELEMENTS (drag_types_source),
+          GDK_ACTION_MOVE | GDK_ACTION_COPY);
+    }
+  else
+    {
+      gtk_drag_source_unset (GTK_WIDGET (self));
+    }
+
+  if (features & EMPATHY_PERSONA_VIEW_FEATURE_PERSONA_DROP)
+    {
+      gtk_drag_dest_set (GTK_WIDGET (self),
+          GTK_DEST_DEFAULT_ALL,
+          drag_types_dest,
+          G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
+    }
+  else
+    {
+      gtk_drag_dest_unset (GTK_WIDGET (self));
+    }
+
+  g_object_notify (G_OBJECT (self), "features");
+}
+
 static void
 empathy_persona_view_init (EmpathyPersonaView *self)
 {
@@ -329,6 +555,7 @@ constructed (GObject *object)
   EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object);
   GtkCellRenderer *cell;
   GtkTreeViewColumn *col;
+  guint i;
 
   /* Set up view */
   g_object_set (self,
@@ -356,6 +583,8 @@ constructed (GObject *object)
   gtk_tree_view_column_set_cell_data_func (col, cell,
       (GtkTreeCellDataFunc) text_cell_data_func, self, NULL);
 
+  /* We (ab)use the name and status properties here to display display ID and
+   * account name, respectively. Harmless. */
   gtk_tree_view_column_add_attribute (col, cell,
       "name", EMPATHY_PERSONA_STORE_COL_DISPLAY_ID);
   gtk_tree_view_column_add_attribute (col, cell,
@@ -363,7 +592,7 @@ constructed (GObject *object)
   gtk_tree_view_column_add_attribute (col, cell,
       "presence-type", EMPATHY_PERSONA_STORE_COL_PRESENCE_TYPE);
   gtk_tree_view_column_add_attribute (col, cell,
-      "status", EMPATHY_PERSONA_STORE_COL_STATUS);
+      "status", EMPATHY_PERSONA_STORE_COL_ACCOUNT_NAME);
 
   /* Audio Call Icon */
   cell = empathy_cell_renderer_activatable_new ();
@@ -391,6 +620,10 @@ constructed (GObject *object)
 
   /* Actually add the column now we have added all cell renderers */
   gtk_tree_view_append_column (GTK_TREE_VIEW (self), col);
+
+  /* Drag & Drop. */
+  for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
+    drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
 }
 
 static void
@@ -409,6 +642,9 @@ get_property (GObject *object,
       case PROP_SHOW_OFFLINE:
         g_value_set_boolean (value, priv->show_offline);
         break;
+      case PROP_FEATURES:
+        g_value_set_flags (value, priv->features);
+        break;
       default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
         break;
@@ -432,6 +668,9 @@ set_property (GObject *object,
         empathy_persona_view_set_show_offline (self,
             g_value_get_boolean (value));
         break;
+      case PROP_FEATURES:
+        set_features (self, g_value_get_flags (value));
+        break;
       default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
         break;
@@ -457,12 +696,27 @@ static void
 empathy_persona_view_class_init (EmpathyPersonaViewClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
   object_class->constructed = constructed;
   object_class->dispose = dispose;
   object_class->get_property = get_property;
   object_class->set_property = set_property;
 
+  widget_class->drag_data_received = drag_data_received;
+  widget_class->drag_drop = drag_drop;
+  widget_class->drag_data_get = drag_data_get;
+  widget_class->drag_motion = drag_motion;
+
+  signals[DRAG_INDIVIDUAL_RECEIVED] =
+      g_signal_new ("drag-individual-received",
+      G_OBJECT_CLASS_TYPE (klass),
+      G_SIGNAL_RUN_LAST,
+      G_STRUCT_OFFSET (EmpathyPersonaViewClass, drag_individual_received),
+      NULL, NULL,
+      g_cclosure_marshal_generic,
+      G_TYPE_BOOLEAN, 2, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL);
+
   /* We override the "model" property so that we can wrap it in a
    * GtkTreeModelFilter for showing/hiding offline personas. */
   g_object_class_override_property (object_class, PROP_MODEL, "model");
@@ -479,12 +733,27 @@ empathy_persona_view_class_init (EmpathyPersonaViewClass *klass)
           FALSE,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
+  /**
+   * EmpathyPersonaStore:features:
+   *
+   * Features of the view, such as whether drag and drop is enabled.
+   */
+  g_object_class_install_property (object_class, PROP_FEATURES,
+      g_param_spec_flags ("features",
+          "Features",
+          "Flags for all enabled features.",
+          EMPATHY_TYPE_PERSONA_VIEW_FEATURE_FLAGS,
+          EMPATHY_PERSONA_VIEW_FEATURE_NONE,
+          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   g_type_class_add_private (object_class, sizeof (EmpathyPersonaViewPriv));
 }
 
 /**
  * empathy_persona_view_new:
  * @store: an #EmpathyPersonaStore
+ * @features: a set of flags specifying the view's functionality, or
+ * %EMPATHY_PERSONA_VIEW_FEATURE_NONE
  *
  * Create a new #EmpathyPersonaView displaying the personas in
  * #EmpathyPersonaStore.
@@ -492,11 +761,15 @@ empathy_persona_view_class_init (EmpathyPersonaViewClass *klass)
  * Return value: a new #EmpathyPersonaView
  */
 EmpathyPersonaView *
-empathy_persona_view_new (EmpathyPersonaStore *store)
+empathy_persona_view_new (EmpathyPersonaStore *store,
+    EmpathyPersonaViewFeatureFlags features)
 {
   g_return_val_if_fail (EMPATHY_IS_PERSONA_STORE (store), NULL);
 
-  return g_object_new (EMPATHY_TYPE_PERSONA_VIEW, "model", store, NULL);
+  return g_object_new (EMPATHY_TYPE_PERSONA_VIEW,
+      "model", store,
+      "features", features,
+      NULL);
 }
 
 /**
@@ -513,7 +786,6 @@ empathy_persona_view_new (EmpathyPersonaStore *store)
 FolksPersona *
 empathy_persona_view_dup_selected (EmpathyPersonaView *self)
 {
-  EmpathyPersonaViewPriv *priv;
   GtkTreeSelection *selection;
   GtkTreeIter iter;
   GtkTreeModel *model;
@@ -521,8 +793,6 @@ empathy_persona_view_dup_selected (EmpathyPersonaView *self)
 
   g_return_val_if_fail (EMPATHY_IS_PERSONA_VIEW (self), NULL);
 
-  priv = GET_PRIV (self);
-
   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
     return NULL;