/* * Copyright (C) 2008 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. * * Authors: * Tristan Van Berkom */ #include #include #include #include #include "glade-cell-renderer-editor.h" #include "glade-column-types.h" static void glade_cell_renderer_editor_finalize (GObject * object); static void glade_cell_renderer_editor_editable_init (GladeEditableIface * iface); static void glade_cell_renderer_editor_grab_focus (GtkWidget * widget); typedef struct { GladeCellRendererEditor *editor; GtkWidget *attributes_check; GladePropertyClass *pclass; GladePropertyClass *attr_pclass; GladePropertyClass *use_attr_pclass; GtkWidget *use_prop_label; GtkWidget *use_attr_label; GtkWidget *use_prop_eprop; GtkWidget *use_attr_eprop; } CheckTab; static GladeEditableIface *parent_editable_iface; G_DEFINE_TYPE_WITH_CODE (GladeCellRendererEditor, glade_cell_renderer_editor, GTK_TYPE_BOX, G_IMPLEMENT_INTERFACE (GLADE_TYPE_EDITABLE, glade_cell_renderer_editor_editable_init)); static void glade_cell_renderer_editor_class_init (GladeCellRendererEditorClass * klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = glade_cell_renderer_editor_finalize; widget_class->grab_focus = glade_cell_renderer_editor_grab_focus; } static void glade_cell_renderer_editor_init (GladeCellRendererEditor * self) { gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL); } static void glade_cell_renderer_editor_load (GladeEditable * editable, GladeWidget * widget) { GladeCellRendererEditor *renderer_editor = GLADE_CELL_RENDERER_EDITOR (editable); GList *l; /* Chain up to default implementation */ parent_editable_iface->load (editable, widget); /* load the embedded editable... */ if (renderer_editor->embed) glade_editable_load (GLADE_EDITABLE (renderer_editor->embed), widget); for (l = renderer_editor->properties; l; l = l->next) glade_editor_property_load_by_widget (GLADE_EDITOR_PROPERTY (l->data), widget); if (widget) { for (l = renderer_editor->checks; l; l = l->next) { CheckTab *tab = l->data; gboolean use_attr = FALSE; glade_widget_property_get (widget, glade_property_class_id (tab->use_attr_pclass), &use_attr); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tab->attributes_check), use_attr); if (use_attr) { //gtk_widget_show (tab->use_attr_label); gtk_widget_show (tab->use_attr_eprop); //gtk_widget_hide (tab->use_prop_label); gtk_widget_hide (tab->use_prop_eprop); } else { gtk_widget_show (tab->use_prop_label); gtk_widget_show (tab->use_prop_eprop); gtk_widget_hide (tab->use_attr_label); gtk_widget_hide (tab->use_attr_eprop); } } } } static void glade_cell_renderer_editor_set_show_name (GladeEditable * editable, gboolean show_name) { GladeCellRendererEditor *renderer_editor = GLADE_CELL_RENDERER_EDITOR (editable); glade_editable_set_show_name (GLADE_EDITABLE (renderer_editor->embed), show_name); } static void glade_cell_renderer_editor_editable_init (GladeEditableIface * iface) { parent_editable_iface = g_type_default_interface_peek (GLADE_TYPE_EDITABLE); iface->load = glade_cell_renderer_editor_load; iface->set_show_name = glade_cell_renderer_editor_set_show_name; } static void glade_cell_renderer_editor_finalize (GObject * object) { GladeCellRendererEditor *renderer_editor = GLADE_CELL_RENDERER_EDITOR (object); g_list_foreach (renderer_editor->checks, (GFunc) g_free, NULL); g_list_free (renderer_editor->checks); g_list_free (renderer_editor->properties); renderer_editor->properties = NULL; renderer_editor->checks = NULL; renderer_editor->embed = NULL; glade_editable_load (GLADE_EDITABLE (object), NULL); G_OBJECT_CLASS (glade_cell_renderer_editor_parent_class)->finalize (object); } static void glade_cell_renderer_editor_grab_focus (GtkWidget * widget) { GladeCellRendererEditor *renderer_editor = GLADE_CELL_RENDERER_EDITOR (widget); gtk_widget_grab_focus (renderer_editor->embed); } static void attributes_toggled (GtkWidget * widget, CheckTab * tab) { GladeCellRendererEditor *renderer_editor = tab->editor; GladeProperty *property; GladeWidget *gwidget; GValue value = { 0, }; gwidget = glade_editable_loaded_widget (GLADE_EDITABLE (renderer_editor)); if (glade_editable_loading (GLADE_EDITABLE (renderer_editor)) || !gwidget) return; glade_editable_block (GLADE_EDITABLE (renderer_editor)); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (tab->attributes_check))) { glade_command_push_group (_("Setting %s to use the %s property as an attribute"), glade_widget_get_name (gwidget), glade_property_class_id (tab->pclass)); property = glade_widget_get_property (gwidget, glade_property_class_id (tab->pclass)); glade_property_get_default (property, &value); glade_command_set_property_value (property, &value); g_value_unset (&value); property = glade_widget_get_property (gwidget, glade_property_class_id (tab->use_attr_pclass)); glade_command_set_property (property, TRUE); glade_command_pop_group (); } else { glade_command_push_group (_("Setting %s to use the %s property directly"), glade_widget_get_name (gwidget), glade_property_class_id (tab->pclass)); property = glade_widget_get_property (gwidget, glade_property_class_id (tab->attr_pclass)); glade_property_get_default (property, &value); glade_command_set_property_value (property, &value); g_value_unset (&value); property = glade_widget_get_property (gwidget, glade_property_class_id (tab->use_attr_pclass)); glade_command_set_property (property, FALSE); glade_command_pop_group (); } glade_editable_unblock (GLADE_EDITABLE (renderer_editor)); /* reload buttons and sensitivity and stuff... */ glade_editable_load (GLADE_EDITABLE (renderer_editor), gwidget); } static gint property_class_comp (gconstpointer a, gconstpointer b) { GladePropertyClass *ca = (GladePropertyClass *)a, *cb = (GladePropertyClass *)b; GParamSpec *pa, *pb; pa = glade_property_class_get_pspec (ca); pb = glade_property_class_get_pspec (cb); if (pa->owner_type == pb->owner_type) { gdouble result = glade_property_class_weight (ca) - glade_property_class_weight (cb); /* Avoid cast to int */ if (result < 0.0) return -1; else if (result > 0.0) return 1; else return 0; } else { if (g_type_is_a (pa->owner_type, pb->owner_type)) return (glade_property_class_common (ca) || glade_property_class_get_is_packing (ca)) ? 1 : -1; else return (glade_property_class_common (ca) || glade_property_class_get_is_packing (ca)) ? -1 : 1; } } static GList * get_sorted_properties (GladeWidgetAdaptor * adaptor, GladeEditorPageType type) { GList *list = NULL; const GList *l; for (l = glade_widget_adaptor_get_properties (adaptor); l; l = l->next) { GladePropertyClass *klass = l->data; if (GLADE_PROPERTY_CLASS_IS_TYPE (klass, type) && (glade_property_class_is_visible (klass))) { list = g_list_prepend (list, klass); } } return g_list_sort (list, property_class_comp); } GtkWidget * glade_cell_renderer_editor_new (GladeWidgetAdaptor * adaptor, GladeEditorPageType type, GladeEditable * embed) { GladeCellRendererEditor *renderer_editor; GladeEditorProperty *eprop; GladePropertyClass *pclass, *attr_pclass, *use_attr_pclass; GList *list, *sorted; GtkWidget *hbox_left, *hbox_right, *grid; gchar *str; gint row = 0; g_return_val_if_fail (GLADE_IS_WIDGET_ADAPTOR (adaptor), NULL); g_return_val_if_fail (GLADE_IS_EDITABLE (embed), NULL); renderer_editor = g_object_new (GLADE_TYPE_CELL_RENDERER_EDITOR, NULL); renderer_editor->embed = GTK_WIDGET (embed); /* Pack the parent on top... */ gtk_box_pack_start (GTK_BOX (renderer_editor), GTK_WIDGET (embed), FALSE, FALSE, 0); /* Next pack in a grid for all the renderers */ grid = gtk_grid_new (); gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL); gtk_grid_set_row_spacing (GTK_GRID (grid), 4); gtk_box_pack_start (GTK_BOX (renderer_editor), grid, FALSE, FALSE, 0); sorted = get_sorted_properties (adaptor, type); /* For each normal property, if we have an attr- and use-attr- counterpart, load * a check button property pair into the table... */ for (list = sorted; list; list = list->next) { gchar *attr_name; gchar *use_attr_name; pclass = list->data; /* "stock-size" is a normal property, but we virtualize it to use the GtkIconSize enumeration */ if (glade_property_class_get_virtual (pclass) && strcmp (glade_property_class_id (pclass), "stock-size") != 0) continue; attr_name = g_strdup_printf ("attr-%s", glade_property_class_id (pclass)); use_attr_name = g_strdup_printf ("use-attr-%s", glade_property_class_id (pclass)); attr_pclass = glade_widget_adaptor_get_property_class (adaptor, attr_name); use_attr_pclass = glade_widget_adaptor_get_property_class (adaptor, use_attr_name); if (attr_pclass && use_attr_pclass) { CheckTab *tab = g_new0 (CheckTab, 1); GParamSpec *pspec; pspec = glade_property_class_get_pspec (pclass); tab->editor = renderer_editor; tab->pclass = pclass; tab->attr_pclass = attr_pclass; tab->use_attr_pclass = use_attr_pclass; /* Label appearance... */ hbox_left = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); hbox_right = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_set_hexpand (hbox_right, TRUE); tab->attributes_check = gtk_check_button_new (); str = g_strdup_printf (_("Retrieve %s from model (type %s)"), glade_property_class_get_name (pclass), g_type_name (pspec->value_type)); gtk_widget_set_tooltip_text (tab->attributes_check, str); g_free (str); gtk_box_pack_start (GTK_BOX (hbox_left), tab->attributes_check, FALSE, FALSE, 4); /* Edit property */ eprop = glade_widget_adaptor_create_eprop (adaptor, pclass, TRUE); gtk_box_pack_start (GTK_BOX (hbox_left), glade_editor_property_get_item_label (eprop), TRUE, TRUE, 4); gtk_box_pack_start (GTK_BOX (hbox_right), GTK_WIDGET (eprop), FALSE, FALSE, 4); renderer_editor->properties = g_list_prepend (renderer_editor->properties, eprop); tab->use_prop_label = glade_editor_property_get_item_label (eprop); tab->use_prop_eprop = GTK_WIDGET (eprop); /* Edit attribute */ eprop = glade_widget_adaptor_create_eprop (adaptor, attr_pclass, TRUE); gtk_box_pack_start (GTK_BOX (hbox_right), GTK_WIDGET (eprop), FALSE, FALSE, 4); renderer_editor->properties = g_list_prepend (renderer_editor->properties, eprop); gtk_grid_attach (GTK_GRID (grid), hbox_left, 0, row, 1, 1); gtk_grid_attach (GTK_GRID (grid), hbox_right, 1, row++, 1, 1); tab->use_attr_label = glade_editor_property_get_item_label (eprop); tab->use_attr_eprop = GTK_WIDGET (eprop); g_signal_connect (G_OBJECT (tab->attributes_check), "toggled", G_CALLBACK (attributes_toggled), tab); renderer_editor->checks = g_list_prepend (renderer_editor->checks, tab); } g_free (attr_name); g_free (use_attr_name); } g_list_free (sorted); gtk_widget_show_all (GTK_WIDGET (renderer_editor)); return GTK_WIDGET (renderer_editor); } /*************************************************************************** * Editor Property * ***************************************************************************/ typedef struct { GladeEditorProperty parent_instance; GtkTreeModel *columns; GtkWidget *spin; GtkWidget *combo; } GladeEPropCellAttribute; GLADE_MAKE_EPROP (GladeEPropCellAttribute, glade_eprop_cell_attribute) #define GLADE_EPROP_CELL_ATTRIBUTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GLADE_TYPE_EPROP_CELL_ATTRIBUTE, GladeEPropCellAttribute)) #define GLADE_EPROP_CELL_ATTRIBUTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GLADE_TYPE_EPROP_CELL_ATTRIBUTE, GladeEPropCellAttributeClass)) #define GLADE_IS_EPROP_CELL_ATTRIBUTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GLADE_TYPE_EPROP_CELL_ATTRIBUTE)) #define GLADE_IS_EPROP_CELL_ATTRIBUTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GLADE_TYPE_EPROP_CELL_ATTRIBUTE)) #define GLADE_EPROP_CELL_ATTRIBUTE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GLADE_EPROP_CELL_ATTRIBUTE, GladeEPropCellAttributeClass)) static void glade_eprop_cell_attribute_finalize (GObject *object) { /* Chain up */ GObjectClass *parent_class = g_type_class_peek_parent (G_OBJECT_GET_CLASS (object)); //GladeEPropCellAttribute *eprop_attribute = GLADE_EPROP_CELL_ATTRIBUTE (object); G_OBJECT_CLASS (parent_class)->finalize (object); } static GladeWidget * glade_cell_renderer_parent_get_model (GladeWidget *widget) { GtkTreeModel *model = NULL; glade_widget_property_get (widget, "model", &model); do { if (GTK_IS_TREE_MODEL_SORT (model)) model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model)); else if (GTK_IS_TREE_MODEL_FILTER (model)) model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model)); else break; } while (model); if (model) return glade_widget_get_from_gobject (model); return NULL; } GladeWidget * glade_cell_renderer_get_model (GladeWidget * renderer) { GladeWidget *gparent; GObject *parent; if ((gparent = glade_widget_get_parent (renderer)) == NULL) return NULL; parent = glade_widget_get_object (gparent); /* Keep inline with all new cell layouts !!! */ if (GTK_IS_TREE_VIEW_COLUMN (parent)) { GladeWidget *treeview = glade_widget_get_parent (gparent); if (treeview && GTK_IS_TREE_VIEW (glade_widget_get_object (treeview))) return glade_cell_renderer_parent_get_model (treeview); } else if (GTK_IS_ICON_VIEW (parent) || GTK_IS_COMBO_BOX (parent) || GTK_IS_ENTRY_COMPLETION (parent)) return glade_cell_renderer_parent_get_model (gparent); return NULL; } static void glade_eprop_cell_attribute_load (GladeEditorProperty * eprop, GladeProperty * property) { GladeEditorPropertyClass *parent_class = g_type_class_peek_parent (GLADE_EDITOR_PROPERTY_GET_CLASS (eprop)); GladeEPropCellAttribute *eprop_attribute = GLADE_EPROP_CELL_ATTRIBUTE (eprop); /* Chain up in a clean state... */ parent_class->load (eprop, property); if (property) { GladeWidget *gmodel; GtkListStore *store = GTK_LIST_STORE (eprop_attribute->columns); GtkTreeIter iter; gtk_list_store_clear (store); /* Generate model and set active iter */ if ((gmodel = glade_cell_renderer_get_model (glade_property_get_widget (property))) != NULL) { GList *columns = NULL, *l; glade_widget_property_get (gmodel, "columns", &columns); gtk_list_store_append (store, &iter); /* translators: the adjective not the verb */ gtk_list_store_set (store, &iter, 0, _("unset"), -1); for (l = columns; l; l = l->next) { GladeColumnType *column = l->data; gchar *str = g_strdup_printf ("%s - %s", column->column_name, column->type_name); gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, str, -1); g_free (str); } gtk_combo_box_set_active (GTK_COMBO_BOX (eprop_attribute->combo), CLAMP (g_value_get_int (glade_property_inline_value (property)) + 1, 0, g_list_length (columns) + 1)); gtk_widget_set_sensitive (eprop_attribute->combo, TRUE); } else { gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, _("no model"), -1); gtk_combo_box_set_active (GTK_COMBO_BOX (eprop_attribute->combo), 0); gtk_widget_set_sensitive (eprop_attribute->combo, FALSE); } gtk_spin_button_set_value (GTK_SPIN_BUTTON (eprop_attribute->spin), (gdouble) g_value_get_int (glade_property_inline_value (property))); } } static void combo_changed (GtkWidget * combo, GladeEditorProperty * eprop) { GValue val = { 0, }; if (glade_editor_property_loading (eprop)) return; g_value_init (&val, G_TYPE_INT); g_value_set_int (&val, (gint) gtk_combo_box_get_active (GTK_COMBO_BOX (combo)) - 1); glade_editor_property_commit (eprop, &val); g_value_unset (&val); } static void spin_changed (GtkWidget * spin, GladeEditorProperty * eprop) { GValue val = { 0, }; if (glade_editor_property_loading (eprop)) return; g_value_init (&val, G_TYPE_INT); g_value_set_int (&val, gtk_spin_button_get_value (GTK_SPIN_BUTTON (spin))); glade_editor_property_commit (eprop, &val); g_value_unset (&val); } static GtkWidget * glade_eprop_cell_attribute_create_input (GladeEditorProperty * eprop) { GladeEPropCellAttribute *eprop_attribute = GLADE_EPROP_CELL_ATTRIBUTE (eprop); GtkWidget *hbox; GtkAdjustment *adjustment; GtkCellRenderer *cell; hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); adjustment = glade_property_class_make_adjustment (glade_editor_property_get_pclass (eprop)); eprop_attribute->spin = gtk_spin_button_new (adjustment, 1.0, 0); eprop_attribute->columns = (GtkTreeModel *) gtk_list_store_new (1, G_TYPE_STRING); eprop_attribute->combo = gtk_combo_box_new_with_model (eprop_attribute->columns); gtk_combo_box_set_popup_fixed_width (GTK_COMBO_BOX (eprop_attribute->combo), FALSE); /* Add cell renderer */ cell = gtk_cell_renderer_text_new (); g_object_set (cell, "xpad", 0, "xalign", 0.0F, "ellipsize", PANGO_ELLIPSIZE_END, "width-chars", 10, NULL); gtk_cell_layout_clear (GTK_CELL_LAYOUT (eprop_attribute->combo)); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (eprop_attribute->combo), cell, TRUE); gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (eprop_attribute->combo), cell, "text", 0, NULL); gtk_box_pack_start (GTK_BOX (hbox), eprop_attribute->spin, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), eprop_attribute->combo, FALSE, FALSE, 0); g_signal_connect (G_OBJECT (eprop_attribute->combo), "changed", G_CALLBACK (combo_changed), eprop); g_signal_connect (G_OBJECT (eprop_attribute->spin), "value-changed", G_CALLBACK (spin_changed), eprop); return hbox; }