[evolution] Improve clipboard behavior.



commit 3e7c7808cc65c22bc40a7d1d30ffa0044097a6ff
Author: Matthew Barnes <mbarnes redhat com>
Date:   Sat Jan 16 13:34:32 2010 -0500

    Improve clipboard behavior.
    
    Add "copy-target-list" and "paste-target-list" to the ESelectable
    interface.  These are underutilized for the moment, but will eventually
    be used to help integrate drag-and-drop support into ESelectable.
    
    Add cut and paste support to EWebView, along with a new "editable"
    property and new clipboard signals "copy-clipboard", "cut-clipboard" and
    "paste-clipboard".
    
    In EFocusTracker, listen for "owner-changed" signals from the default
    clipboard as another trigger to update actions, particularly the Paste
    action.  (Unfortunately this doesn't work for EWebView since GtkHtml
    implements its own clipboard.)
    
    In EMsgComposer, convert GtkhtmlEditor's clipboard methods to empty
    stubs, since EFocusTracker will now trigger EWebView's clipboard
    actions.  Also, intercept EWebView::paste-clipboard signals and improve
    the interaction between the HTML editor and the attachment bar based on
    use cases in bug #603715.

 addressbook/gui/widgets/e-addressbook-view.c |   90 +++++++++-
 addressbook/gui/widgets/e-addressbook-view.h |    4 +
 calendar/gui/e-calendar-view.c               |   90 +++++++++--
 calendar/gui/e-calendar-view.h               |    4 +
 calendar/gui/e-memo-table.c                  |   78 ++++++++-
 calendar/gui/e-memo-table.h                  |    4 +
 calendar/gui/e-task-table.c                  |   84 ++++++++-
 calendar/gui/e-task-table.h                  |    8 +-
 composer/e-composer-private.c                |   42 +++++
 composer/e-composer-private.h                |    5 +
 composer/e-msg-composer.c                    |  134 +++++++--------
 configure.ac                                 |    2 +-
 mail/message-list.c                          |   64 +++++++
 mail/message-list.h                          |    4 +
 widgets/misc/e-focus-tracker.c               |   14 ++
 widgets/misc/e-selectable.c                  |   58 ++++++-
 widgets/misc/e-selectable.h                  |    4 +
 widgets/misc/e-web-view.c                    |  243 ++++++++++++++++++++++++--
 widgets/misc/e-web-view.h                    |   10 +
 19 files changed, 823 insertions(+), 119 deletions(-)
---
diff --git a/addressbook/gui/widgets/e-addressbook-view.c b/addressbook/gui/widgets/e-addressbook-view.c
index cc540c1..48058ad 100644
--- a/addressbook/gui/widgets/e-addressbook-view.c
+++ b/addressbook/gui/widgets/e-addressbook-view.c
@@ -36,6 +36,7 @@
 #include "addressbook/printing/e-contact-print.h"
 #include "ea-addressbook.h"
 
+#include "e-util/e-binding.h"
 #include "e-util/e-print.h"
 #include "e-util/e-selection.h"
 #include "e-util/e-util.h"
@@ -90,11 +91,16 @@ struct _EAddressbookViewPrivate {
 	gchar *search_text;
 	gint search_id;
 	EFilterRule *advanced_search;
+
+	GtkTargetList *copy_target_list;
+	GtkTargetList *paste_target_list;
 };
 
 enum {
 	PROP_0,
+	PROP_COPY_TARGET_LIST,
 	PROP_MODEL,
+	PROP_PASTE_TARGET_LIST,
 	PROP_SHELL_VIEW,
 	PROP_SOURCE
 };
