2008-04-30 A. Walton <awalton@gnome.org>
[nautilus.git] / libnautilus-private / nautilus-clipboard.c
bloba41d1224a4069b583536a5f43b795cc358e115b0
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
3 /* nautilus-clipboard.c
5 * Nautilus Clipboard support. For now, routines to support component cut
6 * and paste.
8 * Copyright (C) 1999, 2000 Free Software Foundaton
9 * Copyright (C) 2000, 2001 Eazel, Inc.
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Library General Public License as
13 * published by the Free Software Foundation; either version 2 of the
14 * License, or (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Library General Public License for more details.
21 * You should have received a copy of the GNU Library General Public
22 * License along with this program; if not, write to the
23 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 * Boston, MA 02111-1307, USA.
26 * Authors: Rebecca Schulman <rebecka@eazel.com>,
27 * Darin Adler <darin@bentspoon.com>
30 #include <config.h>
31 #include "nautilus-clipboard.h"
32 #include "nautilus-file-utilities.h"
34 #include <glib/gi18n.h>
35 #include <gtk/gtkclipboard.h>
36 #include <gtk/gtkstock.h>
37 #include <gtk/gtkinvisible.h>
38 #include <gtk/gtkmain.h>
39 #include <gtk/gtksignal.h>
40 #include <gtk/gtktext.h>
41 #include <gtk/gtktextview.h>
42 #include <string.h>
44 typedef struct _TargetCallbackData TargetCallbackData;
46 typedef void (* SelectAllCallback) (gpointer target);
47 typedef void (* ConnectCallbacksFunc) (GObject *object,
48 TargetCallbackData *target_data);
50 static void selection_changed_callback (GtkWidget *widget,
51 gpointer callback_data);
52 static void owner_change_callback (GtkClipboard *clipboard,
53 GdkEventOwnerChange *event,
54 gpointer callback_data);
55 struct _TargetCallbackData {
56 GtkUIManager *ui_manager;
57 GtkActionGroup *action_group;
58 gboolean shares_selection_changes;
60 SelectAllCallback select_all_callback;
62 ConnectCallbacksFunc connect_callbacks;
63 ConnectCallbacksFunc disconnect_callbacks;
66 static void
67 cut_callback (gpointer target)
69 g_assert (target != NULL);
71 g_signal_emit_by_name (target, "cut-clipboard");
74 static void
75 copy_callback (gpointer target)
77 g_assert (target != NULL);
79 g_signal_emit_by_name (target, "copy-clipboard");
82 static void
83 paste_callback (gpointer target)
85 g_assert (target != NULL);
87 g_signal_emit_by_name (target, "paste-clipboard");
90 static void
91 editable_select_all_callback (gpointer target)
93 GtkEditable *editable;
95 editable = GTK_EDITABLE (target);
96 g_assert (editable != NULL);
98 gtk_editable_set_position (editable, -1);
99 gtk_editable_select_region (editable, 0, -1);
102 static void
103 text_view_select_all_callback (gpointer target)
105 g_assert (GTK_IS_TEXT_VIEW (target));
107 g_signal_emit_by_name (target, "select-all", TRUE);
110 static void
111 action_cut_callback (GtkAction *action,
112 gpointer callback_data)
114 cut_callback (callback_data);
117 static void
118 action_copy_callback (GtkAction *action,
119 gpointer callback_data)
121 copy_callback (callback_data);
124 static void
125 action_paste_callback (GtkAction *action,
126 gpointer callback_data)
128 paste_callback (callback_data);
131 static void
132 action_select_all_callback (GtkAction *action,
133 gpointer callback_data)
135 TargetCallbackData *target_data;
137 g_assert (callback_data != NULL);
139 target_data = g_object_get_data (callback_data, "Nautilus:clipboard_target_data");
140 g_assert (target_data != NULL);
142 target_data->select_all_callback (callback_data);
145 static void
146 received_clipboard_contents (GtkClipboard *clipboard,
147 GtkSelectionData *selection_data,
148 gpointer data)
150 GtkActionGroup *action_group;
151 GtkAction *action;
153 action_group = data;
155 action = gtk_action_group_get_action (action_group,
156 "Paste");
157 if (action != NULL) {
158 gtk_action_set_sensitive (action,
159 gtk_selection_data_targets_include_text (selection_data));
162 g_object_unref (action_group);
166 static void
167 set_paste_sensitive_if_clipboard_contains_data (GtkActionGroup *action_group)
169 GtkAction *action;
170 if (gdk_display_supports_selection_notification (gdk_display_get_default ())) {
171 gtk_clipboard_request_contents (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
172 gdk_atom_intern ("TARGETS", FALSE),
173 received_clipboard_contents,
174 g_object_ref (action_group));
175 } else {
176 /* If selection notification isn't supported, always activate Paste */
177 action = gtk_action_group_get_action (action_group,
178 "Paste");
179 gtk_action_set_sensitive (action, TRUE);
183 static void
184 set_clipboard_menu_items_sensitive (GtkActionGroup *action_group)
186 GtkAction *action;
188 action = gtk_action_group_get_action (action_group,
189 "Cut");
190 gtk_action_set_sensitive (action, TRUE);
191 action = gtk_action_group_get_action (action_group,
192 "Copy");
193 gtk_action_set_sensitive (action, TRUE);
196 static void
197 set_clipboard_menu_items_insensitive (GtkActionGroup *action_group)
199 GtkAction *action;
201 action = gtk_action_group_get_action (action_group,
202 "Cut");
203 gtk_action_set_sensitive (action, FALSE);
204 action = gtk_action_group_get_action (action_group,
205 "Copy");
206 gtk_action_set_sensitive (action, FALSE);
209 static gboolean
210 clipboard_items_are_merged_in (GtkWidget *widget)
212 return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
213 "Nautilus:clipboard_menu_items_merged"));
216 static void
217 set_clipboard_items_are_merged_in (GObject *widget_as_object,
218 gboolean merged_in)
220 g_object_set_data (widget_as_object,
221 "Nautilus:clipboard_menu_items_merged",
222 GINT_TO_POINTER (merged_in));
225 static void
226 editable_connect_callbacks (GObject *object,
227 TargetCallbackData *target_data)
229 g_signal_connect_after (object, "selection_changed",
230 G_CALLBACK (selection_changed_callback), target_data);
231 selection_changed_callback (GTK_WIDGET (object),
232 target_data);
235 static void
236 editable_disconnect_callbacks (GObject *object,
237 TargetCallbackData *target_data)
239 g_signal_handlers_disconnect_matched (object,
240 G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
241 0, 0, NULL,
242 G_CALLBACK (selection_changed_callback),
243 target_data);
246 static void
247 text_buffer_update_sensitivity (GtkTextBuffer *buffer,
248 TargetCallbackData *target_data)
250 g_assert (GTK_IS_TEXT_BUFFER (buffer));
251 g_assert (target_data != NULL);
253 if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
254 set_clipboard_menu_items_sensitive (target_data->action_group);
255 } else {
256 set_clipboard_menu_items_insensitive (target_data->action_group);
260 static void
261 text_buffer_delete_range (GtkTextBuffer *buffer,
262 GtkTextIter *iter1,
263 GtkTextIter *iter2,
264 TargetCallbackData *target_data)
266 text_buffer_update_sensitivity (buffer, target_data);
269 static void
270 text_buffer_mark_set (GtkTextBuffer *buffer,
271 GtkTextIter *iter,
272 GtkTextMark *mark,
273 TargetCallbackData *target_data)
275 /* anonymous marks with NULL names refer to cursor moves */
276 if (gtk_text_mark_get_name (mark) != NULL) {
277 text_buffer_update_sensitivity (buffer, target_data);
281 static void
282 text_view_connect_callbacks (GObject *object,
283 TargetCallbackData *target_data)
285 GtkTextBuffer *buffer;
287 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (object));
288 g_assert (buffer);
290 g_signal_connect_after (buffer, "mark-set",
291 G_CALLBACK (text_buffer_mark_set), target_data);
292 g_signal_connect_after (buffer, "delete-range",
293 G_CALLBACK (text_buffer_delete_range), target_data);
294 text_buffer_update_sensitivity (buffer, target_data);
297 static void
298 text_view_disconnect_callbacks (GObject *object,
299 TargetCallbackData *target_data)
301 GtkTextBuffer *buffer;
303 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (object));
304 g_assert (buffer);
306 g_signal_handlers_disconnect_matched (buffer,
307 G_SIGNAL_MATCH_DATA,
308 0, 0, NULL, NULL,
309 target_data);
312 static void
313 merge_in_clipboard_menu_items (GObject *widget_as_object,
314 TargetCallbackData *target_data)
316 gboolean add_selection_callback;
318 g_assert (target_data != NULL);
320 add_selection_callback = target_data->shares_selection_changes;
322 gtk_ui_manager_insert_action_group (target_data->ui_manager,
323 target_data->action_group, 0);
325 set_paste_sensitive_if_clipboard_contains_data (target_data->action_group);
327 g_signal_connect (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), "owner_change",
328 G_CALLBACK (owner_change_callback), target_data);
330 if (add_selection_callback) {
331 target_data->connect_callbacks (widget_as_object, target_data);
332 } else {
333 /* If we don't use sensitivity, everything should be on */
334 set_clipboard_menu_items_sensitive (target_data->action_group);
336 set_clipboard_items_are_merged_in (widget_as_object, TRUE);
339 static void
340 merge_out_clipboard_menu_items (GObject *widget_as_object,
341 TargetCallbackData *target_data)
344 gboolean selection_callback_was_added;
346 g_assert (target_data != NULL);
348 gtk_ui_manager_remove_action_group (target_data->ui_manager,
349 target_data->action_group);
351 g_signal_handlers_disconnect_matched (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
352 G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
353 0, 0, NULL,
354 G_CALLBACK (owner_change_callback),
355 target_data);
357 selection_callback_was_added = target_data->shares_selection_changes;
359 if (selection_callback_was_added) {
360 target_data->disconnect_callbacks (widget_as_object, target_data);
362 set_clipboard_items_are_merged_in (widget_as_object, FALSE);
365 static gboolean
366 focus_changed_callback (GtkWidget *widget,
367 GdkEventAny *event,
368 gpointer callback_data)
370 /* Connect the component to the container if the widget has focus. */
371 if (GTK_WIDGET_HAS_FOCUS (widget)) {
372 if (!clipboard_items_are_merged_in (widget)) {
373 merge_in_clipboard_menu_items (G_OBJECT (widget), callback_data);
375 } else {
376 if (clipboard_items_are_merged_in (widget)) {
377 merge_out_clipboard_menu_items (G_OBJECT (widget), callback_data);
381 return FALSE;
384 static void
385 selection_changed_callback (GtkWidget *widget,
386 gpointer callback_data)
388 TargetCallbackData *target_data;
389 GtkEditable *editable;
390 int start, end;
392 target_data = (TargetCallbackData *) callback_data;
393 g_assert (target_data != NULL);
395 editable = GTK_EDITABLE (widget);
396 g_assert (editable != NULL);
398 if (gtk_editable_get_selection_bounds (editable, &start, &end) && start != end) {
399 set_clipboard_menu_items_sensitive (target_data->action_group);
400 } else {
401 set_clipboard_menu_items_insensitive (target_data->action_group);
405 static void
406 owner_change_callback (GtkClipboard *clipboard,
407 GdkEventOwnerChange *event,
408 gpointer callback_data)
410 TargetCallbackData *target_data;
412 g_assert (callback_data != NULL);
413 target_data = callback_data;
415 set_paste_sensitive_if_clipboard_contains_data (target_data->action_group);
418 static void
419 target_destroy_callback (GtkObject *object,
420 gpointer callback_data)
422 TargetCallbackData *target_data;
424 g_assert (callback_data != NULL);
425 target_data = callback_data;
427 if (clipboard_items_are_merged_in (GTK_WIDGET(object))) {
428 merge_out_clipboard_menu_items (G_OBJECT (object), callback_data);
432 static void
433 target_data_free (TargetCallbackData *target_data)
435 g_object_unref (target_data->action_group);
436 g_free (target_data);
439 static const GtkActionEntry clipboard_entries[] = {
440 /* name, stock id */ { "Cut", GTK_STOCK_CUT,
441 /* label, accelerator */ NULL, NULL,
442 /* tooltip */ N_("Cut the selected text to the clipboard"),
443 G_CALLBACK (action_cut_callback) },
444 /* name, stock id */ { "Copy", GTK_STOCK_COPY,
445 /* label, accelerator */ NULL, NULL,
446 /* tooltip */ N_("Copy the selected text to the clipboard"),
447 G_CALLBACK (action_copy_callback) },
448 /* name, stock id */ { "Paste", GTK_STOCK_PASTE,
449 /* label, accelerator */ NULL, NULL,
450 /* tooltip */ N_("Paste the text stored on the clipboard"),
451 G_CALLBACK (action_paste_callback) },
452 /* name, stock id */ { "Select All", NULL,
453 /* label, accelerator */ N_("Select _All"), "<control>A",
454 /* tooltip */ N_("Select all the text in a text field"),
455 G_CALLBACK (action_select_all_callback) },
458 static TargetCallbackData *
459 initialize_clipboard_component_with_callback_data (GtkEditable *target,
460 GtkUIManager *ui_manager,
461 gboolean shares_selection_changes,
462 SelectAllCallback select_all_callback,
463 ConnectCallbacksFunc connect_callbacks,
464 ConnectCallbacksFunc disconnect_callbacks)
466 GtkActionGroup *action_group;
467 TargetCallbackData *target_data;
469 action_group = gtk_action_group_new ("ClipboardActions");
470 gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
471 gtk_action_group_add_actions (action_group,
472 clipboard_entries, G_N_ELEMENTS (clipboard_entries),
473 target);
475 /* Do the actual connection of the UI to the container at
476 * focus time, and disconnect at both focus and destroy
477 * time.
479 target_data = g_new (TargetCallbackData, 1);
480 target_data->ui_manager = ui_manager;
481 target_data->action_group = action_group;
482 target_data->shares_selection_changes = shares_selection_changes;
483 target_data->select_all_callback = select_all_callback;
484 target_data->connect_callbacks = connect_callbacks;
485 target_data->disconnect_callbacks = disconnect_callbacks;
487 return target_data;
490 static void
491 nautilus_clipboard_real_set_up (gpointer target,
492 GtkUIManager *ui_manager,
493 gboolean shares_selection_changes,
494 SelectAllCallback select_all_callback,
495 ConnectCallbacksFunc connect_callbacks,
496 ConnectCallbacksFunc disconnect_callbacks)
498 TargetCallbackData *target_data;
500 if (g_object_get_data (G_OBJECT (target), "Nautilus:clipboard_target_data") != NULL) {
501 return;
504 target_data = initialize_clipboard_component_with_callback_data
505 (target,
506 ui_manager,
507 shares_selection_changes,
508 select_all_callback,
509 connect_callbacks,
510 disconnect_callbacks);
512 g_signal_connect (target, "focus_in_event",
513 G_CALLBACK (focus_changed_callback), target_data);
514 g_signal_connect (target, "focus_out_event",
515 G_CALLBACK (focus_changed_callback), target_data);
516 g_signal_connect (target, "destroy",
517 G_CALLBACK (target_destroy_callback), target_data);
519 g_object_set_data_full (G_OBJECT (target), "Nautilus:clipboard_target_data",
520 target_data, (GDestroyNotify) target_data_free);
522 /* Call the focus changed callback once to merge if the window is
523 * already in focus.
525 focus_changed_callback (GTK_WIDGET (target), NULL, target_data);
528 void
529 nautilus_clipboard_set_up_editable (GtkEditable *target,
530 GtkUIManager *ui_manager,
531 gboolean shares_selection_changes)
533 g_return_if_fail (GTK_IS_EDITABLE (target));
534 g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
536 nautilus_clipboard_real_set_up (target, ui_manager,
537 shares_selection_changes,
538 editable_select_all_callback,
539 editable_connect_callbacks,
540 editable_disconnect_callbacks);
543 void
544 nautilus_clipboard_set_up_text_view (GtkTextView *target,
545 GtkUIManager *ui_manager)
547 g_return_if_fail (GTK_IS_TEXT_VIEW (target));
548 g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
550 nautilus_clipboard_real_set_up (target, ui_manager, TRUE,
551 text_view_select_all_callback,
552 text_view_connect_callbacks,
553 text_view_disconnect_callbacks);