summaryrefslogtreecommitdiff
path: root/tools/glade/glade/editor.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/glade/glade/editor.c')
-rw-r--r--tools/glade/glade/editor.c3598
1 files changed, 3598 insertions, 0 deletions
diff --git a/tools/glade/glade/editor.c b/tools/glade/glade/editor.c
new file mode 100644
index 00000000..424c9e43
--- /dev/null
+++ b/tools/glade/glade/editor.c
@@ -0,0 +1,3598 @@
+/* Gtk+ User Interface Builder
+ * Copyright (C) 1998 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 <string.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+#include "gladeconfig.h"
+
+#ifdef USE_GNOME
+#include <gnome.h>
+#endif
+
+#include "glade_clipboard.h"
+#include "glade_palette.h"
+#include "glade_project.h"
+#include "glade_project_window.h"
+#include "property.h"
+#include "gbwidget.h"
+#include "utils.h"
+#include "editor.h"
+#include "tree.h"
+#include "gb.h"
+
+/* The pixmap to use for placeholders */
+#include "graphics/placeholder.xpm"
+
+#define MIN_WIDGET_WIDTH 16
+#define MIN_WIDGET_HEIGHT 16
+#define MAX_INITIAL_WIDGET_WIDTH 300
+#define MAX_INITIAL_WIDGET_HEIGHT 200
+#define DEFAULT_WIDGET_WIDTH 50
+#define DEFAULT_WIDGET_HEIGHT 50
+
+#define PLACEHOLDER_WIDTH 16
+#define PLACEHOLDER_HEIGHT 16
+
+
+/* The grid (for fixed containers) */
+#define GB_GRID_DOTS 1
+#define GB_GRID_LINES 2
+gboolean editor_show_grid = TRUE;
+static gint editor_grid_horz_spacing = 8;
+static gint editor_grid_vert_spacing = 8;
+static gboolean editor_grid_style = GB_GRID_DOTS;
+
+
+/* Snapping to the grid */
+#define GB_SNAP_TOP 1 << 1
+#define GB_SNAP_BOTTOM 1 << 2
+#define GB_SNAP_LEFT 1 << 3
+#define GB_SNAP_RIGHT 1 << 4
+/*#define GB_SNAP_CENTER 1 << 5 maybe in future */
+gboolean editor_snap_to_grid = TRUE;
+static gint editor_snap_to_grid_x = GB_SNAP_LEFT | GB_SNAP_RIGHT;
+static gint editor_snap_to_grid_y = GB_SNAP_TOP | GB_SNAP_BOTTOM;
+
+
+/* Dragging (in a fixed container) - remembers which part of the widget is
+ being dragged, the offset of the mouse (used when moving) and the initial
+ widget rectangle (used when resizing) */
+#define GB_DRAG_NONE 1
+#define GB_TOP_LEFT 2
+#define GB_TOP_RIGHT 3
+#define GB_BOTTOM_LEFT 4
+#define GB_BOTTOM_RIGHT 5
+#define GB_MIDDLE 6
+static gint drag_action;
+static gboolean drag_has_pointer_grab = FALSE;
+static GtkWidget *dragging_widget = NULL;
+static gint drag_offset_x;
+static gint drag_offset_y;
+static gint drag_widget_x1, drag_widget_y1, drag_widget_x2, drag_widget_y2;
+
+/* The list of selected widgets */
+static GList *selected_widgets = NULL;
+
+/* The cursors used when selecting/adding/moving/resizing widgets */
+static GdkCursor *cursor_selector;
+static GdkCursor *cursor_add_widget;
+static GdkCursor *cursor_add_to_fixed;
+static GdkCursor *cursor_move;
+static GdkCursor *cursor_top_left;
+static GdkCursor *cursor_top_right;
+static GdkCursor *cursor_bottom_left;
+static GdkCursor *cursor_bottom_right;
+
+/* Struct only used for find_child_at callback */
+typedef struct _GbFindChildAtData GbFindChildAtData;
+struct _GbFindChildAtData
+ {
+ gint x;
+ gint y;
+ GtkWidget *found_child;
+ };
+
+/* Experimental code to allow typing labels for widgets when the mouse is
+ over them. */
+GtkWidget *mouse_over_widget = NULL;
+
+
+/* Static functions */
+static void editor_on_widget_realize (GtkWidget *widget,
+ gpointer data);
+
+static gint editor_on_key_press_event (GtkWidget * widget,
+ GdkEventKey * event,
+ gpointer data);
+static gint editor_on_key_release_event (GtkWidget * widget,
+ GdkEventKey * event,
+ gpointer data);
+
+static void add_mouse_signals_recursive (GtkWidget *widget,
+ gpointer data);
+static gint editor_on_button_press (GtkWidget * signal_widget,
+ GdkEventButton * event,
+ gpointer data);
+static gint editor_on_button_release (GtkWidget * widget,
+ GdkEvent * event,
+ gpointer data);
+
+static gboolean editor_check_ignore_event (GtkWidget *widget,
+ GdkEventAny *event);
+
+static gint editor_on_motion_notify (GtkWidget * signal_widget,
+ GdkEventMotion * event,
+ gpointer data);
+static gint editor_set_cursor (GtkWidget * signal_widget,
+ GdkEventMotion * event);
+static gint editor_do_drag_action (GtkWidget * signal_widget,
+ GdkEventMotion * event);
+
+static gint editor_on_enter_notify (GtkWidget *widget,
+ GdkEventCrossing *event);
+static gint editor_on_leave_notify (GtkWidget *widget,
+ GdkEventCrossing *event);
+
+static void placeholder_replace (GtkWidget * placeholder);
+static void placeholder_finish_replace (GtkWidget * new_widget,
+ GbWidgetNewData * data);
+static void on_placeholder_destroy (GtkWidget * widget,
+ gpointer data);
+static void on_placeholder_size_request (GtkWidget * widget,
+ GtkRequisition *requisition,
+ gpointer data);
+
+static void add_widget_to_fixed (GtkWidget * parent,
+ gint x,
+ gint y);
+static void add_widget_to_fixed_finish (GtkWidget * new_widget,
+ GbWidgetNewData * data);
+#if GLADE_SUPPORTS_GTK_PACKER
+static void add_widget_to_container (GtkWidget * parent);
+static void add_widget_to_container_finish (GtkWidget * new_widget,
+ GbWidgetNewData * data);
+#endif
+static gint expose_widget (GtkWidget * widget,
+ GdkEventExpose * event,
+ GladeWidgetData * wdata);
+static void draw_grid (GtkWidget * widget);
+static void paint_widget (GtkWidget * widget, GdkEventExpose *event);
+static void clear_child_windows (GdkWindow * window,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+static gint editor_idle_handler (GdkWindow *expose_win);
+
+static void delete (GtkWidget * widget);
+static void delete_placeholder (GtkWidget * widget);
+
+static void on_grid_settings_response (GtkWidget * widget,
+ gint response_id,
+ gpointer data);
+static void editor_redraw_component (GtkWidget * widget,
+ gpointer data);
+
+static void on_snap_settings_response (GtkWidget * widget,
+ gint response_id,
+ gpointer data);
+static gint snap_top_edge (gint y);
+static gint snap_bottom_edge (gint y);
+static gint snap_left_edge (gint x);
+static gint snap_right_edge (gint x);
+
+static gint get_position_in_widget (GtkWidget * widget,
+ gint x,
+ gint y);
+static void raise_fixed_child (GtkWidget * widget);
+
+static void find_child_at (GtkWidget * widget,
+ GbFindChildAtData * data);
+
+
+#if 0
+/* This is probably what we should have used to get all button presses and
+ releases on widget etc. But it may be too awkward to change now.
+ Note that we'll also get all signals for Glade's own widgets.
+
+ Actually I don't think this would help that much, since emission hooks
+ are run after RUN_FIRST class handlers, and you can't stop a signal from
+ them.
+
+ So we'd have to substitute a Glade function for all 'button_press' class
+ functions, and only call the original class functions when appropriate.
+ We'd still need to be able to stop signal handlers. Hmm. */
+static gboolean
+button_press_event_hook (GSignalInvocationHint *ihint,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer data)
+{
+ GtkWidget *widget;
+ GdkEventButton *event;
+
+ widget = g_value_get_object (param_values + 0);
+ event = g_value_get_boxed (param_values + 1);
+
+ g_print ("Watch: \"%s\" emitted for %s at %g,%g\n",
+ gtk_signal_name (ihint->signal_id),
+ gtk_type_name (GTK_OBJECT_TYPE (widget)),
+ event->x, event->y);
+
+ /* Test: try to stop the emission. Can't! */
+ /*g_signal_stop_emission (widget, ihint->signal_id, 0);*/
+
+
+ /* If we return FALSE our emission hook is removed. */
+ return TRUE;
+}
+
+
+static gboolean
+button_release_event_hook (GSignalInvocationHint *ihint,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer data)
+{
+ GtkWidget *widget;
+ GdkEvent *event;
+
+ widget = g_value_get_object (param_values + 0);
+ event = g_value_get_boxed (param_values + 1);
+
+ g_print ("Watch: \"%s\" emitted for %s type:%i\n",
+ gtk_signal_name (ihint->signal_id),
+ gtk_type_name (GTK_OBJECT_TYPE (widget)),
+ event->type);
+
+ /* If we return FALSE our emission hook is removed. */
+ return TRUE;
+}
+#endif
+
+void
+editor_init ()
+{
+ /* Create all cursors needed */
+ cursor_selector = gdk_cursor_new (GDK_TOP_LEFT_ARROW);
+ cursor_add_widget = gdk_cursor_new (GDK_PLUS);
+ cursor_add_to_fixed = gdk_cursor_new (GDK_TCROSS);
+ cursor_move = gdk_cursor_new (GDK_FLEUR);
+ cursor_top_left = gdk_cursor_new (GDK_TOP_LEFT_CORNER);
+ cursor_top_right = gdk_cursor_new (GDK_TOP_RIGHT_CORNER);
+ cursor_bottom_left = gdk_cursor_new (GDK_BOTTOM_LEFT_CORNER);
+ cursor_bottom_right = gdk_cursor_new (GDK_BOTTOM_RIGHT_CORNER);
+
+#if 0
+ {
+ guint button_press_signal_id, button_release_signal_id;
+
+ button_press_signal_id = gtk_signal_lookup ("button_press_event",
+ GTK_TYPE_WIDGET);
+ g_signal_add_emission_hook (button_press_signal_id, 0,
+ button_press_event_hook, NULL, NULL);
+
+ button_release_signal_id = gtk_signal_lookup ("button_release_event",
+ GTK_TYPE_WIDGET);
+ g_signal_add_emission_hook (button_release_signal_id, 0,
+ button_release_event_hook, NULL, NULL);
+ }
+#endif
+}
+
+gint
+editor_close_window (GtkWidget * widget,
+ GdkEvent * event,
+ gpointer data)
+{
+ glade_util_close_window (widget);
+ return TRUE;
+}
+
+
+/*
+ * Grid settings dialog
+ */
+gboolean
+editor_get_show_grid ()
+{
+ return editor_show_grid;
+}
+
+
+void
+editor_set_show_grid (gboolean show)
+{
+ if (editor_show_grid == show)
+ return;
+
+ editor_show_grid = show;
+ if (current_project != NULL)
+ glade_project_foreach_component (current_project, editor_redraw_component,
+ NULL);
+}
+
+
+void
+editor_show_grid_settings_dialog (GtkWidget *widget)
+{
+ GtkWidget *dialog, *vbox, *table, *label, *button, *spinbutton;
+ GtkObject *adjustment;
+ GSList *group;
+ GtkWindow *transient_parent;
+
+ transient_parent = (GtkWindow*) glade_util_get_toplevel (widget);
+ dialog = gtk_dialog_new_with_buttons (_("Grid Options"), transient_parent, 0,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+ NULL);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+ gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+ gtk_window_set_wmclass (GTK_WINDOW (dialog), "grid_options", "Glade");
+
+ vbox = GTK_DIALOG (dialog)->vbox;
+
+ table = gtk_table_new (2, 3, FALSE);
+ gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 5);
+ gtk_widget_show (table);
+
+ label = gtk_label_new (_("Horizontal Spacing:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
+ GTK_FILL, 0, 5, 5);
+ gtk_widget_show (label);
+
+ adjustment = gtk_adjustment_new (editor_grid_horz_spacing, 1, 1000, 1,
+ 10, 10);
+ spinbutton = glade_util_spin_button_new (GTK_OBJECT (dialog), "spinbutton1",
+ GTK_ADJUSTMENT (adjustment), 0, 0);
+ gtk_table_attach (GTK_TABLE (table), spinbutton, 1, 3, 0, 1,
+ GTK_EXPAND | GTK_FILL, 0, 5, 5);
+ gtk_widget_show (spinbutton);
+ gtk_widget_grab_focus (spinbutton);
+
+ label = gtk_label_new (_("Vertical Spacing:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2,
+ GTK_FILL, 0, 5, 5);
+ gtk_widget_show (label);
+
+ adjustment = gtk_adjustment_new (editor_grid_vert_spacing, 1, 1000, 1,
+ 10, 10);
+ spinbutton = glade_util_spin_button_new (GTK_OBJECT (dialog), "spinbutton2",
+ GTK_ADJUSTMENT (adjustment), 0, 0);
+ gtk_table_attach (GTK_TABLE (table), spinbutton, 1, 3, 1, 2,
+ GTK_EXPAND | GTK_FILL, 0, 5, 5);
+ gtk_widget_show (spinbutton);
+
+ table = gtk_table_new (1, 3, FALSE);
+ gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 0);
+ gtk_widget_show (table);
+
+ label = gtk_label_new (_("Grid Style:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
+ GTK_FILL, 0, 5, 5);
+ gtk_widget_show (label);
+
+ button = gtk_radio_button_new_with_label (NULL, _("Dots"));
+ if (editor_grid_style == GB_GRID_DOTS)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+ gtk_table_attach (GTK_TABLE (table), button, 1, 2, 0, 1,
+ GTK_EXPAND | GTK_FILL, 0, 5, 5);
+ gtk_widget_show (button);
+ gtk_object_set_data (GTK_OBJECT (dialog), "button1", button);
+ group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
+
+ button = gtk_radio_button_new_with_label (group, _("Lines"));
+ if (editor_grid_style == GB_GRID_LINES)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+ gtk_table_attach (GTK_TABLE (table), button, 2, 3, 0, 1,
+ GTK_EXPAND | GTK_FILL, 0, 5, 1);
+ gtk_widget_show (button);
+ gtk_object_set_data (GTK_OBJECT (dialog), "button2", button);
+
+ gtk_signal_connect (GTK_OBJECT (dialog), "response",
+ GTK_SIGNAL_FUNC (on_grid_settings_response),
+ NULL);
+
+ gtk_widget_show (dialog);
+}
+
+
+static void
+on_grid_settings_response (GtkWidget * widget, gint response_id, gpointer data)
+{
+ GtkWidget *dialog, *spinbutton, *button;
+
+ dialog = gtk_widget_get_toplevel (widget);
+
+ if (response_id != GTK_RESPONSE_OK)
+ {
+ gtk_widget_destroy (dialog);
+ return;
+ }
+
+ spinbutton = gtk_object_get_data (GTK_OBJECT (dialog), "spinbutton1");
+ editor_grid_horz_spacing = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON
+ (spinbutton));
+ spinbutton = gtk_object_get_data (GTK_OBJECT (dialog), "spinbutton2");
+ editor_grid_vert_spacing = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON
+ (spinbutton));
+
+ button = gtk_object_get_data (GTK_OBJECT (dialog), "button1");
+ if (GTK_TOGGLE_BUTTON (button)->active)
+ editor_grid_style = GB_GRID_DOTS;
+ else
+ editor_grid_style = GB_GRID_LINES;
+
+ gtk_widget_destroy (dialog);
+
+ /* redraw all windows */
+ if (current_project != NULL)
+ glade_project_foreach_component (current_project, editor_redraw_component,
+ NULL);
+}
+
+
+static void
+editor_redraw_component (GtkWidget * widget, gpointer data)
+{
+ gtk_widget_queue_draw (widget);
+}
+
+
+/*
+ * Snap settings
+ */
+gboolean
+editor_get_snap_to_grid ()
+{
+ return editor_snap_to_grid;
+}
+
+
+void
+editor_set_snap_to_grid (gboolean snap)
+{
+ editor_snap_to_grid = snap;
+}
+
+
+void
+editor_show_snap_settings_dialog (GtkWidget *widget)
+{
+ GtkWidget *dialog, *vbox, *table, *label, *button;
+ GtkWindow *transient_parent;
+
+ transient_parent = (GtkWindow*) glade_util_get_toplevel (widget);
+ dialog = gtk_dialog_new_with_buttons (_("Snap Options"), transient_parent, 0,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+ NULL);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+ gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
+ gtk_window_set_wmclass (GTK_WINDOW (dialog), "snap_options", "Glade");
+
+ vbox = GTK_DIALOG (dialog)->vbox;
+
+ table = gtk_table_new (4, 2, FALSE);
+ gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 5);
+ gtk_widget_show (table);
+
+ /* Horizontal snapping */
+ label = gtk_label_new (_("Horizontal Snapping:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
+ GTK_FILL, 0, 5, 5);
+ gtk_widget_show (label);
+
+ button = gtk_check_button_new_with_label (_("Left"));
+ if (editor_snap_to_grid_x & GB_SNAP_LEFT)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+ gtk_table_attach (GTK_TABLE (table), button, 1, 2, 0, 1,
+ GTK_EXPAND | GTK_FILL, 0, 5, 5);
+ gtk_widget_show (button);
+ gtk_object_set_data (GTK_OBJECT (dialog), "button1", button);
+ gtk_widget_grab_focus (button);
+
+ button = gtk_check_button_new_with_label (_("Right"));
+ if (editor_snap_to_grid_x & GB_SNAP_RIGHT)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+ gtk_table_attach (GTK_TABLE (table), button, 1, 2, 1, 2,
+ GTK_EXPAND | GTK_FILL, 0, 5, 5);
+ gtk_widget_show (button);
+ gtk_object_set_data (GTK_OBJECT (dialog), "button2", button);
+
+ /* Vertical snapping */
+ label = gtk_label_new (_("Vertical Snapping:"));
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3,
+ GTK_FILL, 0, 5, 5);
+ gtk_widget_show (label);
+
+ button = gtk_check_button_new_with_label (_("Top"));
+ if (editor_snap_to_grid_y & GB_SNAP_TOP)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+ gtk_table_attach (GTK_TABLE (table), button, 1, 2, 2, 3,
+ GTK_EXPAND | GTK_FILL, 0, 5, 5);
+ gtk_widget_show (button);
+ gtk_object_set_data (GTK_OBJECT (dialog), "button3", button);
+
+ button = gtk_check_button_new_with_label (_("Bottom"));
+ if (editor_snap_to_grid_y & GB_SNAP_BOTTOM)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+ gtk_table_attach (GTK_TABLE (table), button, 1, 2, 3, 4,
+ GTK_EXPAND | GTK_FILL, 0, 5, 5);
+ gtk_widget_show (button);
+ gtk_object_set_data (GTK_OBJECT (dialog), "button4", button);
+
+ gtk_signal_connect (GTK_OBJECT (dialog), "response",
+ GTK_SIGNAL_FUNC (on_snap_settings_response),
+ NULL);
+
+ gtk_widget_show (dialog);
+}
+
+
+static void
+on_snap_settings_response (GtkWidget * widget, gint response_id, gpointer data)
+{
+ GtkWidget *dialog, *button;
+
+ dialog = gtk_widget_get_toplevel (widget);
+
+ if (response_id != GTK_RESPONSE_OK)
+ {
+ gtk_widget_destroy (dialog);
+ return;
+ }
+
+ editor_snap_to_grid_x = 0;
+ editor_snap_to_grid_y = 0;
+ button = gtk_object_get_data (GTK_OBJECT (dialog), "button1");
+ if (GTK_TOGGLE_BUTTON (button)->active)
+ editor_snap_to_grid_x |= GB_SNAP_LEFT;
+ button = gtk_object_get_data (GTK_OBJECT (dialog), "button2");
+ if (GTK_TOGGLE_BUTTON (button)->active)
+ editor_snap_to_grid_x |= GB_SNAP_RIGHT;
+ button = gtk_object_get_data (GTK_OBJECT (dialog), "button3");
+ if (GTK_TOGGLE_BUTTON (button)->active)
+ editor_snap_to_grid_y |= GB_SNAP_TOP;
+ button = gtk_object_get_data (GTK_OBJECT (dialog), "button4");
+ if (GTK_TOGGLE_BUTTON (button)->active)
+ editor_snap_to_grid_y |= GB_SNAP_BOTTOM;
+
+ gtk_widget_destroy (dialog);
+}
+
+
+static gint
+snap_top_edge (gint y)
+{
+ if (editor_snap_to_grid && (editor_snap_to_grid_y & GB_SNAP_TOP))
+ {
+ y += editor_grid_vert_spacing / 2;
+ y -= y % editor_grid_vert_spacing;
+ }
+ return y;
+}
+
+
+static gint
+snap_bottom_edge (gint y)
+{
+ if (editor_snap_to_grid && (editor_snap_to_grid_y & GB_SNAP_BOTTOM))
+ {
+ y += editor_grid_vert_spacing / 2;
+ y -= y % editor_grid_vert_spacing;
+ }
+ return y;
+}
+
+
+static gint
+snap_left_edge (gint x)
+{
+ if (editor_snap_to_grid && (editor_snap_to_grid_x & GB_SNAP_LEFT))
+ {
+ x += editor_grid_horz_spacing / 2;
+ x -= x % editor_grid_horz_spacing;
+ }
+ return x;
+}
+
+
+static gint
+snap_right_edge (gint x)
+{
+ if (editor_snap_to_grid && (editor_snap_to_grid_x & GB_SNAP_RIGHT))
+ {
+ x += editor_grid_horz_spacing / 2;
+ x -= x % editor_grid_horz_spacing;
+ }
+ return x;
+}
+
+
+
+
+static void
+on_placeholder_size_allocate (GtkWidget * widget,
+ GtkAllocation *allocation,
+ gpointer data)
+{
+#if 0
+ g_print ("In on_placeholder_size_allocate %i,%i %ix%i\n",
+ allocation->x, allocation->y,
+ allocation->width, allocation->height);
+#endif
+}
+
+
+GtkWidget *
+editor_new_placeholder ()
+{
+ GtkWidget *placeholder = gtk_drawing_area_new ();
+ gtk_widget_set_events (placeholder,
+ gtk_widget_get_events (placeholder)
+ | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK
+ | GDK_POINTER_MOTION_MASK | GDK_BUTTON1_MOTION_MASK);
+ gtk_widget_set_usize (placeholder, PLACEHOLDER_WIDTH, PLACEHOLDER_HEIGHT);
+ gtk_widget_show (placeholder);
+ editor_add_draw_signals (placeholder);
+ editor_add_mouse_signals (placeholder);
+ gtk_object_set_data (GTK_OBJECT (placeholder), GB_PLACEHOLDER_KEY, "True");
+
+ /* GnomeDock workaround. */
+ gtk_signal_connect_after (GTK_OBJECT (placeholder), "size_request",
+ GTK_SIGNAL_FUNC (on_placeholder_size_request),
+ NULL);
+
+ /* FIXME: Just a test. */
+ gtk_signal_connect_after (GTK_OBJECT (placeholder), "size_allocate",
+ GTK_SIGNAL_FUNC (on_placeholder_size_allocate),
+ NULL);
+
+
+ gtk_signal_connect (GTK_OBJECT (placeholder), "destroy",
+ GTK_SIGNAL_FUNC (on_placeholder_destroy), NULL);
+ return placeholder;
+}
+
+
+static void
+on_placeholder_destroy (GtkWidget * widget,
+ gpointer data)
+{
+ MSG ("IN on_placeholder_destroy");
+ /* If the entire project is being destroyed, we don't need to update the
+ selection or the widget tree. */
+
+ /* FIXME: GTK+2 */
+ if (!(GTK_OBJECT_FLAGS (current_project) & GTK_IN_DESTRUCTION))
+ editor_remove_widget_from_selection (widget);
+}
+
+
+/* This is a gnome-libs 1.0.5 bug workaround - GnomeDockItem doesn't use
+ the child requisition properly. */
+static void
+on_placeholder_size_request (GtkWidget * widget,
+ GtkRequisition *requisition,
+ gpointer data)
+{
+ MSG ("IN on_placeholder_size_request");
+#ifdef USE_GNOME
+ if (BONOBO_IS_DOCK_ITEM (widget->parent))
+ {
+ requisition->width = PLACEHOLDER_WIDTH;
+ requisition->height = PLACEHOLDER_HEIGHT;
+ }
+#endif
+}
+
+
+static void
+placeholder_replace (GtkWidget * placeholder)
+{
+ char *class_name;
+
+ class_name = glade_palette_get_widget_class (GLADE_PALETTE (glade_palette));
+ g_return_if_fail (class_name != NULL);
+ glade_palette_reset_selection (GLADE_PALETTE (glade_palette), TRUE);
+ gb_widget_new_full (class_name, TRUE, placeholder->parent, placeholder, 0, 0,
+ placeholder_finish_replace, GB_CREATING, NULL);
+}
+
+
+static void
+placeholder_finish_replace (GtkWidget * new_widget, GbWidgetNewData * data)
+{
+ editor_clear_selection (NULL);
+
+ /* GtkToolItem widgets can only be added into toolbars and cause problems
+ inside other containers, so check that here. Note that we have to use
+ special tool items for placeholders so data->parent is usually a
+ GtkToolItem rather than a GtkToolbar. */
+ if (GTK_IS_TOOL_ITEM (new_widget)
+ && (!data->parent || (!GTK_IS_TOOLBAR (data->parent)
+ && !GTK_IS_TOOL_ITEM (data->parent))))
+ {
+ glade_util_show_message_box (_("GtkToolItem widgets can only be added to a GtkToolbar."), data->parent);
+ gtk_widget_destroy (new_widget);
+ return;
+ }
+
+ /* If a scrollable widget is added, we automatically add a parent scrolled
+ window, if there isn't one already. */
+ if (GTK_WIDGET_CLASS (G_OBJECT_GET_CLASS (new_widget))->set_scroll_adjustments_signal)
+ {
+ if (data->parent && !GTK_IS_SCROLLED_WINDOW (data->parent))
+ {
+ GtkWidget *scrolledwin;
+
+ scrolledwin = gb_widget_new ("GtkScrolledWindow", data->parent);
+ if (!gb_widget_replace_child (data->parent, data->current_child,
+ scrolledwin))
+ {
+ glade_util_show_message_box (_("Couldn't insert a GtkScrolledWindow widget."), data->parent);
+ gtk_widget_destroy (scrolledwin);
+ gtk_widget_destroy (new_widget);
+ return;
+ }
+
+ if (GTK_BIN (scrolledwin)->child)
+ gtk_container_remove (GTK_CONTAINER (scrolledwin),
+ GTK_BIN (scrolledwin)->child);
+ gtk_container_add (GTK_CONTAINER (scrolledwin), new_widget);
+
+ /* Set the shadow to In, since that is normal. */
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwin),
+ GTK_SHADOW_IN);
+
+ /* Set both scrollbar policies to automatic for GtkIconView. */
+ if (GTK_IS_ICON_VIEW (new_widget))
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ /* GtkText doesn't support horizontal scrolling, so we may as well
+ get rid of the scrollbar. */
+#if GLADE_SUPPORTS_GTK_TEXT
+ if (GTK_IS_TEXT (new_widget))
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwin),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_ALWAYS);
+#endif
+
+ tree_add_widget (scrolledwin);
+ tree_add_widget (new_widget);
+ gb_widget_show_properties (new_widget);
+ glade_project_set_changed (current_project, TRUE);
+ return;
+ }
+ }
+ /* If a non-scrollable widget is added to a GtkScrolledWindow, we add a
+ viewport automatically. */
+ else if (data->parent && GTK_IS_SCROLLED_WINDOW (data->parent))
+ {
+ GtkWidget *viewport;
+
+ viewport = gb_widget_new ("GtkViewport", data->parent);
+ if (!gb_widget_replace_child (data->parent, data->current_child,
+ viewport))
+ {
+ glade_util_show_message_box (_("Couldn't insert a GtkViewport widget."), data->parent);
+ gtk_widget_destroy (viewport);
+ gtk_widget_destroy (new_widget);
+ return;
+ }
+
+ if (GTK_BIN (viewport)->child)
+ gtk_container_remove (GTK_CONTAINER (viewport),
+ GTK_BIN (viewport)->child);
+ gtk_container_add (GTK_CONTAINER (viewport), new_widget);
+
+ tree_add_widget (viewport);
+ tree_add_widget (new_widget);
+ gb_widget_show_properties (new_widget);
+ glade_project_set_changed (current_project, TRUE);
+ return;
+ }
+
+ /* Replace placeholder in parent container */
+ if (gb_widget_replace_child (data->parent, data->current_child, new_widget))
+ {
+ gb_widget_show_properties (new_widget);
+ tree_add_widget (new_widget);
+ glade_project_set_changed (current_project, TRUE);
+ }
+ else
+ {
+ glade_util_show_message_box (_("Couldn't add new widget."),
+ data->parent);
+
+ /* I think we can safely destroy the new widget. */
+ gtk_widget_destroy (new_widget);
+ }
+}
+
+
+
+
+
+/* Note: returns last found child if children overlap */
+static void
+find_child_at (GtkWidget * widget, GbFindChildAtData * data)
+{
+#if 0
+ g_print ("In find_child_at: %s X:%i Y:%i W:%i H:%i\n",
+ gtk_widget_get_name (widget),
+ widget->allocation.x, widget->allocation.y,
+ widget->allocation.width, widget->allocation.height);
+#endif
+ /* Notebook pages are visible but not mapped if they are not showing. */
+ if (GTK_WIDGET_VISIBLE (widget) && GTK_WIDGET_MAPPED (widget)
+ && (widget->allocation.x <= data->x)
+ && (widget->allocation.y <= data->y)
+ && (widget->allocation.x + widget->allocation.width > data->x)
+ && (widget->allocation.y + widget->allocation.height > data->y))
+ {
+#if 0
+ g_print ("found child:%s", gtk_widget_get_name (widget));
+#endif
+ data->found_child = widget;
+ }
+}
+
+
+/* Checks if point is in the given notebook's tabs. */
+static void
+find_notebook_tab (GtkWidget * widget, GbFindChildAtData * data)
+{
+ gint nchildren, i;
+ GtkWidget *child_page, *child_tab;
+
+ /* FIXME: Check this works. I had to change it for GTK+ 2. */
+ nchildren = g_list_length (GTK_NOTEBOOK (widget)->children);
+ for (i = 0; i < nchildren; i++)
+ {
+ child_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (widget), i);
+ child_tab = gtk_notebook_get_tab_label (GTK_NOTEBOOK (widget),
+ child_page);
+ if (child_tab
+ && (child_tab->allocation.x <= data->x)
+ && (child_tab->allocation.y <= data->y)
+ && (child_tab->allocation.x + child_tab->allocation.width > data->x)
+ && (child_tab->allocation.y + child_tab->allocation.height > data->y))
+ {
+ /* Make sure this is a GbWidget. */
+ /*if (GB_IS_GB_WIDGET (child_tab))*/
+ data->found_child = child_tab;
+ }
+ }
+}
+
+
+/* This function is passed a widget which has received a mouse event, and
+ the coordinates of that event. It returns the widget which the event is
+ really meant for (which could be a descendent of the given widget), and
+ the position of the event in the widget's allocated area. */
+static GtkWidget *
+editor_get_event_widget (GtkWidget *widget, GdkWindow *window, gint x, gint y,
+ gint * x_return, gint * y_return)
+{
+ GbFindChildAtData data;
+ gint win_x, win_y, saved_x, saved_y;
+ GtkWidget *found_gbwidget = NULL, *saved_widget;
+ gint found_x = 0, found_y = 0;
+ GdkWindow *parent_window = NULL;
+
+#if 0
+ g_print ("\n\nOriginal:%s X:%i Y:%i\n", gtk_widget_get_name (widget), x, y);
+ if (widget->parent)
+ g_print (" Parent: %s\n", gtk_widget_get_name (widget->parent));
+#endif
+
+ /* FIXME: GTK bug workaround? - need to translate coords if mouse button was
+ pressed in a child window. */
+ /* Remember widgets can have other windows besides their main one, and
+ when dragging the event may be sent to the parent's window? */
+ /* SPECIAL CODE: GnomeDockItem widgets which are floating use a different
+ window. */
+ if (widget->parent)
+ {
+ if (GTK_IS_LAYOUT (widget->parent))
+ parent_window = GTK_LAYOUT (widget->parent)->bin_window;
+
+#ifdef USE_GNOME
+ if (BONOBO_IS_DOCK_ITEM (widget)
+ && BONOBO_DOCK_ITEM (widget)->is_floating)
+ {
+ parent_window = BONOBO_DOCK_ITEM (widget)->float_window;
+ }
+ else if (BONOBO_IS_DOCK_ITEM (widget->parent)
+ && BONOBO_DOCK_ITEM (widget->parent)->is_floating)
+ {
+ parent_window = BONOBO_DOCK_ITEM (widget->parent)->float_window;
+ }
+#endif
+ }
+
+ if (!parent_window)
+ parent_window = widget->parent ? widget->parent->window : widget->window;
+
+ while (window && window != parent_window)
+ {
+ gdk_window_get_position (window, &win_x, &win_y);
+#if 0
+ g_print (" adding X:%i Y:%i\n", win_x, win_y);
+#endif
+ x += win_x;
+ y += win_y;
+ window = gdk_window_get_parent (window);
+ }
+ if (window != parent_window)
+ {
+ MSG (" editor_get_event_widget - unknown window");
+ return NULL;
+ }
+
+#if 0
+ g_print (" Translated X:%i Y:%i\n", x, y);
+#endif
+
+ /* We now have correct coordinates relative to the parent's window,
+ i.e. in the same coordinate space as the widget's allocation.
+ Now we find out which widget this event is really for.
+ We step down the widget tree, trying to find the widget at the given
+ position. We have to translate coordinates for children of widgets with
+ windows. We may need to use bin_window for viewport. */
+ if (GB_IS_GB_WIDGET (widget) || GB_IS_PLACEHOLDER (widget))
+ {
+#if 0
+ g_print ("Found child:%s\n", gtk_widget_get_name (widget));
+#endif
+ found_gbwidget = widget;
+ found_x = x;
+ found_y = y;
+ }
+
+ /* Save the widget and the x & y coords. */
+ saved_widget = widget;
+ saved_x = x;
+ saved_y = y;
+
+ /* Now we want to convert the coordinates into the widget's childrens'
+ coordinate space, so if the widget has a window, we have to subtract the
+ position of it (since the child coordinates are relative to that).
+ Viewports need special treatment. */
+ if (!GTK_WIDGET_NO_WINDOW (widget) && widget->parent)
+ {
+ gdk_window_get_position (widget->window, &win_x, &win_y);
+ x -= win_x;
+ y -= win_y;
+
+ /* SPECIAL CODE: need to translate to bin_window for a viewport. */
+ if (GTK_IS_VIEWPORT (widget))
+ {
+ gdk_window_get_position (GTK_VIEWPORT (widget)->bin_window,
+ &win_x, &win_y);
+ x -= win_x;
+ y -= win_y;
+ }
+
+ /* SPECIAL CODE: need to translate to bin_window for a layout. */
+ if (GTK_IS_LAYOUT (widget))
+ {
+ gdk_window_get_position (GTK_LAYOUT (widget)->bin_window,
+ &win_x, &win_y);
+ x -= win_x;
+ y -= win_y;
+ }
+#if 0
+ g_print (" Translated X:%i Y:%i\n", x, y);
+#endif
+ }
+
+ for (;;)
+ {
+ if (!GTK_IS_CONTAINER (widget) || GTK_IS_MENU_BAR (widget))
+ break;
+ data.x = x;
+ data.y = y;
+ data.found_child = NULL;
+#if 0
+ g_print ("...Finding child widget\n");
+#endif
+ gtk_container_forall (GTK_CONTAINER (widget),
+ (GtkCallback) find_child_at, &data);
+ /* SPECIAL CODE - Check for notebook tabs. */
+ if (GTK_IS_NOTEBOOK (widget))
+ find_notebook_tab (widget, &data);
+
+ if (data.found_child)
+ {
+#if 0
+ g_print ("Found child:%s\n", gtk_widget_get_name (data.found_child));
+#endif
+ widget = data.found_child;
+ if (GB_IS_GB_WIDGET (widget) || GB_IS_PLACEHOLDER (widget))
+ {
+ found_gbwidget = widget;
+ found_x = x;
+ found_y = y;
+ }
+ }
+ else
+ break;
+
+ if (!GTK_WIDGET_NO_WINDOW (widget))
+ {
+ gdk_window_get_position (widget->window, &win_x, &win_y);
+ x -= win_x;
+ y -= win_y;
+
+ /* SPECIAL CODE: need to translate to bin_window for a viewport. */
+ if (GTK_IS_VIEWPORT (widget))
+ {
+ gdk_window_get_position (GTK_VIEWPORT (widget)->bin_window,
+ &win_x, &win_y);
+ x -= win_x;
+ y -= win_y;
+ }
+#if 0
+ g_print (" Translated X:%i Y:%i\n", x, y);
+#endif
+ }
+ }
+
+ /* If we haven't found a GbWidget yet, try moving up the hierarchy. */
+ widget = saved_widget;
+ x = saved_x;
+ y = saved_y;
+ while (!found_gbwidget && widget->parent)
+ {
+ widget = widget->parent;
+#if 0
+ g_print (" Trying parent: %s X:%i Y:%i\n",
+ gtk_widget_get_name (widget), x, y);
+#endif
+ if (GB_IS_GB_WIDGET (widget) || GB_IS_PLACEHOLDER (widget))
+ {
+ found_gbwidget = widget;
+ found_x = x;
+ found_y = y;
+ }
+ else if (!GTK_WIDGET_NO_WINDOW (widget))
+ {
+ gdk_window_get_position (widget->window, &win_x, &win_y);
+ x += win_x;
+ y += win_y;
+
+ /* SPECIAL CODE; use bin_window for viewport. */
+ if (GTK_IS_VIEWPORT (widget))
+ {
+ gdk_window_get_position (GTK_VIEWPORT (widget)->bin_window,
+ &win_x, &win_y);
+ x += win_x;
+ y += win_y;
+ }
+#if 0
+ g_print (" Translated X:%i Y:%i\n", x, y);
+#endif
+ }
+ }
+
+ if (!found_gbwidget)
+ return NULL;
+
+ *x_return = found_x - found_gbwidget->allocation.x;
+ *y_return = found_y - found_gbwidget->allocation.y;
+
+#if 0
+ g_print (" Event widget: %s X:%i Y:%i\n",
+ gtk_widget_get_name (found_gbwidget), *x_return, *y_return);
+#endif
+
+ return found_gbwidget;
+}
+
+
+/* We use the "event" signal since it is emitted before the "button_press"
+ or "button_release" signal is emitted. This means we can catch it and
+ stop the normal signal handlers. */
+static gint
+editor_on_event (GtkWidget * signal_widget,
+ GdkEvent * event,
+ gpointer user_data)
+{
+#if 0
+ g_print ("In editor_on_event widget: %s\n",
+ gtk_widget_get_name (signal_widget));
+#endif
+
+ if (event->type == GDK_BUTTON_PRESS
+ || event->type == GDK_2BUTTON_PRESS
+ || event->type == GDK_3BUTTON_PRESS)
+ return editor_on_button_press (signal_widget, (GdkEventButton*) event,
+ user_data);
+
+ if (event->type == GDK_BUTTON_RELEASE)
+ return editor_on_button_release (signal_widget, event, user_data);
+
+ return FALSE;
+}
+
+
+static gint
+editor_on_button_press (GtkWidget * signal_widget,
+ GdkEventButton * event,
+ gpointer user_data)
+{
+ GtkWidget *widget;
+ gint x, y;
+
+#if 0
+ g_print ("In editor_on_button_press widget: %s\n",
+ gtk_widget_get_name (signal_widget));
+#endif
+ widget = editor_get_event_widget (signal_widget, event->window,
+ event->x, event->y, &x, &y);
+ if (widget == NULL)
+ return FALSE;
+
+ if (editor_check_ignore_event (widget, (GdkEventAny*) event))
+ return FALSE;
+
+ MSG ("...Checking which button pressed");
+ /* We only want single button press events. */
+ if (event->button == 1 && event->type == GDK_BUTTON_PRESS)
+ {
+ if (glade_palette_is_selector_on (GLADE_PALETTE (glade_palette)))
+ {
+ gboolean handled;
+ MSG ("...Selecting widget");
+ handled = editor_select_widget (widget, event, x, y);
+#if 0
+ if (handled)
+ gtk_signal_emit_stop_by_name (GTK_OBJECT (signal_widget),
+ "button_press_event");
+#endif
+ return handled;
+ }
+#ifdef USE_GNOME
+ else if (GTK_IS_FIXED (widget)
+ || (GTK_IS_LAYOUT (widget) && !GNOME_IS_CANVAS (widget)))
+#else
+ else if (GTK_IS_FIXED (widget) || GTK_IS_LAYOUT (widget))
+#endif
+ {
+ add_widget_to_fixed (widget, x, y);
+#if 0
+ gtk_signal_emit_stop_by_name (GTK_OBJECT (signal_widget),
+ "button_press_event");
+#endif
+ return TRUE;
+ }
+ else if (GB_IS_PLACEHOLDER (widget))
+ {
+ placeholder_replace (widget);
+#if 0
+ gtk_signal_emit_stop_by_name (GTK_OBJECT (signal_widget),
+ "button_press_event");
+#endif
+ return TRUE;
+ }
+#if GLADE_SUPPORTS_GTK_PACKER
+ else if (GTK_IS_PACKER (widget))
+ {
+ add_widget_to_container (widget);
+#if 0
+ gtk_signal_emit_stop_by_name (GTK_OBJECT (signal_widget),
+ "button_press_event");
+#endif
+ return TRUE;
+ }
+#endif
+ else
+ {
+ static gboolean already_shown = FALSE;
+ if (already_shown)
+ {
+ /* Beep if user does mistake of invalid positioning from second
+ * time.
+ */
+ gdk_beep ();
+ }
+ else
+ {
+ glade_util_show_message_box (_("You can't add a widget at the selected position.\n"
+ "\n"
+ "Tip: GTK+ uses containers to lay out widgets.\n"
+ "Try deleting the existing widget and using\n"
+ "a box or table container instead.\n"), widget);
+ already_shown = TRUE;
+ }
+ }
+ }
+ else if (event->button == 3)
+ {
+ gb_widget_show_popup_menu (widget, event);
+#if 0
+ gtk_signal_emit_stop_by_name (GTK_OBJECT (signal_widget),
+ "button_press_event");
+#endif
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+static gint
+editor_on_button_release (GtkWidget * widget,
+ GdkEvent * event,
+ gpointer data)
+{
+ MSG ("In editor_on_button_release");
+ if (drag_has_pointer_grab)
+ {
+#if 0
+ g_print ("###### Releasing pointer grab\n");
+#endif
+ gdk_pointer_ungrab (event->button.time);
+ drag_has_pointer_grab = FALSE;
+ }
+
+ if (dragging_widget)
+ {
+ drag_action = GB_DRAG_NONE;
+ MSG (" removing grab");
+ gtk_grab_remove (dragging_widget);
+ dragging_widget = NULL;
+ }
+ return FALSE;
+}
+
+
+/* This adds a new widget to a GtkFixed or GtkLayout container. */
+static void
+add_widget_to_fixed (GtkWidget * parent, gint x, gint y)
+{
+ char *class_name;
+
+ class_name = glade_palette_get_widget_class (GLADE_PALETTE (glade_palette));
+ g_return_if_fail (class_name != NULL);
+ glade_palette_reset_selection (GLADE_PALETTE (glade_palette), TRUE);
+ gb_widget_new_full (class_name, TRUE, parent, NULL, x, y,
+ add_widget_to_fixed_finish, GB_CREATING, NULL);
+}
+
+
+/* This finishes off adding a new widget to a GtkFixed or GtkLayout container,
+ possibly after a dialog has been shown to set some initial properties of
+ the new widget (e.g. number of rows for a new GtkCList). */
+static void
+add_widget_to_fixed_finish (GtkWidget * new_widget, GbWidgetNewData * data)
+{
+ GtkRequisition requisition = {0, 0};
+ GladeWidgetData *wdata;
+ GtkWidget *parent = data->parent;
+ gint x = data->x;
+ gint y = data->y;
+ gint w, h;
+
+ g_return_if_fail (parent != NULL);
+ g_return_if_fail (GTK_IS_FIXED (parent) || GTK_IS_LAYOUT (parent));
+
+ /* I think we should do this first, in case the widget needs to be
+ realized in order to calculate its requisition. */
+ gtk_widget_hide (new_widget);
+
+ if (GTK_IS_FIXED (data->parent))
+ {
+ gtk_fixed_put (GTK_FIXED (data->parent), new_widget, x, y);
+ /*gtk_widget_set_uposition (new_widget, x, y);*/
+ }
+ else if (GTK_IS_LAYOUT (data->parent))
+ {
+ GtkLayout *layout = GTK_LAYOUT (data->parent);
+ x += layout->hadjustment->value;
+ y += layout->vadjustment->value;
+ gtk_layout_put (layout, new_widget, x, y);
+ /*gtk_widget_set_uposition (new_widget, x, y);*/
+ }
+
+
+ gtk_widget_size_request (new_widget, &requisition);
+ w = requisition.width;
+ h = requisition.height;
+ if (w < MIN_WIDGET_WIDTH || h < MIN_WIDGET_HEIGHT)
+ {
+ MSG3 ("Default widget size too small: %s W:%i H:%i\n",
+ gtk_widget_get_name (new_widget), w, h);
+ }
+ if (w > MAX_INITIAL_WIDGET_WIDTH || h > MAX_INITIAL_WIDGET_HEIGHT)
+ {
+ MSG3 ("Default widget size too big: %s W:%i H:%i\n",
+ gtk_widget_get_name (new_widget), w, h);
+ }
+
+ if (w == 0)
+ w = DEFAULT_WIDGET_WIDTH;
+ if (h == 0)
+ h = DEFAULT_WIDGET_HEIGHT;
+ w = MAX (w, MIN_WIDGET_WIDTH);
+ w = MIN (w, MAX_INITIAL_WIDGET_WIDTH);
+ h = MAX (h, MIN_WIDGET_HEIGHT);
+ h = MIN (h, MAX_INITIAL_WIDGET_HEIGHT);
+
+ /* FIXME: Make sure a gamma curve is a reasonable size. */
+ if (GTK_IS_GAMMA_CURVE (new_widget))
+ w = 80;
+
+ /* FIXME: Make sure a clist/ctree or notebook is a reasonable size. */
+ if (GTK_IS_CLIST (new_widget) || GTK_IS_NOTEBOOK (new_widget))
+ {
+ w = 200;
+ h = 100;
+ }
+
+ /* Children of fixed widgets always have their x, y, width & height set
+ explicitly. */
+ wdata = gtk_object_get_data (GTK_OBJECT (new_widget), GB_WIDGET_DATA_KEY);
+ g_return_if_fail (wdata != NULL);
+ wdata->flags |= GLADE_WIDTH_SET | GLADE_HEIGHT_SET;
+
+ /* Calculate real position & size according to grid & snapping */
+ /* FIXME: only snaps top & left at present */
+ if (editor_snap_to_grid)
+ {
+ if (editor_snap_to_grid_x & GB_SNAP_LEFT)
+ {
+ /*x += editor_grid_horz_spacing / 2; */
+ x -= x % editor_grid_horz_spacing;
+ }
+ if (editor_snap_to_grid_y & GB_SNAP_TOP)
+ {
+ /*y += editor_grid_vert_spacing / 2; */
+ y -= y % editor_grid_vert_spacing;
+ }
+ }
+
+ gb_widget_set_usize (new_widget, w, h);
+ wdata->width = w;
+ wdata->height = h;
+
+ if (GTK_IS_FIXED (data->parent))
+ {
+ gtk_fixed_move (GTK_FIXED (data->parent), new_widget, x, y);
+ /*gtk_widget_set_uposition (new_widget, x, y);*/
+ }
+ else if (GTK_IS_LAYOUT (data->parent))
+ {
+ gtk_layout_move (GTK_LAYOUT (data->parent), new_widget, x, y);
+ /*gtk_widget_set_uposition (new_widget, x, y);*/
+ }
+
+ gtk_widget_show (new_widget);
+ gb_widget_show_properties (new_widget);
+ tree_add_widget (new_widget);
+ glade_project_set_changed (data->project, TRUE);
+}
+
+
+#if GLADE_SUPPORTS_GTK_PACKER
+static void
+add_widget_to_container (GtkWidget * parent)
+{
+ char *class_name;
+
+ class_name = glade_palette_get_widget_class (GLADE_PALETTE (glade_palette));
+ g_return_if_fail (class_name != NULL);
+ glade_palette_reset_selection (GLADE_PALETTE (glade_palette), TRUE);
+ gb_widget_new_full (class_name, TRUE, parent, NULL, 0, 0,
+ add_widget_to_container_finish, GB_CREATING, NULL);
+}
+
+
+static void
+add_widget_to_container_finish (GtkWidget * new_widget, GbWidgetNewData * data)
+{
+ gtk_container_add (GTK_CONTAINER (data->parent), new_widget);
+ gtk_widget_show (new_widget);
+ gb_widget_show_properties (new_widget);
+ tree_add_widget (new_widget);
+ glade_project_set_changed (data->project, TRUE);
+}
+#endif
+
+/*
+ * Clears all currently selected widgets, except the given widget.
+ * Returns TRUE if given widget is selected.
+ */
+gint
+editor_clear_selection (GtkWidget * leave_widget)
+{
+ GList *child = selected_widgets, *next_child;
+ GtkWidget *widget;
+ gint selected = FALSE;
+
+ while (child)
+ {
+ next_child = child->next;
+ widget = child->data;
+ if (widget == leave_widget)
+ {
+ selected = TRUE;
+ }
+ else
+ {
+ selected_widgets = g_list_remove (selected_widgets, widget);
+ tree_select_widget (widget, FALSE);
+ editor_refresh_widget_selection (widget);
+ }
+ child = next_child;
+ }
+ return selected;
+}
+
+
+void
+editor_remove_widget_from_selection (GtkWidget * widget)
+{
+ selected_widgets = g_list_remove (selected_widgets, widget);
+
+ /* If the widget is not being destroyed, we deselect it in the tree.
+ If it is being destroyed, it will get removed from the tree so we don't
+ have to bother. */
+ if (!(GTK_OBJECT_FLAGS (widget) & GTK_IN_DESTRUCTION))
+ tree_select_widget (widget, FALSE);
+}
+
+
+void
+editor_deselect_all_placeholders (void)
+{
+ GList *child, *next_child;
+ GtkWidget *widget;
+
+ child = selected_widgets;
+ while (child)
+ {
+ next_child = child->next;
+ widget = child->data;
+
+ if (GB_IS_PLACEHOLDER (widget))
+ {
+ selected_widgets = g_list_remove (selected_widgets, widget);
+ editor_refresh_widget_selection (widget);
+ }
+ child = next_child;
+ }
+}
+
+
+gboolean
+editor_is_selected (GtkWidget *widget)
+{
+ GList *elem = selected_widgets;
+
+ while (elem)
+ {
+ if (GTK_WIDGET (elem->data) == widget)
+ return TRUE;
+ elem = elem->next;
+ }
+ return FALSE;
+}
+
+
+void
+editor_dump_selection (void)
+{
+ GList *elem = selected_widgets;
+
+ g_print ("Selected widgets:\n");
+ while (elem)
+ {
+ g_print (" %p: %s\n", elem->data,
+ gtk_widget_get_name (GTK_WIDGET (elem->data)));
+ elem = elem->next;
+ }
+}
+
+
+static gint
+get_position_in_widget (GtkWidget * widget, gint x, gint y)
+{
+ gint width = widget->allocation.width;
+ gint height = widget->allocation.height;
+ if (x < GB_CORNER_WIDTH && y < GB_CORNER_HEIGHT)
+ return GB_TOP_LEFT;
+ if (x > width - GB_CORNER_WIDTH && y < GB_CORNER_HEIGHT)
+ return GB_TOP_RIGHT;
+ if (x < GB_CORNER_WIDTH && y > height - GB_CORNER_HEIGHT)
+ return GB_BOTTOM_LEFT;
+ if (x > width - GB_CORNER_WIDTH && y > height - GB_CORNER_HEIGHT)
+ return GB_BOTTOM_RIGHT;
+ return GB_MIDDLE;
+}
+
+
+static void
+raise_fixed_child (GtkWidget * widget)
+{
+ GtkFixed *fixed;
+
+ g_return_if_fail (GTK_IS_FIXED (widget->parent));
+ fixed = GTK_FIXED (widget->parent);
+ /* If widget hasn't got a window, move it to the back of the parent fixed's
+ children. If it has got a window, raise it. */
+ /* Note: this is slightly naughty as it changes the GtkFixed's GList of
+ children, but it's better than removing the widget and adding it again. */
+ if (GTK_WIDGET_NO_WINDOW (widget))
+ {
+ GList *child;
+ GtkFixedChild *data;
+ child = fixed->children;
+ while (child)
+ {
+ data = child->data;
+ if (data->widget == widget)
+ {
+ fixed->children = g_list_remove (fixed->children, data);
+ fixed->children = g_list_append (fixed->children, data);
+ break;
+ }
+ child = child->next;
+ }
+ }
+ else
+ {
+ gdk_window_raise (widget->window);
+ }
+}
+
+
+gboolean
+editor_select_widget_control (GtkWidget * widget)
+{
+ MSG ("IN editor_select_widget_control");
+ /* If widget is currently selected, deslect it, else add it to
+ selected. */
+ if (g_list_find (selected_widgets, widget))
+ {
+ selected_widgets = g_list_remove (selected_widgets, widget);
+ }
+ else
+ {
+ gb_widget_show_properties (widget);
+ selected_widgets = g_list_append (selected_widgets, widget);
+ glade_project_view_clear_component_selection (current_project_view,
+ widget);
+ }
+ editor_refresh_widget_selection (widget);
+ return TRUE;
+}
+
+/* FIXME: handle widget-tree better now it has extended selection */
+GList *
+editor_get_selection ()
+{
+ return g_list_first (selected_widgets);
+}
+
+
+/* This sets the list of selected widgets, possibly NULL. It takes control
+ of the GList, so don't free it. */
+void
+editor_set_selection (GList *new_selection)
+{
+ GList *old_selection, *elem;
+
+ old_selection = selected_widgets;
+
+ selected_widgets = new_selection;
+
+ for (elem = old_selection; elem; elem = elem->next)
+ editor_refresh_widget_selection (elem->data);
+
+ for (elem = new_selection; elem; elem = elem->next)
+ {
+ /* Only refresh it if it wasn't already refreshed above. */
+ if (!g_list_find (old_selection, elem->data))
+ editor_refresh_widget_selection (elem->data);
+ }
+
+ /* If just one widget is selected, show its properties. */
+ if (g_list_length (new_selection) == 1)
+ gb_widget_show_properties (GTK_WIDGET (new_selection->data));
+
+ g_list_free (old_selection);
+}
+
+
+static gint
+get_notebook_page (GtkNotebook * notebook,
+ GtkWidget * widget)
+{
+ gint nchildren, page;
+
+ nchildren = g_list_length (notebook->children);
+
+ for (page = 0; page < nchildren; page++)
+ {
+ GtkWidget *child, *tab;
+
+ child = gtk_notebook_get_nth_page (notebook, page);
+ tab = gtk_notebook_get_tab_label (notebook, child);
+ if (tab == widget)
+ return page;
+ }
+
+ return -1;
+}
+
+
+
+/* FIXME: When the editor is rewritten as a GtkObject, we need simple
+ functions to select/deselect widgets separate from the complicated
+ stuff used for moving/resizing. */
+gboolean
+editor_select_widget (GtkWidget * widget, GdkEventButton * event,
+ gint x, gint y)
+{
+ gint already_selected, page;
+ GtkWidget *select_widget, *ancestor;
+ gboolean handled = FALSE;
+
+ MSG ("IN editor_select_widget");
+#if 0
+ g_print ("=+=+ widget: %s Alloc X:%i Y:%i W:%i H:%i\n",
+ gtk_widget_get_name (widget),
+ widget->allocation.x, widget->allocation.y,
+ widget->allocation.width, widget->allocation.height);
+#endif
+
+ /* reset any move/resize action */
+ drag_action = GB_DRAG_NONE;
+
+ /* Shift + selection. Step through parents of this widget, until a selected
+ widget is found. Then clear selection & select that widgets parent.
+ If no parents were selected, or the top-level parent was selected,
+ select this widget. */
+ if (event && event->state & GDK_SHIFT_MASK)
+ {
+ gint found = FALSE;
+ ancestor = widget;
+ while (ancestor)
+ {
+ if (g_list_find (selected_widgets, ancestor))
+ {
+ found = TRUE;
+ break;
+ }
+ ancestor = ancestor->parent;
+ }
+ if (found && ancestor->parent != NULL)
+ {
+ select_widget = ancestor->parent;
+ /* If widget is not a GbWidget, i.e. it has no GladeWidgetData,
+ skip it. */
+ while (select_widget)
+ {
+ if (GB_IS_GB_WIDGET (select_widget))
+ break;
+ select_widget = select_widget->parent;
+ }
+ }
+ else
+ {
+ select_widget = widget;
+ }
+ g_return_val_if_fail (select_widget != NULL, FALSE);
+ gb_widget_show_properties (select_widget);
+ already_selected = editor_clear_selection (select_widget);
+ if (already_selected)
+ return FALSE;
+ selected_widgets = g_list_append (selected_widgets, select_widget);
+ tree_select_widget (select_widget, TRUE);
+ glade_project_view_clear_component_selection (current_project_view,
+ widget);
+ editor_refresh_widget_selection (select_widget);
+ return TRUE;
+ }
+
+#if 0
+ /* Control + selection. If widget is currently selected, deslect it, else
+ add it to the selected widgets. */
+ /* I've taken this out as it can cause crashes and it isn't very useful. */
+ if (event && event->state & GDK_CONTROL_MASK)
+ {
+ /* If widget is currently selected, deselect it, else add it to
+ selected. */
+ editor_select_widget_control (widget);
+
+ if (g_list_find (selected_widgets, widget))
+ tree_select_widget (widget, TRUE);
+ else
+ tree_select_widget (widget, FALSE);
+
+ return TRUE;
+ }
+#endif
+
+ /* Normal selection. If the widget is currently selected, just get the
+ data for a possible resize/drag. If it is not selected, clear all
+ currently selected widgets, then select this one.
+ Also remember where the button press occurred, in case widget is being
+ moved or resized (in a fixed container). */
+ gb_widget_show_properties (widget);
+
+ if (!g_list_find (selected_widgets, widget))
+ {
+ editor_clear_selection (NULL);
+ selected_widgets = g_list_append (selected_widgets, widget);
+ tree_select_widget (widget, TRUE);
+ handled = TRUE;
+ glade_project_view_clear_component_selection (current_project_view,
+ widget);
+ /* If parent is a fixed container, move widget to front */
+ if (widget->parent && GTK_IS_FIXED (widget->parent))
+ raise_fixed_child (widget);
+
+ /* SPECIAL CODE: If the widget or an ancestor is a notebook tab,
+ show the page */
+ ancestor = widget;
+ while (ancestor->parent)
+ {
+ if (GTK_IS_NOTEBOOK (ancestor->parent))
+ {
+ page = get_notebook_page (GTK_NOTEBOOK (ancestor->parent),
+ ancestor);
+ if (page != -1)
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (ancestor->parent),
+ page);
+ break;
+ }
+ ancestor = ancestor->parent;
+ }
+
+ editor_refresh_widget_selection (widget);
+ }
+
+
+ /* NOTE: this will only work in a GtkFixed or a GtkLayout. */
+ if (widget->parent && event
+ && (GTK_IS_FIXED (widget->parent) || GTK_IS_LAYOUT (widget->parent)))
+ {
+ if (gdk_pointer_grab (event->window, FALSE,
+ GDK_POINTER_MOTION_HINT_MASK |
+ GDK_BUTTON1_MOTION_MASK |
+ GDK_BUTTON_RELEASE_MASK,
+ NULL, NULL, event->time))
+ return FALSE;
+
+#if 0
+ g_print ("###### Grabbed pointer\n");
+#endif
+ drag_has_pointer_grab = TRUE;
+ drag_action = get_position_in_widget (widget, x, y);
+ drag_offset_x = x;
+ drag_offset_y = y;
+
+ drag_widget_x1 = widget->allocation.x;
+ drag_widget_y1 = widget->allocation.y;
+
+ if (GTK_IS_FIXED (widget->parent))
+ {
+ drag_widget_x1 -= widget->parent->allocation.x;
+ drag_widget_y1 -= widget->parent->allocation.y;
+ }
+
+ drag_widget_x2 = drag_widget_x1 + widget->allocation.width;
+ drag_widget_y2 = drag_widget_y1 + widget->allocation.height;
+
+#if 0
+ g_print ("drag_action:%i, offset_x:%i offset_y:%i "
+ "x1:%i y1:%i x2:%i y2:%i\n",
+ drag_action, drag_offset_x, drag_offset_y,
+ drag_widget_x1, drag_widget_y1,
+ drag_widget_x2, drag_widget_y2);
+#endif
+
+ /* We return TRUE to make sure the signal is stopped so the widget
+ doesn't popup a menu or something, which would make moving/resizing
+ very difficult. */
+ return TRUE;
+ }
+ return handled;
+}
+
+
+/* This returns TRUE if the event should be ignored. Currently we only
+ ignore events in clist/ctree resize windows, but more may be added. */
+static gboolean
+editor_check_ignore_event (GtkWidget *widget,
+ GdkEventAny *event)
+{
+ GtkWidget *window_widget;
+
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ gdk_window_get_user_data (event->window, (gpointer) &window_widget);
+
+ if (GTK_IS_CLIST (window_widget))
+ {
+ gint i;
+ for (i = 0; i < GTK_CLIST (window_widget)->columns; i++)
+ {
+ GtkCListColumn *col = &GTK_CLIST (window_widget)->column[i];
+ if (event->window == col->window)
+ {
+ MSG ("Ignored event (clist resize window)");
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+
+static gint
+editor_on_motion_notify (GtkWidget * signal_widget,
+ GdkEventMotion * event,
+ gpointer data)
+{
+#if 0
+ g_print ("In editor_on_motion_notify: %s\n",
+ gtk_widget_get_name (signal_widget));
+#endif
+
+ if (event->state & GDK_BUTTON1_MASK)
+ return editor_do_drag_action (signal_widget, event);
+ else
+ return editor_set_cursor (signal_widget, event);
+}
+
+
+static gint
+editor_set_cursor (GtkWidget * signal_widget,
+ GdkEventMotion * event)
+{
+ /* We remember the last cursor set and the last window, so we don't set the
+ same cursor on the same window repeatedly. */
+ static GdkWindow *last_window = NULL;
+ static GdkCursor *last_cursor = NULL;
+
+ GtkWidget *widget, *event_widget;
+ GdkCursor *cursor = NULL;
+ gint x, y, event_x, event_y, pos;
+
+#if 0
+ g_print ("In editor_set_cursor %s hint:%i\n",
+ gtk_widget_get_name (signal_widget), event->is_hint);
+#endif
+
+ /* First of all, we find out which widget the event originated from.
+ We step up the parents until we find the current widget, checking if
+ any GbWidgets are children. If they are, we have already seen this event
+ so we just return. */
+ event_widget = gtk_get_event_widget ((GdkEvent*) event);
+ if (event_widget)
+ {
+ while (event_widget != signal_widget)
+ {
+ if (event_widget == NULL)
+ {
+ g_warning ("motion_notify - didn't find signal widget");
+ break;
+ }
+
+ if (GB_IS_GB_WIDGET (event_widget))
+ return FALSE;
+
+ event_widget = event_widget->parent;
+ }
+
+ }
+
+ if (event->is_hint)
+ gdk_window_get_pointer (event->window, &event_x, &event_y, NULL);
+ else
+ {
+ event_x = event->x;
+ event_y = event->y;
+ }
+
+ widget = editor_get_event_widget (signal_widget, event->window,
+ event_x, event_y, &x, &y);
+ if (widget == NULL)
+ return FALSE;
+
+ if (editor_check_ignore_event (widget, (GdkEventAny*) event))
+ return FALSE;
+
+ /* Remember the widget, so we can redirect key presses to it. But only
+ keep pointers to GbWidgets, as we can reset the pointer to NULL when the
+ widget is destroyed. */
+ if (GB_IS_GB_WIDGET (widget))
+ {
+#if 0
+ g_print (" mouse_over_widget: %s\n", gtk_widget_get_name (widget));
+#endif
+ mouse_over_widget = widget;
+ }
+
+#if 0
+ g_print ("editor_set_cursor widget: %s (%p) X:%i Y:%i\n",
+ gtk_widget_get_name (widget), widget, x, y);
+#endif
+ if (glade_palette_is_selector_on (GLADE_PALETTE (glade_palette)))
+ {
+#ifdef USE_GNOME
+ if (widget->parent
+ && (GTK_IS_FIXED (widget)
+ || (GTK_IS_LAYOUT (widget) && !GNOME_IS_CANVAS (widget))))
+#else
+ if (widget->parent
+ && (GTK_IS_FIXED (widget->parent)
+ || GTK_IS_LAYOUT (widget->parent)))
+#endif
+ {
+ pos = get_position_in_widget (widget, x, y);
+ switch (pos)
+ {
+ case GB_TOP_LEFT:
+#if 0
+ g_print ("TOP_LEFT\n");
+#endif
+ cursor = cursor_top_left;
+ break;
+ case GB_TOP_RIGHT:
+#if 0
+ g_print ("TOP_RIGHT\n");
+#endif
+ cursor = cursor_top_right;
+ break;
+ case GB_BOTTOM_LEFT:
+#if 0
+ g_print ("BOTTOM_LEFT\n");
+#endif
+ cursor = cursor_bottom_left;
+ break;
+ case GB_BOTTOM_RIGHT:
+#if 0
+ g_print ("BOTTOM_RIGHT\n");
+#endif
+ cursor = cursor_bottom_right;
+ break;
+ case GB_MIDDLE:
+#if 0
+ g_print ("MIDDLE\n");
+#endif
+ cursor = cursor_move;
+ break;
+ }
+ }
+ else
+ {
+ cursor = cursor_selector;
+ }
+ }
+ else
+ {
+ if (GTK_IS_FIXED (widget) || GTK_IS_LAYOUT (widget)
+ || (widget->parent && (GTK_IS_FIXED (widget->parent)
+ || GTK_IS_LAYOUT (widget->parent))))
+ cursor = cursor_add_to_fixed;
+ else
+ cursor = cursor_add_widget;
+ }
+
+ if (cursor)
+ {
+ if (last_window != event->window || last_cursor != cursor)
+ {
+ gdk_window_set_cursor (event->window, cursor);
+ last_window = event->window;
+ last_cursor = cursor;
+
+ }
+ }
+
+ return FALSE;
+}
+
+
+static gint
+editor_do_drag_action (GtkWidget * signal_widget, GdkEventMotion * event)
+{
+ GtkWidget *widget;
+ GladeWidgetData *wdata;
+ gint x, y, event_x, event_y;
+ gint mouse_x, mouse_y, new_x = 0, new_y = 0, new_width = 0, new_height = 0;
+ gint old_x, old_y, old_width, old_height;
+
+#if 0
+ g_print ("In editor_do_drag_action %s hint:%i\n",
+ gtk_widget_get_name (signal_widget), event->is_hint);
+#endif
+
+ /* If no move/resize action was started in the button_press event, return. */
+ if (drag_action == GB_DRAG_NONE)
+ return FALSE;
+
+ if (event->is_hint)
+ gdk_window_get_pointer (event->window, &event_x, &event_y, NULL);
+ else
+ {
+ event_x = event->x;
+ event_y = event->y;
+ }
+
+#if 0
+ g_print ("In editor_do_drag_action %s hint:%i %i,%i\n",
+ gtk_widget_get_name (signal_widget), event->is_hint,
+ event_x, event_y);
+#endif
+
+ /* Use our function to figure out which widget the mouse is in, and where
+ in the widget. */
+ widget = editor_get_event_widget (signal_widget, event->window,
+ event_x, event_y, &x, &y);
+ if (widget == NULL)
+ return FALSE;
+
+ if (editor_check_ignore_event (widget, (GdkEventAny*) event))
+ return FALSE;
+
+
+ if (!widget->parent
+ || (!GTK_IS_FIXED (widget->parent) && !GTK_IS_LAYOUT (widget->parent)))
+ return FALSE;
+
+ if (dragging_widget == NULL)
+ {
+ dragging_widget = widget;
+ gtk_grab_add (widget);
+ }
+ else
+ {
+ if (dragging_widget != widget)
+ return FALSE;
+ }
+
+ wdata = gtk_object_get_data (GTK_OBJECT (widget), GB_WIDGET_DATA_KEY);
+ g_return_val_if_fail (wdata != NULL, FALSE);
+
+ old_x = widget->allocation.x;
+ old_y = widget->allocation.y;
+ old_width = widget->allocation.width;
+ old_height = widget->allocation.height;
+
+ /* GtkFixed doesn't normally have a window now, so we need to subtract its
+ position so our coordinates are relative to it. */
+ if (GTK_IS_FIXED (widget->parent))
+ {
+ old_x -= widget->parent->allocation.x;
+ old_y -= widget->parent->allocation.y;
+ }
+
+ gdk_window_get_pointer (widget->parent->window, &mouse_x, &mouse_y, NULL);
+ if (GTK_IS_FIXED (widget->parent))
+ {
+ mouse_x -= widget->parent->allocation.x;
+ mouse_y -= widget->parent->allocation.y;
+ }
+ if (GTK_IS_LAYOUT (widget->parent))
+ {
+ mouse_x += GTK_LAYOUT (widget->parent)->hadjustment->value;
+ mouse_y += GTK_LAYOUT (widget->parent)->vadjustment->value;
+ old_x += GTK_LAYOUT (widget->parent)->hadjustment->value;
+ old_y += GTK_LAYOUT (widget->parent)->vadjustment->value;
+ }
+
+ switch (drag_action)
+ {
+ case GB_TOP_LEFT:
+ new_x = snap_left_edge (mouse_x);
+ new_y = snap_top_edge (mouse_y);
+ new_width = drag_widget_x2 - new_x;
+ new_height = drag_widget_y2 - new_y;
+ if (new_width < MIN_WIDGET_WIDTH)
+ {
+ new_width = MIN_WIDGET_WIDTH;
+ new_x = drag_widget_x2 - new_width;
+ }
+ if (new_height < MIN_WIDGET_HEIGHT)
+ {
+ new_height = MIN_WIDGET_HEIGHT;
+ new_y = drag_widget_y2 - new_height;
+ }
+ break;
+
+ case GB_TOP_RIGHT:
+ new_x = drag_widget_x1;
+ new_y = snap_top_edge (mouse_y);
+ new_width = snap_right_edge (mouse_x) - new_x;
+ new_height = drag_widget_y2 - new_y;
+ if (new_width < MIN_WIDGET_WIDTH)
+ {
+ new_width = MIN_WIDGET_WIDTH;
+ }
+ if (new_height < MIN_WIDGET_HEIGHT)
+ {
+ new_height = MIN_WIDGET_HEIGHT;
+ new_y = drag_widget_y2 - new_height;
+ }
+ break;
+
+ case GB_BOTTOM_LEFT:
+ new_x = snap_left_edge (mouse_x);
+ new_y = drag_widget_y1;
+ new_width = drag_widget_x2 - new_x;
+ new_height = snap_bottom_edge (mouse_y) - new_y;
+ if (new_width < MIN_WIDGET_WIDTH)
+ {
+ new_width = MIN_WIDGET_WIDTH;
+ new_x = drag_widget_x2 - new_width;
+ }
+ if (new_height < MIN_WIDGET_HEIGHT)
+ new_height = MIN_WIDGET_HEIGHT;
+ break;
+
+ case GB_BOTTOM_RIGHT:
+ new_x = drag_widget_x1;
+ new_y = drag_widget_y1;
+ new_width = snap_right_edge (mouse_x) - new_x;
+ new_height = snap_bottom_edge (mouse_y) - new_y;
+ if (new_width < MIN_WIDGET_WIDTH)
+ new_width = MIN_WIDGET_WIDTH;
+ if (new_height < MIN_WIDGET_HEIGHT)
+ new_height = MIN_WIDGET_HEIGHT;
+ break;
+
+ case GB_MIDDLE:
+ new_x = snap_left_edge (mouse_x - drag_offset_x);
+ new_y = snap_top_edge (mouse_y - drag_offset_y);
+ new_width = widget->allocation.width;
+ new_height = widget->allocation.height;
+ if (new_x < 0)
+ new_x = 0;
+ if (new_y < 0)
+ new_y = 0;
+ break;
+ }
+
+#if 0
+ g_print ("old_x: %i old_y: %i new_x: %i new_y: %i w: %i h: %i\n",
+ widget->allocation.x, widget->allocation.y,
+ new_x, new_y, new_width, new_height);
+#endif
+
+ /* Only move/resize widget if values have changed */
+ if (new_width != widget->allocation.width
+ || new_height != widget->allocation.height)
+ {
+ wdata->width = new_width;
+ wdata->height = new_height;
+ gb_widget_set_usize (widget, new_width, new_height);
+ }
+
+ if (new_x != old_x || new_y != old_y)
+ {
+#if 0
+ g_print (" moving widget\n");
+#endif
+ if (GTK_IS_FIXED (widget->parent))
+ {
+ /* FIXME: GTK+ bug workaround. The widget doesn't move if we only
+ call gtk_fixed_move(). This is very slow as well. */
+ gtk_fixed_move (GTK_FIXED (widget->parent), widget, new_x, new_y);
+ /*gtk_widget_set_uposition (widget, new_x, new_y);*/
+ }
+ else if (GTK_IS_LAYOUT (widget->parent))
+ {
+ gtk_layout_move (GTK_LAYOUT (widget->parent), widget, new_x, new_y);
+ }
+
+ gtk_widget_queue_draw (widget->parent);
+ }
+
+ return TRUE;
+}
+
+
+/*
+ * Adding signals to widgets to allow manipulation, e.g. selecting/drawing
+ */
+
+static void
+editor_on_widget_realize (GtkWidget *widget, gpointer data)
+{
+#if 0
+ static GdkPixmap *placeholder_pixmap = NULL;
+
+ g_print ("In editor_on_widget_realize widget:%s (%p)\n",
+ gtk_widget_get_name (widget), widget);
+
+ if (GB_IS_PLACEHOLDER (widget))
+ {
+ /* Create placeholder pixmap if it hasn't already been created.
+ There may be a problem with multi-depth displays. */
+ if (placeholder_pixmap == NULL)
+ {
+ placeholder_pixmap = gdk_pixmap_create_from_xpm_d (widget->window,
+ NULL, NULL,
+ placeholder_xpm);
+ if (!placeholder_pixmap)
+ {
+ g_warning ("Couldn't create placeholder pixmap\n");
+ /* FIXME: Use a color instead? */
+ }
+ }
+
+ if (placeholder_pixmap != NULL)
+ gdk_window_set_back_pixmap (widget->window, placeholder_pixmap, FALSE);
+ }
+#endif
+}
+
+
+/* This adds the button signals to an existing widget (currently only the
+ Clist title buttons). */
+void
+editor_add_mouse_signals_to_existing (GtkWidget * widget)
+{
+ gtk_signal_connect (GTK_OBJECT (widget), "event",
+ GTK_SIGNAL_FUNC (editor_on_event), NULL);
+#if 0
+ gtk_signal_connect (GTK_OBJECT (widget), "button_press_event",
+ GTK_SIGNAL_FUNC (editor_on_button_press), NULL);
+ gtk_signal_connect (GTK_OBJECT (widget), "button_release_event",
+ GTK_SIGNAL_FUNC (editor_on_button_release), NULL);
+#endif
+}
+
+
+static gint
+editor_on_enter_notify (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ /* We try to stop enter/leave notify events when moving/resizing widget in
+ a GtkFixed/GtkLayout as it causes flicker. */
+ return dragging_widget ? TRUE : FALSE;
+}
+
+
+static gint
+editor_on_leave_notify (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ /* We try to stop enter/leave notify events when moving/resizing widget in
+ a GtkFixed/GtkLayout as it causes flicker. */
+ return dragging_widget ? TRUE : FALSE;
+}
+
+
+static void
+add_mouse_signals_recursive (GtkWidget *widget, gpointer data)
+{
+#if 0
+ g_print ("Adding mouse signals to:%s (%s, %p)\n",
+ gtk_widget_get_name (widget),
+ gtk_type_name (GTK_OBJECT_TYPE (widget)),
+ widget);
+#endif
+
+ /* FIXME: We don't add signals to menu items, since it currently makes it
+ impossible to popup the menus in a menubar. */
+ if (GTK_IS_MENU_ITEM (widget))
+ return;
+
+ /* Ensure that the event mask is set so we get button press & release
+ events. */
+ if (!GTK_WIDGET_NO_WINDOW (widget))
+ {
+ if (!GTK_WIDGET_REALIZED (widget))
+ {
+ gtk_widget_set_events (widget, gtk_widget_get_events (widget)
+ | GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK);
+ }
+ else
+ {
+ GdkEventMask event_mask;
+
+ /* FIXME: Here we set the event mask for the main window of a widget,
+ but widgets can have more than one window. How do we get all the
+ windows of a widget? */
+ event_mask = gdk_window_get_events (widget->window);
+ gdk_window_set_events (widget->window, event_mask
+ | GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK);
+ }
+ }
+
+ gtk_signal_connect (GTK_OBJECT (widget), "event",
+ GTK_SIGNAL_FUNC (editor_on_event), NULL);
+#if 0
+ gtk_signal_connect (GTK_OBJECT (widget), "button_press_event",
+ GTK_SIGNAL_FUNC (editor_on_button_press), NULL);
+ gtk_signal_connect (GTK_OBJECT (widget), "button_release_event",
+ GTK_SIGNAL_FUNC (editor_on_button_release), NULL);
+#endif
+
+ /* We connect to these so we can stop widgets getting them while we are
+ dragging/resizing. It stops widgets changing state, i.e. normal/active
+ and so cuts down on flickering a bit. */
+ gtk_signal_connect (GTK_OBJECT (widget), "enter_notify_event",
+ GTK_SIGNAL_FUNC (editor_on_enter_notify), NULL);
+ gtk_signal_connect (GTK_OBJECT (widget), "leave_notify_event",
+ GTK_SIGNAL_FUNC (editor_on_leave_notify), NULL);
+
+ gb_widget_children_foreach (widget,
+ (GtkCallback) add_mouse_signals_recursive, NULL);
+}
+
+/* We need to be careful about passing events on to widgets, especially with
+ regard to mouse grabs - in a GtkEntry the mouse is grabbed while selecting
+ text, and this can cause all sorts of problems for Glade. */
+void
+editor_add_mouse_signals (GtkWidget * widget)
+{
+ /* Widgets without windows will not get events directly from X Windows,
+ but they may have child widgets which pass events up to them, e.g.
+ a GtkCombo has a GtkEntry which will get X events.
+ This doesn't matter too much since we have to call a function to figure
+ out which widget the event is for anyway. */
+ add_mouse_signals_recursive (widget, NULL);
+
+ gtk_signal_connect_after (GTK_OBJECT (widget), "realize",
+ GTK_SIGNAL_FUNC (editor_on_widget_realize), NULL);
+}
+
+void
+editor_add_key_signals (GtkWidget * widget)
+{
+ /* We only add key signal handlers to windows. */
+ if (!GTK_IS_WINDOW (widget))
+ return;
+
+ gtk_signal_connect (GTK_OBJECT (widget), "key_press_event",
+ GTK_SIGNAL_FUNC (editor_on_key_press_event), NULL);
+ gtk_signal_connect (GTK_OBJECT (widget), "key_release_event",
+ GTK_SIGNAL_FUNC (editor_on_key_release_event), NULL);
+}
+
+
+void
+on_size_allocate (GtkWidget * widget,
+ GtkAllocation *allocation,
+ GladeWidgetData * wdata)
+{
+ /* Reset the flag, since the size is allocated now. Note that wdata may be
+ NULL as widget may be a placeholder. */
+ if (wdata)
+ {
+ wdata->flags &= ~GLADE_SIZE_NOT_ALLOCATED;
+ }
+
+#if 0
+ g_print ("In on_size_allocate: %s (%p) x:%i y:%i w:%i h:%i\n",
+ gtk_widget_get_name (widget), widget, allocation->x, allocation->y,
+ allocation->width, allocation->height);
+#endif
+
+ if (property_get_widget () == widget)
+ {
+ gb_widget_show_position_properties (widget);
+
+ if (widget->parent && GTK_IS_FIXED (widget->parent))
+ {
+ property_set_auto_apply (FALSE);
+ property_set_int (GladeFixedChildX,
+ allocation->x - widget->parent->allocation.x);
+ property_set_int (GladeFixedChildY,
+ allocation->y - widget->parent->allocation.y);
+ property_set_auto_apply (TRUE);
+ }
+ else if (widget->parent && GTK_IS_LAYOUT (widget->parent))
+ {
+ property_set_auto_apply (FALSE);
+ property_set_int (GladeLayoutChildX, allocation->x);
+ property_set_int (GladeLayoutChildY, allocation->y);
+ property_set_auto_apply (TRUE);
+ }
+ }
+}
+
+
+void
+editor_add_draw_signals (GtkWidget * widget)
+{
+ GladeWidgetData *widget_data;
+
+ /* FIXME: Note that we set GDK_POINTER_MOTION_HINT_MASK here. This may not
+ be wise since widgets may be designed to work with normal motion events
+ only. Also this won't work if the widget is already realized. */
+ if (!GTK_WIDGET_NO_WINDOW (widget) && !GTK_WIDGET_REALIZED (widget))
+ gtk_widget_set_events (widget, gtk_widget_get_events (widget)
+ | GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK
+ | GDK_BUTTON1_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK);
+
+ widget_data = gtk_object_get_data (GTK_OBJECT (widget), GB_WIDGET_DATA_KEY);
+
+ gtk_signal_connect (GTK_OBJECT (widget), "expose_event",
+ GTK_SIGNAL_FUNC (expose_widget), widget_data);
+ gtk_signal_connect_after (GTK_OBJECT (widget), "size_allocate",
+ GTK_SIGNAL_FUNC (on_size_allocate), widget_data);
+
+ /* FIXME: mouse signal - This also needs to be added to all children. */
+ gtk_signal_connect (GTK_OBJECT (widget), "motion_notify_event",
+ GTK_SIGNAL_FUNC (editor_on_motion_notify), NULL);
+
+ /* Needed for scrolled window, clist? & possibly other widgets */
+ if (GTK_IS_CONTAINER (widget))
+ gtk_container_forall (GTK_CONTAINER (widget),
+ (GtkCallback) editor_add_draw_signals, NULL);
+}
+
+
+
+static gint
+expose_widget (GtkWidget * widget, GdkEventExpose * event,
+ GladeWidgetData * widget_data)
+{
+ GtkWidgetClass *class;
+
+#if 0
+ g_print ("In expose_event widget:%s (%p)\n", gtk_widget_get_name (widget),
+ widget);
+ g_print ("Area x:%i y:%i w:%i h:%i\n", event->area.x, event->area.y,
+ event->area.width, event->area.height);
+#endif
+
+ /* Run the class handler here, then we return TRUE to stop the signal. */
+ class = GTK_WIDGET_GET_CLASS (widget);
+ if (class->expose_event)
+ class->expose_event (widget, event);
+
+ /* Ignore spurious exposes before widget is positioned. */
+ if (widget->allocation.x == -1 || widget->allocation.y == -1)
+ return TRUE;
+
+ paint_widget (widget, event);
+
+ return TRUE;
+}
+
+
+void
+paint_widget (GtkWidget * widget, GdkEventExpose *event)
+{
+ static GdkPixmap *placeholder_pixmap = NULL;
+ GType type;
+
+ MSG3 ("Painting widget: %s W:%i H:%i", gtk_widget_get_name (widget),
+ widget->allocation.width, widget->allocation.height);
+
+ /* Check widget is drawable in case it has been deleted. */
+ if (!GTK_WIDGET_DRAWABLE (widget))
+ return;
+
+ /* Don't try to draw anything if the width or height of the widget is 0. */
+ if (widget->allocation.width == 0 || widget->allocation.height == 0)
+ return;
+
+ /* If widget is a placeholder, draw the placeholder pixmap in it and a
+ 3D border around it. */
+ if (GB_IS_PLACEHOLDER (widget))
+ {
+ GdkGC *light_gc;
+ GdkGC *dark_gc;
+ gint w, h;
+
+ light_gc = widget->style->light_gc[GTK_STATE_NORMAL];
+ dark_gc = widget->style->dark_gc[GTK_STATE_NORMAL];
+ gdk_window_get_size (widget->window, &w, &h);
+
+ /* Draw the background pixmap. */
+ if (placeholder_pixmap == NULL)
+ {
+ /* FIXME: Use a hash of placeholder pixmaps? So we always use the
+ correct depth? */
+ placeholder_pixmap = gdk_pixmap_create_from_xpm_d (widget->window,
+ NULL, NULL,
+ placeholder_xpm);
+ if (!placeholder_pixmap)
+ {
+ g_warning ("Couldn't create placeholder pixmap\n");
+ /* FIXME: Use a color instead? */
+ }
+ }
+
+ if (placeholder_pixmap)
+ {
+ gdk_gc_set_fill (light_gc, GDK_TILED);
+ gdk_gc_set_tile (light_gc, placeholder_pixmap);
+ gdk_draw_rectangle (widget->window, light_gc, TRUE, 0, 0, w, h);
+ gdk_gc_set_fill (light_gc, GDK_SOLID);
+ }
+
+ gdk_draw_line (widget->window, light_gc, 0, 0, w - 1, 0);
+ gdk_draw_line (widget->window, light_gc, 0, 0, 0, h - 1);
+ gdk_draw_line (widget->window, dark_gc, 0, h - 1, w - 1, h - 1);
+ gdk_draw_line (widget->window, dark_gc, w - 1, 0, w - 1, h - 1);
+ }
+
+ if (event->window)
+ {
+ gpointer expose_widget;
+
+ gdk_window_get_user_data (event->window, &expose_widget);
+
+ if (expose_widget)
+ {
+ gtk_idle_add_priority (GTK_PRIORITY_DEFAULT + 10,
+ (GtkFunction)editor_idle_handler,
+ event->window);
+
+ /* We ref the window, to make sure it isn't freed before the idle
+ handler. We unref it there. */
+ gdk_window_ref (event->window);
+ }
+ }
+
+ /* Draw grid for fixed containers */
+ type = G_OBJECT_TYPE (widget);
+ if (GB_IS_GB_WIDGET (widget)
+ && (type == GTK_TYPE_FIXED || type == GTK_TYPE_LAYOUT))
+ draw_grid (widget);
+}
+
+
+/* This returns the window that the given widget's position is relative to.
+ Usually this is the widget's parent's window. But if the widget is a
+ toplevel, we use its own window, as it doesn't have a parent.
+ Some widgets also lay out widgets in different ways. */
+static GdkWindow*
+glade_util_get_window_positioned_in (GtkWidget *widget)
+{
+ GtkWidget *parent;
+
+ parent = widget->parent;
+
+#ifdef USE_GNOME
+ /* BonoboDockItem widgets use a different window when floating. */
+ if (BONOBO_IS_DOCK_ITEM (widget)
+ && BONOBO_DOCK_ITEM (widget)->is_floating) {
+ return BONOBO_DOCK_ITEM (widget)->float_window;
+ }
+
+ if (parent && BONOBO_IS_DOCK_ITEM (parent)
+ && BONOBO_DOCK_ITEM (parent)->is_floating) {
+ return BONOBO_DOCK_ITEM (parent)->float_window;
+ }
+#endif
+
+ if (parent)
+ return parent->window;
+
+ return widget->window;
+}
+
+static void
+glade_util_draw_nodes (GdkWindow *window, GdkGC *gc,
+ gint x, gint y,
+ gint width, gint height)
+{
+#if 0
+ g_print ("draw_nodes window: %p %i,%i %ix%i\n",
+ window, x, y, width, height);
+#endif
+ if (width > GB_CORNER_WIDTH && height > GB_CORNER_HEIGHT) {
+ gdk_draw_rectangle (window, gc, TRUE,
+ x, y,
+ GB_CORNER_WIDTH, GB_CORNER_HEIGHT);
+ gdk_draw_rectangle (window, gc, TRUE,
+ x, y + height - GB_CORNER_HEIGHT,
+ GB_CORNER_WIDTH, GB_CORNER_HEIGHT);
+ gdk_draw_rectangle (window, gc, TRUE,
+ x + width - GB_CORNER_WIDTH, y,
+ GB_CORNER_WIDTH, GB_CORNER_HEIGHT);
+ gdk_draw_rectangle (window, gc, TRUE,
+ x + width - GB_CORNER_WIDTH,
+ y + height - GB_CORNER_HEIGHT,
+ GB_CORNER_WIDTH, GB_CORNER_HEIGHT);
+ }
+
+ gdk_draw_rectangle (window, gc, FALSE, x, y, width - 1, height - 1);
+}
+
+/* This calculates the offset of the given window within its toplevel.
+ It also returns the toplevel. */
+static void
+glade_util_calculate_window_offset (GdkWindow *window,
+ gint *x, gint *y,
+ GdkWindow **toplevel)
+{
+ gint tmp_x, tmp_y;
+
+ /* Calculate the offset of the window within its toplevel. */
+ *x = 0;
+ *y = 0;
+
+ for (;;) {
+ if (gdk_window_get_window_type (window) != GDK_WINDOW_CHILD)
+ break;
+ gdk_window_get_position (window, &tmp_x, &tmp_y);
+ *x += tmp_x;
+ *y += tmp_y;
+ window = gdk_window_get_parent (window);
+ }
+
+ *toplevel = window;
+}
+
+/* This returns TRUE if it is OK to draw the selection nodes for the given
+ selected widget inside the given window that has received an expose event.
+ For most widgets it returns TRUE, but if a selected widget is inside a
+ widget like a viewport, that uses its own coordinate system, then it only
+ returns TRUE if the expose window is inside the viewport as well. */
+static gboolean
+glade_util_can_draw_nodes (GtkWidget *sel_widget, GdkWindow *sel_win,
+ GdkWindow *expose_win)
+{
+ GtkWidget *widget, *viewport = NULL;
+ GdkWindow *viewport_win = NULL;
+
+ /* Check if the selected widget is inside a viewport. */
+ for (widget = sel_widget->parent; widget; widget = widget->parent) {
+ if (GTK_IS_VIEWPORT (widget)) {
+ viewport = widget;
+ viewport_win = GTK_VIEWPORT (widget)->bin_window;
+ break;
+ }
+ }
+
+ /* If there is no viewport-type widget above the selected widget,
+ it is OK to draw the selection anywhere. */
+ if (!viewport)
+ return TRUE;
+
+ /* If we have a viewport-type widget, check if the expose_win is
+ beneath the viewport. If it is, we can draw in it. If not, we
+ can't.*/
+ for (;;) {
+ if (expose_win == sel_win)
+ return TRUE;
+ if (expose_win == viewport_win)
+ return FALSE;
+ if (gdk_window_get_window_type (expose_win) != GDK_WINDOW_CHILD)
+ break;
+ expose_win = gdk_window_get_parent (expose_win);
+ }
+
+ return FALSE;
+}
+
+
+/* This is coped from glade3/src/glade-utils.c glade_util_draw_nodes_idle(). */
+static gint
+editor_idle_handler (GdkWindow *expose_win)
+{
+ GtkWidget *expose_widget;
+ gint expose_win_x, expose_win_y;
+ gint expose_win_w, expose_win_h;
+ GdkWindow *expose_toplevel;
+ GdkGC *gc;
+ GList *elem;
+ gpointer expose_widget_ptr;
+
+ /* Find the corresponding GtkWidget. */
+ gdk_window_get_user_data (expose_win, &expose_widget_ptr);
+ expose_widget = GTK_WIDGET (expose_widget_ptr);
+
+ /* Check that the window is still alive. */
+ if (!expose_widget || !GTK_WIDGET_DRAWABLE (expose_widget)
+ || !gdk_window_is_viewable (expose_win))
+ goto out;
+
+ gc = expose_widget->style->black_gc;
+
+ /* Calculate the offset of the expose window within its toplevel. */
+ glade_util_calculate_window_offset (expose_win,
+ &expose_win_x,
+ &expose_win_y,
+ &expose_toplevel);
+
+#if 0
+ g_print ("expose_win: %p x: %i y: %i toplevel: %p\n",
+ expose_win, expose_win_x, expose_win_y, expose_toplevel);
+#endif
+ gdk_drawable_get_size (expose_win,
+ &expose_win_w, &expose_win_h);
+#if 0
+ g_print ("drawable size %ix%i\n", expose_win_w, expose_win_h);
+#endif
+ /* Step through all the selected widgets in the project. */
+ for (elem = selected_widgets; elem; elem = elem->next) {
+ GtkWidget *sel_widget;
+ GdkWindow *sel_win, *sel_toplevel;
+ gint sel_x, sel_y, x, y, w, h, sel_alloc_x, sel_alloc_y;
+
+ sel_widget = elem->data;
+
+ /* Skip the selected widget if it isn't realized. */
+ if (!GTK_WIDGET_REALIZED (sel_widget))
+ continue;
+
+ sel_win = glade_util_get_window_positioned_in (sel_widget);
+
+ /* Calculate the offset of the selected widget's window
+ within its toplevel. */
+ glade_util_calculate_window_offset (sel_win, &sel_x, &sel_y,
+ &sel_toplevel);
+
+#if 0
+ g_print ("sel_win: %p x: %i y: %i toplevel: %p allocation %i,%i\n",
+ sel_win, sel_x, sel_y, sel_toplevel,
+ sel_widget->allocation.x, sel_widget->allocation.y);
+#endif
+ /* Toplevel windows/dialogs may have their allocation set
+ relative to the root window, so we need to ignore that. */
+ if (sel_widget->parent)
+ {
+ sel_alloc_x = sel_widget->allocation.x;
+ sel_alloc_y = sel_widget->allocation.y;
+ }
+ else
+ {
+ sel_alloc_x = 0;
+ sel_alloc_y = 0;
+ }
+
+ /* We only draw the nodes if the window that got the expose
+ event is in the same toplevel as the selected widget. */
+ if (expose_toplevel == sel_toplevel
+ && glade_util_can_draw_nodes (sel_widget, sel_win,
+ expose_win)) {
+ x = sel_x + sel_alloc_x - expose_win_x;
+ y = sel_y + sel_alloc_y - expose_win_y;
+ w = sel_widget->allocation.width;
+ h = sel_widget->allocation.height;
+
+#if 0
+ g_print ("checking coords %i,%i %ix%i\n",
+ x, y, w, h);
+#endif
+ /* Draw the selection nodes if they intersect the
+ expose window bounds. */
+ if (x < expose_win_w && x + w >= 0
+ && y < expose_win_h && y + h >= 0) {
+ glade_util_draw_nodes (expose_win, gc,
+ x, y, w, h);
+ }
+ }
+ }
+
+ out:
+ /* Remove the reference added in glade_util_queue_draw_nodes(). */
+ gdk_window_unref (expose_win);
+
+ /* Return FALSE so the idle handler isn't called again. */
+ return FALSE;
+}
+
+
+static void
+draw_grid (GtkWidget * widget)
+{
+ GdkGC *gc = widget->style->dark_gc[GTK_STATE_NORMAL];
+ gint min_x = 0, max_x = widget->allocation.width - 1;
+ gint min_y = 0, max_y = widget->allocation.height - 1;
+ gint gridx, gridy, origin_x, origin_y;
+ GdkWindow *window;
+
+ if (!editor_show_grid)
+ return;
+
+ if (GTK_IS_LAYOUT (widget))
+ {
+ gint offset;
+
+ /* The window size is the entire size of the layout. The allocation is
+ the part that is showing. */
+ window = GTK_LAYOUT (widget)->bin_window;
+
+ origin_x = (int) GTK_LAYOUT (widget)->hadjustment->value;
+ min_x += origin_x;
+ max_x += origin_x;
+ offset = origin_x % editor_grid_horz_spacing;
+ if (offset != 0)
+ origin_x += editor_grid_horz_spacing - offset;
+
+ origin_y = (int) GTK_LAYOUT (widget)->vadjustment->value;
+ min_y += origin_y;
+ max_y += origin_y;
+ offset = origin_y % editor_grid_vert_spacing;
+ if (offset != 0)
+ origin_y += editor_grid_vert_spacing - offset;
+ }
+ else
+ {
+ /* The GtkFixed container doesn't have a window in GTK+ 2.0 (by default),
+ so we have to use the allocation. */
+ window = widget->window;
+ origin_x = widget->allocation.x;
+ origin_y = widget->allocation.y;
+ max_x += origin_x;
+ max_y += origin_y;
+ }
+
+ /* Note: should we take the border_width into account? - i.e. start the
+ grid inside the border. It makes it awkward if you change the border
+ size. */
+ if (editor_grid_style == GB_GRID_DOTS)
+ {
+ for (gridx = origin_x; gridx <= max_x; gridx += editor_grid_horz_spacing)
+ {
+ for (gridy = origin_y; gridy <= max_y;
+ gridy += editor_grid_vert_spacing)
+ gdk_draw_point (window, gc, gridx, gridy);
+ }
+ }
+ else
+ {
+ for (gridx = origin_x; gridx <= max_x; gridx += editor_grid_horz_spacing)
+ gdk_draw_line (window, gc, gridx, min_y, gridx, max_y);
+ for (gridy = origin_y; gridy <= max_y; gridy += editor_grid_vert_spacing)
+ gdk_draw_line (window, gc, min_x, gridy, max_x, gridy);
+ }
+}
+
+/*
+ * Redraw the given widget completely, including all space allocated by its
+ * parent (since this may be used for drawing the widget's selection).
+ * If widget has no parent (i.e. its a toplevel window) just clear
+ * it all and redraw.
+ */
+
+void
+editor_refresh_widget (GtkWidget * widget)
+{
+#if 0
+ g_print ("In editor_refresh_widget widget: %s (%p)\n",
+ gtk_widget_get_name (widget), widget);
+#endif
+
+ editor_refresh_widget_area (widget,
+ widget->allocation.x,
+ widget->allocation.y,
+ widget->allocation.width,
+ widget->allocation.height);
+ gtk_widget_draw (widget, NULL);
+}
+
+
+void
+editor_refresh_widget_selection (GtkWidget * widget)
+{
+ gint x, y, w, h;
+
+#if 0
+ g_print ("In editor_refresh_widget_selection widget: %s (%p)\n",
+ gtk_widget_get_name (widget), widget);
+#endif
+
+ x = widget->allocation.x;
+ y = widget->allocation.y;
+ w = widget->allocation.width;
+ h = widget->allocation.height;
+
+ /* Don't try to refresh an area if the width or height is 0. */
+ if (w == 0 || h == 0)
+ return;
+
+ /* Clear the four corners. */
+ editor_refresh_widget_area (widget,
+ x, y,
+ GB_CORNER_WIDTH, GB_CORNER_HEIGHT);
+ editor_refresh_widget_area (widget,
+ x, y + h - GB_CORNER_HEIGHT,
+ GB_CORNER_WIDTH, GB_CORNER_HEIGHT);
+ editor_refresh_widget_area (widget,
+ x + w - GB_CORNER_WIDTH, y,
+ GB_CORNER_WIDTH, GB_CORNER_HEIGHT);
+ editor_refresh_widget_area (widget,
+ x + w - GB_CORNER_WIDTH,
+ y + h - GB_CORNER_HEIGHT,
+ GB_CORNER_WIDTH, GB_CORNER_HEIGHT);
+ /* Clear the four lines along the edges. */
+ editor_refresh_widget_area (widget,
+ x + GB_CORNER_WIDTH, y,
+ w - 2 * GB_CORNER_WIDTH, 1);
+ editor_refresh_widget_area (widget,
+ x + GB_CORNER_WIDTH, y + h - 1,
+ w - 2 * GB_CORNER_WIDTH, 1);
+ editor_refresh_widget_area (widget,
+ x, y + GB_CORNER_HEIGHT,
+ 1, h - 2 * GB_CORNER_HEIGHT);
+ editor_refresh_widget_area (widget,
+ x + w - 1,
+ y + GB_CORNER_HEIGHT,
+ 1, h - 2 * GB_CORNER_HEIGHT);
+
+ gtk_widget_draw (widget, NULL);
+}
+
+
+void
+editor_refresh_widget_area (GtkWidget * widget, gint x, gint y, gint w, gint h)
+{
+ GdkWindow *window;
+
+ if (!GTK_WIDGET_DRAWABLE (widget))
+ return;
+
+ /* Don't try to refresh an area if the width or height is 0. */
+ if (w <= 0 || h <= 0)
+ return;
+
+ window = glade_util_get_window_positioned_in (widget);
+ gdk_window_clear_area (window, x, y, w, h);
+ clear_child_windows (window, x, y, w, h);
+}
+
+
+/* This clears all child windows which fall within the given rectangle.
+ If the rectangle width is -1, then all children are cleared. */
+static void
+clear_child_windows (GdkWindow * window, gint x, gint y, gint w, gint h)
+{
+ GList *children, *orig_children;
+ GdkWindow *child_window;
+ gint win_x, win_y, win_w, win_h;
+ GdkRectangle area, child, intersection;
+
+ area.x = x;
+ area.y = y;
+ area.width = w;
+ area.height = h;
+
+ orig_children = children = gdk_window_get_children (window);
+ while (children)
+ {
+ child_window = children->data;
+ gdk_window_get_position (child_window, &win_x, &win_y);
+ gdk_window_get_size (child_window, &win_w, &win_h);
+
+ child.x = win_x;
+ child.y = win_y;
+ child.width = win_w;
+ child.height = win_h;
+
+ if (gdk_rectangle_intersect (&area, &child, &intersection))
+ {
+ /* We need to make sure this is not an InputOnly window, or we get
+ a BadMatch. CList uses InputOnly windows - for resizing columns.
+ */
+ if (! GDK_WINDOW_OBJECT(child_window)->input_only)
+ {
+ /* Convert to the child's coordinate space. */
+ intersection.x -= child.x;
+ intersection.y -= child.y;
+ gdk_window_clear_area (child_window,
+ intersection.x, intersection.y,
+ intersection.width, intersection.height);
+ clear_child_windows (child_window,
+ intersection.x, intersection.y,
+ intersection.width, intersection.height);
+ }
+ }
+ children = children->next;
+ }
+ g_list_free (orig_children);
+}
+
+
+
+/*
+ * Key events
+ */
+static gint
+editor_on_key_press_event (GtkWidget * widget, GdkEventKey * event,
+ gpointer data)
+{
+ gboolean handled = FALSE;
+ guint key = event->keyval;
+
+ MSG ("In on_key_press_event");
+ switch (key)
+ {
+ case GDK_Delete:
+ /* If we are typing over the widget, the delete key is used for that
+ rather than deleting the widget. */
+ if (!property_is_typing_over_widget ())
+ {
+ if (selected_widgets)
+ delete (selected_widgets->data);
+ handled = TRUE;
+ }
+ break;
+ case GDK_Escape:
+ editor_clear_selection (NULL);
+ handled = TRUE;
+ break;
+ case GDK_l:
+ /* Ctrl-L refreshes the entire window/dialog. */
+ if (event->state & GDK_CONTROL_MASK)
+ {
+ editor_refresh_widget (glade_util_get_toplevel (widget));
+ handled = TRUE;
+ }
+ break;
+ case GDK_r:
+ /* Ctrl-R hides the window and shows it again in the same position.
+ Hopefully it will appear at the 'real' size. */
+ if (event->state & GDK_CONTROL_MASK)
+ {
+ GtkWidget *toplevel;
+
+ /* See also gb_widget_redisplay_window() in gbwidget.c. */
+ toplevel = glade_util_get_toplevel (widget);
+ glade_util_close_window (toplevel);
+ gtk_window_reshow_with_initial_size (GTK_WINDOW (toplevel));
+ handled = TRUE;
+ }
+ break;
+ }
+
+#if 0
+ /* We don't want modifier keys to be redirected, since trying to use an
+ accelerator, e.g. Ctrl-X to cut the widget, would clear the label. */
+ if (!handled
+ && !(event->state & GDK_CONTROL_MASK)
+ && key != GDK_Caps_Lock
+ && key != GDK_Tab && key != GDK_KP_Tab
+ && key != GDK_Left && key != GDK_KP_Left
+ && key != GDK_Right && key != GDK_KP_Right
+ && key != GDK_Up && key != GDK_KP_Up
+ && key != GDK_Down && key != GDK_KP_Down
+ && key != GDK_Page_Up && key != GDK_KP_Page_Up
+ && key != GDK_Page_Down && key != GDK_KP_Page_Down
+ && key != GDK_Home && key != GDK_KP_Home
+ && key != GDK_End && key != GDK_KP_End
+ && key != GDK_Control_L && key != GDK_Control_R
+ && key != GDK_Shift_L && key != GDK_Shift_R
+ && key != GDK_Meta_L && key != GDK_Meta_R
+ && key != GDK_Alt_L && key != GDK_Alt_R
+ && key != GDK_Super_L && key != GDK_Super_R
+ && key != GDK_Hyper_L && key != GDK_Hyper_R)
+ {
+ /* Experimental code. */
+#if 0
+ g_print ("Set label? widget=%s\n",
+ mouse_over_widget ? gtk_widget_get_name (mouse_over_widget) : "NULL");
+#endif
+ if (mouse_over_widget
+ && (GTK_IS_ACCEL_LABEL (mouse_over_widget)
+ || GTK_IS_LABEL (mouse_over_widget)
+ || GTK_IS_BUTTON (mouse_over_widget)))
+ {
+ gb_widget_show_properties (mouse_over_widget);
+ property_redirect_key_press (event);
+ handled = TRUE;
+ }
+ }
+#endif
+
+ if (handled)
+ {
+#if 0
+ gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
+#endif
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+static gint
+editor_on_key_release_event (GtkWidget * widget, GdkEventKey * event,
+ gpointer data)
+{
+ MSG ("In on_key_release_event");
+
+ return FALSE;
+}
+
+
+
+/* This is when the 'Select' menuitem on the popup menu is selected */
+void
+editor_on_select_activate (GtkWidget * menuitem, GtkWidget * widget)
+{
+ editor_select_widget (widget, NULL, 0, 0);
+}
+
+
+
+void
+editor_on_delete ()
+{
+ if (selected_widgets)
+ delete (selected_widgets->data);
+}
+
+
+/* This is when the 'Cut' menuitem on the popup menu is selected */
+void
+editor_on_cut_activate (GtkWidget * menuitem, GtkWidget * widget)
+{
+ glade_clipboard_cut (GLADE_CLIPBOARD (glade_clipboard), current_project,
+ widget);
+}
+
+
+/* This is when the 'Copy' menuitem on the popup menu is selected */
+void
+editor_on_copy_activate (GtkWidget * menuitem, GtkWidget * widget)
+{
+ glade_clipboard_copy (GLADE_CLIPBOARD (glade_clipboard), current_project,
+ widget);
+}
+
+
+/* This is when the 'Cut' menuitem on the popup menu is selected */
+void
+editor_on_paste_activate (GtkWidget * menuitem, GtkWidget * widget)
+{
+ glade_clipboard_paste (GLADE_CLIPBOARD (glade_clipboard), current_project,
+ widget);
+}
+
+
+/* This is when the 'Delete' menuitem on the popup menu is selected */
+void
+editor_on_delete_activate (GtkWidget * menuitem, GtkWidget * widget)
+{
+ delete (widget);
+}
+
+
+static void
+delete (GtkWidget * widget)
+{
+ if (GB_IS_PLACEHOLDER (widget))
+ delete_placeholder (widget);
+ else
+ editor_delete_widget (widget);
+}
+
+
+static void
+delete_placeholder (GtkWidget * placeholder)
+{
+ GtkWidget *parent = placeholder->parent;
+ gchar *child_name;
+
+ /* SPECIAL CODE: Don't allow placeholders in clist titles to be deleted. */
+ child_name = gb_widget_get_child_name (placeholder);
+ if (child_name)
+ {
+ if (!strcmp (child_name, GladeChildCListTitle))
+ {
+ MSG1 ("Not deleting special widget: %s\n", child_name);
+ return;
+ }
+ }
+
+ /* SPECIAL CODE: Don't allow placeholder in BonoboDock to be deleted. */
+#ifdef USE_GNOME
+ if (BONOBO_IS_DOCK (parent))
+ return;
+#endif
+
+ /* Remove widget from the selection */
+ editor_clear_selection (NULL);
+
+ /* Can't delete children of a paned or a viewport */
+ if (GTK_IS_PANED (parent) || GTK_IS_VIEWPORT (parent))
+ return;
+
+ /* For a Clist, we can delete everything except column title widgets */
+ if (GTK_IS_CLIST (parent))
+ {
+ g_warning ("Deleting a widget in a clist - not implemented yet");
+ return;
+ }
+
+ /* If the parent is a toolitem, delete that. */
+ if (GTK_IS_TOOL_ITEM (parent))
+ {
+ gtk_container_remove (GTK_CONTAINER (parent->parent), parent);
+ return;
+ }
+
+ /* Widgets with these parents can all be deleted OK */
+ if (GTK_IS_TOOLBAR (parent)
+#if GLADE_SUPPORTS_GTK_TREE
+ || GTK_IS_TREE (parent)
+#endif
+ || GTK_IS_LIST (parent))
+ {
+ gtk_widget_destroy (placeholder);
+ return;
+ }
+
+ /* For a frame we can delete a placeholder in the label widget on its own.
+ Otherwise we delete the parent, just like GtkBin. */
+ if (GTK_IS_FRAME (parent))
+ {
+ if (gtk_frame_get_label_widget (GTK_FRAME (parent)) == placeholder)
+ gtk_widget_destroy (placeholder);
+ else
+ editor_delete_widget (parent);
+ return;
+ }
+
+ /* For these widgets replace the parent with a placeholder, or delete the
+ component if parent is a toplevel widget */
+ if (GTK_IS_BIN (parent) || GTK_IS_BUTTON (parent))
+ {
+ editor_delete_widget (parent);
+ return;
+ }
+
+ /* For a box, if the placeholder is the only child then replace the box with
+ a placeholder, else just delete the placeholder */
+ if (GTK_IS_BOX (parent))
+ {
+ if (g_list_length (GTK_BOX (parent)->children) == 1)
+ {
+ editor_delete_widget (parent);
+ }
+ else
+ {
+ gtk_container_remove (GTK_CONTAINER (parent), placeholder);
+ /* Shouldn't really need to do this */
+ gtk_widget_queue_resize (parent);
+ }
+ return;
+ }
+
+ /* For a notebook, if placeholder is the only page, replace the notebook with
+ a placeholder, else delete the current notebook page (i.e. placeholder) */
+ if (GTK_IS_NOTEBOOK (parent))
+ {
+ if (g_list_length (GTK_NOTEBOOK (parent)->children) == 1)
+ {
+ editor_delete_widget (parent);
+ }
+ else
+ {
+ gtk_notebook_remove_page (GTK_NOTEBOOK (parent), gtk_notebook_get_current_page (GTK_NOTEBOOK (parent)));
+ }
+ return;
+ }
+
+ /* For a table, can't delete placeholder, unless there is only 1 row or
+ column. In this case delete the placeholder, and move all the other
+ children up/left. If the table is 1 x 1 then delete the table. */
+ if (GTK_IS_TABLE (parent))
+ {
+ gint nrows, ncols, position = 0, distance_to_move = 0;
+ GList *item;
+ GtkTableChild *table_child;
+
+ nrows = GTK_TABLE (parent)->nrows;
+ ncols = GTK_TABLE (parent)->ncols;
+ if (nrows > 1 && ncols > 1)
+ return;
+ if (nrows == 1 && ncols == 1)
+ {
+ editor_delete_widget (parent);
+ return;
+ }
+
+ /* Find out where placeholder is */
+ item = GTK_TABLE (parent)->children;
+ while (item)
+ {
+ table_child = (GtkTableChild *) item->data;
+
+ if (table_child->widget == placeholder)
+ {
+ /* Calculate how far up/left we will have to move the rest of the
+ children */
+ if (nrows == 1)
+ {
+ position = table_child->left_attach;
+ distance_to_move = table_child->right_attach
+ - table_child->left_attach;
+ }
+ else
+ {
+ position = table_child->top_attach;
+ distance_to_move = table_child->bottom_attach
+ - table_child->top_attach;
+ }
+ break;
+ }
+ item = item->next;
+ }
+ /* Shouldn't reach the end of the list */
+ g_return_if_fail (item != NULL);
+ gtk_widget_destroy (placeholder);
+
+ /* Now step through the table again, moving children up or left */
+ item = GTK_TABLE (parent)->children;
+ while (item)
+ {
+ table_child = (GtkTableChild *) item->data;
+ if (nrows == 1)
+ {
+ if (table_child->left_attach > position)
+ {
+ table_child->left_attach -= distance_to_move;
+ table_child->right_attach -= distance_to_move;
+ }
+ }
+ else
+ {
+ if (table_child->top_attach > position)
+ {
+ table_child->top_attach -= distance_to_move;
+ table_child->bottom_attach -= distance_to_move;
+ }
+ }
+ item = item->next;
+ }
+
+ /* Now update the tables nrows & ncols */
+ if (nrows == 1)
+ GTK_TABLE (parent)->ncols -= distance_to_move;
+ else
+ GTK_TABLE (parent)->nrows -= distance_to_move;
+
+ return;
+ }
+
+ g_warning ("Don't know how to delete widget");
+}
+
+
+void
+editor_delete_widget (GtkWidget * widget)
+{
+ GtkWidget *parent, *placeholder;
+ gchar *error;
+
+ MSG1 ("In editor_delete_widget: %s\n", gtk_widget_get_name (widget));
+
+ error = editor_can_delete_widget (widget);
+ if (error)
+ {
+ glade_util_show_message_box (error, widget);
+ return;
+ }
+
+ /* If we are deleting a GtkTextView set the text to "". This avoids an odd
+ crash. See bug #111604. */
+ if (GTK_IS_TEXT_VIEW (widget))
+ gtk_text_buffer_set_text (gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)), "", 0);
+
+ /* Set properties widget to NULL, in case the widget or parent is deleted */
+ property_set_widget (NULL);
+
+ /* Remove widget from the selection */
+ editor_clear_selection (NULL);
+
+ if (GTK_IS_MENU (widget))
+ parent = gtk_menu_get_attach_widget (GTK_MENU (widget));
+ else
+ parent = widget->parent;
+
+ /* If widget is a toplevel widget (i.e. a project component) delete the
+ component. */
+ if (parent == NULL)
+ {
+ glade_project_remove_component (current_project, widget);
+ return;
+ }
+
+ /* If the widget's parent is a fixed container or a packer remove the widget
+ completely. */
+ if (GTK_IS_FIXED (widget->parent)
+#if GLADE_SUPPORTS_GTK_PACKER
+ || GTK_IS_PACKER (widget->parent)
+#endif
+ || GTK_IS_LAYOUT (widget->parent))
+ {
+ gtk_widget_destroy (widget);
+ return;
+ }
+
+ /* If the widget's parent is a button box remove widget completely. */
+ if (GTK_IS_BUTTON_BOX (widget->parent))
+ {
+ gtk_widget_destroy (widget);
+ return;
+ }
+
+ /* If the widget is a menu item remove widget completely. */
+ if (GTK_IS_MENU_ITEM (widget))
+ {
+ gtk_widget_destroy (widget);
+ return;
+ }
+
+#ifdef USE_GNOME
+ /* GnomeDockItem widgets are also removed completely rather than being
+ replaced by a placeholder. */
+ if (BONOBO_IS_DOCK_ITEM (widget))
+ {
+ gtk_widget_destroy (widget);
+ return;
+ }
+
+ /* If this is a page in a GnomeDruid, then if it is the only page delete
+ the entire GnomeDruid, else delete the page. We need to make sure the
+ current page is set to something else before deleting the page. */
+ if (GNOME_IS_DRUID_PAGE (widget))
+ {
+ gint num_pages;
+ GList *children, *elem;
+ GnomeDruidPage *new_current_page;
+
+ g_return_if_fail (GNOME_IS_DRUID (parent));
+
+ children = gtk_container_get_children (GTK_CONTAINER (parent));
+ num_pages = g_list_length (children);
+ if (num_pages == 1)
+ {
+ editor_delete_widget (parent);
+ }
+ else
+ {
+ elem = g_list_find (children, widget);
+ g_return_if_fail (elem != NULL);
+
+ if (elem->next)
+ new_current_page = elem->next->data;
+ else
+ new_current_page = elem->prev->data;
+
+ gnome_druid_set_page (GNOME_DRUID (parent), new_current_page);
+
+ gtk_widget_destroy (widget);
+ }
+
+ g_list_free (children);
+
+ return;
+ }
+#endif
+
+ /* Replace normal widget's with a placeholder & select it so it can also
+ be deleted easily using the Delete key. But we must be careful since
+ there is a slight chance that the placeholder will be automatically
+ destroyed, e.g. if it is placed in a table which already has another
+ widget in the same position. */
+ placeholder = editor_new_placeholder ();
+ gtk_widget_ref (placeholder);
+ if (gb_widget_replace_child (widget->parent, widget, placeholder))
+ {
+ if (placeholder->parent)
+ editor_select_widget (placeholder, NULL, 0, 0);
+ gtk_widget_unref (placeholder);
+ }
+ else
+ {
+ glade_util_show_message_box (_("Couldn't delete widget."), widget);
+ gtk_object_sink (GTK_OBJECT (placeholder));
+ gtk_widget_unref (placeholder);
+ }
+
+ MSG ("Out editor_delete_widget");
+}
+
+
+/* This sees if a widget can be deleted. It returns an appropriate error
+ message if it can't. */
+gchar*
+editor_can_delete_widget (GtkWidget * widget)
+{
+ gchar *child_name;
+
+ /* Button & item children are special - they can be deleted. */
+ if (widget->parent && GB_IS_GB_WIDGET (widget->parent)
+ && (GTK_IS_BUTTON (widget->parent) || GTK_IS_ITEM (widget->parent)))
+ return NULL;
+
+ /* Don't allow widgets which aren't GbWidgets to be deleted, since we know
+ nothing about them. */
+ if (!GB_IS_PLACEHOLDER (widget) && !GB_IS_GB_WIDGET (widget))
+ return _("The widget can't be deleted");
+
+ /* Non-toplevel menus are created automatically so we can't delete them. */
+ if (GTK_IS_MENU (widget) && gtk_menu_get_attach_widget (GTK_MENU (widget)))
+ return _("The widget can't be deleted");
+
+
+ /* SPECIAL CODE: Don't allow dialog buttons & widgets to be deleted. */
+ 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)
+#ifdef USE_GNOME
+ || !strcmp (child_name, GladeChildGnomeAppDock)
+ || !strcmp (child_name, GladeChildGnomeAppBar)
+ || !strcmp (child_name, GladeChildGnomeDruidVBox)
+ || !strcmp (child_name, GladeChildGnomeEntry)
+#endif
+ )
+ {
+ return _("The widget is created automatically as part of the parent widget, and it can't be deleted.");
+ }
+ }
+
+ return NULL;
+}
+
+
+/* Called when a GbWidget is destroyed so the editor can remove any references
+ to it. */
+void
+editor_on_widget_destroyed (GtkWidget *widget)
+{
+#if 0
+ const char *name = gtk_widget_get_name (widget);
+ g_print ("In editor_on_widget_destroyed: %s\n", name ? name : "NULL");
+#endif
+
+ if (mouse_over_widget == widget)
+ {
+#if 0
+ g_print (" resetting mouse_over_widget to NULL\n");
+#endif
+ mouse_over_widget = NULL;
+ }
+}
+