summaryrefslogtreecommitdiff
path: root/tools/glade/glade/glade_clipboard.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/glade/glade/glade_clipboard.c')
-rw-r--r--tools/glade/glade/glade_clipboard.c654
1 files changed, 654 insertions, 0 deletions
diff --git a/tools/glade/glade/glade_clipboard.c b/tools/glade/glade/glade_clipboard.c
new file mode 100644
index 00000000..cde84f87
--- /dev/null
+++ b/tools/glade/glade/glade_clipboard.c
@@ -0,0 +1,654 @@
+/* Gtk+ User Interface Builder
+ * Copyright (C) 1998-1999 Damon Chaplin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <locale.h>
+#include <stdlib.h>
+
+#ifdef USE_GNOME
+#include <gnome.h>
+#else
+#include <gtk/gtk.h>
+#endif
+
+#include "gladeconfig.h"
+
+#include "editor.h"
+#include "glade_clipboard.h"
+#include "gbwidget.h"
+#include "load.h"
+#include "tree.h"
+#include "utils.h"
+#include "gb.h"
+#include "glade_project_window.h"
+
+typedef struct _GladeClipboardItem GladeClipboardItem;
+
+struct _GladeClipboardItem
+{
+ GladeProject *project;
+ GtkType type;
+ gchar *xml_data;
+ gboolean names_unique;
+};
+
+
+static GtkWindowClass *parent_class = NULL;
+
+static void glade_clipboard_class_init (GladeClipboardClass * klass);
+static void glade_clipboard_init (GladeClipboard * clipboard);
+
+static void glade_clipboard_cut_or_copy (GladeClipboard *clipboard,
+ GladeProject *project,
+ GtkWidget *widget,
+ gboolean cut);
+static void glade_clipboard_add (GladeClipboard *clipboard,
+ GladeProject *project,
+ gboolean names_unique,
+ GtkWidget *widget,
+ gchar *xml_data);
+static GladeClipboardItem* glade_clipboard_get_current_item (GladeClipboard *clipboard);
+
+static void glade_clipboard_on_project_destroy (GladeProject *project,
+ GladeClipboardItem *item);
+
+
+#define GLADE_PASTE_BUFFER_INCREMENT 1024
+
+
+GType
+glade_clipboard_get_type (void)
+{
+ static GType glade_clipboard_type = 0;
+
+ if (!glade_clipboard_type)
+ {
+ GtkTypeInfo glade_clipboard_info =
+ {
+ "GladeClipboard",
+ sizeof (GladeClipboard),
+ sizeof (GladeClipboardClass),
+ (GtkClassInitFunc) glade_clipboard_class_init,
+ (GtkObjectInitFunc) glade_clipboard_init,
+ /* reserved_1 */ NULL,
+ /* reserved_2 */ NULL,
+ (GtkClassInitFunc) NULL,
+ };
+
+ glade_clipboard_type = gtk_type_unique (gtk_window_get_type (),
+ &glade_clipboard_info);
+ }
+ return glade_clipboard_type;
+}
+
+
+static void
+glade_clipboard_class_init (GladeClipboardClass * klass)
+{
+ GtkObjectClass *object_class;
+
+ object_class = (GtkObjectClass *) klass;
+
+ parent_class = gtk_type_class (gtk_window_get_type ());
+}
+
+
+static void
+glade_clipboard_init (GladeClipboard * clipboard)
+{
+ GtkWidget *vbox, *scrolled_win;
+
+ gtk_window_set_title (GTK_WINDOW (clipboard), _ ("Clipboard"));
+ gtk_window_set_policy (GTK_WINDOW (clipboard), FALSE, TRUE, FALSE);
+ gtk_window_set_wmclass (GTK_WINDOW (clipboard), "clipboard", "Glade");
+ gtk_window_set_default_size (GTK_WINDOW (clipboard), 150, 200);
+ gtk_window_add_accel_group (GTK_WINDOW (clipboard),
+ glade_get_global_accel_group ());
+
+ vbox = gtk_vbox_new (FALSE, 4);
+ gtk_widget_show (vbox);
+ gtk_container_add (GTK_CONTAINER (clipboard), vbox);
+
+ clipboard->clist = gtk_clist_new (1);
+ gtk_clist_set_row_height (GTK_CLIST (clipboard->clist), 23);
+ gtk_widget_set_usize (clipboard->clist, 100, 100);
+ gtk_clist_set_column_width (GTK_CLIST (clipboard->clist), 0, 100);
+ gtk_clist_set_selection_mode (GTK_CLIST (clipboard->clist),
+ GTK_SELECTION_BROWSE);
+ gtk_widget_show (clipboard->clist);
+
+ scrolled_win = gtk_scrolled_window_new (NULL, NULL);
+ gtk_container_add (GTK_CONTAINER (scrolled_win), clipboard->clist);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_box_pack_start (GTK_BOX (vbox), scrolled_win, TRUE, TRUE, 0);
+ gtk_widget_show (scrolled_win);
+
+ gtk_signal_connect (GTK_OBJECT (clipboard), "delete_event",
+ GTK_SIGNAL_FUNC (glade_util_close_window_on_delete),
+ NULL);
+
+ gtk_signal_connect_after (GTK_OBJECT (clipboard), "hide",
+ GTK_SIGNAL_FUNC (glade_project_window_uncheck_clipboard_menu_item),
+ NULL);
+}
+
+
+GtkWidget *
+glade_clipboard_new (void)
+{
+ return GTK_WIDGET (gtk_type_new (glade_clipboard_get_type ()));
+}
+
+
+void
+glade_clipboard_cut (GladeClipboard *clipboard,
+ GladeProject *project,
+ GtkWidget *widget)
+{
+ glade_clipboard_cut_or_copy (clipboard, project, widget, TRUE);
+}
+
+
+void
+glade_clipboard_copy (GladeClipboard *clipboard,
+ GladeProject *project,
+ GtkWidget *widget)
+{
+ glade_clipboard_cut_or_copy (clipboard, project, widget, FALSE);
+}
+
+
+static void
+glade_clipboard_cut_or_copy (GladeClipboard *clipboard,
+ GladeProject *project,
+ GtkWidget *widget,
+ gboolean cut)
+{
+ GbWidgetGetArgData data = { 0 };
+ gchar *old_locale, *saved_locale;
+ GList *selection;
+ gboolean is_component;
+
+ if (widget == NULL)
+ {
+ selection = editor_get_selection ();
+ if (selection)
+ widget = GTK_WIDGET (selection->data);
+ }
+
+ if (widget == NULL || GB_IS_PLACEHOLDER (widget))
+ return;
+
+ data.project = project;
+ data.action = GB_SAVING;
+ data.copying_to_clipboard = TRUE;
+ data.error = NULL;
+
+ /* Initialize the output buffer. */
+ data.buffer = g_string_sized_new (1024);
+ data.indent = 0;
+
+ /* We don't need the translatable strings. */
+ data.save_translatable_strings = FALSE;
+ data.translatable_strings = NULL;
+
+ old_locale = setlocale (LC_NUMERIC, NULL);
+ saved_locale = g_strdup (old_locale);
+ setlocale (LC_NUMERIC, "C");
+
+ g_string_append (data.buffer,
+ "<?xml version=\"1.0\" standalone=\"no\"?> <!--*- mode: xml -*-->\n"
+ "<!DOCTYPE glade-interface SYSTEM \"http://glade.gnome.org/glade-project-2.0.dtd\">\n"
+ "\n"
+ "<glade-interface>\n");
+
+ /* If the widget is not a toplevel widget, we add a dummy parent to it.
+ This is so that we keep the packing properties of the widget. */
+ is_component = glade_util_is_component (widget);
+ if (!is_component)
+ {
+ g_string_append (data.buffer,
+ "<widget class=\"GtkWindow\" id=\"glade-dummy-container\">\n");
+ data.indent++;
+ }
+
+ gb_widget_save (widget, &data);
+
+ if (!is_component)
+ {
+ g_string_append (data.buffer,
+ "</widget>\n");
+ }
+
+ g_string_append (data.buffer,
+ "\n</glade-interface>\n");
+
+ setlocale (LC_NUMERIC, saved_locale);
+ g_free (saved_locale);
+
+ if (data.error == NULL)
+ {
+#if 0
+ g_print ("Adding to clipboard:\n%s\n", data.buffer->str);
+#endif
+ glade_clipboard_add (GLADE_CLIPBOARD (clipboard), project, cut,
+ widget, data.buffer->str);
+ if (cut)
+ editor_delete_widget (widget);
+ }
+ else
+ /* This shouldn't happen. */
+ g_warning ("Error saving widget to clipboard");
+
+ g_string_free (data.buffer, TRUE);
+}
+
+
+static void
+initialize_all_widgets_cb (GtkWidget *widget, GbWidgetSetArgData *data)
+{
+ const gchar *widget_name;
+
+ /* If this is the widget we are replacing, just return. We don't want to
+ add it or its descendants to all_widgets. They will no longer exist, so
+ ATK relations can't use them. */
+ if (widget == data->replacing_widget)
+ return;
+
+ /* Add this widget to the all_widgets hash. */
+ if (GB_IS_GB_WIDGET (widget))
+ {
+ widget_name = gtk_widget_get_name (widget);
+ if (widget_name && *widget_name)
+ {
+#if 0
+ g_print ("Initializing pointer to widget: %s, %p\n",
+ widget_name, widget);
+#endif
+ g_hash_table_insert (data->all_widgets, (gpointer) widget_name,
+ widget);
+ }
+ }
+
+ /* Now recursively add the children. */
+ gb_widget_children_foreach (widget, (GtkCallback) initialize_all_widgets_cb,
+ data);
+}
+
+
+/* This adds all widgets that are in the window being pasted into to the
+ all_widgets hash, so ATK relations to other widgets will be set when
+ pasting. */
+static void
+glade_clipboard_initialize_all_widgets (GbWidgetSetArgData *data)
+{
+ GtkWidget *toplevel;
+
+ /* If we aren't pasting into a component, just return. */
+ if (!data->replacing_widget)
+ return;
+
+ toplevel = glade_util_get_toplevel (data->replacing_widget);
+
+ initialize_all_widgets_cb (toplevel, data);
+}
+
+
+/* The widget argument is the widget to be pasted over, i.e. the widget in
+ the current interface that will be replaced by the clipboard item.
+ If it is NULL, the currently selected widget will be used. */
+void
+glade_clipboard_paste (GladeClipboard *clipboard,
+ GladeProject *project,
+ GtkWidget *widget)
+{
+ GladeClipboardItem *item;
+ GbWidgetSetArgData data = { 0 };
+ GtkWidget *parent, *new_widget = NULL;
+ gchar *saved_locale, *saved_timezone, *child_name;
+ GList *selection;
+ gboolean is_component = FALSE;
+
+ item = glade_clipboard_get_current_item (clipboard);
+ if (item == NULL)
+ return;
+
+ if (widget == NULL)
+ {
+ selection = editor_get_selection ();
+ if (selection)
+ widget = GTK_WIDGET (selection->data);
+ }
+
+ if (gtk_type_is_a (item->type, gtk_window_get_type ())
+ || gtk_type_is_a (item->type, gtk_menu_get_type ()))
+ is_component = TRUE;
+
+ /* We can only paste toplevel components, i.e. windows and menus, into the
+ project, and not into other widgets. */
+ if (widget == NULL)
+ {
+ if (!is_component)
+ {
+ glade_util_show_message_box (_("You need to select a widget to paste into"), widget);
+ return;
+ }
+ }
+ else
+ {
+ if (is_component)
+ widget = NULL;
+ }
+
+ if (widget)
+ {
+ /* Don't allow pasting into any windows/dialogs. */
+ if (GTK_IS_WINDOW (widget)
+ || GTK_IS_DIALOG (widget)
+ || GTK_IS_COLOR_SELECTION_DIALOG (widget)
+ || GTK_IS_INPUT_DIALOG (widget)
+ || GTK_IS_FONT_SELECTION_DIALOG (widget)
+ || GTK_IS_FILE_SELECTION (widget)
+#ifdef USE_GNOME
+ || GNOME_IS_APP (widget)
+ || GNOME_IS_DIALOG (widget)
+#endif
+ )
+ {
+ glade_util_show_message_box (_("You can't paste into windows or dialogs."), widget);
+ return;
+ }
+
+ /* SPECIAL CODE: Don't allow pasting into dialog widgets. */
+ child_name = gb_widget_get_child_name (widget);
+ if (child_name)
+ {
+ if (!strcmp (child_name, GladeChildOKButton)
+ || !strcmp (child_name, GladeChildCancelButton)
+ || !strcmp (child_name, GladeChildHelpButton)
+ || !strcmp (child_name, GladeChildApplyButton)
+ || !strcmp (child_name, GladeChildSaveButton)
+ || !strcmp (child_name, GladeChildCloseButton)
+ || !strcmp (child_name, GladeChildDialogVBox)
+ || !strcmp (child_name, GladeChildDialogActionArea)
+ || !strcmp (child_name, GladeChildComboEntry)
+ || !strcmp (child_name, GladeChildComboList)
+ || !strcmp (child_name, GladeChildFontSelection)
+ || !strcmp (child_name, GladeChildColorSelection)
+ || !strcmp (child_name, GladeChildGnomeEntry)
+ )
+ {
+ glade_util_show_message_box (_("You can't paste into the selected widget, since\nit is created automatically by its parent."), widget);
+ return;
+ }
+ }
+
+ /* Only allow menuitems to be pasted into menus or menubars. */
+ if (GTK_IS_MENU_SHELL (widget)
+ && !gtk_type_is_a (item->type, gtk_menu_item_get_type ()))
+ {
+ glade_util_show_message_box (_("Only menu items can be pasted into a menu or menu bar."), widget);
+ return;
+ }
+
+ /* Only allow menuitems to replace menuitems. */
+ if (GTK_IS_MENU_ITEM (widget)
+ && !gtk_type_is_a (item->type, gtk_menu_item_get_type ()))
+ {
+ glade_util_show_message_box (_("Only menu items can be pasted into a menu or menu bar."), widget);
+ return;
+ }
+
+ /* Only buttons can be pasted into a dialog action area. */
+ if (widget->parent)
+ {
+ child_name = gb_widget_get_child_name (widget->parent);
+ if (child_name && !strcmp (child_name, GladeChildDialogActionArea)
+ && item->type != GTK_TYPE_BUTTON)
+ {
+ glade_util_show_message_box (_("Only buttons can be pasted into a dialog action area."), widget);
+ return;
+ }
+ }
+
+#ifdef USE_GNOME
+ if (BONOBO_IS_DOCK (widget))
+ {
+ if (!gtk_type_is_a (item->type, bonobo_dock_item_get_type ()))
+ {
+ glade_util_show_message_box (_("Only GnomeDockItem widgets can be pasted into a GnomeDock."), widget);
+ return;
+ }
+ }
+
+ if (BONOBO_IS_DOCK_ITEM (widget))
+ {
+ if (!gtk_type_is_a (item->type, bonobo_dock_item_get_type ()))
+ {
+ glade_util_show_message_box (_("Only GnomeDockItem widgets can be pasted over a GnomeDockItem."), widget);
+ return;
+ }
+ glade_util_show_message_box (_("Sorry - pasting over a GnomeDockItem is not implemented yet."), widget);
+ return;
+ }
+
+ if (gtk_type_is_a (item->type, bonobo_dock_item_get_type ()))
+ {
+ if (!BONOBO_IS_DOCK (widget) && !BONOBO_IS_DOCK_ITEM (widget))
+ {
+ glade_util_show_message_box (_("GnomeDockItem widgets can only be pasted into a GnomeDock."), widget);
+ return;
+ }
+ }
+#endif
+ }
+
+ data.project = project;
+ data.filename = NULL;
+ data.xml_buffer = item->xml_data;
+ data.status = GLADE_STATUS_OK;
+ data.all_widgets = g_hash_table_new (g_str_hash, g_str_equal);
+
+ /* We normally create new names for the widgets pasted, discarding the names
+ in the XML data. However, if the widgets were cut to the clipboard, and
+ we are pasting into the same project for the first time, then we use
+ the original names. */
+ data.discard_names = TRUE;
+ if (project == item->project)
+ {
+ if (item->names_unique)
+ {
+ data.discard_names = FALSE;
+ item->names_unique = FALSE;
+ }
+ }
+
+ /* We always try to replace the selected widget, unless it is a GtkFixed,
+ GtkLayout, or GtkPacker, in which case we add the pasted widget as a
+ child. If we didn't do this, then it would be quite difficult to paste
+ children into these widgets. I did consider adding the widget as a child
+ when pasting into other containers like boxes and tables, but then we
+ have a problem with composite widgets, where we may accidentally paste
+ inside the composite (e.g. inside its toplevel vbox).
+ For Gnome, we also allow pasting GnomeDockItems into GnomeDocks. */
+ parent = NULL;
+ data.replacing_widget = NULL;
+ if (widget)
+ {
+ if (GTK_IS_FIXED (widget)
+ || GTK_IS_LAYOUT (widget)
+#ifdef USE_GNOME
+ || BONOBO_IS_DOCK (widget)
+#endif
+ )
+ {
+ parent = widget;
+ }
+ else
+ {
+ parent = widget->parent;
+ data.replacing_widget = widget;
+ }
+ }
+
+ /* Initialize the all_widgets hash with all widget names in the component,
+ except the widget being replaced and any of its descendants. This is
+ done so that ATK relations to targets in the component will still be OK.
+ */
+ glade_clipboard_initialize_all_widgets (&data);
+
+ tree_freeze ();
+
+ /* Set the locale to "C". */
+ saved_locale = g_strdup (setlocale (LC_NUMERIC, NULL));
+ setlocale (LC_NUMERIC, "C");
+
+ /* Set the timezone to "UTC". */
+ saved_timezone = glade_util_set_timezone ("UTC");
+
+ /* Now parse the clipboard data. */
+ data.interface = glade_parser_parse_buffer (data.xml_buffer,
+ strlen (data.xml_buffer), NULL);
+ if (!data.interface || data.interface->n_toplevels != 1)
+ {
+ /* I don't think this should happen, except due to bugs. */
+ g_warning ("Error pasting from clipboard");
+ glade_interface_destroy (data.interface);
+ g_hash_table_destroy (data.all_widgets);
+ return;
+ }
+
+ /* If the widget is a toplevel component, we use the top widget info and
+ no child info. If not, we use the first child, as the toplevel will be
+ a dummy container. */
+ data.child_info = NULL;
+ data.widget_info = data.interface->toplevels[0];
+
+ if (!is_component)
+ {
+ if (data.widget_info->n_children != 1
+ || data.widget_info->children == NULL)
+ {
+ data.status = GLADE_STATUS_ERROR;
+ }
+ else
+ {
+ data.child_info = &data.widget_info->children[0];
+ data.widget_info = data.child_info->child;
+ }
+ }
+
+ /* Create the widget. */
+ if (data.status == GLADE_STATUS_OK)
+ new_widget = gb_widget_load (NULL, &data, parent);
+
+ /* Destroy the parse data. */
+ glade_interface_destroy (data.interface);
+
+ /* Reset the timezone. */
+ glade_util_reset_timezone (saved_timezone);
+
+ /* Reset the locale. */
+ setlocale (LC_NUMERIC, saved_locale);
+ g_free (saved_locale);
+
+ tree_thaw ();
+
+ g_hash_table_destroy (data.all_widgets);
+
+ if (data.status != GLADE_STATUS_OK)
+ {
+ /* I don't think this should happen, except due to bugs. */
+ g_warning ("Error pasting from clipboard");
+ return;
+ }
+
+ /* If a window was pasted, show it. */
+ if (GTK_IS_WINDOW (new_widget))
+ glade_project_show_component (project, new_widget);
+}
+
+
+/* This adds the XML for a widget (and any descendants) to the clipboard.
+ If the widget has been cut and is later pasted into the same project,
+ then the same widget names can be used. Otherwise new widget names have
+ to be created. The widget parameter is used to get the widget's name and
+ class and to look up its pixmap to display in the clipboard.
+ The xml_data parameter is copied. */
+static void
+glade_clipboard_add (GladeClipboard *clipboard,
+ GladeProject *project,
+ gboolean names_unique,
+ GtkWidget *widget,
+ gchar *xml_data)
+{
+ GladeClipboardItem *item;
+ GbWidget *gbwidget;
+ gchar *name;
+
+ name = (char*) gtk_widget_get_name (widget);
+ gbwidget = gb_widget_lookup (widget);
+ g_return_if_fail (gbwidget != NULL);
+
+ item = g_new (GladeClipboardItem, 1);
+ item->project = project;
+ item->type = GTK_OBJECT_TYPE (widget);
+ item->xml_data = g_strdup (xml_data);
+ item->names_unique = names_unique;
+
+ /* Connect to the project's destroy signal to set the pointer to NULL, so
+ we never have invalid pointers. */
+ gtk_signal_connect (GTK_OBJECT (project), "destroy",
+ GTK_SIGNAL_FUNC (glade_clipboard_on_project_destroy),
+ item);
+
+ gtk_clist_insert (GTK_CLIST (clipboard->clist), 0, &name);
+ gtk_clist_set_row_data (GTK_CLIST (clipboard->clist), 0, item);
+
+ gtk_clist_set_pixtext (GTK_CLIST (clipboard->clist), 0, 0, name, 3,
+ gbwidget->gdkpixmap, gbwidget->mask);
+
+ gtk_clist_select_row (GTK_CLIST (clipboard->clist), 0, 0);
+}
+
+
+/* This returns the currently-selected GladeClipboardItem, or NULL if no item
+ is currently selected (i.e. the clipboard is empty). */
+static GladeClipboardItem*
+glade_clipboard_get_current_item (GladeClipboard *clipboard)
+{
+ GList *selection;
+
+ selection = GTK_CLIST (clipboard->clist)->selection;
+ if (selection == NULL)
+ return NULL;
+
+ return (GladeClipboardItem*) gtk_clist_get_row_data (GTK_CLIST (clipboard->clist), GPOINTER_TO_INT (selection->data));
+}
+
+
+static void
+glade_clipboard_on_project_destroy (GladeProject *project,
+ GladeClipboardItem *item)
+{
+ if (item)
+ item->project = NULL;
+}