mirror of
https://gitlab.gnome.org/GNOME/nautilus
synced 2024-11-05 16:04:31 +00:00
010d502704
2002-08-15 Dave Camp <dave@ximian.com> * libnautilus/nautilus-clipboard.c (select_all_callback): Save the source so the idle callback can cancel the weak ref. (select_all_idle_callback): Cancel the weak ref so the source isn't destroyed twice. This should fix #74403.
512 lines
14 KiB
C
512 lines
14 KiB
C
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
|
|
|
|
/* nautilus-clipboard.c
|
|
*
|
|
* Nautilus Clipboard support. For now, routines to support component cut
|
|
* and paste.
|
|
*
|
|
* Copyright (C) 1999, 2000 Free Software Foundaton
|
|
* Copyright (C) 2000, 2001 Eazel, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this program; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*
|
|
* Authors: Rebecca Schulman <rebecka@eazel.com>,
|
|
* Darin Adler <darin@bentspoon.com>
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include "nautilus-clipboard.h"
|
|
|
|
#include "nautilus-bonobo-ui.h"
|
|
#include <bonobo/bonobo-ui-util.h>
|
|
#include <gtk/gtkinvisible.h>
|
|
#include <gtk/gtkmain.h>
|
|
#include <gtk/gtksignal.h>
|
|
#include <gtk/gtktext.h>
|
|
#include <string.h>
|
|
|
|
typedef void (* EditableFunction) (GtkEditable *editable);
|
|
|
|
static void disconnect_set_up_in_control_handlers (GtkObject *object,
|
|
gpointer callback_data);
|
|
static void selection_changed_callback (GtkWidget *widget,
|
|
gpointer callback_data);
|
|
|
|
static void
|
|
cut_callback (BonoboUIComponent *ui,
|
|
gpointer callback_data,
|
|
const char *command_name)
|
|
{
|
|
g_assert (BONOBO_IS_UI_COMPONENT (ui));
|
|
g_assert (strcmp (command_name, "Cut") == 0);
|
|
|
|
gtk_editable_cut_clipboard (GTK_EDITABLE (callback_data));
|
|
}
|
|
|
|
static void
|
|
copy_callback (BonoboUIComponent *ui,
|
|
gpointer callback_data,
|
|
const char *command_name)
|
|
{
|
|
g_assert (BONOBO_IS_UI_COMPONENT (ui));
|
|
g_assert (strcmp (command_name, "Copy") == 0);
|
|
|
|
gtk_editable_copy_clipboard (GTK_EDITABLE (callback_data));
|
|
}
|
|
|
|
static void
|
|
paste_callback (BonoboUIComponent *ui,
|
|
gpointer callback_data,
|
|
const char *command_name)
|
|
{
|
|
g_assert (BONOBO_IS_UI_COMPONENT (ui));
|
|
g_assert (strcmp (command_name, "Paste") == 0);
|
|
|
|
gtk_editable_paste_clipboard (GTK_EDITABLE (callback_data));
|
|
}
|
|
|
|
static void
|
|
clear_callback (BonoboUIComponent *ui,
|
|
gpointer callback_data,
|
|
const char *command_name)
|
|
{
|
|
g_assert (BONOBO_IS_UI_COMPONENT (ui));
|
|
g_assert (strcmp (command_name, "Clear") == 0);
|
|
|
|
gtk_editable_delete_selection (GTK_EDITABLE (callback_data));
|
|
}
|
|
|
|
static void
|
|
select_all (GtkEditable *editable)
|
|
{
|
|
gtk_editable_set_position (editable, -1);
|
|
gtk_editable_select_region (editable, 0, -1);
|
|
}
|
|
|
|
static void
|
|
idle_source_destroy_callback (gpointer data,
|
|
GObject *where_the_object_was)
|
|
{
|
|
g_source_destroy (data);
|
|
}
|
|
|
|
static gboolean
|
|
select_all_idle_callback (gpointer callback_data)
|
|
{
|
|
GtkEditable *editable;
|
|
GSource *source;
|
|
|
|
editable = GTK_EDITABLE (callback_data);
|
|
|
|
source = g_object_get_data (G_OBJECT (editable),
|
|
"clipboard-select-all-source");
|
|
|
|
g_object_weak_unref (G_OBJECT (editable),
|
|
idle_source_destroy_callback,
|
|
source);
|
|
|
|
g_object_set_data (G_OBJECT (editable),
|
|
"clipboard-select-all-source",
|
|
NULL);
|
|
|
|
select_all (editable);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
select_all_callback (BonoboUIComponent *ui,
|
|
gpointer callback_data,
|
|
const char *command_name)
|
|
{
|
|
GSource *source;
|
|
GtkEditable *editable;
|
|
|
|
g_assert (BONOBO_IS_UI_COMPONENT (ui));
|
|
g_assert (strcmp (command_name, "Select All") == 0);
|
|
|
|
editable = GTK_EDITABLE (callback_data);
|
|
|
|
if (g_object_get_data (G_OBJECT (editable),
|
|
"clipboard-select-all-source")) {
|
|
return;
|
|
}
|
|
|
|
source = g_idle_source_new ();
|
|
g_source_set_callback (source, select_all_idle_callback, editable, NULL);
|
|
g_object_weak_ref (G_OBJECT (editable),
|
|
idle_source_destroy_callback,
|
|
source);
|
|
g_source_attach (source, NULL);
|
|
g_source_unref (source);
|
|
|
|
g_object_set_data (G_OBJECT (editable),
|
|
"clipboard-select-all-source",
|
|
source);
|
|
}
|
|
|
|
static void
|
|
set_menu_item_sensitive (BonoboUIComponent *component,
|
|
const char *path,
|
|
gboolean sensitive)
|
|
{
|
|
bonobo_ui_component_set_prop (component, path,
|
|
"sensitive", sensitive ? "1" : "0", NULL);
|
|
|
|
}
|
|
|
|
#if 0
|
|
static void
|
|
set_paste_sensitive_if_clipboard_contains_data (BonoboUIComponent *component)
|
|
{
|
|
gboolean clipboard_contains_data;
|
|
|
|
/* FIXME: This check is wrong, because gdk_selection_owner_get
|
|
* will only return non-null if the clipboard owner is in
|
|
* process, which may not be the case, and we may still be
|
|
* able to paste data.
|
|
*/
|
|
/* FIXME: PRIMARY is wrong here. We are interested in
|
|
* CLIPBOARD, not PRIMARY.
|
|
*/
|
|
/* FIXME: This doesn't tell us what kind of data is on the
|
|
* clipboard, and we only want to be sensitive if it's text.
|
|
*/
|
|
clipboard_contains_data =
|
|
(gdk_selection_owner_get (GDK_SELECTION_PRIMARY) != NULL);
|
|
|
|
set_menu_item_sensitive (component,
|
|
NAUTILUS_COMMAND_PASTE,
|
|
clipboard_contains_data);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
set_clipboard_menu_items_sensitive (BonoboUIComponent *component)
|
|
{
|
|
set_menu_item_sensitive (component,
|
|
NAUTILUS_COMMAND_CUT,
|
|
TRUE);
|
|
set_menu_item_sensitive (component,
|
|
NAUTILUS_COMMAND_COPY,
|
|
TRUE);
|
|
set_menu_item_sensitive (component,
|
|
NAUTILUS_COMMAND_CLEAR,
|
|
TRUE);
|
|
}
|
|
|
|
static void
|
|
set_clipboard_menu_items_insensitive (BonoboUIComponent *component)
|
|
{
|
|
set_menu_item_sensitive (component,
|
|
NAUTILUS_COMMAND_CUT,
|
|
FALSE);
|
|
set_menu_item_sensitive (component,
|
|
NAUTILUS_COMMAND_COPY,
|
|
FALSE);
|
|
set_menu_item_sensitive (component,
|
|
NAUTILUS_COMMAND_CLEAR,
|
|
FALSE);
|
|
}
|
|
|
|
typedef struct {
|
|
BonoboUIComponent *component;
|
|
Bonobo_UIContainer container;
|
|
gboolean editable_shares_selection_changes;
|
|
} TargetCallbackData;
|
|
|
|
static gboolean
|
|
clipboard_items_are_merged_in (GtkWidget *widget)
|
|
{
|
|
return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
|
|
"Nautilus:clipboard_menu_items_merged"));
|
|
}
|
|
|
|
static void
|
|
set_clipboard_items_are_merged_in (GObject *widget_as_object,
|
|
gboolean merged_in)
|
|
{
|
|
g_object_set_data (widget_as_object,
|
|
"Nautilus:clipboard_menu_items_merged",
|
|
GINT_TO_POINTER (merged_in));
|
|
}
|
|
|
|
static void
|
|
merge_in_clipboard_menu_items (GObject *widget_as_object,
|
|
TargetCallbackData *target_data)
|
|
{
|
|
BonoboUIComponent *ui;
|
|
Bonobo_UIContainer container;
|
|
gboolean add_selection_callback;
|
|
|
|
g_assert (target_data != NULL);
|
|
ui = target_data->component;
|
|
container = target_data->container;
|
|
add_selection_callback = target_data->editable_shares_selection_changes;
|
|
|
|
if (ui == NULL || container == CORBA_OBJECT_NIL) {
|
|
return;
|
|
}
|
|
|
|
bonobo_ui_component_set_container (ui, container, NULL);
|
|
bonobo_ui_component_freeze (ui, NULL);
|
|
bonobo_ui_util_set_ui (ui,
|
|
DATADIR,
|
|
"nautilus-clipboard-ui.xml",
|
|
"nautilus", NULL);
|
|
|
|
if (add_selection_callback) {
|
|
g_signal_connect_after (widget_as_object, "selection_changed",
|
|
G_CALLBACK (selection_changed_callback), target_data);
|
|
selection_changed_callback (GTK_WIDGET (widget_as_object),
|
|
target_data);
|
|
|
|
} else {
|
|
/* If we don't use sensitivity, everything should be on */
|
|
set_clipboard_menu_items_sensitive (ui);
|
|
}
|
|
set_clipboard_items_are_merged_in (widget_as_object, TRUE);
|
|
bonobo_ui_component_thaw (ui, NULL);
|
|
}
|
|
|
|
static void
|
|
merge_out_clipboard_menu_items (GObject *widget_as_object,
|
|
TargetCallbackData *target_data)
|
|
|
|
{
|
|
BonoboUIComponent *ui;
|
|
gboolean selection_callback_was_added;
|
|
|
|
g_assert (target_data != NULL);
|
|
ui = BONOBO_UI_COMPONENT (target_data->component);
|
|
selection_callback_was_added = target_data->editable_shares_selection_changes;
|
|
|
|
if (ui == NULL) {
|
|
return;
|
|
}
|
|
|
|
bonobo_ui_component_unset_container (ui, NULL);
|
|
|
|
if (selection_callback_was_added) {
|
|
g_signal_handlers_disconnect_matched (widget_as_object,
|
|
G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
|
|
0, 0, NULL,
|
|
G_CALLBACK (selection_changed_callback),
|
|
target_data);
|
|
}
|
|
set_clipboard_items_are_merged_in (widget_as_object, FALSE);
|
|
}
|
|
|
|
static gboolean
|
|
focus_changed_callback (GtkWidget *widget,
|
|
GdkEventAny *event,
|
|
gpointer callback_data)
|
|
{
|
|
/* Connect the component to the container if the widget has focus. */
|
|
if (GTK_WIDGET_HAS_FOCUS (widget)) {
|
|
if (!clipboard_items_are_merged_in (widget)) {
|
|
merge_in_clipboard_menu_items (G_OBJECT (widget), callback_data);
|
|
}
|
|
} else {
|
|
if (clipboard_items_are_merged_in (widget)) {
|
|
merge_out_clipboard_menu_items (G_OBJECT (widget), callback_data);
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
selection_changed_callback (GtkWidget *widget,
|
|
gpointer callback_data)
|
|
{
|
|
TargetCallbackData *target_data;
|
|
BonoboUIComponent *component;
|
|
GtkEditable *editable;
|
|
int start, end;
|
|
|
|
target_data = (TargetCallbackData *) callback_data;
|
|
g_assert (target_data != NULL);
|
|
|
|
component = target_data->component;
|
|
editable = GTK_EDITABLE (widget);
|
|
|
|
if (gtk_editable_get_selection_bounds (editable, &start, &end) && start != end) {
|
|
set_clipboard_menu_items_sensitive (component);
|
|
} else {
|
|
set_clipboard_menu_items_insensitive (component);
|
|
}
|
|
}
|
|
|
|
static void
|
|
target_destroy_callback (GtkObject *object,
|
|
gpointer callback_data)
|
|
{
|
|
TargetCallbackData *target_data;
|
|
|
|
g_assert (callback_data != NULL);
|
|
target_data = callback_data;
|
|
|
|
if (target_data->component != NULL) {
|
|
bonobo_ui_component_unset_container (target_data->component, NULL);
|
|
bonobo_object_unref (target_data->component);
|
|
target_data->component = NULL;
|
|
}
|
|
bonobo_object_release_unref (target_data->container, NULL);
|
|
target_data->container = CORBA_OBJECT_NIL;
|
|
}
|
|
|
|
static TargetCallbackData *
|
|
initialize_clipboard_component_with_callback_data (GtkEditable *target,
|
|
Bonobo_UIContainer ui_container,
|
|
gboolean shares_selection_changes)
|
|
{
|
|
BonoboUIVerb verbs [] = {
|
|
BONOBO_UI_VERB ("Cut", cut_callback),
|
|
BONOBO_UI_VERB ("Copy", copy_callback),
|
|
BONOBO_UI_VERB ("Paste", paste_callback),
|
|
BONOBO_UI_VERB ("Clear", clear_callback),
|
|
BONOBO_UI_VERB ("Select All", select_all_callback),
|
|
BONOBO_UI_VERB_END
|
|
};
|
|
BonoboUIComponent *ui;
|
|
TargetCallbackData *target_data;
|
|
|
|
/* Create the UI component and set up the verbs. */
|
|
ui = bonobo_ui_component_new_default ();
|
|
bonobo_ui_component_add_verb_list_with_data (ui, verbs, target);
|
|
|
|
/* Do the actual connection of the UI to the container at
|
|
* focus time, and disconnect at both focus and destroy
|
|
* time.
|
|
*/
|
|
target_data = g_new (TargetCallbackData, 1);
|
|
target_data->component = ui;
|
|
target_data->container = bonobo_object_dup_ref (ui_container, NULL);
|
|
target_data->editable_shares_selection_changes = shares_selection_changes;
|
|
|
|
return target_data;
|
|
}
|
|
|
|
void
|
|
nautilus_clipboard_set_up_editable (GtkEditable *target,
|
|
Bonobo_UIContainer ui_container,
|
|
gboolean shares_selection_changes)
|
|
{
|
|
TargetCallbackData *target_data;
|
|
|
|
g_return_if_fail (GTK_IS_EDITABLE (target));
|
|
g_return_if_fail (ui_container != CORBA_OBJECT_NIL);
|
|
|
|
target_data = initialize_clipboard_component_with_callback_data
|
|
(target,
|
|
ui_container,
|
|
shares_selection_changes);
|
|
|
|
g_signal_connect (target, "focus_in_event",
|
|
G_CALLBACK (focus_changed_callback), target_data);
|
|
g_signal_connect (target, "focus_out_event",
|
|
G_CALLBACK (focus_changed_callback), target_data);
|
|
g_signal_connect (target, "destroy",
|
|
G_CALLBACK (target_destroy_callback), target_data);
|
|
|
|
g_object_weak_ref (G_OBJECT (target), (GWeakNotify) g_free, target_data);
|
|
|
|
/* Call the focus changed callback once to merge if the window is
|
|
* already in focus.
|
|
*/
|
|
focus_changed_callback (GTK_WIDGET (target), NULL, target_data);
|
|
}
|
|
|
|
static gboolean
|
|
widget_was_set_up_with_selection_sensitivity (GtkWidget *widget)
|
|
{
|
|
return GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
|
|
"Nautilus:shares_selection_changes"));
|
|
}
|
|
|
|
static gboolean
|
|
first_focus_callback (GtkWidget *widget,
|
|
GdkEventAny *event,
|
|
gpointer callback_data)
|
|
{
|
|
/* Don't set up the clipboard again on future focus_in's. This
|
|
* is one-time work.
|
|
*/
|
|
disconnect_set_up_in_control_handlers (GTK_OBJECT (widget), callback_data);
|
|
|
|
/* Do the rest of the setup. */
|
|
nautilus_clipboard_set_up_editable
|
|
(GTK_EDITABLE (widget),
|
|
bonobo_control_get_remote_ui_container (BONOBO_CONTROL (callback_data), NULL),
|
|
widget_was_set_up_with_selection_sensitivity (widget));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
control_destroyed_callback (GtkObject *object,
|
|
gpointer callback_data)
|
|
{
|
|
disconnect_set_up_in_control_handlers (object, callback_data);
|
|
}
|
|
|
|
void
|
|
nautilus_clipboard_set_up_editable_in_control (GtkEditable *target,
|
|
BonoboControl *control,
|
|
gboolean shares_selection_changes)
|
|
{
|
|
g_return_if_fail (GTK_IS_EDITABLE (target));
|
|
g_return_if_fail (BONOBO_IS_CONTROL (control));
|
|
|
|
if (GTK_WIDGET_HAS_FOCUS (target)) {
|
|
nautilus_clipboard_set_up_editable
|
|
(target,
|
|
bonobo_control_get_remote_ui_container (control, NULL),
|
|
shares_selection_changes);
|
|
return;
|
|
}
|
|
|
|
/* Use lazy initialization, so that we wait until after
|
|
* embedding. At that point, the UI container will be set up,
|
|
* but it's not necessarily set up now.
|
|
*/
|
|
/* We'd like to use gtk_signal_connect_while_alive, but it's
|
|
* not compatible with gtk_signal_disconnect calls.
|
|
*/
|
|
g_object_set_data (G_OBJECT (target), "Nautilus:shares_selection_changes",
|
|
GINT_TO_POINTER (shares_selection_changes));
|
|
g_signal_connect (target, "focus_in_event",
|
|
G_CALLBACK (first_focus_callback), control);
|
|
g_signal_connect (target, "destroy",
|
|
G_CALLBACK (control_destroyed_callback), control);
|
|
}
|
|
|
|
static void
|
|
disconnect_set_up_in_control_handlers (GtkObject *object,
|
|
gpointer callback_data)
|
|
{
|
|
g_signal_handlers_disconnect_matched (object,
|
|
G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
|
|
0, 0, NULL,
|
|
G_CALLBACK (first_focus_callback),
|
|
callback_data);
|
|
g_signal_handlers_disconnect_matched (object,
|
|
G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
|
|
0, 0, NULL,
|
|
G_CALLBACK (control_destroyed_callback),
|
|
callback_data);
|
|
}
|