/* * glade-gtk-table.c - GladeWidgetAdaptor for GtkTable widget * * Copyright (C) 2008 Tristan Van Berkom * * Author(s): * Tristan Van Berkom * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include "glade-fixed.h" typedef struct { /* comparable part: */ GladeWidget *widget; gint left_attach; gint right_attach; gint top_attach; gint bottom_attach; } GladeGtkTableChild; typedef enum { DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT } GladeTableDir; typedef enum { GROUP_ACTION_INSERT_ROW, GROUP_ACTION_INSERT_COLUMN, GROUP_ACTION_REMOVE_COLUMN, GROUP_ACTION_REMOVE_ROW } GroupAction; /* Redefine GTK_TABLE() macro, as GtkTable is deprecated */ #undef GTK_TABLE #define GTK_TABLE(obj) ((GtkTable *)obj) static void glade_gtk_table_get_child_attachments (GtkWidget * table, GtkWidget * child, GtkTableChild * tchild) { guint left, right, top, bottom; gtk_container_child_get (GTK_CONTAINER (table), child, "left-attach", (guint *) & left, "right-attach", (guint *) & right, "bottom-attach", (guint *) & bottom, "top-attach", (guint *) & top, NULL); tchild->widget = child; tchild->left_attach = left; tchild->right_attach = right; tchild->top_attach = top; tchild->bottom_attach = bottom; } static gboolean glade_gtk_table_widget_exceeds_bounds (GtkTable * table, gint n_rows, gint n_cols) { GList *list, *children; gboolean ret = FALSE; children = gtk_container_get_children (GTK_CONTAINER (table)); for (list = children; list && list->data; list = list->next) { GtkTableChild child; glade_gtk_table_get_child_attachments (GTK_WIDGET (table), GTK_WIDGET (list->data), &child); if (GLADE_IS_PLACEHOLDER (child.widget) == FALSE && (child.right_attach > n_cols || child.bottom_attach > n_rows)) { ret = TRUE; break; } } g_list_free (children); return ret; } #define TABLE_OCCUPIED(occmap, n_columns, col, row) \ (occmap)[row * n_columns + col] static void glade_gtk_table_build_occupation_maps(GtkTable *table, guint n_columns, guint n_rows, gchar **child_map, gpointer **placeholder_map) { guint i, j; GList *list, *children = gtk_container_get_children (GTK_CONTAINER (table)); *child_map = g_malloc0(n_columns * n_rows * sizeof(gchar)); /* gchar is smaller than gboolean */ *placeholder_map = g_malloc0(n_columns * n_rows * sizeof(gpointer)); for (list = children; list && list->data; list = list->next) { GtkTableChild child; glade_gtk_table_get_child_attachments (GTK_WIDGET (table), GTK_WIDGET (list->data), &child); if (GLADE_IS_PLACEHOLDER(list->data)) { /* assumption: placeholders are always attached to exactly 1 cell */ TABLE_OCCUPIED(*placeholder_map, n_columns, child.left_attach, child.top_attach) = list->data; } else { for (i = child.left_attach; i < child.right_attach && i < n_columns; i++) { for (j = child.top_attach; j < child.bottom_attach && j < n_rows; j++) { TABLE_OCCUPIED(*child_map, n_columns, i, j) = 1; } } } } g_list_free (children); } static void glade_gtk_table_refresh_placeholders (GtkTable * table) { guint n_columns, n_rows, i, j; gchar *child_map; gpointer *placeholder_map; g_object_get (table, "n-columns", &n_columns, "n-rows", &n_rows, NULL); glade_gtk_table_build_occupation_maps (table, n_columns, n_rows, &child_map, &placeholder_map); for (i = 0; i < n_columns; i++) { for (j = 0; j < n_rows; j++) { gpointer placeholder = TABLE_OCCUPIED(placeholder_map, n_columns, i, j); if (TABLE_OCCUPIED(child_map, n_columns, i, j)) { if (placeholder) { gtk_container_remove (GTK_CONTAINER (table), GTK_WIDGET (placeholder)); } } else { if (!placeholder) { G_GNUC_BEGIN_IGNORE_DEPRECATIONS; gtk_table_attach_defaults (table, glade_placeholder_new (), i, i + 1, j, j + 1); G_GNUC_END_IGNORE_DEPRECATIONS; } } } } g_free(child_map); g_free(placeholder_map); if (gtk_widget_get_realized (GTK_WIDGET (table))) gtk_container_check_resize (GTK_CONTAINER (table)); } static void gtk_table_children_callback (GtkWidget * widget, gpointer client_data) { GList **children; children = (GList **) client_data; *children = g_list_prepend (*children, widget); } GList * glade_gtk_table_get_children (GladeWidgetAdaptor * adaptor, GtkContainer * container) { GList *children = NULL; gtk_container_forall (container, gtk_table_children_callback, &children); /* GtkTable has the children list already reversed */ return children; } void glade_gtk_table_add_child (GladeWidgetAdaptor * adaptor, GObject * object, GObject * child) { gtk_container_add (GTK_CONTAINER (object), GTK_WIDGET (child)); glade_gtk_table_refresh_placeholders (GTK_TABLE (object)); } void glade_gtk_table_remove_child (GladeWidgetAdaptor * adaptor, GObject * object, GObject * child) { gtk_container_remove (GTK_CONTAINER (object), GTK_WIDGET (child)); glade_gtk_table_refresh_placeholders (GTK_TABLE (object)); } void glade_gtk_table_replace_child (GladeWidgetAdaptor * adaptor, GtkWidget * container, GtkWidget * current, GtkWidget * new_widget) { /* Chain Up */ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->replace_child (adaptor, G_OBJECT (container), G_OBJECT (current), G_OBJECT (new_widget)); /* If we are replacing a GladeWidget, we must refresh placeholders * because the widget may have spanned multiple rows/columns, we must * not do so in the case we are pasting multiple widgets into a table, * where destroying placeholders results in default packing properties * (since the remaining placeholder templates no longer exist, only the * first pasted widget would have proper packing properties). */ if (!GLADE_IS_PLACEHOLDER (new_widget)) glade_gtk_table_refresh_placeholders (GTK_TABLE (container)); } static void glade_gtk_table_set_n_common (GObject * object, const GValue * value, gboolean for_rows) { GladeWidget *widget; GtkTable *table; guint new_size, old_size, n_columns, n_rows; table = GTK_TABLE (object); g_object_get (table, "n-columns", &n_columns, "n-rows", &n_rows, NULL); new_size = g_value_get_uint (value); old_size = for_rows ? n_rows : n_columns; if (new_size < 1) return; if (glade_gtk_table_widget_exceeds_bounds (table, for_rows ? new_size : n_rows, for_rows ? n_columns : new_size)) /* Refuse to shrink if it means orphaning widgets */ return; widget = glade_widget_get_from_gobject (GTK_WIDGET (table)); g_return_if_fail (widget != NULL); G_GNUC_BEGIN_IGNORE_DEPRECATIONS; if (for_rows) gtk_table_resize (table, new_size, n_columns); else gtk_table_resize (table, n_rows, new_size); G_GNUC_END_IGNORE_DEPRECATIONS; /* Fill table with placeholders */ glade_gtk_table_refresh_placeholders (table); if (new_size < old_size) { /* Remove from the bottom up */ GList *list, *children; GList *list_to_free = NULL; children = gtk_container_get_children (GTK_CONTAINER (table)); for (list = children; list && list->data; list = list->next) { GtkTableChild child; guint start, end; glade_gtk_table_get_child_attachments (GTK_WIDGET (table), GTK_WIDGET (list->data), &child); start = for_rows ? child.top_attach : child.left_attach; end = for_rows ? child.bottom_attach : child.right_attach; /* We need to completely remove it */ if (start >= new_size) { list_to_free = g_list_prepend (list_to_free, child.widget); continue; } /* If the widget spans beyond the new border, * we should resize it to fit on the new table */ if (end > new_size) gtk_container_child_set (GTK_CONTAINER (table), GTK_WIDGET (child.widget), for_rows ? "bottom_attach" : "right_attach", new_size, NULL); } g_list_free (children); if (list_to_free) { for (list = g_list_first (list_to_free); list && list->data; list = list->next) { g_object_ref (G_OBJECT (list->data)); gtk_container_remove (GTK_CONTAINER (table), GTK_WIDGET (list->data)); /* This placeholder is no longer valid, force destroy */ gtk_widget_destroy (GTK_WIDGET (list->data)); } g_list_free (list_to_free); } G_GNUC_BEGIN_IGNORE_DEPRECATIONS; gtk_table_resize (table, for_rows ? new_size : n_rows, for_rows ? n_columns : new_size); G_GNUC_END_IGNORE_DEPRECATIONS; } } void glade_gtk_table_set_property (GladeWidgetAdaptor * adaptor, GObject * object, const gchar * id, const GValue * value) { if (!strcmp (id, "n-rows")) glade_gtk_table_set_n_common (object, value, TRUE); else if (!strcmp (id, "n-columns")) glade_gtk_table_set_n_common (object, value, FALSE); else GWA_GET_CLASS (GTK_TYPE_CONTAINER)->set_property (adaptor, object, id, value); } static gboolean glade_gtk_table_verify_n_common (GObject * object, const GValue * value, gboolean for_rows) { GtkTable *table = GTK_TABLE (object); guint n_columns, n_rows, new_size = g_value_get_uint (value); g_object_get (table, "n-columns", &n_columns, "n-rows", &n_rows, NULL); if (glade_gtk_table_widget_exceeds_bounds (table, for_rows ? new_size : n_rows, for_rows ? n_columns : new_size)) /* Refuse to shrink if it means orphaning widgets */ return FALSE; return TRUE; } gboolean glade_gtk_table_verify_property (GladeWidgetAdaptor * adaptor, GObject * object, const gchar * id, const GValue * value) { if (!strcmp (id, "n-rows")) return glade_gtk_table_verify_n_common (object, value, TRUE); else if (!strcmp (id, "n-columns")) return glade_gtk_table_verify_n_common (object, value, FALSE); else if (GWA_GET_CLASS (GTK_TYPE_CONTAINER)->verify_property) GWA_GET_CLASS (GTK_TYPE_CONTAINER)->verify_property (adaptor, object, id, value); return TRUE; } void glade_gtk_table_set_child_property (GladeWidgetAdaptor * adaptor, GObject * container, GObject * child, const gchar * property_name, GValue * value) { GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_set_property (adaptor, container, child, property_name, value); if (strcmp (property_name, "bottom-attach") == 0 || strcmp (property_name, "left-attach") == 0 || strcmp (property_name, "right-attach") == 0 || strcmp (property_name, "top-attach") == 0) { /* Refresh placeholders */ glade_gtk_table_refresh_placeholders (GTK_TABLE (container)); } } static gboolean glade_gtk_table_verify_attach_common (GObject * object, GValue * value, guint * val, const gchar * prop, guint * prop_val, const gchar * parent_prop, guint * parent_val) { GladeWidget *widget, *parent; widget = glade_widget_get_from_gobject (object); g_return_val_if_fail (GLADE_IS_WIDGET (widget), TRUE); parent = glade_widget_get_parent (widget); g_return_val_if_fail (GLADE_IS_WIDGET (parent), TRUE); *val = g_value_get_uint (value); glade_widget_property_get (widget, prop, prop_val); glade_widget_property_get (parent, parent_prop, parent_val); return FALSE; } static gboolean glade_gtk_table_verify_left_top_attach (GObject * object, GValue * value, const gchar * prop, const gchar * parent_prop) { guint val, prop_val, parent_val; if (glade_gtk_table_verify_attach_common (object, value, &val, prop, &prop_val, parent_prop, &parent_val)) return FALSE; if (val >= parent_val || val >= prop_val) return FALSE; return TRUE; } static gboolean glade_gtk_table_verify_right_bottom_attach (GObject * object, GValue * value, const gchar * prop, const gchar * parent_prop) { guint val, prop_val, parent_val; if (glade_gtk_table_verify_attach_common (object, value, &val, prop, &prop_val, parent_prop, &parent_val)) return FALSE; if (val <= prop_val || val > parent_val) return FALSE; return TRUE; } gboolean glade_gtk_table_child_verify_property (GladeWidgetAdaptor * adaptor, GObject * container, GObject * child, const gchar * id, GValue * value) { if (!strcmp (id, "left-attach")) return glade_gtk_table_verify_left_top_attach (child, value, "right-attach", "n-columns"); else if (!strcmp (id, "right-attach")) return glade_gtk_table_verify_right_bottom_attach (child, value, "left-attach", "n-columns"); else if (!strcmp (id, "top-attach")) return glade_gtk_table_verify_left_top_attach (child, value, "bottom-attach", "n-rows"); else if (!strcmp (id, "bottom-attach")) return glade_gtk_table_verify_right_bottom_attach (child, value, "top-attach", "n-rows"); else if (GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_verify_property) GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_verify_property (adaptor, container, child, id, value); return TRUE; } static void glade_gtk_table_child_insert_remove_action (GladeWidgetAdaptor *adaptor, GObject *container, GObject *object, GroupAction group_action, const gchar *n_row_col, const gchar *attach1, /* should be smaller (top/left) attachment */ const gchar *attach2, /* should be larger (bot/right) attachment */ gboolean remove, gboolean after) { GladeWidget *parent; GList *children, *l; gint child_pos, size, offset; gtk_container_child_get (GTK_CONTAINER (container), GTK_WIDGET (object), after ? attach2 : attach1, &child_pos, NULL); parent = glade_widget_get_from_gobject (container); switch (group_action) { case GROUP_ACTION_INSERT_ROW: glade_command_push_group (_("Insert Row on %s"), glade_widget_get_name (parent)); break; case GROUP_ACTION_INSERT_COLUMN: glade_command_push_group (_("Insert Column on %s"), glade_widget_get_name (parent)); break; case GROUP_ACTION_REMOVE_COLUMN: glade_command_push_group (_("Remove Column on %s"), glade_widget_get_name (parent)); break; case GROUP_ACTION_REMOVE_ROW: glade_command_push_group (_("Remove Row on %s"), glade_widget_get_name (parent)); break; default: g_assert_not_reached (); } children = glade_widget_adaptor_get_children (adaptor, container); /* Make sure widgets does not get destroyed */ g_list_foreach (children, (GFunc) g_object_ref, NULL); glade_widget_property_get (parent, n_row_col, &size); if (remove) { GList *del = NULL; /* Remove children first */ for (l = children; l; l = g_list_next (l)) { GladeWidget *gchild = glade_widget_get_from_gobject (l->data); gint pos1, pos2; /* Skip placeholders */ if (gchild == NULL) continue; glade_widget_pack_property_get (gchild, attach1, &pos1); glade_widget_pack_property_get (gchild, attach2, &pos2); if ((pos1 + 1 == pos2) && ((after ? pos2 : pos1) == child_pos)) { del = g_list_prepend (del, gchild); } } if (del) { glade_command_delete (del); g_list_free (del); } offset = -1; } else { /* Expand the table */ glade_command_set_property (glade_widget_get_property (parent, n_row_col), size + 1); offset = 1; } /* Reorder children */ for (l = children; l; l = g_list_next (l)) { GladeWidget *gchild = glade_widget_get_from_gobject (l->data); gint pos; /* Skip placeholders */ if (gchild == NULL) continue; /* if removing, do top/left before bot/right */ if (remove) { /* adjust top-left attachment */ glade_widget_pack_property_get (gchild, attach1, &pos); if (pos > child_pos || (after && pos == child_pos)) { glade_command_set_property (glade_widget_get_pack_property (gchild, attach1), pos + offset); } /* adjust bottom-right attachment */ glade_widget_pack_property_get (gchild, attach2, &pos); if (pos > child_pos || (after && pos == child_pos)) { glade_command_set_property (glade_widget_get_pack_property (gchild, attach2), pos + offset); } } /* if inserting, do bot/right before top/left */ else { /* adjust bottom-right attachment */ glade_widget_pack_property_get (gchild, attach2, &pos); if (pos > child_pos) { glade_command_set_property (glade_widget_get_pack_property (gchild, attach2), pos + offset); } /* adjust top-left attachment */ glade_widget_pack_property_get (gchild, attach1, &pos); if (pos >= child_pos) { glade_command_set_property (glade_widget_get_pack_property (gchild, attach1), pos + offset); } } } if (remove) { /* Shrink the table */ glade_command_set_property (glade_widget_get_property (parent, n_row_col), size - 1); } g_list_foreach (children, (GFunc) g_object_unref, NULL); g_list_free (children); glade_command_pop_group (); } void glade_gtk_table_child_action_activate (GladeWidgetAdaptor * adaptor, GObject * container, GObject * object, const gchar * action_path) { if (strcmp (action_path, "insert_row/after") == 0) { glade_gtk_table_child_insert_remove_action (adaptor, container, object, GROUP_ACTION_INSERT_ROW, "n-rows", "top-attach", "bottom-attach", FALSE, TRUE); } else if (strcmp (action_path, "insert_row/before") == 0) { glade_gtk_table_child_insert_remove_action (adaptor, container, object, GROUP_ACTION_INSERT_ROW, "n-rows", "top-attach", "bottom-attach", FALSE, FALSE); } else if (strcmp (action_path, "insert_column/after") == 0) { glade_gtk_table_child_insert_remove_action (adaptor, container, object, GROUP_ACTION_INSERT_COLUMN, "n-columns", "left-attach", "right-attach", FALSE, TRUE); } else if (strcmp (action_path, "insert_column/before") == 0) { glade_gtk_table_child_insert_remove_action (adaptor, container, object, GROUP_ACTION_INSERT_COLUMN, "n-columns", "left-attach", "right-attach", FALSE, FALSE); } else if (strcmp (action_path, "remove_column") == 0) { glade_gtk_table_child_insert_remove_action (adaptor, container, object, GROUP_ACTION_REMOVE_COLUMN, "n-columns", "left-attach", "right-attach", TRUE, FALSE); } else if (strcmp (action_path, "remove_row") == 0) { glade_gtk_table_child_insert_remove_action (adaptor, container, object, GROUP_ACTION_REMOVE_ROW, "n-rows", "top-attach", "bottom-attach", TRUE, FALSE); } else GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_action_activate (adaptor, container, object, action_path); }