X-Git-Url: https://git.0d.be/?p=empathy.git;a=blobdiff_plain;f=libempathy-gtk%2Fempathy-contact-list-view.c;h=05823435a04727f9b9d42f501b5477156e38a733;hp=09544a4666fe499d8d66176e9ecf9f1d79fbe3e3;hb=940d0e9778828657a6ffbcadd35a8a84d706ac70;hpb=e69fdb29794b4c183ef0391998c6805700d5cec3 diff --git a/libempathy-gtk/empathy-contact-list-view.c b/libempathy-gtk/empathy-contact-list-view.c index 09544a46..05823435 100644 --- a/libempathy-gtk/empathy-contact-list-view.c +++ b/libempathy-gtk/empathy-contact-list-view.c @@ -15,8 +15,8 @@ * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA * * Authors: Mikael Hallendal * Martyn Russell @@ -27,14 +27,15 @@ #include -#include +#include #include #include -#include -#include +#include +#include -#include +#include +#include #include #include #include @@ -64,6 +65,7 @@ typedef struct { EmpathyContactListFeatureFlags list_features; EmpathyContactFeatureFlags contact_features; GtkWidget *tooltip_widget; + GtkTargetList *file_targets; } EmpathyContactListViewPriv; typedef struct { @@ -87,17 +89,23 @@ enum { enum DndDragType { DND_DRAG_TYPE_CONTACT_ID, - DND_DRAG_TYPE_URL, + DND_DRAG_TYPE_URI_LIST, DND_DRAG_TYPE_STRING, }; static const GtkTargetEntry drag_types_dest[] = { + { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST }, + { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST }, { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID }, - { "text/uri-list", 0, DND_DRAG_TYPE_URL }, { "text/plain", 0, DND_DRAG_TYPE_STRING }, { "STRING", 0, DND_DRAG_TYPE_STRING }, }; +static const GtkTargetEntry drag_types_dest_file[] = { + { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST }, + { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST }, +}; + static const GtkTargetEntry drag_types_source[] = { { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID }, }; @@ -114,6 +122,19 @@ static guint signals[LAST_SIGNAL]; G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW); +static void +contact_list_view_tooltip_destroy_cb (GtkWidget *widget, + EmpathyContactListView *view) +{ + EmpathyContactListViewPriv *priv = GET_PRIV (view); + + if (priv->tooltip_widget) { + DEBUG ("Tooltip destroyed"); + g_object_unref (priv->tooltip_widget); + priv->tooltip_widget = NULL; + } +} + static gboolean contact_list_view_query_tooltip_cb (EmpathyContactListView *view, gint x, @@ -127,16 +148,24 @@ contact_list_view_query_tooltip_cb (EmpathyContactListView *view, GtkTreeModel *model; GtkTreeIter iter; GtkTreePath *path; + static gint running = 0; + gboolean ret = FALSE; - /* FIXME: We need GTK version >= 2.12.10. See GNOME bug #504087 */ - if (gtk_check_version (2, 12, 10)) { + /* Avoid an infinite loop. See GNOME bug #574377 */ + if (running > 0) { return FALSE; } + running++; + + /* Don't show the tooltip if there's already a popup menu */ + if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) { + goto OUT; + } if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y, keyboard_mode, &model, &path, &iter)) { - return FALSE; + goto OUT; } gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path); @@ -146,118 +175,278 @@ contact_list_view_query_tooltip_cb (EmpathyContactListView *view, EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact, -1); if (!contact) { - return FALSE; + goto OUT; } if (!priv->tooltip_widget) { priv->tooltip_widget = empathy_contact_widget_new (contact, - EMPATHY_CONTACT_WIDGET_EDIT_NONE); - g_object_add_weak_pointer (G_OBJECT (priv->tooltip_widget), - (gpointer) &priv->tooltip_widget); + EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP | + EMPATHY_CONTACT_WIDGET_SHOW_LOCATION); + gtk_container_set_border_width ( + GTK_CONTAINER (priv->tooltip_widget), 8); + g_object_ref (priv->tooltip_widget); + g_signal_connect (priv->tooltip_widget, "destroy", + G_CALLBACK (contact_list_view_tooltip_destroy_cb), + view); + gtk_widget_show (priv->tooltip_widget); } else { empathy_contact_widget_set_contact (priv->tooltip_widget, contact); } gtk_tooltip_set_custom (tooltip, priv->tooltip_widget); + ret = TRUE; g_object_unref (contact); +OUT: + running--; - return TRUE; + return ret; } +typedef struct { + gchar *new_group; + gchar *old_group; + GdkDragAction action; +} DndGetContactData; + static void -contact_list_view_drag_data_received (GtkWidget *widget, - GdkDragContext *context, - gint x, - gint y, - GtkSelectionData *selection, - guint info, - guint time) +contact_list_view_dnd_get_contact_free (DndGetContactData *data) { - EmpathyContactListViewPriv *priv; + g_free (data->new_group); + g_free (data->old_group); + g_slice_free (DndGetContactData, data); +} + +static void +contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory, + EmpathyContact *contact, + const GError *error, + gpointer user_data, + GObject *view) +{ + EmpathyContactListViewPriv *priv = GET_PRIV (view); + DndGetContactData *data = user_data; EmpathyContactList *list; - EmpathyContactFactory *factory; - McAccount *account; - GtkTreeModel *model; - GtkTreePath *path; - GtkTreeViewDropPosition position; - EmpathyContact *contact = NULL; - const gchar *id; - gchar **strv; - gchar *new_group = NULL; - gchar *old_group = NULL; - gboolean is_row; - priv = GET_PRIV (widget); + if (error != NULL) { + DEBUG ("Error: %s", error->message); + return; + } - id = (const gchar*) selection->data; - DEBUG ("Received %s%s drag & drop contact from roster with id:'%s'", - context->action == GDK_ACTION_MOVE ? "move" : "", - context->action == GDK_ACTION_COPY ? "copy" : "", - id); + DEBUG ("contact %s (%d) dragged from '%s' to '%s'", + empathy_contact_get_id (contact), + empathy_contact_get_handle (contact), + data->old_group, data->new_group); - strv = g_strsplit (id, "/", 2); - factory = empathy_contact_factory_new (); - account = mc_account_lookup (strv[0]); - if (account) { - contact = empathy_contact_factory_get_from_id (factory, - account, - strv[1]); - g_object_unref (account); - } - g_object_unref (factory); - g_strfreev (strv); + list = empathy_contact_list_store_get_list_iface (priv->store); - if (!contact) { - DEBUG ("No contact found associated with drag & drop"); + if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) { + /* Mark contact as favourite */ + empathy_contact_list_add_to_favourites (list, contact); return; } - empathy_contact_run_until_ready (contact, - EMPATHY_CONTACT_READY_HANDLE, - NULL); + if (!tp_strdiff (data->old_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) { + /* Remove contact as favourite */ + empathy_contact_list_remove_from_favourites (list, contact); + /* Don't try to remove it */ + g_free (data->old_group); + data->old_group = NULL; + } - model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget)); + if (data->new_group) { + empathy_contact_list_add_to_group (list, contact, data->new_group); + } + if (data->old_group && data->action == GDK_ACTION_MOVE) { + empathy_contact_list_remove_from_group (list, contact, data->old_group); + } +} + +static gboolean +group_can_be_modified (const gchar *name, + gboolean is_fake_group, + gboolean adding) +{ + /* Real groups can always be modified */ + if (!is_fake_group) + return TRUE; + + /* The favorite fake group can be modified so users can + * add/remove favorites using DnD */ + if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) + return TRUE; + + /* We can remove contacts from the 'ungrouped' fake group */ + if (!adding && !tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_UNGROUPED)) + return TRUE; + + return FALSE; +} + +static gboolean +contact_list_view_contact_drag_received (GtkWidget *view, + GdkDragContext *context, + GtkTreeModel *model, + GtkTreePath *path, + GtkSelectionData *selection) +{ + EmpathyContactListViewPriv *priv; + TpAccountManager *account_manager; + EmpathyTpContactFactory *factory = NULL; + TpAccount *account; + DndGetContactData *data; + GtkTreePath *source_path; + const gchar *sel_data; + gchar **strv = NULL; + const gchar *account_id = NULL; + const gchar *contact_id = NULL; + gchar *new_group = NULL; + gchar *old_group = NULL; + gboolean success = TRUE; + gboolean new_group_is_fake, old_group_is_fake = TRUE; + + priv = GET_PRIV (view); + + sel_data = (const gchar *) gtk_selection_data_get_data (selection); + new_group = empathy_contact_list_store_get_parent_group (model, + path, 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) { - path = gtk_tree_row_reference_get_path (priv->drag_row); - if (path) { - old_group = empathy_contact_list_store_get_parent_group (model, path, NULL); - gtk_tree_path_free (path); + source_path = gtk_tree_row_reference_get_path (priv->drag_row); + if (source_path) { + old_group = empathy_contact_list_store_get_parent_group ( + model, source_path, 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 (!tp_strdiff (old_group, new_group)) { + g_free (new_group); + g_free (old_group); + return FALSE; + } + + account_manager = tp_account_manager_dup (); + strv = g_strsplit (sel_data, ":", 2); + if (g_strv_length (strv) == 2) { + account_id = strv[0]; + contact_id = strv[1]; + account = tp_account_manager_ensure_account (account_manager, account_id); + } + if (account) { + TpConnection *connection; + + connection = tp_account_get_connection (account); + if (connection) { + factory = empathy_tp_contact_factory_dup_singleton (connection); } } + g_object_unref (account_manager); + + if (!factory) { + DEBUG ("Failed to get factory for account '%s'", account_id); + success = FALSE; + g_free (new_group); + g_free (old_group); + return FALSE; + } + + data = g_slice_new0 (DndGetContactData); + data->new_group = new_group; + data->old_group = old_group; + data->action = context->action; + + /* FIXME: We should probably wait for the cb before calling + * gtk_drag_finish */ + empathy_tp_contact_factory_get_from_id (factory, contact_id, + contact_list_view_drag_got_contact, + data, (GDestroyNotify) contact_list_view_dnd_get_contact_free, + G_OBJECT (view)); + g_strfreev (strv); + g_object_unref (factory); + + return TRUE; +} + +static gboolean +contact_list_view_file_drag_received (GtkWidget *view, + GdkDragContext *context, + GtkTreeModel *model, + GtkTreePath *path, + GtkSelectionData *selection) +{ + GtkTreeIter iter; + const gchar *sel_data; + EmpathyContact *contact; + + sel_data = (const gchar *) gtk_selection_data_get_data (selection); + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact, + -1); + if (!contact) { + return FALSE; + } + + empathy_send_file_from_uri_list (contact, sel_data); + + g_object_unref (contact); + + return TRUE; +} + +static void +contact_list_view_drag_data_received (GtkWidget *view, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection, + guint info, + guint time_) +{ + GtkTreeModel *model; + gboolean is_row; + GtkTreeViewDropPosition position; + GtkTreePath *path; + gboolean success = TRUE; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); /* Get destination group information. */ - is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget), + is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view), x, y, &path, &position); - - if (is_row) { - new_group = empathy_contact_list_store_get_parent_group (model, path, NULL); - gtk_tree_path_free (path); + if (!is_row) { + success = FALSE; } - - DEBUG ("contact %s (%d) dragged from '%s' to '%s'", - empathy_contact_get_id (contact), - empathy_contact_get_handle (contact), - old_group, new_group); - - list = empathy_contact_list_store_get_list_iface (priv->store); - if (new_group) { - empathy_contact_list_add_to_group (list, contact, new_group); + else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) { + success = contact_list_view_contact_drag_received (view, + context, + model, + path, + selection); } - if (old_group && context->action == GDK_ACTION_MOVE) { - empathy_contact_list_remove_from_group (list, contact, old_group); + else if (info == DND_DRAG_TYPE_URI_LIST) { + success = contact_list_view_file_drag_received (view, + context, + model, + path, + selection); } - g_free (old_group); - g_free (new_group); - - gtk_drag_finish (context, TRUE, FALSE, GDK_CURRENT_TIME); + gtk_tree_path_free (path); + gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME); } static gboolean @@ -277,13 +466,21 @@ contact_list_view_drag_motion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, - guint time) + guint time_) { + EmpathyContactListViewPriv *priv; + GtkTreeModel *model; + GdkAtom target; + GtkTreeIter iter; static DragMotionData *dm = NULL; GtkTreePath *path; gboolean is_row; gboolean is_different = FALSE; gboolean cleanup = TRUE; + gboolean retval = TRUE; + + priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget)); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget)); is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), x, @@ -302,8 +499,82 @@ contact_list_view_drag_motion (GtkWidget *widget, cleanup &= FALSE; } + if (path == NULL) { + /* Coordinates don't point to an actual row, so make sure the pointer + and highlighting don't indicate that a drag is possible. + */ + gdk_drag_status (context, GDK_ACTION_DEFAULT, time_); + 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); + gtk_tree_model_get_iter (model, &iter, path); + + if (target == GDK_NONE) { + /* 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, + set the drag position to before the first row for a drag into + the "non-group" at the top. + */ + GtkTreeIter group_iter; + gboolean is_group; + GtkTreePath *group_path; + gtk_tree_model_get (model, &iter, + EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group, + -1); + if (is_group) { + group_iter = iter; + } + else { + if (gtk_tree_model_iter_parent (model, &group_iter, &iter)) + gtk_tree_model_get (model, &group_iter, + EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group, + -1); + } + if (is_group) { + gdk_drag_status (context, GDK_ACTION_MOVE, time_); + group_path = gtk_tree_model_get_path (model, &group_iter); + gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), + group_path, + GTK_TREE_VIEW_DROP_INTO_OR_BEFORE); + gtk_tree_path_free (group_path); + } + else { + group_path = gtk_tree_path_new_first (); + gdk_drag_status (context, GDK_ACTION_MOVE, time_); + gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), + group_path, + GTK_TREE_VIEW_DROP_BEFORE); + } + } + else { + /* This is a file drag, and it can only be dropped on contacts, + not groups. + */ + EmpathyContact *contact; + gtk_tree_model_get (model, &iter, + EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact, + -1); + if (contact != NULL && + empathy_contact_is_online (contact) && + (empathy_contact_get_capabilities (contact) & 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); + g_object_unref (contact); + } + else { + gdk_drag_status (context, 0, time_); + gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0); + retval = FALSE; + } + } + if (!is_different && !cleanup) { - return TRUE; + return retval; } if (dm) { @@ -328,7 +599,7 @@ contact_list_view_drag_motion (GtkWidget *widget, dm); } - return TRUE; + return retval; } static void @@ -361,14 +632,14 @@ contact_list_view_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection, guint info, - guint time) + guint time_) { EmpathyContactListViewPriv *priv; GtkTreePath *src_path; GtkTreeIter iter; GtkTreeModel *model; EmpathyContact *contact; - McAccount *account; + TpAccount *account; const gchar *contact_id; const gchar *account_id; gchar *str; @@ -392,21 +663,21 @@ contact_list_view_drag_data_get (GtkWidget *widget, gtk_tree_path_free (src_path); - contact = empathy_contact_list_view_get_selected (EMPATHY_CONTACT_LIST_VIEW (widget)); + contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget)); if (!contact) { return; } account = empathy_contact_get_account (contact); - account_id = mc_account_get_unique_name (account); + account_id = tp_proxy_get_object_path (account); contact_id = empathy_contact_get_id (contact); g_object_unref (contact); - str = g_strconcat (account_id, "/", contact_id, NULL); + str = g_strconcat (account_id, ":", contact_id, NULL); switch (info) { case DND_DRAG_TYPE_CONTACT_ID: gtk_selection_data_set (selection, drag_atoms_source[info], 8, - (guchar*)str, strlen (str) + 1); + (guchar *) str, strlen (str) + 1); break; } @@ -435,7 +706,7 @@ contact_list_view_drag_drop (GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, - guint time) + guint time_) { return FALSE; } @@ -458,10 +729,16 @@ contact_list_view_popup_menu_idle_cb (gpointer user_data) } if (menu) { + g_signal_connect (menu, "deactivate", + G_CALLBACK (gtk_menu_detach), NULL); + gtk_menu_attach_to_widget (GTK_MENU (menu), + GTK_WIDGET (data->view), NULL); gtk_widget_show (menu); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button, data->time); + g_object_ref_sink (menu); + g_object_unref (menu); } g_slice_free (MenuPopupData, data); @@ -527,38 +804,61 @@ contact_list_view_row_activated (GtkTreeView *view, if (contact) { DEBUG ("Starting a chat"); - empathy_dispatcher_chat_with_contact (contact); + empathy_dispatcher_chat_with_contact (contact, NULL, NULL); g_object_unref (contact); } } static void -contact_list_view_voip_activated_cb (EmpathyCellRendererActivatable *cell, - const gchar *path_string, - EmpathyContactListView *view) +contact_list_view_call_activated_cb ( + EmpathyCellRendererActivatable *cell, + const gchar *path_string, + EmpathyContactListView *view) { - EmpathyContactListViewPriv *priv = GET_PRIV (view); - GtkTreeModel *model; - GtkTreeIter iter; - EmpathyContact *contact; - - if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CALL)) { - return; - } + GtkWidget *menu; + GtkTreeModel *model; + GtkTreeIter iter; + EmpathyContact *contact; + GdkEventButton *event; + GtkMenuShell *shell; + GtkWidget *item; model = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); - if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) { + if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) return; - } gtk_tree_model_get (model, &iter, EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact, -1); + if (contact == NULL) + return; - if (contact) { - empathy_dispatcher_call_with_contact (contact); - g_object_unref (contact); - } + event = (GdkEventButton *) gtk_get_current_event (); + + menu = gtk_menu_new (); + shell = GTK_MENU_SHELL (menu); + + /* audio */ + item = empathy_contact_audio_call_menu_item_new (contact); + gtk_menu_shell_append (shell, item); + gtk_widget_show (item); + + /* video */ + item = empathy_contact_video_call_menu_item_new (contact); + gtk_menu_shell_append (shell, item); + gtk_widget_show (item); + + g_signal_connect (menu, "deactivate", + G_CALLBACK (gtk_menu_detach), NULL); + gtk_menu_attach_to_widget (GTK_MENU (menu), + GTK_WIDGET (view), NULL); + gtk_widget_show (menu); + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, + event->button, event->time); + g_object_ref_sink (menu); + g_object_unref (menu); + + g_object_unref (contact); } static void @@ -601,28 +901,71 @@ contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column, GtkTreeIter *iter, EmpathyContactListView *view) { - gchar *icon_name; - gboolean is_group; - gboolean is_active; + GdkPixbuf *pixbuf; + gboolean is_group; + gboolean is_active; gtk_tree_model_get (model, iter, EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group, EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active, - EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &icon_name, + EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf, -1); g_object_set (cell, "visible", !is_group, - "icon-name", icon_name, + "pixbuf", pixbuf, NULL); - g_free (icon_name); + if (pixbuf != NULL) { + g_object_unref (pixbuf); + } contact_list_view_cell_set_background (view, cell, is_group, is_active); } static void -contact_list_view_voip_cell_data_func (GtkTreeViewColumn *tree_column, +contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + EmpathyContactListView *view) +{ + GdkPixbuf *pixbuf = NULL; + gboolean is_group; + gchar *name; + + gtk_tree_model_get (model, iter, + EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group, + EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name, + -1); + + if (!is_group) + goto out; + + if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) { + pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite", + GTK_ICON_SIZE_MENU); + } + else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) { + pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp", + GTK_ICON_SIZE_MENU); + } + +out: + g_object_set (cell, + "visible", pixbuf != NULL, + "pixbuf", pixbuf, + NULL); + + if (pixbuf != NULL) + g_object_unref (pixbuf); + + g_free (name); +} + +static void +contact_list_view_audio_call_cell_data_func ( + GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, @@ -630,17 +973,18 @@ contact_list_view_voip_cell_data_func (GtkTreeViewColumn *tree_column, { gboolean is_group; gboolean is_active; - gboolean can_voip; + gboolean can_audio, can_video; gtk_tree_model_get (model, iter, EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group, EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active, - EMPATHY_CONTACT_LIST_STORE_COL_CAN_VOIP, &can_voip, + EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio, + EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video, -1); g_object_set (cell, - "visible", !is_group && can_voip, - "icon-name", EMPATHY_IMAGE_VOIP, + "visible", !is_group && (can_audio || can_video), + "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP, NULL); contact_list_view_cell_set_background (view, cell, is_group, is_active); @@ -686,18 +1030,12 @@ contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column, { gboolean is_group; gboolean is_active; - gboolean show_status; gtk_tree_model_get (model, iter, EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group, EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active, - EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status, -1); - g_object_set (cell, - "show-status", show_status, - NULL); - contact_list_view_cell_set_background (view, cell, is_group, is_active); } @@ -721,7 +1059,7 @@ contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column, gboolean row_expanded; path = gtk_tree_model_get_path (model, iter); - row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path); + row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path); gtk_tree_path_free (path); g_object_set (cell, @@ -777,7 +1115,7 @@ contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model, EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name, -1); - if (!is_group || G_STR_EMPTY (name)) { + if (!is_group || EMP_STR_EMPTY (name)) { g_free (name); return; } @@ -810,7 +1148,7 @@ contact_list_view_setup (EmpathyContactListView *view) EmpathyContactListViewPriv *priv; GtkCellRenderer *cell; GtkTreeViewColumn *col; - gint i; + guint i; priv = GET_PRIV (view); @@ -825,6 +1163,11 @@ contact_list_view_setup (EmpathyContactListView *view) GTK_TREE_MODEL (priv->store)); /* Setup view */ + /* 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. + */ g_object_set (view, "headers-visible", FALSE, "reorderable", TRUE, @@ -847,6 +1190,22 @@ contact_list_view_setup (EmpathyContactListView *view) "visible", FALSE, NULL); + /* Group icon */ + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (col, cell, FALSE); + gtk_tree_view_column_set_cell_data_func ( + col, cell, + (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func, + view, NULL); + + g_object_set (cell, + "xpad", 0, + "ypad", 0, + "visible", FALSE, + "width", 16, + "height", 16, + NULL); + /* Name */ cell = empathy_cell_renderer_text_new (); gtk_tree_view_column_pack_start (col, cell, TRUE); @@ -857,17 +1216,23 @@ contact_list_view_setup (EmpathyContactListView *view) gtk_tree_view_column_add_attribute (col, cell, "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME); + gtk_tree_view_column_add_attribute (col, cell, + "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME); + gtk_tree_view_column_add_attribute (col, cell, + "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE); gtk_tree_view_column_add_attribute (col, cell, "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS); gtk_tree_view_column_add_attribute (col, cell, "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP); + gtk_tree_view_column_add_attribute (col, cell, + "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT); - /* Voip Capability Icon */ + /* Audio Call Icon */ cell = empathy_cell_renderer_activatable_new (); gtk_tree_view_column_pack_start (col, cell, FALSE); gtk_tree_view_column_set_cell_data_func ( col, cell, - (GtkTreeCellDataFunc) contact_list_view_voip_cell_data_func, + (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func, view, NULL); g_object_set (cell, @@ -875,7 +1240,7 @@ contact_list_view_setup (EmpathyContactListView *view) NULL); g_signal_connect (cell, "path-activated", - G_CALLBACK (contact_list_view_voip_activated_cb), + G_CALLBACK (contact_list_view_call_activated_cb), view); /* Avatar */ @@ -966,6 +1331,12 @@ contact_list_view_finalize (GObject *object) if (priv->store) { g_object_unref (priv->store); } + if (priv->tooltip_widget) { + gtk_widget_destroy (priv->tooltip_widget); + } + if (priv->file_targets) { + gtk_target_list_unref (priv->file_targets); + } G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object); } @@ -1092,10 +1463,14 @@ empathy_contact_list_view_init (EmpathyContactListView *view) /* Get saved group states. */ empathy_contact_groups_get_all (); - gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view), + gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view), empathy_contact_list_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 (contact_list_view_button_press_event_cb), @@ -1120,7 +1495,7 @@ empathy_contact_list_view_new (EmpathyContactListStore *store, EmpathyContactFeatureFlags contact_features) { g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL); - + return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW, "store", store, "contact-features", contact_features, @@ -1129,7 +1504,7 @@ empathy_contact_list_view_new (EmpathyContactListStore *store, } EmpathyContact * -empathy_contact_list_view_get_selected (EmpathyContactListView *view) +empathy_contact_list_view_dup_selected (EmpathyContactListView *view) { EmpathyContactListViewPriv *priv; GtkTreeSelection *selection; @@ -1153,8 +1528,34 @@ empathy_contact_list_view_get_selected (EmpathyContactListView *view) return contact; } +EmpathyContactListFlags +empathy_contact_list_view_get_flags (EmpathyContactListView *view) +{ + EmpathyContactListViewPriv *priv; + GtkTreeSelection *selection; + GtkTreeIter iter; + GtkTreeModel *model; + EmpathyContactListFlags flags; + + g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0); + + priv = GET_PRIV (view); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) { + return 0; + } + + gtk_tree_model_get (model, &iter, + EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags, + -1); + + return flags; +} + gchar * -empathy_contact_list_view_get_selected_group (EmpathyContactListView *view) +empathy_contact_list_view_get_selected_group (EmpathyContactListView *view, + gboolean *is_fake_group) { EmpathyContactListViewPriv *priv; GtkTreeSelection *selection; @@ -1162,6 +1563,7 @@ empathy_contact_list_view_get_selected_group (EmpathyContactListView *view) GtkTreeModel *model; gboolean is_group; gchar *name; + gboolean fake; g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL); @@ -1175,6 +1577,7 @@ empathy_contact_list_view_get_selected_group (EmpathyContactListView *view) gtk_tree_model_get (model, &iter, EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group, EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name, + EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake, -1); if (!is_group) { @@ -1182,17 +1585,20 @@ empathy_contact_list_view_get_selected_group (EmpathyContactListView *view) return NULL; } + if (is_fake_group != NULL) + *is_fake_group = fake; + return name; } static gboolean -contact_list_view_remove_dialog_show (GtkWindow *parent, - const gchar *message, +contact_list_view_remove_dialog_show (GtkWindow *parent, + const gchar *message, const gchar *secondary_text) { GtkWidget *dialog; gboolean res; - + dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message); @@ -1202,9 +1608,9 @@ contact_list_view_remove_dialog_show (GtkWindow *parent, NULL); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", secondary_text); - + gtk_widget_show (dialog); - + res = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); @@ -1218,7 +1624,7 @@ contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem, EmpathyContactListViewPriv *priv = GET_PRIV (view); gchar *group; - group = empathy_contact_list_view_get_selected_group (view); + group = empathy_contact_list_view_get_selected_group (view, NULL); if (group) { gchar *text; GtkWindow *parent; @@ -1246,6 +1652,7 @@ empathy_contact_list_view_get_group_menu (EmpathyContactListView *view) GtkWidget *menu; GtkWidget *item; GtkWidget *image; + gboolean is_fake_group; g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL); @@ -1254,8 +1661,9 @@ empathy_contact_list_view_get_group_menu (EmpathyContactListView *view) return NULL; } - group = empathy_contact_list_view_get_selected_group (view); - if (!group) { + group = empathy_contact_list_view_get_selected_group (view, &is_fake_group); + if (!group || is_fake_group) { + /* We can't alter fake groups */ return NULL; } @@ -1294,22 +1702,21 @@ contact_list_view_remove_activate_cb (GtkMenuItem *menuitem, { EmpathyContactListViewPriv *priv = GET_PRIV (view); EmpathyContact *contact; - - contact = empathy_contact_list_view_get_selected (view); + + contact = empathy_contact_list_view_dup_selected (view); if (contact) { - gchar *text; + gchar *text; GtkWindow *parent; parent = empathy_get_toplevel_window (GTK_WIDGET (view)); text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"), - empathy_contact_get_name (contact)); + empathy_contact_get_name (contact)); if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) { EmpathyContactList *list; list = empathy_contact_list_store_get_list_iface (priv->store); - empathy_contact_list_remove (list, contact, - _("Sorry, I don't want you in my contact list anymore.")); + empathy_contact_list_remove (list, contact, ""); } g_free (text); @@ -1325,32 +1732,31 @@ empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view) GtkWidget *menu; GtkWidget *item; GtkWidget *image; + EmpathyContactListFlags flags; g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL); - contact = empathy_contact_list_view_get_selected (view); + contact = empathy_contact_list_view_dup_selected (view); if (!contact) { return NULL; } + flags = empathy_contact_list_view_get_flags (view); menu = empathy_contact_menu_new (contact, priv->contact_features); - if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE)) { - g_object_unref (contact); - return menu; - } - - if (menu) { - /* Separator */ - item = gtk_separator_menu_item_new (); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); - gtk_widget_show (item); - } else { - menu = gtk_menu_new (); - } - /* Remove contact */ - if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE) { + if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE && + flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) { + /* create the menu if required, or just add a separator */ + if (!menu) { + menu = gtk_menu_new (); + } else { + item = gtk_separator_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + } + + /* Remove */ item = gtk_image_menu_item_new_with_mnemonic (_("_Remove")); image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU);