diff options
-rw-r--r-- | libempathy-gtk/empathy-individual-linker.c | 87 | ||||
-rw-r--r-- | libempathy-gtk/empathy-individual-view.c | 376 | ||||
-rw-r--r-- | libempathy-gtk/empathy-individual-view.h | 26 | ||||
-rw-r--r-- | libempathy-gtk/empathy-persona-view.c | 268 | ||||
-rw-r--r-- | libempathy-gtk/empathy-persona-view.h | 15 | ||||
-rw-r--r-- | libempathy/empathy-individual-manager.c | 13 | ||||
-rw-r--r-- | src/empathy-main-window.c | 5 |
7 files changed, 634 insertions, 156 deletions
diff --git a/libempathy-gtk/empathy-individual-linker.c b/libempathy-gtk/empathy-individual-linker.c index 9ebb4718b..b4226a71c 100644 --- a/libempathy-gtk/empathy-individual-linker.c +++ b/libempathy-gtk/empathy-individual-linker.c @@ -216,6 +216,78 @@ row_toggled_cb (GtkCellRendererToggle *cell_renderer, gtk_tree_path_free (tree_path); } +static gboolean +individual_view_drag_motion_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time_) +{ + EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (widget); + GdkAtom target; + + target = gtk_drag_dest_find_target (GTK_WIDGET (view), context, NULL); + + if (target == gdk_atom_intern_static_string ("text/persona-id")) + { + 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; + } + + /* 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 set_up (EmpathyIndividualLinker *self) { @@ -258,11 +330,18 @@ set_up (EmpathyIndividualLinker *self) 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); 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 (); @@ -322,9 +401,13 @@ set_up (EmpathyIndividualLinker *self) 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)); diff --git a/libempathy-gtk/empathy-individual-view.c b/libempathy-gtk/empathy-individual-view.c index 6767c3bfd..b8365d175 100644 --- a/libempathy-gtk/empathy-individual-view.c +++ b/libempathy-gtk/empathy-individual-view.c @@ -68,9 +68,8 @@ typedef struct EmpathyIndividualStore *store; GtkTreeRowReference *drag_row; EmpathyIndividualViewFeatureFlags view_features; - EmpathyContactFeatureFlags individual_features; + EmpathyIndividualFeatureFlags individual_features; GtkWidget *tooltip_widget; - GtkTargetList *file_targets; gboolean show_offline; @@ -110,6 +109,7 @@ enum enum DndDragType { DND_DRAG_TYPE_INDIVIDUAL_ID, + DND_DRAG_TYPE_PERSONA_ID, DND_DRAG_TYPE_URI_LIST, DND_DRAG_TYPE_STRING, }; @@ -118,20 +118,16 @@ enum DndDragType { (gchar *) T, 0, I } static const GtkTargetEntry drag_types_dest[] = { + DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID), + DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID), DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST), DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST), - DRAG_TYPE ("text/contact-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_dest_file[] = { - DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST), - DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST), -}; - static const GtkTargetEntry drag_types_source[] = { - DRAG_TYPE ("text/contact-id", DND_DRAG_TYPE_INDIVIDUAL_ID), + DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID), }; #undef DRAG_TYPE @@ -141,7 +137,8 @@ static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)]; enum { - DRAG_CONTACT_RECEIVED, + DRAG_INDIVIDUAL_RECEIVED, + DRAG_PERSONA_RECEIVED, LAST_SIGNAL }; @@ -247,44 +244,6 @@ groups_change_group_cb (GObject *source, } } -static void -individual_view_handle_drag (EmpathyIndividualView *self, - FolksIndividual *individual, - const gchar *old_group, - const gchar *new_group, - GdkDragAction action) -{ - g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self)); - g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual)); - - DEBUG ("individual %s dragged from '%s' to '%s'", - folks_individual_get_id (individual), old_group, new_group); - - if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE)) - { - /* Mark contact as favourite */ - folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE); - return; - } - - if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE)) - { - /* Remove contact as favourite */ - folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE); - - /* Don't try to remove it */ - old_group = NULL; - } - - if (new_group != NULL) - folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE, - groups_change_group_cb, NULL); - - if (old_group != NULL && action == GDK_ACTION_MOVE) - folks_groups_change_group (FOLKS_GROUPS (individual), old_group, FALSE, - groups_change_group_cb, NULL); -} - static gboolean group_can_be_modified (const gchar *name, gboolean is_fake_group, @@ -307,20 +266,20 @@ group_can_be_modified (const gchar *name, } static gboolean -individual_view_contact_drag_received (GtkWidget *self, +individual_view_individual_drag_received (GtkWidget *self, GdkDragContext *context, GtkTreeModel *model, GtkTreePath *path, GtkSelectionData *selection) { EmpathyIndividualViewPriv *priv; - EmpathyIndividualManager *manager; + EmpathyIndividualManager *manager = NULL; FolksIndividual *individual; GtkTreePath *source_path; const gchar *sel_data; gchar *new_group = NULL; gchar *old_group = NULL; - gboolean new_group_is_fake, old_group_is_fake = TRUE; + gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE; priv = GET_PRIV (self); @@ -329,10 +288,16 @@ individual_view_contact_drag_received (GtkWidget *self, NULL, &new_group_is_fake); if (!group_can_be_modified (new_group, new_group_is_fake, TRUE)) - return FALSE; - - /* Get source group information. */ - if (priv->drag_row) + goto finished; + + /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE + * feature. Otherwise, we just add the dropped contact to whichever group + * they were dropped in, and don't remove them from their old group. This + * allows for Individual views which shouldn't allow Individuals to have + * their groups changed, and also for dragging Individuals between Individual + * views. */ + if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) && + priv->drag_row != NULL) { source_path = gtk_tree_row_reference_get_path (priv->drag_row); if (source_path) @@ -342,16 +307,19 @@ individual_view_contact_drag_received (GtkWidget *self, NULL, &old_group_is_fake); gtk_tree_path_free (source_path); } - } - if (!group_can_be_modified (old_group, old_group_is_fake, FALSE)) - return FALSE; + if (!group_can_be_modified (old_group, old_group_is_fake, FALSE)) + goto finished; - if (!tp_strdiff (old_group, new_group)) + if (!tp_strdiff (old_group, new_group)) + goto finished; + } + else if (priv->drag_row != NULL) { - g_free (new_group); - g_free (old_group); - return FALSE; + /* We don't allow changing Individuals' groups, and this Individual was + * dragged from another group in *this* Individual view, so we disallow + * the drop. */ + goto finished; } /* XXX: for contacts, we used to ensure the account, create the contact @@ -364,23 +332,130 @@ individual_view_contact_drag_received (GtkWidget *self, if (individual == NULL) { DEBUG ("failed to find drag event individual with ID '%s'", sel_data); - - g_object_unref (manager); - - return FALSE; + goto finished; } /* FIXME: We should probably wait for the cb before calling * gtk_drag_finish */ - individual_view_handle_drag (EMPATHY_INDIVIDUAL_VIEW (self), individual, - old_group, new_group, gdk_drag_context_get_selected_action (context)); + /* Emit a signal notifying of the drag. We change the Individual's groups in + * the default signal handler. */ + g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0, + gdk_drag_context_get_selected_action (context), individual, new_group, + old_group); + + retval = TRUE; - g_object_unref (G_OBJECT (manager)); +finished: + tp_clear_object (&manager); g_free (old_group); g_free (new_group); - return TRUE; + return retval; +} + +static void +real_drag_individual_received_cb (EmpathyIndividualView *self, + GdkDragAction action, + FolksIndividual *individual, + const gchar *new_group, + const gchar *old_group) +{ + DEBUG ("individual %s dragged from '%s' to '%s'", + folks_individual_get_id (individual), old_group, new_group); + + if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE)) + { + /* Mark contact as favourite */ + folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE); + return; + } + + if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE)) + { + /* Remove contact as favourite */ + folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE); + + /* Don't try to remove it */ + old_group = NULL; + } + + if (new_group != NULL) + { + folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE, + groups_change_group_cb, NULL); + } + + if (old_group != NULL && action == GDK_ACTION_MOVE) + { + folks_groups_change_group (FOLKS_GROUPS (individual), old_group, + FALSE, groups_change_group_cb, NULL); + } +} + +static gboolean +individual_view_persona_drag_received (GtkWidget *self, + GdkDragContext *context, + GtkTreeModel *model, + GtkTreePath *path, + GtkSelectionData *selection) +{ + EmpathyIndividualViewPriv *priv; + EmpathyIndividualManager *manager = NULL; + FolksIndividual *individual = NULL; + FolksPersona *persona = NULL; + const gchar *persona_uid; + GList *individuals, *l; + gboolean retval = FALSE; + + priv = GET_PRIV (self); + + persona_uid = (const gchar *) gtk_selection_data_get_data (selection); + + /* FIXME: This is slow, but the only way to find the Persona we're having + * dropped on us. */ + manager = empathy_individual_manager_dup_singleton (); + individuals = empathy_individual_manager_get_members (manager); + + for (l = individuals; l != NULL; l = l->next) + { + GList *personas, *p; + + personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data)); + + for (p = personas; p != NULL; p = p->next) + { + if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)), + persona_uid)) + { + persona = g_object_ref (p->data); + individual = g_object_ref (l->data); + goto got_persona; + } + } + } + +got_persona: + g_list_free (individuals); + + if (persona == NULL || individual == NULL) + { + DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid); + } + else + { + /* Emit a signal notifying of the drag. We change the Individual's groups in + * the default signal handler. */ + g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0, + gdk_drag_context_get_selected_action (context), persona, individual, + &retval); + } + + tp_clear_object (&manager); + tp_clear_object (&persona); + tp_clear_object (&individual); + + return retval; } static gboolean @@ -436,13 +511,17 @@ individual_view_drag_data_received (GtkWidget *view, { success = FALSE; } - else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID - || info == DND_DRAG_TYPE_STRING) + else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID) { - success = individual_view_contact_drag_received (view, + success = individual_view_individual_drag_received (view, context, model, path, selection); } - else if (info == DND_DRAG_TYPE_URI_LIST) + else if (info == DND_DRAG_TYPE_PERSONA_ID) + { + success = individual_view_persona_drag_received (view, context, model, + path, selection); + } + else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING) { success = individual_view_file_drag_received (view, context, model, path, selection); @@ -511,17 +590,73 @@ individual_view_drag_motion (GtkWidget *widget, gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0); return FALSE; } - target = gtk_drag_dest_find_target (widget, context, priv->file_targets); + target = gtk_drag_dest_find_target (widget, context, NULL); gtk_tree_model_get_iter (model, &iter, path); - if (target == GDK_NONE) + if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] || + target == drag_atoms_dest[DND_DRAG_TYPE_STRING]) + { + /* This is a file drag, and it can only be dropped on contacts, + * not groups. + * If we don't have FEATURE_FILE_DROP, disallow the drop completely, + * even if we have a valid target. */ + FolksIndividual *individual = NULL; + EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE; + + if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP) + { + gtk_tree_model_get (model, &iter, + EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, + -1); + } + + if (individual != NULL) + { + EmpathyContact *contact = NULL; + + contact = empathy_contact_dup_from_folks_individual (individual); + caps = empathy_contact_get_capabilities (contact); + + tp_clear_object (&contact); + } + + if (individual != NULL && + folks_individual_is_online (individual) && + (caps & EMPATHY_CAPABILITIES_FT)) + { + gdk_drag_status (context, GDK_ACTION_COPY, time_); + gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), + path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE); + } + else + { + gdk_drag_status (context, 0, time_); + gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0); + retval = FALSE; + } + + if (individual != NULL) + g_object_unref (individual); + } + else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] && + (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE || + priv->drag_row == NULL)) || + (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] && + priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP)) { - /* If target == GDK_NONE, then we don't have a target that can be - dropped on a contact. This means a contact drag. If we're - pointing to a group, highlight it. Otherwise, if the contact - we're pointing to is in a group, highlight that. Otherwise, + /* If target != GDK_NONE, then we have a contact (individual or persona) + drag. If we're pointing to a group, highlight it. Otherwise, if the + contact we're pointing to is in a group, highlight that. Otherwise, set the drag position to before the first row for a drag into the "non-group" at the top. + If it's an Individual: + We only highlight things if the contact is from a different + Individual view, or if this Individual view has + FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views + which don't have FEATURE_GROUPS_CHANGE, but do have + FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP. + If it's a Persona: + We only highlight things if we have FEATURE_PERSONA_DROP. */ GtkTreeIter group_iter; gboolean is_group; @@ -554,44 +689,6 @@ individual_view_drag_motion (GtkWidget *widget, group_path, GTK_TREE_VIEW_DROP_BEFORE); } } - else - { - /* This is a file drag, and it can only be dropped on contacts, - not groups. - */ - FolksIndividual *individual; - EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE; - - gtk_tree_model_get (model, &iter, - EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1); - if (individual != NULL) - { - EmpathyContact *contact = NULL; - - contact = empathy_contact_dup_from_folks_individual (individual); - caps = empathy_contact_get_capabilities (contact); - - tp_clear_object (&contact); - } - - if (individual != NULL && - folks_individual_is_online (individual) && - (caps & EMPATHY_CAPABILITIES_FT)) - { - gdk_drag_status (context, GDK_ACTION_COPY, time_); - gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), - path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE); - } - else - { - gdk_drag_status (context, 0, time_); - gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0); - retval = FALSE; - } - - if (individual != NULL) - g_object_unref (individual); - } if (!is_different && !cleanup) return retval; @@ -807,7 +904,7 @@ individual_view_row_activated (GtkTreeView *view, GtkTreeModel *model; GtkTreeIter iter; - if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT)) + if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT)) return; model = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); @@ -1731,10 +1828,10 @@ individual_view_set_view_features (EmpathyIndividualView *view, is enabled). */ gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view), - (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)); + (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)); /* Update DnD source/dest */ - if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG) + if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG) { gtk_drag_source_set (GTK_WIDGET (view), GDK_BUTTON1_MASK, @@ -1748,7 +1845,7 @@ individual_view_set_view_features (EmpathyIndividualView *view, } - if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP) + if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP) { gtk_drag_dest_set (GTK_WIDGET (view), GTK_DEST_DEFAULT_ALL, @@ -1763,7 +1860,7 @@ individual_view_set_view_features (EmpathyIndividualView *view, /* Update has-tooltip */ has_tooltip = - (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0; + (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0; gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip); } @@ -1776,7 +1873,6 @@ individual_view_dispose (GObject *object) tp_clear_object (&priv->store); tp_clear_object (&priv->filter); tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy); - tp_clear_pointer (&priv->file_targets, gtk_target_list_unref); empathy_individual_view_set_live_search (view, NULL); @@ -1878,14 +1974,26 @@ empathy_individual_view_class_init (EmpathyIndividualViewClass *klass) * won't be called. */ tree_view_class->row_activated = individual_view_row_activated; - signals[DRAG_CONTACT_RECEIVED] = - g_signal_new ("drag-contact-received", + klass->drag_individual_received = real_drag_individual_received_cb; + + signals[DRAG_INDIVIDUAL_RECEIVED] = + g_signal_new ("drag-individual-received", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, - 0, + G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received), NULL, NULL, - _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING, - G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING); + _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING, + G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL, + G_TYPE_STRING, G_TYPE_STRING); + + signals[DRAG_PERSONA_RECEIVED] = + g_signal_new ("drag-persona-received", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received), + NULL, NULL, + _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT, + G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL); g_object_class_install_property (object_class, PROP_STORE, @@ -1904,10 +2012,10 @@ empathy_individual_view_class_init (EmpathyIndividualViewClass *klass) g_object_class_install_property (object_class, PROP_INDIVIDUAL_FEATURES, g_param_spec_flags ("individual-features", - "Features of the contact menu", + "Features of the individual menu", "Flags for all enabled features for the menu", EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS, - EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE)); + EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_SHOW_OFFLINE, g_param_spec_boolean ("show-offline", @@ -1934,10 +2042,6 @@ empathy_individual_view_init (EmpathyIndividualView *view) gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view), empathy_individual_store_row_separator_func, NULL, NULL); - /* Set up drag target lists. */ - priv->file_targets = gtk_target_list_new (drag_types_dest_file, - G_N_ELEMENTS (drag_types_dest_file)); - /* Connect to tree view signals rather than override. */ g_signal_connect (view, "button-press-event", G_CALLBACK (individual_view_button_press_event_cb), NULL); @@ -2213,7 +2317,7 @@ empathy_individual_view_get_individual_menu (EmpathyIndividualView *view) /* Remove contact */ if (priv->view_features & - EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE && + EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE && flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE) { diff --git a/libempathy-gtk/empathy-individual-view.h b/libempathy-gtk/empathy-individual-view.h index 003ff2ab3..8a250bf20 100644 --- a/libempathy-gtk/empathy-individual-view.h +++ b/libempathy-gtk/empathy-individual-view.h @@ -53,11 +53,16 @@ typedef enum EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE = 1 << 0, EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME = 1 << 1, EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE = 1 << 2, - EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE = 1 << 3, - EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP = 1 << 4, - EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG = 1 << 5, - EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP = 1 << 6, - EMPATHY_INDIVIDUAL_VIEW_FEATURE_ALL = (1 << 7) - 1, + /* NOTE: For this to behave as expected, FEATURE_INDIVIDUAL_DRAG and + * FEATURE_INDIVIDUAL_DROP should also be specified. */ + EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE = 1 << 3, + EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE = 1 << 4, + EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP = 1 << 5, + EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG = 1 << 6, + EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP = 1 << 7, + EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP = 1 << 8, + EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP = 1 << 9, + EMPATHY_INDIVIDUAL_VIEW_FEATURE_ALL = (1 << 10) - 1, } EmpathyIndividualViewFeatureFlags; struct _EmpathyIndividualView @@ -69,6 +74,17 @@ struct _EmpathyIndividualView struct _EmpathyIndividualViewClass { GtkTreeViewClass parent_class; + + void (* drag_individual_received) (EmpathyIndividualView *self, + GdkDragAction action, + FolksIndividual *individual, + const gchar *new_group, + const gchar *old_group); + + void (* drag_persona_received) (EmpathyIndividualView *self, + GdkDragAction action, + FolksPersona *persona, + FolksIndividual *individual); }; GType empathy_individual_view_get_type (void) G_GNUC_CONST; diff --git a/libempathy-gtk/empathy-persona-view.c b/libempathy-gtk/empathy-persona-view.c index 04777b1b6..adfe6be5c 100644 --- a/libempathy-gtk/empathy-persona-view.c +++ b/libempathy-gtk/empathy-persona-view.c @@ -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-gtk-marshal.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, }; +enum DndDragType +{ + DND_DRAG_TYPE_INDIVIDUAL_ID, + DND_DRAG_TYPE_PERSONA_ID, + DND_DRAG_TYPE_STRING, +}; + +#define DRAG_TYPE(T,I) \ + { (gchar *) T, 0, I } + +static const GtkTargetEntry drag_types_dest[] = { + DRAG_TYPE ("text/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/persona-id", DND_DRAG_TYPE_PERSONA_ID), +}; + +#undef DRAG_TYPE + +static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)]; +static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)]; + +enum +{ + DRAG_INDIVIDUAL_RECEIVED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + G_DEFINE_TYPE (EmpathyPersonaView, empathy_persona_view, GTK_TYPE_TREE_VIEW); static gboolean @@ -311,6 +349,184 @@ 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) +{ + EmpathyPersonaViewPriv *priv; + EmpathyIndividualManager *manager = NULL; + FolksIndividual *individual; + const gchar *individual_id; + gboolean success = FALSE; + + priv = GET_PRIV (self); + + 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); + EmpathyPersonaViewPriv *priv; + GdkAtom target; + + priv = GET_PRIV (self); + + target = gtk_drag_dest_find_target (GTK_WIDGET (self), context, NULL); + + if (target == drag_atoms_dest[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); + EmpathyPersonaViewPriv *priv; + FolksPersona *persona; + const gchar *persona_uid; + + if (info != DND_DRAG_TYPE_PERSONA_ID) + return; + + priv = GET_PRIV (self); + + persona = empathy_persona_view_dup_selected (self); + if (persona == NULL) + return; + + persona_uid = folks_persona_get_uid (persona); + gtk_selection_data_set (selection, drag_atoms_source[info], 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 +545,7 @@ constructed (GObject *object) EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object); GtkCellRenderer *cell; GtkTreeViewColumn *col; + guint i; /* Set up view */ g_object_set (self, @@ -393,6 +610,13 @@ 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); + + for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) + drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target, FALSE); } static void @@ -411,6 +635,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; @@ -434,6 +661,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; @@ -459,12 +689,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, + _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT, + 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"); @@ -481,12 +726,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. @@ -494,11 +754,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); } /** diff --git a/libempathy-gtk/empathy-persona-view.h b/libempathy-gtk/empathy-persona-view.h index 11fe039eb..0a6317c6b 100644 --- a/libempathy-gtk/empathy-persona-view.h +++ b/libempathy-gtk/empathy-persona-view.h @@ -36,6 +36,14 @@ G_BEGIN_DECLS +typedef enum +{ + EMPATHY_PERSONA_VIEW_FEATURE_NONE = 0, + EMPATHY_PERSONA_VIEW_FEATURE_PERSONA_DRAG = 1 << 0, + EMPATHY_PERSONA_VIEW_FEATURE_PERSONA_DROP = 1 << 1, + EMPATHY_PERSONA_VIEW_FEATURE_ALL = (1 << 2) - 1, +} EmpathyPersonaViewFeatureFlags; + #define EMPATHY_TYPE_PERSONA_VIEW (empathy_persona_view_get_type ()) #define EMPATHY_PERSONA_VIEW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), \ EMPATHY_TYPE_PERSONA_VIEW, EmpathyPersonaView)) @@ -57,11 +65,16 @@ typedef struct typedef struct { GtkTreeViewClass parent_class; + + void (* drag_individual_received) (EmpathyPersonaView *self, + GdkDragAction action, + FolksIndividual *individual); } EmpathyPersonaViewClass; GType empathy_persona_view_get_type (void) G_GNUC_CONST; -EmpathyPersonaView *empathy_persona_view_new (EmpathyPersonaStore *store); +EmpathyPersonaView *empathy_persona_view_new (EmpathyPersonaStore *store, + EmpathyPersonaViewFeatureFlags features); FolksPersona *empathy_persona_view_dup_selected (EmpathyPersonaView *self); diff --git a/libempathy/empathy-individual-manager.c b/libempathy/empathy-individual-manager.c index 4d3eabb22..1e76cbf83 100644 --- a/libempathy/empathy-individual-manager.c +++ b/libempathy/empathy-individual-manager.c @@ -171,7 +171,7 @@ aggregator_individuals_changed_cb (FolksIndividualAggregator *aggregator, EmpathyIndividualManager *self) { EmpathyIndividualManagerPriv *priv = GET_PRIV (self); - GList *l, *added_filtered = NULL, *removed_filtered = NULL; + GList *l, *added_filtered = NULL; /* Filter the individuals for ones which contain EmpathyContacts */ for (l = added; l; l = l->next) @@ -197,26 +197,21 @@ aggregator_individuals_changed_cb (FolksIndividualAggregator *aggregator, if (g_hash_table_lookup (priv->individuals, folks_individual_get_id (ind)) != NULL) - { - removed_filtered = g_list_prepend (removed_filtered, ind); - remove_individual (self, ind); - } + remove_individual (self, ind); } /* Bail if we have no individuals left */ - if (added_filtered == NULL && removed_filtered == NULL) + if (added_filtered == NULL && removed == NULL) return; added_filtered = g_list_reverse (added_filtered); - removed_filtered = g_list_reverse (removed_filtered); g_signal_emit (self, signals[MEMBERS_CHANGED], 0, message, - added_filtered, removed_filtered, + added_filtered, removed, tp_chanel_group_change_reason_from_folks_groups_change_reason (reason), TRUE); g_list_free (added_filtered); - g_list_free (removed_filtered); } static void diff --git a/src/empathy-main-window.c b/src/empathy-main-window.c index 5f9767639..fa6f15310 100644 --- a/src/empathy-main-window.c +++ b/src/empathy-main-window.c @@ -1680,9 +1680,12 @@ empathy_main_window_init (EmpathyMainWindow *window) individual_manager); g_object_unref (individual_manager); + /* For the moment, we disallow Persona drops onto the main contact list (e.g. from things such as + * the EmpathyPersonaView in the linking dialogue). No code is hooked up to do anything on a Persona + * drop, so allowing them would achieve nothing except confusion. */ priv->individual_view = empathy_individual_view_new ( priv->individual_store, - EMPATHY_INDIVIDUAL_VIEW_FEATURE_ALL, + EMPATHY_INDIVIDUAL_VIEW_FEATURE_ALL ^ EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP, EMPATHY_INDIVIDUAL_FEATURE_ALL); priv->butterfly_log_migration_members_changed_id = g_signal_connect ( |