@@ -437,21 +443,38 @@ addressbook_view_get_property (GObject *object,
                                GParamSpec *pspec)
 {
 	switch (property_id) {
+		case PROP_COPY_TARGET_LIST:
+			g_value_set_boxed (
+				value,
+				e_addressbook_view_get_copy_target_list (
+				E_ADDRESSBOOK_VIEW (object)));
+			return;
+
 		case PROP_MODEL:
 			g_value_set_object (
-				value, e_addressbook_view_get_model (
+				value,
+				e_addressbook_view_get_model (
+				E_ADDRESSBOOK_VIEW (object)));
+			return;
+
+		case PROP_PASTE_TARGET_LIST:
+			g_value_set_boxed (
+				value,
+				e_addressbook_view_get_paste_target_list (
 				E_ADDRESSBOOK_VIEW (object)));
 			return;
 
 		case PROP_SHELL_VIEW:
 			g_value_set_object (
-				value, e_addressbook_view_get_shell_view (
+				value,
+				e_addressbook_view_get_shell_view (
 				E_ADDRESSBOOK_VIEW (object)));
 			return;
 
 		case PROP_SOURCE:
 			g_value_set_object (
-				value, e_addressbook_view_get_source (
+				value,
+				e_addressbook_view_get_source (
 				E_ADDRESSBOOK_VIEW (object)));
 			return;
 	}
@@ -511,6 +534,16 @@ addressbook_view_dispose (GObject *object)
 		priv->advanced_search = NULL;
 	}
 
+	if (priv->copy_target_list != NULL) {
+		gtk_target_list_unref (priv->copy_target_list);
+		priv->copy_target_list = NULL;
+	}
+
+	if (priv->paste_target_list != NULL) {
+		gtk_target_list_unref (priv->paste_target_list);
+		priv->paste_target_list = NULL;
+	}
+
 	/* Chain up to parent's dispose() method. */
 	G_OBJECT_CLASS (parent_class)->dispose (object);
 }
@@ -548,12 +581,14 @@ addressbook_view_update_actions (ESelectable *selectable,
 	EAddressbookModel *model;
 	ESelectionModel *selection_model;
 	GtkAction *action;
-	gboolean clipboard_has_directory;
+	GtkTargetList *target_list;
+	gboolean can_paste = FALSE;
 	gboolean source_is_editable;
 	gboolean sensitive;
 	const gchar *tooltip;
 	gint n_contacts;
 	gint n_selected;
+	gint ii;
 
 	view = E_ADDRESSBOOK_VIEW (selectable);
 	model = e_addressbook_view_get_model (view);
@@ -565,9 +600,10 @@ addressbook_view_update_actions (ESelectable *selectable,
 	n_selected = (selection_model != NULL) ?
 		e_selection_model_selected_count (selection_model) : 0;
 
-	clipboard_has_directory = (clipboard_targets != NULL) &&
-		e_targets_include_directory (
-		clipboard_targets, n_clipboard_targets);
+	target_list = e_selectable_get_paste_target_list (selectable);
+	for (ii = 0; ii < n_clipboard_targets && !can_paste; ii++)
+		can_paste = gtk_target_list_find (
+			target_list, clipboard_targets[ii], NULL);
 
 	action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
 	sensitive = source_is_editable && (n_selected > 0);
@@ -582,7 +618,7 @@ addressbook_view_update_actions (ESelectable *selectable,
 	gtk_action_set_tooltip (action, tooltip);
 
 	action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
-	sensitive = source_is_editable && clipboard_has_directory;
+	sensitive = source_is_editable && can_paste;
 	tooltip = _("Paste contacts from the clipboard");
 	gtk_action_set_sensitive (action, sensitive);
 	gtk_action_set_tooltip (action, tooltip);
@@ -702,6 +738,12 @@ addressbook_view_class_init (EAddressbookViewClass *class)
 	object_class->dispose = addressbook_view_dispose;
 	object_class->constructed = addressbook_view_constructed;
 
+	/* Inherited from ESelectableInterface */
+	g_object_class_override_property (
+		object_class,
+		PROP_COPY_TARGET_LIST,
+		"copy-target-list");
+
 	g_object_class_install_property (
 		object_class,
 		PROP_MODEL,
@@ -712,6 +754,12 @@ addressbook_view_class_init (EAddressbookViewClass *class)
 			E_TYPE_ADDRESSBOOK_MODEL,
 			G_PARAM_READABLE));
 
+	/* Inherited from ESelectableInterface */
+	g_object_class_override_property (
+		object_class,
+		PROP_PASTE_TARGET_LIST,
+		"paste-target-list");
+
 	g_object_class_install_property (
 		object_class,
 		PROP_SHELL_VIEW,
@@ -780,10 +828,20 @@ addressbook_view_class_init (EAddressbookViewClass *class)
 static void
 addressbook_view_init (EAddressbookView *view)
 {
+	GtkTargetList *target_list;
+
 	view->priv = E_ADDRESSBOOK_VIEW_GET_PRIVATE (view);
 
 	view->priv->model = e_addressbook_model_new ();
 
+	target_list = gtk_target_list_new (NULL, 0);
+	e_target_list_add_directory_targets (target_list, 0);
+	view->priv->copy_target_list = target_list;
+
+	target_list = gtk_target_list_new (NULL, 0);
+	e_target_list_add_directory_targets (target_list, 0);
+	view->priv->paste_target_list = target_list;
+
 	gtk_scrolled_window_set_policy (
 		GTK_SCROLLED_WINDOW (view),
 		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
@@ -972,6 +1030,22 @@ e_addressbook_view_get_source (EAddressbookView *view)
 	return view->priv->source;
 }
 
+GtkTargetList *
+e_addressbook_view_get_copy_target_list (EAddressbookView *view)
+{
+	g_return_val_if_fail (E_IS_ADDRESSBOOK_VIEW (view), NULL);
+
+	return view->priv->copy_target_list;
+}
+
+GtkTargetList *
+e_addressbook_view_get_paste_target_list (EAddressbookView *view)
+{
+	g_return_val_if_fail (E_IS_ADDRESSBOOK_VIEW (view), NULL);
+
+	return view->priv->paste_target_list;
+}
+
 static void
 status_message (EAddressbookView *view,
                 const gchar *status)
diff --git a/addressbook/gui/widgets/e-addressbook-view.h b/addressbook/gui/widgets/e-addressbook-view.h
index 55f114a..4b85293 100644
--- a/addressbook/gui/widgets/e-addressbook-view.h
+++ b/addressbook/gui/widgets/e-addressbook-view.h
@@ -96,6 +96,10 @@ ESelectionModel *
 EShellView *	e_addressbook_view_get_shell_view
 						(EAddressbookView *view);
 ESource *	e_addressbook_view_get_source	(EAddressbookView *view);
+GtkTargetList *	e_addressbook_view_get_copy_target_list
+						(EAddressbookView *view);
+GtkTargetList *	e_addressbook_view_get_paste_target_list
+						(EAddressbookView *view);
 void		e_addressbook_view_view		(EAddressbookView *view);
 void		e_addressbook_view_print	(EAddressbookView *view,
 						 gboolean selection_only,
diff --git a/calendar/gui/e-calendar-view.c b/calendar/gui/e-calendar-view.c
index 5797382..fd8650e 100644
--- a/calendar/gui/e-calendar-view.c
+++ b/calendar/gui/e-calendar-view.c
@@ -74,11 +74,16 @@ struct _ECalendarViewPrivate {
 
 	/* The default category */
 	gchar *default_category;
+
+	GtkTargetList *copy_target_list;
+	GtkTargetList *paste_target_list;
 };
 
 enum {
 	PROP_0,
-	PROP_MODEL
+	PROP_COPY_TARGET_LIST,
+	PROP_MODEL,
+	PROP_PASTE_TARGET_LIST
 };
 
 /* FIXME Why are we emitting these event signals here? Can't the model just be listened to? */
@@ -268,11 +273,23 @@ calendar_view_get_property (GObject *object,
                             GParamSpec *pspec)
 {
 	switch (property_id) {
+		case PROP_COPY_TARGET_LIST:
+			g_value_set_boxed (
+				value, e_calendar_view_get_copy_target_list (
+				E_CALENDAR_VIEW (object)));
+			return;
+
 		case PROP_MODEL:
 			g_value_set_object (
 				value, e_calendar_view_get_model (
 				E_CALENDAR_VIEW (object)));
 			return;
+
+		case PROP_PASTE_TARGET_LIST:
+			g_value_set_boxed (
+				value, e_calendar_view_get_paste_target_list (
+				E_CALENDAR_VIEW (object)));
+			return;
 	}
 
 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -293,6 +310,16 @@ calendar_view_dispose (GObject *object)
 		priv->model = NULL;
 	}
 
+	if (priv->copy_target_list != NULL) {
+		gtk_target_list_unref (priv->copy_target_list);
+		priv->copy_target_list = NULL;
+	}
+
+	if (priv->paste_target_list != NULL) {
+		gtk_target_list_unref (priv->paste_target_list);
+		priv->paste_target_list = NULL;
+	}
+
 	/* Chain up to parent's dispose() method. */
 	G_OBJECT_CLASS (e_calendar_view_parent_class)->dispose (object);
 }
@@ -318,13 +345,15 @@ calendar_view_update_actions (ESelectable *selectable,
 {
 	ECalendarView *view;
 	GtkAction *action;
+	GtkTargetList *target_list;
 	GList *list, *iter;
+	gboolean can_paste = FALSE;
 	gboolean sources_are_editable = TRUE;
-	gboolean clipboard_has_calendar;
 	gboolean recurring = FALSE;
 	gboolean sensitive;
 	const gchar *tooltip;
 	gint n_selected;
+	gint ii;
 
 	view = E_CALENDAR_VIEW (selectable);
 
@@ -353,9 +382,10 @@ calendar_view_update_actions (ESelectable *selectable,
 
 	g_list_free (list);
 
-	clipboard_has_calendar = (clipboard_targets != NULL) &&
-		e_targets_include_calendar (
-		clipboard_targets, n_clipboard_targets);
+	target_list = e_selectable_get_paste_target_list (selectable);
+	for (ii = 0; ii < n_clipboard_targets && !can_paste; ii++)
+		can_paste = gtk_target_list_find (
+			target_list, clipboard_targets[ii], NULL);
 
 	action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
 	sensitive = (n_selected > 0) && sources_are_editable;
@@ -370,7 +400,7 @@ calendar_view_update_actions (ESelectable *selectable,
 	gtk_action_set_tooltip (action, tooltip);
 
 	action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
-	sensitive = sources_are_editable && clipboard_has_calendar;
+	sensitive = sources_are_editable && can_paste;
 	tooltip = _("Paste events from the clipboard");
 	gtk_action_set_sensitive (action, sensitive);
 	gtk_action_set_tooltip (action, tooltip);
@@ -718,6 +748,12 @@ e_calendar_view_class_init (ECalendarViewClass *class)
 	class->open_event = e_calendar_view_open_event;
 	class->paste_text = NULL;
 
+	/* Inherited from ESelectableInterface */
+	g_object_class_override_property (
+		object_class,
+		PROP_COPY_TARGET_LIST,
+		"copy-target-list");
+
 	g_object_class_install_property (
 		object_class,
 		PROP_MODEL,
@@ -729,6 +765,12 @@ e_calendar_view_class_init (ECalendarViewClass *class)
 			G_PARAM_READWRITE |
 			G_PARAM_CONSTRUCT_ONLY));
 
+	/* Inherited from ESelectableInterface */
+	g_object_class_override_property (
+		object_class,
+		PROP_PASTE_TARGET_LIST,
+		"paste-target-list");
+
 	signals[POPUP_EVENT] = g_signal_new (
 		"popup-event",
 		G_TYPE_FROM_CLASS (class),
@@ -820,7 +862,17 @@ e_calendar_view_class_init (ECalendarViewClass *class)
 static void
 e_calendar_view_init (ECalendarView *calendar_view)
 {
+	GtkTargetList *target_list;
+
 	calendar_view->priv = E_CALENDAR_VIEW_GET_PRIVATE (calendar_view);
+
+	target_list = gtk_target_list_new (NULL, 0);
+	e_target_list_add_calendar_targets (target_list, 0);
+	calendar_view->priv->copy_target_list = target_list;
+
+	target_list = gtk_target_list_new (NULL, 0);
+	e_target_list_add_calendar_targets (target_list, 0);
+	calendar_view->priv->paste_target_list = target_list;
 }
 
 static void
@@ -1017,7 +1069,8 @@ const gchar *
 e_calendar_view_get_default_category (ECalendarView *cal_view)
 {
 	g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
-	return (const gchar *) cal_view->priv->default_category;
+
+	return cal_view->priv->default_category;
 }
 
 /**
@@ -1029,16 +1082,31 @@ e_calendar_view_get_default_category (ECalendarView *cal_view)
  * components from the given calendar view.
  */
 void
-e_calendar_view_set_default_category (ECalendarView *cal_view, const gchar *category)
+e_calendar_view_set_default_category (ECalendarView *cal_view,
+                                      const gchar *category)
 {
 	g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
 
-	if (cal_view->priv->default_category)
-		g_free (cal_view->priv->default_category);
-
+	g_free (cal_view->priv->default_category);
 	cal_view->priv->default_category = g_strdup (category);
 }
 
+GtkTargetList *
+e_calendar_view_get_copy_target_list (ECalendarView *cal_view)
+{
+	g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
+
+	return cal_view->priv->copy_target_list;
+}
+
+GtkTargetList *
+e_calendar_view_get_paste_target_list (ECalendarView *cal_view)
+{
+	g_return_val_if_fail (E_IS_CALENDAR_VIEW (cal_view), NULL);
+
+	return cal_view->priv->paste_target_list;
+}
+
 GList *
 e_calendar_view_get_selected_events (ECalendarView *cal_view)
 {
diff --git a/calendar/gui/e-calendar-view.h b/calendar/gui/e-calendar-view.h
index d79a6fd..62fcabc 100644
--- a/calendar/gui/e-calendar-view.h
+++ b/calendar/gui/e-calendar-view.h
@@ -159,6 +159,10 @@ void		e_calendar_view_set_status_message
 						(ECalendarView *cal_view,
 						 const gchar *message,
 						 gint percent);
+GtkTargetList *	e_calendar_view_get_copy_target_list
+						(ECalendarView *cal_view);
+GtkTargetList *	e_calendar_view_get_paste_target_list
+						(ECalendarView *cal_view);
 
 GList *		e_calendar_view_get_selected_events
 						(ECalendarView *cal_view);
diff --git a/calendar/gui/e-memo-table.c b/calendar/gui/e-memo-table.c
index 5f2cd26..11c7482 100644
--- a/calendar/gui/e-memo-table.c
+++ b/calendar/gui/e-memo-table.c
@@ -67,11 +67,16 @@
 struct _EMemoTablePrivate {
 	gpointer shell_view;  /* weak pointer */
 	ECalModel *model;
+
+	GtkTargetList *copy_target_list;
+	GtkTargetList *paste_target_list;
 };
 
 enum {
 	PROP_0,
+	PROP_COPY_TARGET_LIST,
 	PROP_MODEL,
+	PROP_PASTE_TARGET_LIST,
 	PROP_SHELL_VIEW
 };
 
@@ -300,12 +305,24 @@ memo_table_get_property (GObject *object,
                          GParamSpec *pspec)
 {
 	switch (property_id) {
+		case PROP_COPY_TARGET_LIST:
+			g_value_set_boxed (
+				value, e_memo_table_get_copy_target_list (
+				E_MEMO_TABLE (object)));
+			return;
+
 		case PROP_MODEL:
 			g_value_set_object (
 				value, e_memo_table_get_model (
 				E_MEMO_TABLE (object)));
 			return;
 
+		case PROP_PASTE_TARGET_LIST:
+			g_value_set_boxed (
+				value, e_memo_table_get_paste_target_list (
+				E_MEMO_TABLE (object)));
+			return;
+
 		case PROP_SHELL_VIEW:
 			g_value_set_object (
 				value, e_memo_table_get_shell_view (
@@ -334,6 +351,16 @@ memo_table_dispose (GObject *object)
 		priv->model = NULL;
 	}
 
+	if (priv->copy_target_list != NULL) {
+		gtk_target_list_unref (priv->copy_target_list);
+		priv->copy_target_list = NULL;
+	}
+
+	if (priv->paste_target_list != NULL) {
+		gtk_target_list_unref (priv->paste_target_list);
+		priv->paste_target_list = NULL;
+	}
+
 	/* Chain up to parent's dispose() method. */
 	G_OBJECT_CLASS (parent_class)->dispose (object);
 }
@@ -689,12 +716,14 @@ memo_table_update_actions (ESelectable *selectable,
 {
 	EMemoTable *memo_table;
 	GtkAction *action;
+	GtkTargetList *target_list;
 	GSList *list, *iter;
+	gboolean can_paste = FALSE;
 	gboolean sources_are_editable = TRUE;
-	gboolean clipboard_has_calendar;
 	gboolean sensitive;
 	const gchar *tooltip;
 	gint n_selected;
+	gint ii;
 
 	memo_table = E_MEMO_TABLE (selectable);
 	n_selected = e_table_selected_count (E_TABLE (memo_table));
@@ -709,9 +738,10 @@ memo_table_update_actions (ESelectable *selectable,
 	}
 	g_slist_free (list);
 
-	clipboard_has_calendar = (clipboard_targets != NULL) &&
-		e_targets_include_calendar (
-		clipboard_targets, n_clipboard_targets);
+	target_list = e_selectable_get_paste_target_list (selectable);
+	for (ii = 0; ii < n_clipboard_targets && !can_paste; ii++)
+		can_paste = gtk_target_list_find (
+			target_list, clipboard_targets[ii], NULL);
 
 	action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
 	sensitive = (n_selected > 0) && sources_are_editable;
@@ -726,7 +756,7 @@ memo_table_update_actions (ESelectable *selectable,
 	gtk_action_set_tooltip (action, tooltip);
 
 	action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
-	sensitive = sources_are_editable && clipboard_has_calendar;
+	sensitive = sources_are_editable && can_paste;
 	tooltip = _("Paste memos from the clipboard");
 	gtk_action_set_sensitive (action, sensitive);
 	gtk_action_set_tooltip (action, tooltip);
@@ -1046,6 +1076,12 @@ memo_table_class_init (EMemoTableClass *class)
 	table_class->double_click = memo_table_double_click;
 	table_class->right_click = memo_table_right_click;
 
+	/* Inherited from ESelectableInterface */
+	g_object_class_override_property (
+		object_class,
+		PROP_COPY_TARGET_LIST,
+		"copy-target-list");
+
 	g_object_class_install_property (
 		object_class,
 		PROP_MODEL,
@@ -1057,6 +1093,12 @@ memo_table_class_init (EMemoTableClass *class)
 			G_PARAM_READWRITE |
 			G_PARAM_CONSTRUCT_ONLY));
 
+	/* Inherited from ESelectableInterface */
+	g_object_class_override_property (
+		object_class,
+		PROP_PASTE_TARGET_LIST,
+		"paste-target-list");
+
 	g_object_class_install_property (
 		object_class,
 		PROP_SHELL_VIEW,
@@ -1111,7 +1153,17 @@ memo_table_class_init (EMemoTableClass *class)
 static void
 memo_table_init (EMemoTable *memo_table)
 {
+	GtkTargetList *target_list;
+
 	memo_table->priv = E_MEMO_TABLE_GET_PRIVATE (memo_table);
+
+	target_list = gtk_target_list_new (NULL, 0);
+	e_target_list_add_calendar_targets (target_list, 0);
+	memo_table->priv->copy_target_list = target_list;
+
+	target_list = gtk_target_list_new (NULL, 0);
+	e_target_list_add_calendar_targets (target_list, 0);
+	memo_table->priv->paste_target_list = target_list;
 }
 
 static void
@@ -1249,3 +1301,19 @@ e_memo_table_get_selected (EMemoTable *memo_table)
 
 	return closure.objects;
 }
+
+GtkTargetList *
+e_memo_table_get_copy_target_list (EMemoTable *memo_table)
+{
+	g_return_val_if_fail (E_IS_MEMO_TABLE (memo_table), NULL);
+
+	return memo_table->priv->copy_target_list;
+}
+
+GtkTargetList *
+e_memo_table_get_paste_target_list (EMemoTable *memo_table)
+{
+	g_return_val_if_fail (E_IS_MEMO_TABLE (memo_table), NULL);
+
+	return memo_table->priv->paste_target_list;
+}
diff --git a/calendar/gui/e-memo-table.h b/calendar/gui/e-memo-table.h
index c891117..b0e4ba6 100644
--- a/calendar/gui/e-memo-table.h
+++ b/calendar/gui/e-memo-table.h
@@ -103,6 +103,10 @@ void		e_memo_table_set_use_24_hour_format
 						(EMemoTable *memo_table,
 						 gboolean use_24_hour_format);
 GSList *	e_memo_table_get_selected	(EMemoTable *memo_table);
+GtkTargetList *	e_memo_table_get_copy_target_list
+						(EMemoTable *memo_table);
+GtkTargetList *	e_memo_table_get_paste_target_list
+						(EMemoTable *memo_table);
 
 G_END_DECLS
 
diff --git a/calendar/gui/e-task-table.c b/calendar/gui/e-task-table.c
index 9f0962d..4a378f2 100644
--- a/calendar/gui/e-task-table.c
+++ b/calendar/gui/e-task-table.c
@@ -69,11 +69,16 @@
 struct _ETaskTablePrivate {
 	gpointer shell_view;  /* weak pointer */
 	ECalModel *model;
+
+	GtkTargetList *copy_target_list;
+	GtkTargetList *paste_target_list;
 };
 
 enum {
 	PROP_0,
+	PROP_COPY_TARGET_LIST,
 	PROP_MODEL,
+	PROP_PASTE_TARGET_LIST,
 	PROP_SHELL_VIEW
 };
 
@@ -338,12 +343,24 @@ task_table_get_property (GObject *object,
                          GParamSpec *pspec)
 {
 	switch (property_id) {
+		case PROP_COPY_TARGET_LIST:
+			g_value_set_boxed (
+				value, e_task_table_get_copy_target_list (
+				E_TASK_TABLE (object)));
+			return;
+
 		case PROP_MODEL:
 			g_value_set_object (
 				value, e_task_table_get_model (
 				E_TASK_TABLE (object)));
 			return;
 
+		case PROP_PASTE_TARGET_LIST:
+			g_value_set_boxed (
+				value, e_task_table_get_paste_target_list (
+				E_TASK_TABLE (object)));
+			return;
+
 		case PROP_SHELL_VIEW:
 			g_value_set_object (
 				value, e_task_table_get_shell_view (
@@ -372,6 +389,16 @@ task_table_dispose (GObject *object)
 		priv->model = NULL;
 	}
 
+	if (priv->copy_target_list != NULL) {
+		gtk_target_list_unref (priv->copy_target_list);
+		priv->copy_target_list = NULL;
+	}
+
+	if (priv->paste_target_list != NULL) {
+		gtk_target_list_unref (priv->paste_target_list);
+		priv->paste_target_list = NULL;
+	}
+
 	/* Chain up to parent's dispose() method. */
 	G_OBJECT_CLASS (parent_class)->dispose (object);
 }
@@ -870,12 +897,14 @@ task_table_update_actions (ESelectable *selectable,
 {
 	ETaskTable *task_table;
 	GtkAction *action;
+	GtkTargetList *target_list;
 	GSList *list, *iter;
+	gboolean can_paste = FALSE;
 	gboolean sources_are_editable = TRUE;
-	gboolean clipboard_has_calendar;
 	gboolean sensitive;
 	const gchar *tooltip;
 	gint n_selected;
+	gint ii;
 
 	task_table = E_TASK_TABLE (selectable);
 	n_selected = e_table_selected_count (E_TABLE (task_table));
@@ -890,9 +919,10 @@ task_table_update_actions (ESelectable *selectable,
 	}
 	g_slist_free (list);
 
-	clipboard_has_calendar = (clipboard_targets != NULL) &&
-		e_targets_include_calendar (
-		clipboard_targets, n_clipboard_targets);
+	target_list = e_selectable_get_paste_target_list (selectable);
+	for (ii = 0; ii < n_clipboard_targets && !can_paste; ii++)
+		can_paste = gtk_target_list_find (
+			target_list, clipboard_targets[ii], NULL);
 
 	action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
 	sensitive = (n_selected > 0) && sources_are_editable;
@@ -907,7 +937,7 @@ task_table_update_actions (ESelectable *selectable,
 	gtk_action_set_tooltip (action, tooltip);
 
 	action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
-	sensitive = sources_are_editable && clipboard_has_calendar;
+	sensitive = sources_are_editable && can_paste;
 	tooltip = _("Paste tasks from the clipboard");
 	gtk_action_set_sensitive (action, sensitive);
 	gtk_action_set_tooltip (action, tooltip);
@@ -1010,7 +1040,7 @@ clipboard_get_calendar_data (ETaskTable *task_table,
 	icalcomponent_kind kind;
 	const gchar *status_message;
 
-	g_return_if_fail (E_IS_CALENDAR_TABLE (task_table));
+	g_return_if_fail (E_IS_TASK_TABLE (task_table));
 
 	if (!text || !*text)
 		return;
@@ -1305,6 +1335,12 @@ task_table_class_init (ETaskTableClass *class)
 	table_class->double_click = task_table_double_click;
 	table_class->right_click = task_table_right_click;
 
+	/* Inherited from ESelectableInterface */
+	g_object_class_override_property (
+		object_class,
+		PROP_COPY_TARGET_LIST,
+		"copy-target-list");
+
 	g_object_class_install_property (
 		object_class,
 		PROP_MODEL,
@@ -1316,6 +1352,12 @@ task_table_class_init (ETaskTableClass *class)
 			G_PARAM_READWRITE |
 			G_PARAM_CONSTRUCT_ONLY));
 
+	/* Inherited from ESelectableInterface */
+	g_object_class_override_property (
+		object_class,
+		PROP_PASTE_TARGET_LIST,
+		"paste-target-list");
+
 	g_object_class_install_property (
 		object_class,
 		PROP_SHELL_VIEW,
@@ -1370,7 +1412,17 @@ task_table_class_init (ETaskTableClass *class)
 static void
 task_table_init (ETaskTable *task_table)
 {
+	GtkTargetList *target_list;
+
 	task_table->priv = E_TASK_TABLE_GET_PRIVATE (task_table);
+
+	target_list = gtk_target_list_new (NULL, 0);
+	e_target_list_add_calendar_targets (target_list, 0);
+	task_table->priv->copy_target_list = target_list;
+
+	target_list = gtk_target_list_new (NULL, 0);
+	e_target_list_add_calendar_targets (target_list, 0);
+	task_table->priv->paste_target_list = target_list;
 }
 
 static void
@@ -1451,7 +1503,7 @@ e_task_table_new (EShellView *shell_view,
 ECalModel *
 e_task_table_get_model (ETaskTable *task_table)
 {
-	g_return_val_if_fail (E_IS_CALENDAR_TABLE (task_table), NULL);
+	g_return_val_if_fail (E_IS_TASK_TABLE (task_table), NULL);
 
 	return task_table->priv->model;
 }
@@ -1459,7 +1511,7 @@ e_task_table_get_model (ETaskTable *task_table)
 EShellView *
 e_task_table_get_shell_view (ETaskTable *task_table)
 {
-	g_return_val_if_fail (E_IS_CALENDAR_TABLE (task_table), NULL);
+	g_return_val_if_fail (E_IS_TASK_TABLE (task_table), NULL);
 
 	return task_table->priv->shell_view;
 }
@@ -1506,6 +1558,22 @@ e_task_table_get_selected (ETaskTable *task_table)
 	return closure.objects;
 }
 
+GtkTargetList *
+e_task_table_get_copy_target_list (ETaskTable *task_table)
+{
+	g_return_val_if_fail (E_IS_TASK_TABLE (task_table), NULL);
+
+	return task_table->priv->copy_target_list;
+}
+
+GtkTargetList *
+e_task_table_get_paste_target_list (ETaskTable *task_table)
+{
+	g_return_val_if_fail (E_IS_TASK_TABLE (task_table), NULL);
+
+	return task_table->priv->paste_target_list;
+}
+
 static void
 hide_completed_rows (ECalModel *model,
                      GList *clients_list,
diff --git a/calendar/gui/e-task-table.h b/calendar/gui/e-task-table.h
index b23f128..a112e22 100644
--- a/calendar/gui/e-task-table.h
+++ b/calendar/gui/e-task-table.h
@@ -45,10 +45,10 @@
 #define E_TASK_TABLE_CLASS(cls) \
 	(G_TYPE_CHECK_CLASS_CAST \
 	((cls), E_TYPE_TASK_TABLE, ETaskTableClass))
-#define E_IS_CALENDAR_TABLE(obj) \
+#define E_IS_TASK_TABLE(obj) \
 	(G_TYPE_CHECK_INSTANCE_TYPE \
 	((obj), E_TYPE_TASK_TABLE))
-#define E_IS_CALENDAR_TABLE_CLASS(cls) \
+#define E_IS_TASK_TABLE_CLASS(cls) \
 	(G_TYPE_CHECK_CLASS_TYPE \
 	((cls), E_TYPE_TASK_TABLE))
 #define E_TASK_TABLE_GET_CLASS(obj) \
@@ -93,6 +93,10 @@ GtkWidget *	e_task_table_new		(EShellView *shell_view,
 ECalModel *	e_task_table_get_model		(ETaskTable *task_table);
 EShellView *	e_task_table_get_shell_view	(ETaskTable *task_table);
 GSList *	e_task_table_get_selected	(ETaskTable *task_table);
+GtkTargetList *	e_task_table_get_copy_target_list
+						(ETaskTable *task_table);
+GtkTargetList *	e_task_table_get_paste_target_list
+						(ETaskTable *task_table);
 ECalModelComponent *
 		e_task_table_get_selected_comp
 						(ETaskTable *task_table);
diff --git a/composer/e-composer-private.c b/composer/e-composer-private.c
index b0bc0c9..0f7ffbb 100644
--- a/composer/e-composer-private.c
+++ b/composer/e-composer-private.c
@@ -509,6 +509,27 @@ e_composer_get_default_charset (void)
 }
 
 gboolean
+e_composer_paste_html (EMsgComposer *composer,
+                       GtkClipboard *clipboard)
+{
+	GtkhtmlEditor *editor;
+	gchar *html;
+
+	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
+	g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);
+
+	html = e_clipboard_wait_for_html (clipboard);
+	g_return_val_if_fail (html != NULL, FALSE);
+
+	editor = GTKHTML_EDITOR (composer);
+	gtkhtml_editor_insert_html (editor, html);
+
+	g_free (html);
+
+	return TRUE;
+}
+
+gboolean
 e_composer_paste_image (EMsgComposer *composer,
                         GtkClipboard *clipboard)
 {
@@ -583,6 +604,27 @@ exit:
 }
 
 gboolean
+e_composer_paste_text (EMsgComposer *composer,
+                       GtkClipboard *clipboard)
+{
+	GtkhtmlEditor *editor;
+	gchar *text;
+
+	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
+	g_return_val_if_fail (GTK_IS_CLIPBOARD (clipboard), FALSE);
+
+	text = gtk_clipboard_wait_for_text (clipboard);
+	g_return_val_if_fail (text != NULL, FALSE);
+
+	editor = GTKHTML_EDITOR (composer);
+	gtkhtml_editor_insert_text (editor, text);
+
+	g_free (text);
+
+	return TRUE;
+}
+
+gboolean
 e_composer_paste_uris (EMsgComposer *composer,
                        GtkClipboard *clipboard)
 {
diff --git a/composer/e-composer-private.h b/composer/e-composer-private.h
index 1711e0c..cac41aa 100644
--- a/composer/e-composer-private.h
+++ b/composer/e-composer-private.h
@@ -52,6 +52,7 @@
 #include "e-util/e-binding.h"
 #include "e-util/e-charset.h"
 #include "e-util/e-mktemp.h"
+#include "e-util/e-selection.h"
 #include "e-util/e-util.h"
 #include "e-util/gconf-bridge.h"
 #include "widgets/misc/e-attachment-icon-view.h"
@@ -157,8 +158,12 @@ void		e_composer_private_finalize	(EMsgComposer *composer);
 void		e_composer_actions_init		(EMsgComposer *composer);
 gchar *		e_composer_find_data_file	(const gchar *basename);
 gchar *		e_composer_get_default_charset	(void);
+gboolean	e_composer_paste_html		(EMsgComposer *composer,
+						 GtkClipboard *clipboard);
 gboolean	e_composer_paste_image		(EMsgComposer *composer,
 						 GtkClipboard *clipboard);
+gboolean	e_composer_paste_text		(EMsgComposer *composer,
+						 GtkClipboard *clipboard);
 gboolean	e_composer_paste_uris		(EMsgComposer *composer,
 						 GtkClipboard *clipboard);
 
diff --git a/composer/e-msg-composer.c b/composer/e-msg-composer.c
index bdac5d4..b57bd15 100644
--- a/composer/e-msg-composer.c
+++ b/composer/e-msg-composer.c
@@ -1488,6 +1488,60 @@ msg_composer_account_changed_cb (EMsgComposer *composer)
 	e_msg_composer_show_sig_file (composer);
 }
 
+static void
+msg_composer_paste_clipboard_targets_cb (GtkClipboard *clipboard,
+                                         GdkAtom *targets,
+                                         gint n_targets,
+                                         EMsgComposer *composer)
+{
+	GtkhtmlEditor *editor;
+	gboolean html_mode;
+
+	editor = GTKHTML_EDITOR (composer);
+	html_mode = gtkhtml_editor_get_html_mode (editor);
+
+	/* Order is important here to ensure common use cases are
+	 * handled correctly.  See GNOME bug #603715 for details. */
+
+	if (gtk_targets_include_uri (targets, n_targets)) {
+		e_composer_paste_uris (composer, clipboard);
+		return;
+	}
+
+	/* Only paste HTML content in HTML mode. */
+	if (html_mode) {
+		if (e_targets_include_html (targets, n_targets)) {
+			e_composer_paste_html (composer, clipboard);
+			return;
+		}
+	}
+
+	if (gtk_targets_include_text (targets, n_targets)) {
+		e_composer_paste_text (composer, clipboard);
+		return;
+	}
+
+	if (gtk_targets_include_image (targets, n_targets, TRUE)) {
+		e_composer_paste_image (composer, clipboard);
+		return;
+	}
+}
+
+static void
+msg_composer_paste_clipboard_cb (EWebView *web_view,
+                                 EMsgComposer *composer)
+{
+	GtkClipboard *clipboard;
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+
+	gtk_clipboard_request_targets (
+		clipboard, (GtkClipboardTargetsReceivedFunc)
+		msg_composer_paste_clipboard_targets_cb, composer);
+
+	g_signal_stop_emission_by_name (web_view, "paste-clipboard");
+}
+
 struct _drop_data {
 	EMsgComposer *composer;
 
@@ -1616,6 +1670,12 @@ msg_composer_constructed (GObject *object)
 		shell_settings, "composer-request-receipt");
 	gtk_toggle_action_set_active (action, active);
 
+	/* Clipboard Support */
+
+	g_signal_connect (
+		html, "paste-clipboard",
+		G_CALLBACK (msg_composer_paste_clipboard_cb), composer);
+
 	/* Drag-and-Drop Support */
 
 	target_list = e_attachment_view_get_target_list (view);
@@ -1840,91 +1900,25 @@ msg_composer_drag_data_received (GtkWidget *widget,
 static void
 msg_composer_cut_clipboard (GtkhtmlEditor *editor)
 {
-	EMsgComposer *composer;
-	GtkWidget *parent;
-	GtkWidget *widget;
-
-	composer = E_MSG_COMPOSER (editor);
-	widget = gtk_window_get_focus (GTK_WINDOW (editor));
-	parent = gtk_widget_get_parent (widget);
-
-	/* EFocusTracker handles the header widgets. */
-	if (parent == composer->priv->header_table)
-		return;
-
-	/* Chain up to parent's cut_clipboard() method. */
-	GTKHTML_EDITOR_CLASS (parent_class)->cut_clipboard (editor);
+	/* Do nothing.  EFocusTracker handles this. */
 }
 
 static void
 msg_composer_copy_clipboard (GtkhtmlEditor *editor)
 {
-	EMsgComposer *composer;
-	GtkWidget *parent;
-	GtkWidget *widget;
-
-	composer = E_MSG_COMPOSER (editor);
-	widget = gtk_window_get_focus (GTK_WINDOW (editor));
-	parent = gtk_widget_get_parent (widget);
-
-	/* EFocusTracker handles the header widgets. */
-	if (parent == composer->priv->header_table)
-		return;
-
-	/* Chain up to parent's copy_clipboard() method. */
-	GTKHTML_EDITOR_CLASS (parent_class)->copy_clipboard (editor);
+	/* Do nothing.  EFocusTracker handles this. */
 }
 
 static void
 msg_composer_paste_clipboard (GtkhtmlEditor *editor)
 {
-	EMsgComposer *composer;
-	GtkClipboard *clipboard;
-	GtkWidget *parent;
-	GtkWidget *widget;
-
-	composer = E_MSG_COMPOSER (editor);
-
-	widget = gtk_window_get_focus (GTK_WINDOW (editor));
-	parent = gtk_widget_get_parent (widget);
-
-	/* EFocusTracker handles the header widgets. */
-	if (parent == composer->priv->header_table)
-		return;
-
-	clipboard = gtk_widget_get_clipboard (widget, GDK_SELECTION_CLIPBOARD);
-
-	if (gtk_clipboard_wait_is_image_available (clipboard)) {
-		e_composer_paste_image (composer, clipboard);
-		return;
-	}
-
-	if (gtk_clipboard_wait_is_uris_available (clipboard)) {
-		e_composer_paste_uris (composer, clipboard);
-		return;
-	}
-
-	/* Chain up to parent's paste_clipboard() method. */
-	GTKHTML_EDITOR_CLASS (parent_class)->paste_clipboard (editor);
+	/* Do nothing.  EFocusTracker handles this. */
 }
 
 static void
 msg_composer_select_all (GtkhtmlEditor *editor)
 {
-	EMsgComposer *composer;
-	GtkWidget *parent;
-	GtkWidget *widget;
-
-	composer = E_MSG_COMPOSER (editor);
-	widget = gtk_window_get_focus (GTK_WINDOW (editor));
-	parent = gtk_widget_get_parent (widget);
-
-	/* EFocusTracker handles the header widgets. */
-	if (parent == composer->priv->header_table)
-		return;
-
-	/* Chain up to the parent's select_all() method. */
-	GTKHTML_EDITOR_CLASS (parent_class)->select_all (editor);
+	/* Do nothing.  EFocusTracker handles this. */
 }
 
 static void
diff --git a/configure.ac b/configure.ac
index e32b429..826ac56 100644
--- a/configure.ac
+++ b/configure.ac
@@ -40,7 +40,7 @@ m4_define([gtk_minimum_version], [2.18.0])
 m4_define([eds_minimum_version], [evo_version])
 m4_define([gnome_icon_theme_minimum_version], [2.19.91])
 m4_define([gnome_desktop_minimum_version], [2.26.0])
-m4_define([libgtkhtml_minimum_version], [3.29.5])
+m4_define([libgtkhtml_minimum_version], [3.29.6])
 m4_define([gconf_minimum_version], [2.0.0])		dnl XXX Just a Guess
 m4_define([libsoup_minimum_version], [2.4.0])		dnl XXX Just a Guess
 m4_define([libgnomecanvas_minimum_version], [2.0.0])	dnl XXX Just a Guess
diff --git a/mail/message-list.c b/mail/message-list.c
index 288b4a5..9046b69 100644
--- a/mail/message-list.c
+++ b/mail/message-list.c
@@ -117,10 +117,15 @@ struct _MessageListPrivate {
 
 	gboolean thread_latest;
 	gboolean any_row_changed; /* save state before regen list when this is set to true */
+
+	GtkTargetList *copy_target_list;
+	GtkTargetList *paste_target_list;
 };
 
 enum {
 	PROP_0,
+	PROP_COPY_TARGET_LIST,
+	PROP_PASTE_TARGET_LIST,
 	PROP_SHELL_BACKEND
 };
 
@@ -2295,6 +2300,7 @@ static void
 message_list_init (MessageList *message_list)
 {
 	MessageListPrivate *p;
+	GtkTargetList *target_list;
 	GdkAtom matom;
 
 	message_list->priv = MESSAGE_LIST_GET_PRIVATE (message_list);
@@ -2336,6 +2342,14 @@ message_list_init (MessageList *message_list)
 	g_signal_connect(p->invisible, "selection_get", G_CALLBACK(ml_selection_get), message_list);
 	g_signal_connect(p->invisible, "selection_clear_event", G_CALLBACK(ml_selection_clear_event), message_list);
 	g_signal_connect(p->invisible, "selection_received", G_CALLBACK(ml_selection_received), message_list);
+
+	/* FIXME This is currently unused. */
+	target_list = gtk_target_list_new (NULL, 0);
+	message_list->priv->copy_target_list = target_list;
+
+	/* FIXME This is currently unused. */
+	target_list = gtk_target_list_new (NULL, 0);
+	message_list->priv->paste_target_list = target_list;
 }
 
 static void
@@ -2418,6 +2432,18 @@ message_list_get_property (GObject *object,
                            GParamSpec *pspec)
 {
 	switch (property_id) {
+		case PROP_COPY_TARGET_LIST:
+			g_value_set_boxed (
+				value, message_list_get_copy_target_list (
+				MESSAGE_LIST (object)));
+			return;
+
+		case PROP_PASTE_TARGET_LIST:
+			g_value_set_boxed (
+				value, message_list_get_paste_target_list (
+				MESSAGE_LIST (object)));
+			return;
+
 		case PROP_SHELL_BACKEND:
 			g_value_set_object (
 				value, message_list_get_shell_backend (
@@ -2440,6 +2466,16 @@ message_list_dispose (GObject *object)
 		priv->shell_backend = NULL;
 	}
 
+	if (priv->copy_target_list != NULL) {
+		g_object_unref (priv->copy_target_list);
+		priv->copy_target_list = NULL;
+	}
+
+	if (priv->paste_target_list != NULL) {
+		g_object_unref (priv->paste_target_list);
+		priv->paste_target_list = NULL;
+	}
+
 	/* Chain up to parent's dispose() method. */
 	G_OBJECT_CLASS (parent_class)->dispose (object);
 }
@@ -2533,6 +2569,18 @@ message_list_class_init (MessageListClass *class)
 
 	class->message_list_built = message_list_built;
 
+	/* Inherited from ESelectableInterface */
+	g_object_class_override_property (
+		object_class,
+		PROP_COPY_TARGET_LIST,
+		"copy-target-list");
+
+	/* Inherited from ESelectableInterface */
+	g_object_class_override_property (
+		object_class,
+		PROP_PASTE_TARGET_LIST,
+		"paste-target-list");
+
 	g_object_class_install_property (
 		object_class,
 		PROP_SHELL_BACKEND,
@@ -3614,6 +3662,22 @@ message_list_set_folder (MessageList *message_list, CamelFolder *folder, const g
 	}
 }
 
+GtkTargetList *
+message_list_get_copy_target_list (MessageList *message_list)
+{
+	g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
+
+	return message_list->priv->copy_target_list;
+}
+
+GtkTargetList *
+message_list_get_paste_target_list (MessageList *message_list)
+{
+	g_return_val_if_fail (IS_MESSAGE_LIST (message_list), NULL);
+
+	return message_list->priv->paste_target_list;
+}
+
 static gboolean
 on_cursor_activated_idle (gpointer data)
 {
diff --git a/mail/message-list.h b/mail/message-list.h
index 953a444..c7efc5d 100644
--- a/mail/message-list.h
+++ b/mail/message-list.h
@@ -189,6 +189,10 @@ void		message_list_set_folder		(MessageList *message_list,
 						 CamelFolder *camel_folder,
 						 const gchar *uri,
 						 gboolean outgoing);
+GtkTargetList *	message_list_get_copy_target_list
+						(MessageList *message_list);
+GtkTargetList *	message_list_get_paste_target_list
+						(MessageList *message_list);
 void		message_list_freeze		(MessageList *message_list);
 void		message_list_thaw		(MessageList *message_list);
 GPtrArray *	message_list_get_uids		(MessageList *message_list);
diff --git a/widgets/misc/e-focus-tracker.c b/widgets/misc/e-focus-tracker.c
index d1cb32a..8eb1b25 100644
--- a/widgets/misc/e-focus-tracker.c
+++ b/widgets/misc/e-focus-tracker.c
@@ -360,6 +360,10 @@ focus_tracker_dispose (GObject *object)
 		gtk_clipboard_get (GDK_SELECTION_PRIMARY),
 		G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object);
 
+	g_signal_handlers_disconnect_matched (
+		gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
+		G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object);
+
 	if (priv->window != NULL) {
 		g_signal_handlers_disconnect_matched (
 			priv->window, G_SIGNAL_MATCH_DATA,
@@ -427,6 +431,16 @@ focus_tracker_constructed (GObject *object)
 	g_signal_connect_swapped (
 		clipboard, "owner-change",
 		G_CALLBACK (e_focus_tracker_update_actions), object);
+
+	/* Listen for "owner-change" signals from the default clipboard
+	 * so we can update the paste action when the user cuts or copies
+	 * something.  This is how GEdit does it. */
+
+	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+
+	g_signal_connect_swapped (
+		clipboard, "owner-change",
+		G_CALLBACK (e_focus_tracker_update_actions), object);
 }
 
 static void
diff --git a/widgets/misc/e-selectable.c b/widgets/misc/e-selectable.c
index bb5948c..da998d3 100644
--- a/widgets/misc/e-selectable.c
+++ b/widgets/misc/e-selectable.c
@@ -21,6 +21,28 @@
 
 #include "e-selectable.h"
 
+static void
+selectable_class_init (ESelectableInterface *interface)
+{
+	g_object_interface_install_property (
+		interface,
+		g_param_spec_boxed (
+			"copy-target-list",
+			"Copy Target List",
+			NULL,
+			GTK_TYPE_TARGET_LIST,
+			G_PARAM_READABLE));
+
+	g_object_interface_install_property (
+		interface,
+		g_param_spec_boxed (
+			"paste-target-list",
+			"Paste Target List",
+			NULL,
+			GTK_TYPE_TARGET_LIST,
+			G_PARAM_READABLE));
+}
+
 GType
 e_selectable_get_type (void)
 {
@@ -31,7 +53,7 @@ e_selectable_get_type (void)
 			sizeof (ESelectableInterface),
 			(GBaseInitFunc) NULL,
 			(GBaseFinalizeFunc) NULL,
-			(GClassInitFunc) NULL,
+			(GClassInitFunc) selectable_class_init,
 			(GClassFinalizeFunc) NULL,
 			NULL,  /* class_data */
 			0,     /* instance_size */
@@ -43,7 +65,7 @@ e_selectable_get_type (void)
 		type = g_type_register_static (
 			G_TYPE_INTERFACE, "ESelectable", &type_info, 0);
 
-		g_type_interface_add_prerequisite (type, G_TYPE_OBJECT);
+		g_type_interface_add_prerequisite (type, GTK_TYPE_WIDGET);
 	}
 
 	return type;
@@ -131,3 +153,35 @@ e_selectable_select_all (ESelectable *selectable)
 	if (interface->select_all != NULL)
 		interface->select_all (selectable);
 }
+
+GtkTargetList *
+e_selectable_get_copy_target_list (ESelectable *selectable)
+{
+	GtkTargetList *target_list;
+
+	g_return_val_if_fail (E_IS_SELECTABLE (selectable), NULL);
+
+	g_object_get (selectable, "copy-target-list", &target_list, NULL);
+
+	/* We want to return a borrowed reference to the target
+	 * list, so undo the reference that g_object_get() added. */
+	gtk_target_list_unref (target_list);
+
+	return target_list;
+}
+
+GtkTargetList *
+e_selectable_get_paste_target_list (ESelectable *selectable)
+{
+	GtkTargetList *target_list;
+
+	g_return_val_if_fail (E_IS_SELECTABLE (selectable), NULL);
+
+	g_object_get (selectable, "paste-target-list", &target_list, NULL);
+
+	/* We want to return a borrowed reference to the target
+	 * list, so undo the reference that g_object_get() added. */
+	gtk_target_list_unref (target_list);
+
+	return target_list;
+}
diff --git a/widgets/misc/e-selectable.h b/widgets/misc/e-selectable.h
index fca400c..c9a0b6d 100644
--- a/widgets/misc/e-selectable.h
+++ b/widgets/misc/e-selectable.h
@@ -70,6 +70,10 @@ void		e_selectable_copy_clipboard	(ESelectable *selectable);
 void		e_selectable_paste_clipboard	(ESelectable *selectable);
 void		e_selectable_delete_selection	(ESelectable *selectable);
 void		e_selectable_select_all		(ESelectable *selectable);
+GtkTargetList *	e_selectable_get_copy_target_list
+						(ESelectable *selectable);
+GtkTargetList *	e_selectable_get_paste_target_list
+						(ESelectable *selectable);
 
 G_END_DECLS
 
diff --git a/widgets/misc/e-web-view.c b/widgets/misc/e-web-view.c
index 31516bf..a5f98a4 100644
--- a/widgets/misc/e-web-view.c
+++ b/widgets/misc/e-web-view.c
@@ -50,6 +50,9 @@ struct _EWebViewPrivate {
 	GtkAction *print_proxy;
 	GtkAction *save_as_proxy;
 
+	GtkTargetList *copy_target_list;
+	GtkTargetList *paste_target_list;
+
 	/* Lockdown Options */
 	guint disable_printing     : 1;
 	guint disable_save_to_disk : 1;
@@ -68,15 +71,21 @@ enum {
 	PROP_0,
 	PROP_ANIMATE,
 	PROP_CARET_MODE,
+	PROP_COPY_TARGET_LIST,
 	PROP_DISABLE_PRINTING,
 	PROP_DISABLE_SAVE_TO_DISK,
+	PROP_EDITABLE,
 	PROP_OPEN_PROXY,
+	PROP_PASTE_TARGET_LIST,
 	PROP_PRINT_PROXY,
 	PROP_SAVE_AS_PROXY,
 	PROP_SELECTED_URI
 };
 
 enum {
+	COPY_CLIPBOARD,
+	CUT_CLIPBOARD,
+	PASTE_CLIPBOARD,
 	POPUP_EVENT,
 	STATUS_MESSAGE,
 	STOP_LOADING,
@@ -488,6 +497,12 @@ web_view_set_property (GObject *object,
 				g_value_get_boolean (value));
 			return;
 
+		case PROP_EDITABLE:
+			e_web_view_set_editable (
+				E_WEB_VIEW (object),
+				g_value_get_boolean (value));
+			return;
+
 		case PROP_OPEN_PROXY:
 			e_web_view_set_open_proxy (
 				E_WEB_VIEW (object),
@@ -535,6 +550,12 @@ web_view_get_property (GObject *object,
 				E_WEB_VIEW (object)));
 			return;
 
+		case PROP_COPY_TARGET_LIST:
+			g_value_set_boxed (
+				value, e_web_view_get_copy_target_list (
+				E_WEB_VIEW (object)));
+			return;
+
 		case PROP_DISABLE_PRINTING:
 			g_value_set_boolean (
 				value, e_web_view_get_disable_printing (
@@ -547,12 +568,24 @@ web_view_get_property (GObject *object,
 				E_WEB_VIEW (object)));
 			return;
 
+		case PROP_EDITABLE:
+			g_value_set_boolean (
+				value, e_web_view_get_editable (
+				E_WEB_VIEW (object)));
+			return;
+
 		case PROP_OPEN_PROXY:
 			g_value_set_object (
 				value, e_web_view_get_open_proxy (
 				E_WEB_VIEW (object)));
 			return;
 
+		case PROP_PASTE_TARGET_LIST:
+			g_value_set_boxed (
+				value, e_web_view_get_paste_target_list (
+				E_WEB_VIEW (object)));
+			return;
+
 		case PROP_PRINT_PROXY:
 			g_value_set_object (
 				value, e_web_view_get_print_proxy (
@@ -602,6 +635,16 @@ web_view_dispose (GObject *object)
 		priv->save_as_proxy = NULL;
 	}
 
+	if (priv->copy_target_list != NULL) {
+		gtk_target_list_unref (priv->copy_target_list);
+		priv->copy_target_list = NULL;
+	}
+
+	if (priv->paste_target_list != NULL) {
+		gtk_target_list_unref (priv->paste_target_list);
+		priv->paste_target_list = NULL;
+	}
+
 	/* Chain up to parent's dispose() method. */
 	G_OBJECT_CLASS (parent_class)->dispose (object);
 }
@@ -765,6 +808,26 @@ web_view_extract_uri (EWebView *web_view,
 	return uri;
 }
 
+static void
+web_view_copy_clipboard (EWebView *web_view)
+{
+	gtk_html_command (GTK_HTML (web_view), "copy");
+}
+
+static void
+web_view_cut_clipboard (EWebView *web_view)
+{
+	if (e_web_view_get_editable (web_view))
+		gtk_html_command (GTK_HTML (web_view), "cut");
+}
+
+static void
+web_view_paste_clipboard (EWebView *web_view)
+{
+	if (e_web_view_get_editable (web_view))
+		gtk_html_command (GTK_HTML (web_view), "paste");
+}
+
 static gboolean
 web_view_popup_event (EWebView *web_view,
                       GdkEventButton *event,
@@ -865,39 +928,76 @@ web_view_selectable_update_actions (ESelectable *selectable,
 {
 	EWebView *web_view;
 	GtkAction *action;
-	const gchar *tooltip;
+	/*GtkTargetList *target_list;*/
+	gboolean can_paste = FALSE;
+	gboolean editable;
+	gboolean have_selection;
 	gboolean sensitive;
+	const gchar *tooltip;
+	/*gint ii;*/
 
 	web_view = E_WEB_VIEW (selectable);
+	editable = e_web_view_get_editable (web_view);
+	have_selection = e_web_view_is_selection_active (web_view);
 
-	/* Copy Clipboard */
-
-	action = e_web_view_get_action (web_view, "copy-clipboard");
-	sensitive = gtk_action_get_sensitive (action);
-	tooltip = gtk_action_get_tooltip (action);
+	/* XXX GtkHtml implements its own clipboard instead of using
+	 *     GDK_SELECTION_CLIPBOARD, so we don't get notifications
+	 *     when the clipboard contents change.  The logic below
+	 *     is what we would do if GtkHtml worked properly.
+	 *     Instead, we need to keep the Paste action sensitive so
+	 *     its accelerator overrides GtkHtml's key binding. */
+#if 0
+	target_list = e_selectable_get_paste_target_list (selectable);
+	for (ii = 0; ii < n_clipboard_targets && !can_paste; ii++)
+		can_paste = gtk_target_list_find (
+			target_list, clipboard_targets[ii], NULL);
+#endif
+	can_paste = TRUE;
+
+	action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
+	sensitive = editable && have_selection;
+	tooltip = _("Cut the selection");
+	gtk_action_set_sensitive (action, sensitive);
+	gtk_action_set_tooltip (action, tooltip);
 
 	action = e_focus_tracker_get_copy_clipboard_action (focus_tracker);
+	sensitive = have_selection;
+	tooltip = _("Copy the selection");
 	gtk_action_set_sensitive (action, sensitive);
 	gtk_action_set_tooltip (action, tooltip);
 
-	/* Select All */
-
-	action = e_web_view_get_action (web_view, "select-all");
-	sensitive = gtk_action_get_sensitive (action);
-	tooltip = gtk_action_get_tooltip (action);
+	action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
+	sensitive = editable && can_paste;
+	tooltip = _("Paste the clipboard");
+	gtk_action_set_sensitive (action, sensitive);
+	gtk_action_set_tooltip (action, tooltip);
 
 	action = e_focus_tracker_get_select_all_action (focus_tracker);
+	sensitive = TRUE;
+	tooltip = _("Select all text and images");
 	gtk_action_set_sensitive (action, sensitive);
 	gtk_action_set_tooltip (action, tooltip);
 }
 
 static void
+web_view_selectable_cut_clipboard (ESelectable *selectable)
+{
+	e_web_view_cut_clipboard (E_WEB_VIEW (selectable));
+}
+
+static void
 web_view_selectable_copy_clipboard (ESelectable *selectable)
 {
 	e_web_view_copy_clipboard (E_WEB_VIEW (selectable));
 }
 
 static void
+web_view_selectable_paste_clipboard (ESelectable *selectable)
+{
+	e_web_view_paste_clipboard (E_WEB_VIEW (selectable));
+}
+
+static void
 web_view_selectable_select_all (ESelectable *selectable)
 {
 	e_web_view_select_all (E_WEB_VIEW (selectable));
@@ -930,6 +1030,9 @@ web_view_class_init (EWebViewClass *class)
 	html_class->iframe_created = web_view_iframe_created;
 
 	class->extract_uri = web_view_extract_uri;
+	class->copy_clipboard = web_view_copy_clipboard;
+	class->cut_clipboard = web_view_cut_clipboard;
+	class->paste_clipboard = web_view_paste_clipboard;
 	class->popup_event = web_view_popup_event;
 	class->stop_loading = web_view_stop_loading;
 	class->update_actions = web_view_update_actions;
@@ -954,6 +1057,12 @@ web_view_class_init (EWebViewClass *class)
 			FALSE,
 			G_PARAM_READWRITE));
 
+	/* Inherited from ESelectableInterface */
+	g_object_class_override_property (
+		object_class,
+		PROP_COPY_TARGET_LIST,
+		"copy-target-list");
+
 	g_object_class_install_property (
 		object_class,
 		PROP_DISABLE_PRINTING,
@@ -976,6 +1085,16 @@ web_view_class_init (EWebViewClass *class)
 
 	g_object_class_install_property (
 		object_class,
+		PROP_EDITABLE,
+		g_param_spec_boolean (
+			"editable",
+			"Editable",
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
+	g_object_class_install_property (
+		object_class,
 		PROP_OPEN_PROXY,
 		g_param_spec_object (
 			"open-proxy",
@@ -984,6 +1103,12 @@ web_view_class_init (EWebViewClass *class)
 			GTK_TYPE_ACTION,
 			G_PARAM_READWRITE));
 
+	/* Inherited from ESelectableInterface */
+	g_object_class_override_property (
+		object_class,
+		PROP_PASTE_TARGET_LIST,
+		"paste-target-list");
+
 	g_object_class_install_property (
 		object_class,
 		PROP_PRINT_PROXY,
@@ -1014,6 +1139,33 @@ web_view_class_init (EWebViewClass *class)
 			NULL,
 			G_PARAM_READWRITE));
 
+	signals[COPY_CLIPBOARD] = g_signal_new (
+		"copy-clipboard",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (EWebViewClass, copy_clipboard),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[CUT_CLIPBOARD] = g_signal_new (
+		"cut-clipboard",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (EWebViewClass, cut_clipboard),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	signals[PASTE_CLIPBOARD] = g_signal_new (
+		"paste-clipboard",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		G_STRUCT_OFFSET (EWebViewClass, paste_clipboard),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
 	signals[POPUP_EVENT] = g_signal_new (
 		"popup-event",
 		G_TYPE_FROM_CLASS (class),
@@ -1058,7 +1210,9 @@ static void
 web_view_selectable_init (ESelectableInterface *interface)
 {
 	interface->update_actions = web_view_selectable_update_actions;
+	interface->cut_clipboard = web_view_selectable_cut_clipboard;
 	interface->copy_clipboard = web_view_selectable_copy_clipboard;
+	interface->paste_clipboard = web_view_selectable_paste_clipboard;
 	interface->select_all = web_view_selectable_select_all;
 }
 
@@ -1067,6 +1221,7 @@ web_view_init (EWebView *web_view)
 {
 	GtkUIManager *ui_manager;
 	GtkActionGroup *action_group;
+	GtkTargetList *target_list;
 	EPopupAction *popup_action;
 	const gchar *domain = GETTEXT_PACKAGE;
 	const gchar *id;
@@ -1081,6 +1236,12 @@ web_view_init (EWebView *web_view)
 		ui_manager, "connect-proxy",
 		G_CALLBACK (web_view_connect_proxy_cb), web_view);
 
+	target_list = gtk_target_list_new (NULL, 0);
+	web_view->priv->copy_target_list = target_list;
+
+	target_list = gtk_target_list_new (NULL, 0);
+	web_view->priv->paste_target_list = target_list;
+
 	action_group = gtk_action_group_new ("uri");
 	gtk_action_group_set_translation_domain (action_group, domain);
 	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
@@ -1287,6 +1448,14 @@ e_web_view_set_caret_mode (EWebView *web_view,
 	g_object_notify (G_OBJECT (web_view), "caret-mode");
 }
 
+GtkTargetList *
+e_web_view_get_copy_target_list (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+	return web_view->priv->copy_target_list;
+}
+
 gboolean
 e_web_view_get_disable_printing (EWebView *web_view)
 {
@@ -1325,6 +1494,32 @@ e_web_view_set_disable_save_to_disk (EWebView *web_view,
 	g_object_notify (G_OBJECT (web_view), "disable-save-to-disk");
 }
 
+gboolean
+e_web_view_get_editable (EWebView *web_view)
+{
+	/* XXX This is just here to maintain symmetry
+	 *     with e_web_view_set_editable(). */
+
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+	return gtk_html_get_editable (GTK_HTML (web_view));
+}
+
+void
+e_web_view_set_editable (EWebView *web_view,
+                         gboolean editable)
+{
+	/* XXX GtkHTML does not utilize GObject properties as well
+	 *     as it could.  This just wraps gtk_html_set_editable()
+	 *     so we can get a "notify::editable" signal. */
+
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	gtk_html_set_editable (GTK_HTML (web_view), editable);
+
+	g_object_notify (G_OBJECT (web_view), "editable");
+}
+
 const gchar *
 e_web_view_get_selected_uri (EWebView *web_view)
 {
@@ -1372,6 +1567,14 @@ e_web_view_set_open_proxy (EWebView *web_view,
 	g_object_notify (G_OBJECT (web_view), "open-proxy");
 }
 
+GtkTargetList *
+e_web_view_get_paste_target_list (EWebView *web_view)
+{
+	g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+	return web_view->priv->paste_target_list;
+}
+
 GtkAction *
 e_web_view_get_print_proxy (EWebView *web_view)
 {
@@ -1477,7 +1680,15 @@ e_web_view_copy_clipboard (EWebView *web_view)
 {
 	g_return_if_fail (E_IS_WEB_VIEW (web_view));
 
-	gtk_html_command (GTK_HTML (web_view), "copy");
+	g_signal_emit (web_view, signals[COPY_CLIPBOARD], 0);
+}
+
+void
+e_web_view_cut_clipboard (EWebView *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	g_signal_emit (web_view, signals[CUT_CLIPBOARD], 0);
 }
 
 gboolean
@@ -1488,6 +1699,14 @@ e_web_view_is_selection_active (EWebView *web_view)
 	return gtk_html_command (GTK_HTML (web_view), "is-selection-active");
 }
 
+void
+e_web_view_paste_clipboard (EWebView *web_view)
+{
+	g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+	g_signal_emit (web_view, signals[PASTE_CLIPBOARD], 0);
+}
+
 gboolean
 e_web_view_scroll_forward (EWebView *web_view)
 {
diff --git a/widgets/misc/e-web-view.h b/widgets/misc/e-web-view.h
index 123965c..788eadb 100644
--- a/widgets/misc/e-web-view.h
+++ b/widgets/misc/e-web-view.h
@@ -71,6 +71,9 @@ struct _EWebViewClass {
 						 GtkHTML *frame);
 
 	/* Signals */
+	void		(*copy_clipboard)	(EWebView *web_view);
+	void		(*cut_clipboard)	(EWebView *web_view);
+	void		(*paste_clipboard)	(EWebView *web_view);
 	gboolean	(*popup_event)		(EWebView *web_view,
 						 GdkEventButton *event,
 						 const gchar *uri);
@@ -91,6 +94,7 @@ void		e_web_view_set_animate		(EWebView *web_view,
 gboolean	e_web_view_get_caret_mode	(EWebView *web_view);
 void		e_web_view_set_caret_mode	(EWebView *web_view,
 						 gboolean caret_mode);
+GtkTargetList *	e_web_view_get_copy_target_list	(EWebView *web_view);
 gboolean	e_web_view_get_disable_printing	(EWebView *web_view);
 void		e_web_view_set_disable_printing	(EWebView *web_view,
 						 gboolean disable_printing);
@@ -99,12 +103,16 @@ gboolean	e_web_view_get_disable_save_to_disk
 void		e_web_view_set_disable_save_to_disk
 						(EWebView *web_view,
 						 gboolean disable_save_to_disk);
+gboolean	e_web_view_get_editable		(EWebView *web_view);
+void		e_web_view_set_editable		(EWebView *web_view,
+						 gboolean editable);
 const gchar *	e_web_view_get_selected_uri	(EWebView *web_view);
 void		e_web_view_set_selected_uri	(EWebView *web_view,
 						 const gchar *selected_uri);
 GtkAction *	e_web_view_get_open_proxy	(EWebView *web_view);
 void		e_web_view_set_open_proxy	(EWebView *web_view,
 						 GtkAction *open_proxy);
+GtkTargetList *	e_web_view_get_paste_target_list(EWebView *web_view);
 GtkAction *	e_web_view_get_print_proxy	(EWebView *web_view);
 void		e_web_view_set_print_proxy	(EWebView *web_view,
 						 GtkAction *print_proxy);
@@ -119,7 +127,9 @@ gchar *		e_web_view_extract_uri		(EWebView *web_view,
 						 GdkEventButton *event,
 						 GtkHTML *frame);
 void		e_web_view_copy_clipboard	(EWebView *web_view);
+void		e_web_view_cut_clipboard	(EWebView *web_view);
 gboolean	e_web_view_is_selection_active	(EWebView *web_view);
+void		e_web_view_paste_clipboard	(EWebView *web_view);
 gboolean	e_web_view_scroll_forward	(EWebView *web_view);
 gboolean	e_web_view_scroll_backward	(EWebView *web_view);
 void		e_web_view_select_all		(EWebView *web_view);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]