aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libempathy-gtk/empathy-chat.c6
-rw-r--r--libempathy-gtk/empathy-contact-list-view.c267
-rw-r--r--libempathy-gtk/empathy-ui-utils.c66
-rw-r--r--libempathy-gtk/empathy-ui-utils.h4
-rw-r--r--src/empathy-chat-window.c116
5 files changed, 381 insertions, 78 deletions
diff --git a/libempathy-gtk/empathy-chat.c b/libempathy-gtk/empathy-chat.c
index d9a72d3e9..9d8b89c26 100644
--- a/libempathy-gtk/empathy-chat.c
+++ b/libempathy-gtk/empathy-chat.c
@@ -2010,6 +2010,12 @@ chat_create_ui (EmpathyChat *chat)
/* Add message view. */
chat->view = empathy_theme_manager_create_view (empathy_theme_manager_get ());
+ /* If this is a GtkTextView, it's set as a drag destination for text/plain
+ and other types, even though it's non-editable and doesn't accept any
+ drags. This steals drag motion for anything inside the scrollbars,
+ making drag destinations on chat windows far less useful.
+ */
+ gtk_drag_dest_unset (GTK_WIDGET (chat->view));
g_signal_connect (chat->view, "focus_in_event",
G_CALLBACK (chat_text_view_focus_in_event_cb),
chat);
diff --git a/libempathy-gtk/empathy-contact-list-view.c b/libempathy-gtk/empathy-contact-list-view.c
index c19ad32df..6b25cc10f 100644
--- a/libempathy-gtk/empathy-contact-list-view.c
+++ b/libempathy-gtk/empathy-contact-list-view.c
@@ -65,6 +65,7 @@ typedef struct {
EmpathyContactListFeatureFlags list_features;
EmpathyContactFeatureFlags contact_features;
GtkWidget *tooltip_widget;
+ GtkTargetList *file_targets;
} EmpathyContactListViewPriv;
typedef struct {
@@ -88,17 +89,21 @@ 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/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/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
+};
+
static const GtkTargetEntry drag_types_source[] = {
{ "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
};
@@ -234,74 +239,51 @@ contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
}
}
-static void
-contact_list_view_drag_data_received (GtkWidget *view,
- GdkDragContext *context,
- gint x,
- gint y,
- GtkSelectionData *selection,
- guint info,
- guint time_)
+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 = NULL;
- GtkTreeModel *model;
- GtkTreeViewDropPosition position;
- GtkTreePath *path;
- const gchar *id;
- gchar **strv = NULL;
- const gchar *account_id = NULL;
- const gchar *contact_id = NULL;
- gchar *new_group = NULL;
- gchar *old_group = NULL;
+ TpAccount *account;
DndGetContactData *data;
- gboolean is_row;
- gboolean success = TRUE;
+ 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;
priv = GET_PRIV (view);
- 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 (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);
- }
+ sel_data = (const gchar *) gtk_selection_data_get_data (selection);
+ new_group = empathy_contact_list_store_get_parent_group (model,
+ path, NULL);
/* Get source group information. */
if (priv->drag_row) {
- path = gtk_tree_row_reference_get_path (priv->drag_row);
- if (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, path, NULL);
- gtk_tree_path_free (path);
+ model, source_path, NULL);
+ gtk_tree_path_free (source_path);
}
}
if (!tp_strdiff (old_group, new_group)) {
g_free (new_group);
g_free (old_group);
- goto OUT;
+ return FALSE;
}
- id = (const gchar*) gtk_selection_data_get_data (selection);
- 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);
-
- /* FIXME: should probably make sure the account manager is prepared
- * before calling _ensure_account on it. See bug 600115. */
account_manager = tp_account_manager_dup ();
- strv = g_strsplit (id, ":", 2);
+ strv = g_strsplit (sel_data, ":", 2);
if (g_strv_length (strv) == 2) {
account_id = strv[0];
contact_id = strv[1];
@@ -322,7 +304,7 @@ contact_list_view_drag_data_received (GtkWidget *view,
success = FALSE;
g_free (new_group);
g_free (old_group);
- goto OUT;
+ return FALSE;
}
data = g_slice_new0 (DndGetContactData);
@@ -333,14 +315,85 @@ contact_list_view_drag_data_received (GtkWidget *view,
/* 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));
-
+ 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);
-OUT:
- g_strfreev (strv);
+ 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 (view),
+ x,
+ y,
+ &path,
+ &position);
+ if (!is_row) {
+ success = FALSE;
+ }
+ 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);
+ }
+ else if (info == DND_DRAG_TYPE_URI_LIST) {
+ success = contact_list_view_file_drag_received (view,
+ context,
+ model,
+ path,
+ selection);
+ }
+
+ gtk_tree_path_free (path);
gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
}
@@ -363,12 +416,19 @@ contact_list_view_drag_motion (GtkWidget *widget,
gint y,
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;
- int action = 0;
+ 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,
@@ -387,15 +447,82 @@ contact_list_view_drag_motion (GtkWidget *widget,
cleanup &= FALSE;
}
- if (context->actions == GDK_ACTION_COPY) {
- action = context->suggested_action;
- } else if (context->actions & GDK_ACTION_MOVE) {
- action = GDK_ACTION_MOVE;
+ 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;
+ }
}
- gdk_drag_status (context, action, time_);
if (!is_different && !cleanup) {
- return TRUE;
+ return retval;
}
if (dm) {
@@ -420,7 +547,7 @@ contact_list_view_drag_motion (GtkWidget *widget,
dm);
}
- return TRUE;
+ return retval;
}
static void
@@ -971,6 +1098,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,
@@ -1131,6 +1263,9 @@ contact_list_view_finalize (GObject *object)
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);
}
@@ -1261,6 +1396,10 @@ empathy_contact_list_view_init (EmpathyContactListView *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),
diff --git a/libempathy-gtk/empathy-ui-utils.c b/libempathy-gtk/empathy-ui-utils.c
index c737873d1..6906d8228 100644
--- a/libempathy-gtk/empathy-ui-utils.c
+++ b/libempathy-gtk/empathy-ui-utils.c
@@ -1454,30 +1454,70 @@ empathy_toggle_button_set_state_quietly (GtkWidget *widget,
g_signal_handlers_unblock_by_func (widget, callback, user_data);
}
+void
+empathy_send_file (EmpathyContact *contact, GFile *file)
+{
+ EmpathyFTFactory *factory;
+ GtkRecentManager *manager;
+ gchar *uri;
+
+ g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+ g_return_if_fail (G_IS_FILE (file));
+
+ factory = empathy_ft_factory_dup_singleton ();
+
+ empathy_ft_factory_new_transfer_outgoing (factory, contact, file);
+
+ uri = g_file_get_uri (file);
+ manager = gtk_recent_manager_get_default ();
+ gtk_recent_manager_add_item (manager, uri);
+ g_free (uri);
+
+ g_object_unref (factory);
+}
+
+void
+empathy_send_file_from_uri_list (EmpathyContact *contact, const gchar *uri_list)
+{
+ const gchar *nl;
+ GFile *file;
+
+ /* Only handle a single file for now. It would be wicked cool to be
+ able to do multiple files, offering to zip them or whatever like
+ nautilus-sendto does. Note that text/uri-list is defined to have
+ each line terminated by \r\n, but we can be tolerant of applications
+ that only use \n or don't terminate single-line entries.
+ */
+ nl = strstr (uri_list, "\r\n");
+ if (!nl) {
+ nl = strchr (uri_list, '\n');
+ }
+ if (nl) {
+ gchar *uri = g_strndup (uri_list, nl - uri_list);
+ file = g_file_new_for_uri (uri);
+ g_free (uri);
+ }
+ else {
+ file = g_file_new_for_uri (uri_list);
+ }
+
+ empathy_send_file (contact, file);
+
+ g_object_unref (file);
+}
+
static void
file_manager_send_file_response_cb (GtkDialog *widget,
gint response_id,
EmpathyContact *contact)
{
- EmpathyFTFactory *factory;
GFile *file;
- gchar *uri;
- GtkRecentManager *manager;
if (response_id == GTK_RESPONSE_OK) {
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (widget));
- uri = g_file_get_uri (file);
-
- factory = empathy_ft_factory_dup_singleton ();
-
- empathy_ft_factory_new_transfer_outgoing (factory, contact,
- file);
- manager = gtk_recent_manager_get_default ();
- gtk_recent_manager_add_item (manager, uri);
+ empathy_send_file (contact, file);
- g_free (uri);
- g_object_unref (factory);
g_object_unref (file);
}
diff --git a/libempathy-gtk/empathy-ui-utils.h b/libempathy-gtk/empathy-ui-utils.h
index 65fc9ac0e..0eacd49b8 100644
--- a/libempathy-gtk/empathy-ui-utils.h
+++ b/libempathy-gtk/empathy-ui-utils.h
@@ -110,6 +110,10 @@ void empathy_toggle_button_set_state_quietly (GtkWidget *widge
GtkWidget * empathy_link_button_new (const gchar *url,
const gchar *title);
+void empathy_send_file (EmpathyContact *contact,
+ GFile *file);
+void empathy_send_file_from_uri_list (EmpathyContact *contact,
+ const gchar *uri_list);
void empathy_send_file_with_file_chooser (EmpathyContact *contact);
void empathy_receive_file_with_file_chooser (EmpathyFTHandler *handler);
diff --git a/src/empathy-chat-window.c b/src/empathy-chat-window.c
index b9e1e1bf3..ea63f29aa 100644
--- a/src/empathy-chat-window.c
+++ b/src/empathy-chat-window.c
@@ -80,6 +80,9 @@ typedef struct {
NotifyNotification *notification;
NotificationData *notification_data;
+ GtkTargetList *contact_targets;
+ GtkTargetList *file_targets;
+
/* Menu items. */
GtkUIManager *ui_manager;
GtkAction *menu_conv_insert_smiley;
@@ -106,12 +109,22 @@ static const guint tab_accel_keys[] = {
typedef enum {
DND_DRAG_TYPE_CONTACT_ID,
+ DND_DRAG_TYPE_URI_LIST,
DND_DRAG_TYPE_TAB
} DndDragType;
static const GtkTargetEntry drag_types_dest[] = {
{ "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
{ "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
+ { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
+};
+
+static const GtkTargetEntry drag_types_dest_contact[] = {
+ { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
+};
+
+static const GtkTargetEntry drag_types_dest_file[] = {
+ { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
};
static void chat_window_update (EmpathyChatWindow *window);
@@ -1410,6 +1423,68 @@ chat_window_focus_in_event_cb (GtkWidget *widget,
return FALSE;
}
+static gboolean
+chat_window_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ guint time_,
+ EmpathyChatWindow *window)
+{
+ GdkAtom target;
+ EmpathyChatWindowPriv *priv;
+ GdkAtom dest_target;
+
+ priv = GET_PRIV (window);
+
+ target = gtk_drag_dest_find_target (widget, context, NULL);
+
+ dest_target = gdk_atom_intern_static_string ("text/uri-list");
+ if (target == dest_target) {
+ /* This is a file drag. Ensure the contact is online and set the
+ drag type to COPY. Note that it's possible that the tab will
+ be switched by GTK+ after a timeout from drag_motion without
+ getting another drag_motion to disable the drop. You have
+ to hold your mouse really still.
+ */
+ EmpathyContact *contact;
+
+ priv = GET_PRIV (window);
+ contact = empathy_chat_get_remote_contact (priv->current_chat);
+ /* contact is NULL for multi-user chats. We don't do
+ * file transfers to MUCs. We also don't send files
+ * to offline contacts or contacts that don't support
+ * file transfer.
+ */
+ if ((contact == NULL) || !empathy_contact_is_online (contact)) {
+ gdk_drag_status (context, 0, time_);
+ return FALSE;
+ }
+ if (!(empathy_contact_get_capabilities (contact)
+ & EMPATHY_CAPABILITIES_FT)) {
+ gdk_drag_status (context, 0, time_);
+ return FALSE;
+ }
+ gdk_drag_status (context, GDK_ACTION_COPY, time_);
+ return TRUE;
+ }
+
+ dest_target = gdk_atom_intern_static_string ("text/contact-id");
+ if (target == dest_target) {
+ /* This is a drag of a contact from a contact list. Set to COPY.
+ FIXME: If this drag is to a MUC window, it invites the user.
+ Otherwise, it opens a chat. Should we use a different drag
+ type for invites? Should we allow ASK?
+ */
+ gdk_drag_status (context, GDK_ACTION_COPY, time_);
+ return TRUE;
+ }
+
+ /* Otherwise, it must be a notebook tab drag. Set to MOVE. */
+ gdk_drag_status (context, GDK_ACTION_MOVE, time_);
+ return TRUE;
+}
+
static void
chat_window_drag_data_received (GtkWidget *widget,
GdkDragContext *context,
@@ -1492,6 +1567,27 @@ chat_window_drag_data_received (GtkWidget *widget,
*/
gtk_drag_finish (context, TRUE, FALSE, time_);
}
+ else if (info == DND_DRAG_TYPE_URI_LIST) {
+ EmpathyChatWindowPriv *priv;
+ EmpathyContact *contact;
+ const gchar *data;
+
+ priv = GET_PRIV (window);
+ contact = empathy_chat_get_remote_contact (priv->current_chat);
+
+ /* contact is NULL when current_chat is a multi-user chat.
+ * We don't do file transfers to MUCs, so just cancel the drag.
+ */
+ if (contact == NULL) {
+ gtk_drag_finish (context, TRUE, FALSE, time_);
+ return;
+ }
+
+ data = (const gchar *) gtk_selection_data_get_data (selection);
+ empathy_send_file_from_uri_list (contact, data);
+
+ gtk_drag_finish (context, TRUE, FALSE, time_);
+ }
else if (info == DND_DRAG_TYPE_TAB) {
EmpathyChat **chat;
EmpathyChatWindow *old_window = NULL;
@@ -1554,6 +1650,13 @@ chat_window_finalize (GObject *object)
}
}
+ if (priv->contact_targets) {
+ gtk_target_list_unref (priv->contact_targets);
+ }
+ if (priv->file_targets) {
+ gtk_target_list_unref (priv->file_targets);
+ }
+
chat_windows = g_list_remove (chat_windows, window);
gtk_widget_destroy (priv->dialog);
@@ -1665,6 +1768,12 @@ empathy_chat_window_init (EmpathyChatWindow *window)
g_object_unref (accel_group);
+ /* Set up drag target lists */
+ priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
+ G_N_ELEMENTS (drag_types_dest_contact));
+ priv->file_targets = gtk_target_list_new (drag_types_dest_file,
+ G_N_ELEMENTS (drag_types_dest_file));
+
/* Set up smiley menu */
smiley_manager = empathy_smiley_manager_dup_singleton ();
submenu = empathy_smiley_menu_new (smiley_manager,
@@ -1705,8 +1814,13 @@ empathy_chat_window_init (EmpathyChatWindow *window)
GTK_DEST_DEFAULT_ALL,
drag_types_dest,
G_N_ELEMENTS (drag_types_dest),
- GDK_ACTION_MOVE);
+ GDK_ACTION_MOVE | GDK_ACTION_COPY);
+ /* connect_after to allow GtkNotebook's built-in tab switching */
+ g_signal_connect_after (priv->notebook,
+ "drag-motion",
+ G_CALLBACK (chat_window_drag_motion),
+ window);
g_signal_connect (priv->notebook,
"drag-data-received",
G_CALLBACK (chat_window_drag_data_received),