diff options
Diffstat (limited to 'tools/glade/glade/editor.c')
-rw-r--r-- | tools/glade/glade/editor.c | 3598 |
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 = >K_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; + } +} + |