/* * mooglade.c * * Copyright (C) 2004-2010 by Yevgen Muntyan * * This file is part of medit. medit 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. * * You should have received a copy of the GNU Lesser General Public * License along with medit. If not, see . */ /** * class:MooGladeXml: (parent GObject) (moo.private 1) **/ #ifdef GTK_DISABLE_DEPRECATED #undef GTK_DISABLE_DEPRECATED #include #include #include #include #define GTK_DISABLE_DEPRECATED #endif #include "mooutils/mooglade.h" #include "mooutils/moomarkup.h" #include "mooutils/mooutils-gobject.h" #include "mooutils/mooutils-misc.h" #include "mooutils/mooprefsdialog.h" #include "mooutils/mooprefs.h" #include "mooutils/moofontsel.h" #include "mooutils/moohistorycombo.h" #include "mooutils/mooentry.h" #include "mooutils/mooaccelbutton.h" #include "mooutils/mooi18n.h" #include "mooutils/mootype-macros.h" #include "mooutils/moolist.h" #include #include #include #include typedef enum { MOO_GLADE_XML_ERROR_FAILED } MooGladeXMLError; #define MOO_GLADE_XML_ERROR (moo_glade_xml_error_quark ()) MOO_DEFINE_QUARK_STATIC (moo-glade-xml-error, moo_glade_xml_error_quark) G_DEFINE_TYPE (MooGladeXML, moo_glade_xml, G_TYPE_OBJECT) #define FOREACH_ELM_START(parent,elm) \ G_STMT_START { \ MooMarkupNode *elm; \ for (elm = parent->children; elm != NULL; elm = elm->next) \ { \ if (MOO_MARKUP_IS_ELEMENT (elm)) \ #define FOREACH_ELM_END \ } \ } G_STMT_END typedef struct _Widget Widget; typedef struct _Signal Signal; typedef struct _WidgetProps WidgetProps; typedef struct _PackingProps PackingProps; typedef struct _Child Child; MOO_DEFINE_SLIST(ChildList, child_list, Child) MOO_DEFINE_SLIST(SignalList, signal_list, Signal) struct _Widget { Child *parent_node; GtkWidget *widget; char *id; GType type; WidgetProps *props; ChildList *children; SignalList *signals; }; struct _Signal { char *name; char *handler; char *object; }; struct _Child { Widget *parent_node; gboolean empty; char *internal_child; char *internal_parent_id; Widget *widget; PackingProps *props; }; typedef enum { PROP_RESPONSE_ID = 1 << 0, PROP_HAS_DEFAULT = 1 << 1, PROP_HAS_FOCUS = 1 << 2, PROP_TOOLTIP = 1 << 3, PROP_MNEMONIC_WIDGET = 1 << 4, PROP_RADIO_GROUP = 1 << 5, PROP_LABEL = 1 << 6, PROP_USE_UNDERLINE = 1 << 7, PROP_USE_STOCK = 1 << 8, PROP_ENABLE_TOOLTIPS = 1 << 9, PROP_HISTORY = 1 << 10 } PropMask; struct _WidgetProps { GParameter *params; guint n_params; GPtrArray *custom_props; guint mask; gboolean visible; int response_id; gboolean has_default; gboolean has_focus; char *tooltip; char *mnemonic_widget; char *radio_group; char *label; int history; }; typedef enum { PACK_PROP_LABEL_ITEM = 1 << 0, PACK_PROP_TAB = 1 << 1 } PackPropMask; struct _PackingProps { GParameter *params; guint n_params; guint mask; }; struct _MooGladeXMLPrivate { GHashTable *widgets; char *root_id; char *translation_domain; GHashTable *class_to_type; GHashTable *id_to_type; GHashTable *id_to_func; GHashTable *props; /* char* -> char** */ MooGladeSignalFunc signal_func; gpointer signal_func_data; GDestroyNotify signal_func_notify; MooGladePropFunc prop_func; gpointer prop_func_data; GDestroyNotify prop_func_notify; guint done : 1; }; typedef struct { MooGladeCreateFunc func; gpointer data; } FuncDataPair; #define NODE_IS_WIDGET(node) (!strcmp (node->name, "widget")) #define NODE_IS_SIGNAL(node) (!strcmp (node->name, "signal")) #define NODE_IS_PROPERTY(node) (!strcmp (node->name, "property")) #define NODE_IS_CHILD(node) (!strcmp (node->name, "child")) #define NODE_IS_PACKING(node) (!strcmp (node->name, "packing")) static FuncDataPair *func_data_pair_new (MooGladeCreateFunc func, gpointer data); static void func_data_pair_free (FuncDataPair *pair); static Widget *widget_new (MooGladeXML *xml, Child *parent, MooMarkupNode *node, GError **error); static void widget_free (Widget *widget); static Child *child_new (MooGladeXML *xml, Widget *parent, MooMarkupNode *node, GError **error); static void child_free (Child *child); static WidgetProps *widget_props_new (MooMarkupNode *node, GType widget_type, GHashTable *add_props, gboolean ignore_errors, const char *translation_domain, GError **error); static void widget_props_free (WidgetProps *props); static PackingProps *packing_props_new (MooMarkupNode *node, GType container_type, GError **error); static void packing_props_free (PackingProps *props); static Signal *signal_new (const char *id, const char *handler, const char *object); static void signal_free (Signal *signal); static void collect_signals (MooMarkupNode *node, Widget *widget); static GType get_type_by_name (const char *name); static gboolean parse_bool (const char *value); static int parse_int (const char *value); static GtkObject *parse_adjustment (const char *value); static gboolean parse_property (GParamSpec *param_spec, const char *value, GParameter *param); static gboolean moo_glade_xml_parse_markup (MooGladeXML *xml, MooMarkupDoc *doc, const char *root, GtkWidget *root_widget, GError **error); static void moo_glade_xml_dispose (GObject *object); static gboolean moo_glade_xml_add_widget (MooGladeXML *xml, const char *id, GtkWidget *widget, GError **error); static gboolean moo_glade_xml_build (MooGladeXML *xml, Widget *widget_node, GtkWidget *widget, GError **error); static void moo_glade_xml_cleanup (MooGladeXML *xml); static gboolean create_child (MooGladeXML *xml, GtkWidget *parent, Child *child, GError **error); static void set_special_props (MooGladeXML *xml, GtkWidget *widget, WidgetProps *props); static void set_custom_props (MooGladeXML *xml, Widget *node); static void set_mnemonics (MooGladeXML *xml, Widget *node); static gboolean set_default (MooGladeXML *xml, Widget *node); static gboolean set_focus (MooGladeXML *xml, Widget *node); static void connect_signals (MooGladeXML *xml, Widget *node); static GtkWidget *create_widget (MooGladeXML *xml, Widget *widget, GError **error); static gboolean create_children (MooGladeXML *xml, Widget *widget_node, GtkWidget *widget, GError **error); static void pack_children (MooGladeXML *xml, Widget *widget_node, GtkWidget *widget); static GtkWidget *moo_glade_xml_create_widget(MooGladeXML *xml, Widget *node, GtkWidget *widget, GError **error); static void dump_widget (Widget *widget_node); static gboolean moo_glade_xml_build (MooGladeXML *xml, Widget *widget_node, GtkWidget *widget, GError **error) { if (widget) { if (!widget_node->type) widget_node->type = G_OBJECT_TYPE (widget); if (!g_type_is_a (G_OBJECT_TYPE (widget), widget_node->type)) { g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "incompatible types %s and %s", g_type_name (G_OBJECT_TYPE (widget)), g_type_name (widget_node->type)); return FALSE; } } widget = moo_glade_xml_create_widget (xml, widget_node, widget, error); if (!widget || !moo_glade_xml_add_widget (xml, widget_node->id, widget, error)) goto error; widget_node->widget = widget; if (!create_children (xml, widget_node, widget, error)) goto error; set_special_props (xml, widget, widget_node->props); set_custom_props (xml, widget_node); set_mnemonics (xml, widget_node); set_default (xml, widget_node); set_focus (xml, widget_node); connect_signals (xml, widget_node); return TRUE; error: gtk_widget_destroy (widget); return FALSE; } static void set_mnemonic_widget (GtkLabel *label, GtkWidget *widget) { if (MOO_IS_COMBO (widget)) widget = MOO_COMBO (widget)->entry; gtk_label_set_mnemonic_widget (label, widget); } static void set_mnemonics (MooGladeXML *xml, Widget *node) { ChildList *l; g_return_if_fail (node != NULL); if (node->props->mask & PROP_MNEMONIC_WIDGET) { GtkWidget *mnemonic; mnemonic = GTK_WIDGET (moo_glade_xml_get_widget (xml, node->props->mnemonic_widget)); if (!mnemonic) g_warning ("could not find widget '%s'", node->props->mnemonic_widget ? node->props->mnemonic_widget : "NULL"); else if (!GTK_IS_LABEL (node->widget)) g_warning ("mnemonic widget property specified for widget of class %s", g_type_name (G_OBJECT_TYPE (node->widget))); else set_mnemonic_widget (GTK_LABEL (node->widget), mnemonic); } for (l = node->children; l != NULL; l = l->next) { Child *child = l->data; if (!child->empty) set_mnemonics (xml, child->widget); } } static gboolean set_default (MooGladeXML *xml, Widget *node) { ChildList *l; g_return_val_if_fail (node != NULL, FALSE); if ((node->props->mask & PROP_HAS_DEFAULT) && node->props->has_default) { gtk_widget_grab_default (node->widget); return TRUE; } for (l = node->children; l != NULL; l = l->next) { Child *child = l->data; if (!child->empty && set_default (xml, child->widget)) break; } return FALSE; } static gboolean set_focus (MooGladeXML *xml, Widget *node) { ChildList *l; g_return_val_if_fail (node != NULL, FALSE); if ((node->props->mask & PROP_HAS_FOCUS) && node->props->has_focus) { gtk_widget_grab_focus (node->widget); return TRUE; } for (l = node->children; l != NULL; l = l->next) { Child *child = l->data; if (!child->empty && set_focus (xml, child->widget)) break; } return FALSE; } static void set_moo_sensitive (MooGladeXML *xml, GtkWidget *widget, const char *value) { GtkWidget *btn; gboolean invert = FALSE; if (value[0] == '!') { value = value + 1; invert = TRUE; } btn = GTK_WIDGET (moo_glade_xml_get_widget (xml, value)); g_return_if_fail (GTK_IS_TOGGLE_BUTTON (btn)); moo_bind_sensitive (btn, widget, invert); } static void set_custom_props (MooGladeXML *xml, Widget *node) { guint i; if (!node->props->custom_props) return; for (i = 0; i < node->props->custom_props->len; i += 2) { const char *prop, *value; prop = (const char*) node->props->custom_props->pdata[i]; value = (const char*) node->props->custom_props->pdata[i+1]; if (!strcmp (prop, "moo-sensitive")) { set_moo_sensitive (xml, node->widget, value); } else if (!xml->priv->prop_func || !xml->priv->prop_func (xml, node->id, node->widget, prop, value, xml->priv->prop_func_data)) { /* g_message ("unknown property '%s'", prop); */ } } } static gboolean connect_special_signal (MooGladeXML *xml, Widget *node, Signal *signal) { if (!strcmp (signal->name, "moo-sensitive")) { GtkWidget *btn; gboolean invert = FALSE; btn = GTK_WIDGET (moo_glade_xml_get_widget (xml, signal->handler)); g_return_val_if_fail (GTK_IS_TOGGLE_BUTTON (btn), FALSE); if (signal->object) { if (!strcmp (signal->object, "invert")) invert = TRUE; else g_warning ("invalid string '%s'", signal->object); } moo_bind_sensitive (btn, node->widget, invert); return TRUE; } return FALSE; } static void connect_signals (MooGladeXML *xml, Widget *node) { ChildList *lch; SignalList *lsig; g_return_if_fail (node != NULL); for (lsig = node->signals; lsig != NULL; lsig = lsig->next) { Signal *signal = lsig->data; gboolean connected = FALSE; gpointer object = NULL; if (!signal->name || !signal->name[0]) { g_warning ("empty signal name in widget %s", node->id); continue; } if (!signal->handler || !signal->handler[0]) { g_warning ("empty handler of signal '%s' in widget %s", signal->name, node->id); continue; } if (connect_special_signal (xml, node, signal)) continue; if (xml->priv->signal_func) { connected = xml->priv->signal_func (xml, node->id, node->widget, signal->name, signal->handler, signal->object, xml->priv->signal_func_data); if (connected) continue; } if (signal->object) { object = moo_glade_xml_get_widget (xml, signal->object); if (!object) { g_warning ("could not find object '%s' for signal '%s' of widget '%s'", signal->object, signal->name, node->id); continue; } } g_warning ("unconnected signal '%s' of widget '%s'", signal->name, node->id); } for (lch = node->children; lch != NULL; lch = lch->next) { Child *child = lch->data; if (child && child->widget) connect_signals (xml, child->widget); } } static GtkWidget* create_widget (MooGladeXML *xml, Widget *widget_node, GError **error) { GtkWidget *widget; widget = moo_glade_xml_create_widget (xml, widget_node, NULL, error); if (!widget || !moo_glade_xml_add_widget (xml, widget_node->id, widget, error) || !create_children (xml, widget_node, widget, error)) { gtk_widget_destroy (widget); return NULL; } return widget; } /* XXX */ static void set_special_props (MooGladeXML *xml, GtkWidget *widget, WidgetProps *props) { if (props->visible) gtk_widget_show (widget); if (props->mask & PROP_TOOLTIP) _moo_widget_set_tooltip (widget, props->tooltip); if (props->mask & PROP_ENABLE_TOOLTIPS) { if (GTK_IS_TOOLBAR (widget)) gtk_toolbar_set_tooltips (GTK_TOOLBAR (widget), TRUE); else g_warning ("oops"); } if (props->mask & PROP_HISTORY) { if (GTK_IS_OPTION_MENU (widget)) gtk_option_menu_set_history (GTK_OPTION_MENU (widget), props->history); else g_warning ("oops"); } if (props->mask & PROP_RADIO_GROUP) { if (GTK_IS_RADIO_BUTTON (widget)) { GtkWidget *group_button; GSList *group; group_button = GTK_WIDGET (moo_glade_xml_get_widget (xml, props->radio_group)); g_return_if_fail (GTK_IS_RADIO_BUTTON (group_button)); group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (group_button)); gtk_radio_button_set_group (GTK_RADIO_BUTTON (widget), group); } else if (GTK_IS_RADIO_MENU_ITEM (widget)) { GtkWidget *group_item; GSList *group; group_item = GTK_WIDGET (moo_glade_xml_get_widget (xml, props->radio_group)); g_return_if_fail (GTK_IS_RADIO_MENU_ITEM (group_item)); group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (group_item)); gtk_radio_menu_item_set_group (GTK_RADIO_MENU_ITEM (widget), group); } else { g_return_if_reached (); } } } static GtkWidget* moo_glade_xml_create_widget (MooGladeXML *xml, Widget *node, GtkWidget *widget, GError **error) { WidgetProps *props; GType type = 0; g_return_val_if_fail (node != NULL, NULL); if (!widget) { FuncDataPair *pair; pair = (FuncDataPair*) g_hash_table_lookup (xml->priv->id_to_func, node->id); if (pair) widget = pair->func (xml, node->id, pair->data); } if (!widget) type = node->type; if (!widget && !type) { g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "coudl not create widget '%s'", node->id); return NULL; } props = node->props; if (!widget) { if (props->mask & PROP_LABEL) { if (!props->label) { g_warning ("oops"); } if (type == GTK_TYPE_MENU_ITEM) { if (props->mask & PROP_USE_UNDERLINE) widget = gtk_menu_item_new_with_mnemonic (props->label); else widget = gtk_menu_item_new_with_label (props->label); } else if (type == GTK_TYPE_IMAGE_MENU_ITEM) { if (props->mask & PROP_USE_STOCK) widget = gtk_image_menu_item_new_from_stock (props->label, NULL); else if (props->mask & PROP_USE_UNDERLINE) widget = gtk_image_menu_item_new_with_mnemonic (props->label); else widget = gtk_image_menu_item_new_with_label (props->label); } else if (type == GTK_TYPE_RADIO_MENU_ITEM) { if (props->mask & PROP_USE_UNDERLINE) widget = gtk_radio_menu_item_new_with_mnemonic (NULL, props->label); else widget = gtk_radio_menu_item_new_with_label (NULL, props->label); } else if (type == GTK_TYPE_CHECK_BUTTON) { if (props->mask & PROP_USE_UNDERLINE) widget = gtk_check_button_new_with_mnemonic (props->label); else widget = gtk_check_button_new_with_label (props->label); } else if (type == GTK_TYPE_RADIO_BUTTON) { if (props->mask & PROP_USE_UNDERLINE) widget = gtk_radio_button_new_with_mnemonic (NULL, props->label); else widget = gtk_radio_button_new_with_label (NULL, props->label); } else if (type == GTK_TYPE_LIST_ITEM) { widget = gtk_list_item_new_with_label (props->label); } else { g_warning ("oops"); } } } if (!widget) { widget = GTK_WIDGET (g_object_newv (type, props->n_params, props->params)); } else { guint i; for (i = 0; i < props->n_params; i++) g_object_set_property (G_OBJECT (widget), props->params[i].name, &props->params[i].value); } g_return_val_if_fail (widget != NULL, NULL); return widget; } static gboolean create_children (MooGladeXML *xml, Widget *widget_node, GtkWidget *widget, GError **error) { ChildList *l; for (l = widget_node->children; l != NULL; l = l->next) { Child *child = l->data; if (!create_child (xml, widget, child, error)) return FALSE; } pack_children (xml, widget_node, widget); return TRUE; } static gboolean create_child (MooGladeXML *xml, GtkWidget *parent, Child *child, GError **error) { GtkWidget *widget = NULL; g_return_val_if_fail (child != NULL, FALSE); g_return_val_if_fail (GTK_IS_CONTAINER (parent), FALSE); if (child->empty) return TRUE; if (child->internal_child) { gboolean added = FALSE; GtkWidget *real_parent; real_parent = (GtkWidget*) moo_glade_xml_get_widget (xml, child->internal_parent_id); if (!real_parent) { g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "could not find widget '%s'", child->internal_parent_id); return FALSE; } if (!strcmp (child->internal_child, "vbox") && GTK_IS_DIALOG (real_parent)) { widget = GTK_DIALOG (real_parent)->vbox; } else if (!strcmp (child->internal_child, "action_area") && GTK_IS_DIALOG (real_parent)) { widget = GTK_DIALOG (real_parent)->action_area; } else if (!strcmp (child->internal_child, "entry") && GTK_IS_COMBO_BOX_ENTRY (real_parent)) { widget = GTK_BIN (real_parent)->child; } else if (!strcmp (child->internal_child, "entry") && GTK_IS_COMBO (real_parent)) { widget = GTK_COMBO (real_parent)->entry; } else if (!strcmp (child->internal_child, "list") && GTK_IS_COMBO (real_parent)) { widget = GTK_COMBO (real_parent)->list; } else if (!strcmp (child->internal_child, "image") && GTK_IS_IMAGE_MENU_ITEM (real_parent)) { g_object_get (real_parent, "image", &widget, NULL); if (!widget) { widget = create_widget (xml, child->widget, error); if (!widget) return FALSE; gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (real_parent), widget); added = TRUE; } } if (!widget) { g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "could not find internal child '%s' for widget '%s'", child->internal_child, child->internal_parent_id); return FALSE; } if (!added && !moo_glade_xml_add_widget (xml, child->widget->id, widget, error)) return FALSE; if (child->widget->props->n_params) { guint i; for (i = 0; i < child->widget->props->n_params; ++i) g_object_set_property (G_OBJECT (widget), child->widget->props->params[i].name, &child->widget->props->params[i].value); } if (!create_children (xml, child->widget, widget, error)) return FALSE; set_special_props (xml, widget, child->widget->props); } else { widget = create_widget (xml, child->widget, error); if (!widget) return FALSE; } child->widget->widget = widget; set_custom_props (xml, child->widget); return TRUE; } static void pack_children (MooGladeXML *xml, Widget *parent_node, GtkWidget *parent_widget) { ChildList *l; g_return_if_fail (parent_node != NULL); g_return_if_fail (parent_widget != NULL); for (l = parent_node->children; l != NULL; l = l->next) { GtkWidget *widget; Child *child = l->data; gboolean packed = FALSE; if (child->empty || child->internal_child) continue; widget = child->widget->widget; g_return_if_fail (widget != NULL); if (GTK_IS_FRAME (parent_widget) && (child->props->mask & PACK_PROP_LABEL_ITEM)) { gtk_frame_set_label_widget (GTK_FRAME (parent_widget), widget); packed = TRUE; } else if (GTK_IS_NOTEBOOK (parent_widget) && (child->props->mask & PACK_PROP_TAB)) { int index = g_slist_index (child_list_to_gslist (parent_node->children), child); if (index <= 0) { g_warning ("oops"); } else { Child *page_child = (Child*) g_slist_nth_data (child_list_to_gslist (parent_node->children), index - 1); if (!page_child->widget || !page_child->widget->widget) g_message ("empty notebook page with non-empty label"); else gtk_notebook_set_tab_label (GTK_NOTEBOOK (parent_widget), page_child->widget->widget, widget); packed = TRUE; } } else if (GTK_IS_MENU_ITEM (parent_widget) && GTK_IS_MENU (widget)) { gtk_menu_item_set_submenu (GTK_MENU_ITEM (parent_widget), widget); packed = TRUE; } else if (GTK_IS_OPTION_MENU (parent_widget) && GTK_IS_MENU (widget)) { gtk_option_menu_set_menu (GTK_OPTION_MENU (parent_widget), widget); packed = TRUE; } else if (child->widget->props->mask & PROP_RESPONSE_ID) { Child *parent = child->parent_node->parent_node; /* glade saves response_id with all buttons now, so do not complain * if response_id found not in a dialog button */ if (parent && parent->internal_child && !strcmp (parent->internal_child, "action_area")) { GtkWidget *dialog = (GtkWidget*) moo_glade_xml_get_widget (xml, parent->internal_parent_id); if (!dialog || !GTK_IS_DIALOG (dialog)) { g_warning ("oops"); } else { gtk_dialog_add_action_widget (GTK_DIALOG (dialog), widget, child->widget->props->response_id); packed = TRUE; } } } if (!packed) gtk_container_add (GTK_CONTAINER (parent_widget), widget); if (child->props->n_params) { guint i; for (i = 0; i < child->props->n_params; ++i) gtk_container_child_set_property (GTK_CONTAINER (parent_widget), widget, child->props->params[i].name, &child->props->params[i].value); } set_special_props (xml, widget, child->widget->props); set_custom_props (xml, child->widget); } } static Signal* signal_new (const char *name, const char *handler, const char *object) { Signal *signal = g_new (Signal, 1); signal->name = g_strdup (name); signal->handler = g_strdup (handler); signal->object = g_strdup (object); if (signal->name) g_strdelimit (signal->name, "-_", '-'); return signal; } static void signal_free (Signal *signal) { if (signal) { g_free (signal->name); g_free (signal->handler); g_free (signal->object); g_free (signal); } } static void collect_signals (MooMarkupNode *node, Widget *widget) { g_return_if_fail (node != NULL && NODE_IS_WIDGET (node)); g_return_if_fail (widget != NULL); FOREACH_ELM_START (node, elm) { if (NODE_IS_SIGNAL (elm)) { const char *name, *handler, *object; name = moo_markup_get_prop (elm, "name"); handler = moo_markup_get_prop (elm, "handler"); object = moo_markup_get_prop (elm, "object"); g_return_if_fail (name != NULL && handler != NULL); widget->signals = signal_list_prepend (widget->signals, signal_new (name, handler, object)); } } FOREACH_ELM_END; widget->signals = signal_list_reverse (widget->signals); } static void widget_free (Widget *widget) { if (widget) { g_free (widget->id); widget_props_free (widget->props); child_list_foreach (widget->children, (ChildListFunc) child_free, NULL); child_list_free_links (widget->children); signal_list_foreach (widget->signals, (SignalListFunc) signal_free, NULL); signal_list_free_links (widget->signals); g_free (widget); } } static void child_free (Child *child) { if (child) { g_free (child->internal_child); g_free (child->internal_parent_id); widget_free (child->widget); packing_props_free (child->props); g_free (child); } } static void widget_props_free (WidgetProps *props) { if (props) { if (props->params) _moo_param_array_free (props->params, props->n_params); if (props->custom_props) { g_ptr_array_foreach (props->custom_props, (GFunc) g_free, NULL); g_ptr_array_free (props->custom_props, TRUE); } g_free (props->tooltip); g_free (props->mnemonic_widget); g_free (props->label); g_free (props->radio_group); g_free (props); } } static void packing_props_free (PackingProps *props) { if (props) { if (props->params) _moo_param_array_free (props->params, props->n_params); g_free (props); } } static GType moo_glade_xml_get_type_by_name (MooGladeXML *xml, const char *class_name) { GType type; g_return_val_if_fail (class_name != NULL, 0); type = GPOINTER_TO_SIZE (g_hash_table_lookup (xml->priv->class_to_type, class_name)); if (!type) type = get_type_by_name (class_name); return type; } static Widget* widget_new (MooGladeXML *xml, Child *parent, MooMarkupNode *node, GError **error) { Widget *widget; WidgetProps *props; const char *id; const char *class_name = NULL; char *freeme = NULL; GType type; g_return_val_if_fail (NODE_IS_WIDGET (node), NULL); id = moo_markup_get_prop (node, "id"); if (g_hash_table_lookup (xml->priv->id_to_func, id)) { type = GTK_TYPE_WIDGET; } else { type = GPOINTER_TO_SIZE (g_hash_table_lookup (xml->priv->id_to_type, id)); if (!type) { const char *colon = strchr (id, ':'); if (colon != NULL) { const char *old_id; old_id = id; freeme = g_strndup (id, colon - id); id = freeme; class_name = colon + 1; type = moo_glade_xml_get_type_by_name (xml, class_name); if (!type) { g_free (freeme); freeme = NULL; id = old_id; } } else { class_name = moo_markup_get_prop (node, "class"); g_return_val_if_fail (id != NULL && class_name != NULL, NULL); type = moo_glade_xml_get_type_by_name (xml, class_name); } } if (!type) { g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "could not find type of class '%s'", class_name); return NULL; } } props = widget_props_new (node, type, (GHashTable*) g_hash_table_lookup (xml->priv->props, id), FALSE, xml->priv->translation_domain, error); g_return_val_if_fail (props != NULL, NULL); widget = g_new0 (Widget, 1); widget->parent_node = parent; widget->type = type; widget->props = props; widget->children = NULL; widget->id = g_strdup (id); id = NULL; g_free (freeme); collect_signals (node, widget); FOREACH_ELM_START (node, elm) { if (NODE_IS_CHILD (elm)) { Child *child = child_new (xml, widget, elm, error); if (!child) { widget_free (widget); g_return_val_if_fail (child != NULL, NULL); } widget->children = child_list_prepend (widget->children, child); } } FOREACH_ELM_END; widget->children = child_list_reverse (widget->children); return widget; } static Child* child_new (MooGladeXML *xml, Widget *parent, MooMarkupNode *node, GError **error) { Child *child; PackingProps *props; const char *class_name, *internal_child, *internal_parent_id = NULL; MooMarkupNode *widget_node, *packing_node; g_return_val_if_fail (parent != NULL, NULL); g_return_val_if_fail (NODE_IS_CHILD (node), NULL); if (moo_markup_get_element (node, "placeholder")) { child = g_new0 (Child, 1); child->empty = TRUE; child->parent_node = parent; return child; } widget_node = moo_markup_get_element (node, "widget"); if (!widget_node) { char *text = moo_markup_node_get_string (node); g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "no widget element in '%s'", text); g_free (text); return NULL; } class_name = moo_markup_get_prop (widget_node, "class"); if (!class_name) { g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "no class attribute in widget element"); return NULL; } #if 0 if (!strcmp (class_name, "GtkToolbar")) { g_warning ("Ignoring %s", class_name); child = g_new0 (Child, 1); child->empty = TRUE; child->parent_node = parent; return child; } #endif internal_child = moo_markup_get_prop (node, "internal-child"); if (internal_child) { GType parent_types[2] = {0, 0}; guint n_parent_types = 1; Widget *real_parent; if (!strcmp (internal_child, "vbox") || !strcmp (internal_child, "action_area")) { parent_types[0] = GTK_TYPE_DIALOG; } else if (!strcmp (internal_child, "entry")) { parent_types[0] = GTK_TYPE_COMBO; parent_types[1] = GTK_TYPE_COMBO_BOX_ENTRY; n_parent_types = 2; } else if (!strcmp (internal_child, "list")) { parent_types[0] = GTK_TYPE_COMBO; } else if (!strcmp (internal_child, "image")) { parent_types[0] = GTK_TYPE_IMAGE_MENU_ITEM; } else { g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "unknown internal child name '%s'", internal_child); return NULL; } real_parent = parent; while (TRUE) { guint i; GType pt = 0; for (i = 0; i < n_parent_types && pt == 0; ++i) if (g_type_is_a (real_parent->type, parent_types[i])) pt = parent_types[i]; if (pt == 0) { if (!real_parent->parent_node) { g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "could not find parent of '%s'", internal_child); return NULL; } else { real_parent = real_parent->parent_node->parent_node; } } else { internal_parent_id = real_parent->id; break; } } } packing_node = moo_markup_get_element (node, "packing"); if (packing_node) props = packing_props_new (packing_node, parent->type, error); else props = packing_props_new (NULL, 0, error); if (!props) return NULL; child = g_new0 (Child, 1); child->parent_node = parent; child->internal_child = g_strdup (internal_child); child->internal_parent_id = g_strdup (internal_parent_id); child->props = props; child->widget = widget_new (xml, child, widget_node, error); if (!child->widget) { child_free (child); return NULL; } return child; } static gboolean widget_props_add (WidgetProps *props, GArray *params, GObjectClass *klass, const char *name, const char *value, gboolean ignore_errors, GError **error) { GParameter param = {NULL, {0, {{0}, {0}}}}; gboolean special = TRUE; if (!strcmp (name, "visible")) { props->visible = parse_bool (value); } else if (!strcmp (name, "response_id")) { props->response_id = parse_int (value); props->mask |= PROP_RESPONSE_ID; } else if (!strcmp (name, "has_default")) { props->has_default = parse_bool (value); props->mask |= PROP_HAS_DEFAULT; } else if (!strcmp (name, "has_focus")) { props->has_focus = parse_bool (value); props->mask |= PROP_HAS_FOCUS; } else if (!strcmp (name, "tooltip")) { props->tooltip = g_strdup (value); props->mask |= PROP_TOOLTIP; } else if (!strcmp (name, "mnemonic_widget") && GTK_IS_LABEL_CLASS (klass)) { props->mnemonic_widget = g_strdup (value); props->mask |= PROP_MNEMONIC_WIDGET; } else if (!strcmp (name, "text") && GTK_IS_TEXT_VIEW_CLASS (klass)) { if (value && value[0]) g_message ("ignoring TextView text property"); } else if (!strcmp (name, "group") && (GTK_IS_RADIO_BUTTON_CLASS (klass) || GTK_IS_RADIO_MENU_ITEM_CLASS (klass))) { props->radio_group = g_strdup (value); props->mask |= PROP_RADIO_GROUP; } else if (!strcmp (name, "label") && (GTK_IS_MENU_ITEM_CLASS (klass) || GTK_IS_CHECK_BUTTON_CLASS (klass) || GTK_IS_LIST_ITEM_CLASS (klass))) { props->label = g_strdup (value); props->mask |= PROP_LABEL; } else if (!strcmp (name, "use_underline") && GTK_IS_MENU_ITEM_CLASS (klass)) { props->mask |= PROP_USE_UNDERLINE; } else if (!strcmp (name, "use_stock") && GTK_IS_IMAGE_MENU_ITEM_CLASS (klass)) { props->mask |= PROP_USE_STOCK; } else if (!strcmp (name, "tooltips") && GTK_IS_TOOLBAR_CLASS (klass)) { props->mask |= PROP_ENABLE_TOOLTIPS; } else if (!strcmp (name, "history") && GTK_IS_OPTION_MENU_CLASS (klass)) { props->mask |= PROP_HISTORY; props->history = parse_int (value); } else if (!strcmp (name, "items") && GTK_IS_COMBO_BOX_CLASS (klass)) { #if 0 // if (value && value[0]) // g_message ("ignoring ComboBox items property"); #endif } else { special = FALSE; } if (!special) { GParamSpec *param_spec = g_object_class_find_property (klass, name); if (!param_spec) { if (!props->custom_props) props->custom_props = g_ptr_array_new (); g_ptr_array_add (props->custom_props, g_strdelimit (g_strdup (name), "_", '-')); g_ptr_array_add (props->custom_props, g_strdup (value)); } else if (parse_property (param_spec, value, ¶m)) { g_array_append_val (params, param); } else if (!ignore_errors) { g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "could not convert '%s' to property '%s'", value, name); return FALSE; } } return TRUE; } typedef struct { WidgetProps *props; GArray *params; GObjectClass *klass; gboolean result; gboolean ignore_errors; GError **error; } WidgetPropsAddOneData; static void widget_props_add_one (const char *name, const char *value, gpointer user_data) { WidgetPropsAddOneData *data = (WidgetPropsAddOneData*) user_data; if (!data->result) return; data->result = widget_props_add (data->props, data->params, data->klass, name, value, data->ignore_errors, data->error); } static const char * get_property_text (MooMarkupNode *node, const char *translation_domain) { gboolean translatable = FALSE, strip_context = FALSE; const char *prop, *translatable_attr; prop = moo_markup_get_content (node); if (!prop || !prop[0]) return prop; translatable_attr = moo_markup_get_prop (node, "translatable"); if (translatable_attr && !strcmp (translatable_attr, "yes")) { const char *ctx_attr = moo_markup_get_prop (node, "context"); if (ctx_attr && !strcmp (ctx_attr, "yes")) strip_context = TRUE; translatable = TRUE; } if (!translatable) return prop; if (translation_domain) { const char *translated; if (strip_context) translated = g_strip_context (prop, dgettext (translation_domain, prop)); else translated = dgettext (translation_domain, prop); if (strcmp (translated, prop) != 0) return translated; } if (strip_context) return g_strip_context (prop, gettext (prop)); else return gettext (prop); } static WidgetProps* widget_props_new (MooMarkupNode *node, GType type, GHashTable *add_props, gboolean ignore_errors, const char *translation_domain, GError **error) { GArray *params; GObjectClass *klass; WidgetProps *props; g_return_val_if_fail (node != NULL, NULL); klass = G_OBJECT_CLASS (g_type_class_ref (type)); g_return_val_if_fail (klass != NULL, NULL); props = g_new0 (WidgetProps, 1); params = g_array_new (FALSE, FALSE, sizeof (GParameter)); props->visible = FALSE; FOREACH_ELM_START(node, elm) { if (NODE_IS_PROPERTY (elm)) { const char *name = moo_markup_get_prop (elm, "name"); const char *value = get_property_text (elm, translation_domain); gboolean result; result = widget_props_add (props, params, klass, name, value, ignore_errors, error); if (!result) goto error; } } FOREACH_ELM_END; if (add_props) { WidgetPropsAddOneData data; data.props = props; data.params = params; data.klass = klass; data.result = TRUE; data.ignore_errors = ignore_errors; data.error = error; g_hash_table_foreach (add_props, (GHFunc) widget_props_add_one, &data); if (!data.result) goto error; } props->n_params = params->len; props->params = (GParameter*) g_array_free (params, FALSE); g_type_class_unref (klass); return props; error: _moo_param_array_free ((GParameter*) params->data, params->len); g_array_free (params, FALSE); g_type_class_unref (klass); widget_props_free (props); return NULL; } static PackingProps * packing_props_new (MooMarkupNode *node, GType container_type, GError **error) { GArray *params; GObjectClass *klass; PackingProps *props; if (!node && !container_type) return g_new0 (PackingProps, 1); g_return_val_if_fail (node != NULL, NULL); klass = G_OBJECT_CLASS (g_type_class_ref (container_type)); g_return_val_if_fail (klass != NULL, NULL); props = g_new0 (PackingProps, 1); params = g_array_new (FALSE, FALSE, sizeof (GParameter)); FOREACH_ELM_START(node, elm) { if (NODE_IS_PROPERTY (elm)) { GParameter param = {NULL, {0, {{0}, {0}}}}; const char *name = moo_markup_get_prop (elm, "name"); const char *value = moo_markup_get_content (elm); gboolean special = FALSE; if (!strcmp (name, "type")) { if (!strcmp (value, "label_item") && GTK_IS_FRAME_CLASS (klass)) { special = TRUE; props->mask |= PACK_PROP_LABEL_ITEM; } else if (!strcmp (value, "tab") && GTK_IS_NOTEBOOK_CLASS (klass)) { special = TRUE; props->mask |= PACK_PROP_TAB; } } if (!special) { GParamSpec *param_spec = gtk_container_class_find_child_property (klass, name); if (!param_spec) { g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "could not find property '%s'", name); _moo_param_array_free ((GParameter*) params->data, params->len); g_array_free (params, FALSE); g_type_class_unref (klass); packing_props_free (props); return NULL; } if (parse_property (param_spec, value, ¶m)) { g_array_append_val (params, param); } else { g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "could not convert '%s' to property '%s'", value, name); _moo_param_array_free ((GParameter*) params->data, params->len); g_array_free (params, FALSE); g_type_class_unref (klass); packing_props_free (props); return NULL; } } } } FOREACH_ELM_END; props->n_params = params->len; props->params = (GParameter*) g_array_free (params, FALSE); g_type_class_unref (klass); return props; } static gboolean parse_property (GParamSpec *param_spec, const char *value, GParameter *param) { gboolean take_null = FALSE; g_return_val_if_fail (param_spec != NULL, FALSE); if (param_spec->value_type == GTK_TYPE_ADJUSTMENT) take_null = TRUE; if (!value && !take_null) { g_warning ("NULL value for property '%s' of type '%s'", param_spec->name, g_type_name (param_spec->value_type)); return FALSE; } g_value_init (¶m->value, param_spec->value_type); if (param_spec->value_type == G_TYPE_CHAR) { #if GLIB_CHECK_VERSION(2,32,0) g_value_set_schar (¶m->value, value[0]); #else g_value_set_char (¶m->value, value[0]); #endif } else if (param_spec->value_type == G_TYPE_UCHAR) { g_value_set_uchar (¶m->value, value[0]); } else if (param_spec->value_type == G_TYPE_BOOLEAN) { g_value_set_boolean (¶m->value, parse_bool (value)); } else if (param_spec->value_type == G_TYPE_INT || param_spec->value_type == G_TYPE_LONG || param_spec->value_type == G_TYPE_INT64) /* XXX */ { long val = strtol (value, NULL, 0); if (errno) { g_warning ("could not convert string '%s' to an int", value); return FALSE; } else { if (param_spec->value_type == G_TYPE_INT) g_value_set_int (¶m->value, val); else if (param_spec->value_type == G_TYPE_LONG) g_value_set_long (¶m->value, val); else g_value_set_int64 (¶m->value, val); } } else if (param_spec->value_type == G_TYPE_UINT || param_spec->value_type == G_TYPE_ULONG || param_spec->value_type == G_TYPE_UINT64) /* XXX */ { mgw_errno_t err; guint64 val = mgw_ascii_strtoull (value, NULL, 0, &err); if (mgw_errno_is_set (err)) { g_warning ("could not convert string '%s' to a guint", value); return FALSE; } else { if (param_spec->value_type == G_TYPE_UINT) { moo_assert (val <= G_MAXUINT); g_value_set_uint (¶m->value, (guint) val); } else if (param_spec->value_type == G_TYPE_ULONG) { moo_assert (val <= G_MAXULONG); g_value_set_ulong (¶m->value, (gulong) val); } else { g_value_set_uint64 (¶m->value, val); } } } else if (param_spec->value_type == G_TYPE_FLOAT || param_spec->value_type == G_TYPE_DOUBLE) /* XXX */ { mgw_errno_t err; double val = mgw_ascii_strtod (value, NULL, &err); if (mgw_errno_is_set (err)) { g_warning ("could not convert string '%s' to double", value); return FALSE; } else { if (param_spec->value_type == G_TYPE_FLOAT) g_value_set_float (¶m->value, (float) val); else g_value_set_double (¶m->value, val); } } else if (param_spec->value_type == G_TYPE_STRING) { g_value_set_string (¶m->value, value); } else if (G_TYPE_IS_ENUM (param_spec->value_type)) { if (!_moo_value_convert_from_string (value, ¶m->value)) { const char *type_name = g_type_name (param_spec->value_type); g_warning ("can not convert string '%s' to a value of type %s", value, type_name ? type_name : ""); return FALSE; } } else if (G_TYPE_IS_FLAGS (param_spec->value_type)) { if (!_moo_value_convert_from_string (value, ¶m->value)) { const char *type_name = g_type_name (param_spec->value_type); g_warning ("can not convert string '%s' to a value of type %s", value, type_name ? type_name : ""); return FALSE; } } else if (param_spec->value_type == GDK_TYPE_PIXBUF) { GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file (value, NULL); if (!pixbuf) { g_message ("could not find %s", value); g_value_set_object (¶m->value, NULL); } else { g_value_set_object (¶m->value, pixbuf); g_object_unref (pixbuf); } } else if (param_spec->value_type == GTK_TYPE_ADJUSTMENT) { GtkObject *adjustment = parse_adjustment (value); if (!adjustment) { const char *type_name = g_type_name (param_spec->value_type); g_warning ("could not convert string '%s' to a value of type %s", value, type_name ? type_name : ""); return FALSE; } else { g_object_ref_sink (adjustment); g_value_set_object (¶m->value, adjustment); g_object_unref (adjustment); } } else if (param_spec->value_type == G_TYPE_STRV) { char **strv = moo_splitlines (value); g_value_take_boxed (¶m->value, strv); } else { const char *type_name = g_type_name (param_spec->value_type); g_warning ("could not convert string '%s' to a value of type %s", value, type_name ? type_name : ""); return FALSE; } param->name = g_strdup (param_spec->name); return TRUE; } static gboolean parse_bool (const char *value) { g_return_val_if_fail (value != NULL, FALSE); return !g_ascii_strcasecmp (value, "true"); } static int parse_int (const char *value) { long val; g_return_val_if_fail (value != NULL, 0); errno = 0; val = strtol (value, NULL, 0); if (errno) { g_warning ("could not convert string '%s' to an int", value); return 0; } else { return val; } } static GtkObject* parse_adjustment (const char *value) { char **pieces; GtkObject *adj = NULL; double vals[6]; guint i; /* XXX is this correct? */ if (!value) return GTK_OBJECT (g_object_new (GTK_TYPE_ADJUSTMENT, (const char*) NULL)); pieces = g_strsplit (value, " ", 0); g_return_val_if_fail (pieces != NULL, NULL); if (g_strv_length (pieces) != 6) goto out; for (i = 0; i < 6; ++i) { mgw_errno_t err; vals[i] = mgw_ascii_strtod (pieces[i], NULL, &err); if (mgw_errno_is_set (err)) { g_warning ("could not convert '%s' to double", pieces[i]); goto out; } } adj = gtk_adjustment_new (vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]); out: g_strfreev (pieces); return adj; } static void moo_glade_xml_class_init (MooGladeXMLClass *klass) { G_OBJECT_CLASS(klass)->dispose = moo_glade_xml_dispose; } static void moo_glade_xml_init (MooGladeXML *xml) { xml->priv = g_new0 (MooGladeXMLPrivate, 1); xml->priv->widgets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); xml->priv->class_to_type = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); xml->priv->id_to_type = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); xml->priv->id_to_func = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) func_data_pair_free); xml->priv->props = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_hash_table_destroy); } static char * normalize_prop_name (const char *prop_name) { char *norm, *p; norm = g_strdup (prop_name); p = norm; while ((p = strchr (p, '_'))) *p = '-'; return norm; } void moo_glade_xml_set_property (MooGladeXML *xml, const char *widget, const char *prop_name, const char *value) { GHashTable *props; char *norm_prop_name; g_return_if_fail (MOO_IS_GLADE_XML (xml)); g_return_if_fail (widget != NULL && prop_name != NULL); props = (GHashTable*) g_hash_table_lookup (xml->priv->props, widget); if (!props && !value) return; norm_prop_name = normalize_prop_name (prop_name); if (!props) { props = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); g_hash_table_insert (xml->priv->props, g_strdup (widget), props); } if (value) { g_hash_table_insert (props, norm_prop_name, g_strdup (value)); } else { g_hash_table_remove (props, norm_prop_name); g_free (norm_prop_name); } } MooGladeXML * moo_glade_xml_new_empty (const char *domain) { MooGladeXML *xml = MOO_GLADE_XML (g_object_new (MOO_TYPE_GLADE_XML, (const char*) NULL)); xml->priv->translation_domain = g_strdup (domain); return xml; } gboolean moo_glade_xml_parse_file (MooGladeXML *xml, const char *file, const char *root, GError **error) { MooMarkupDoc *doc; gboolean result; g_return_val_if_fail (xml != NULL, FALSE); g_return_val_if_fail (file != NULL, FALSE); doc = moo_markup_parse_file (file, error); if (!doc) return FALSE; result = moo_glade_xml_parse_markup (xml, doc, root, NULL, error); moo_markup_doc_unref (doc); return result; } gboolean moo_glade_xml_parse_memory (MooGladeXML *xml, const char *buffer, int size, const char *root, GError **error) { MooMarkupDoc *doc; gboolean result; GError *error_here = NULL; g_return_val_if_fail (xml != NULL, FALSE); g_return_val_if_fail (buffer != NULL, FALSE); doc = moo_markup_parse_memory (buffer, size, &error_here); if (!doc) { if (error) { g_propagate_error (error, error_here); } else { g_warning ("could not parse markup: %s", error_here->message); g_error_free (error_here); } return FALSE; } result = moo_glade_xml_parse_markup (xml, doc, root, NULL, &error_here); if (!result) { if (error) { g_propagate_error (error, error_here); } else { g_warning ("%s", moo_error_message (error_here)); g_error_free (error_here); } } moo_markup_doc_unref (doc); return result; } MooGladeXML * moo_glade_xml_new (const char *file, const char *root, const char *domain, GError **error) { MooGladeXML *xml; g_return_val_if_fail (file != NULL, NULL); xml = moo_glade_xml_new_empty (domain); if (!moo_glade_xml_parse_file (xml, file, root, error)) { g_object_unref (xml); return NULL; } return xml; } MooGladeXML* moo_glade_xml_new_from_buf (const char *buffer, int size, const char *root, const char *domain, GError **error) { MooGladeXML *xml; GError *error_here = NULL; g_return_val_if_fail (buffer != NULL, NULL); xml = moo_glade_xml_new_empty (domain); if (!moo_glade_xml_parse_memory (xml, buffer, size, root, &error_here)) { if (error) { g_propagate_error (error, error_here); } else { g_warning ("%s", moo_error_message (error_here)); g_error_free (error_here); } g_object_unref (xml); return NULL; } return xml; } static MooMarkupNode* find_widget (MooMarkupNode *node, const char *id) { const char *wid_id; g_return_val_if_fail (NODE_IS_WIDGET (node), NULL); wid_id = moo_markup_get_prop (node, "id"); g_return_val_if_fail (wid_id != NULL, NULL); if (!strcmp (wid_id, id)) return node; FOREACH_ELM_START(node, elm) { if (NODE_IS_CHILD (elm)) { FOREACH_ELM_START (elm, child) { if (NODE_IS_WIDGET (child)) { MooMarkupNode *wid = find_widget (child, id); if (wid) return wid; } } FOREACH_ELM_END; } } FOREACH_ELM_END; return NULL; } static gboolean moo_glade_xml_parse_markup (MooGladeXML *xml, MooMarkupDoc *doc, const char *root_id, GtkWidget *root_widget, GError **error) { MooMarkupNode *glade_elm; MooMarkupNode *root = NULL; Widget *widget = NULL; gboolean result = FALSE; g_return_val_if_fail (doc != NULL, FALSE); g_return_val_if_fail (!xml->priv->done, FALSE); g_return_val_if_fail (!root_widget || GTK_IS_WIDGET (root_widget), FALSE); glade_elm = moo_markup_get_root_element (doc, "glade-interface"); if (!glade_elm) { g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "no 'glade-interface' element"); return FALSE; } FOREACH_ELM_START(glade_elm, elm) { if (NODE_IS_WIDGET (elm)) { if (root_id) { root = find_widget (elm, root_id); if (root) break; } else { root = elm; break; } } else if (strcmp (elm->name, "requires") == 0 || strcmp (elm->name, "requires-version") == 0) { // g_message ("ignoring '%s'", elm->name); } else { g_warning ("invalid element '%s'", elm->name); } } FOREACH_ELM_END; if (!root) { if (root_id) g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "could not find root element '%s'", root_id); else g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "could not find root element"); goto out; } widget = widget_new (xml, NULL, root, error); if (!widget) goto out; dump_widget (widget); if (!moo_glade_xml_build (xml, widget, root_widget, error)) goto out; #if 0 gtk_widget_show_all (widget->widget); #endif xml->priv->root_id = g_strdup (widget->id); result = TRUE; out: if (widget) widget_free (widget); moo_glade_xml_cleanup (xml); xml->priv->done = TRUE; return result; } gboolean moo_glade_xml_fill_widget (MooGladeXML *xml, GtkWidget *target, const char *buffer, int size, const char *target_name, GError **error) { MooMarkupDoc *doc; gboolean result; g_return_val_if_fail (xml != NULL, FALSE); g_return_val_if_fail (buffer != NULL, FALSE); g_return_val_if_fail (GTK_IS_WIDGET (target), FALSE); doc = moo_markup_parse_memory (buffer, size, error); if (!doc) return FALSE; result = moo_glade_xml_parse_markup (xml, doc, target_name, target, error); moo_markup_doc_unref (doc); return result; } GtkWidget* moo_glade_xml_get_root (MooGladeXML *xml) { g_return_val_if_fail (xml != NULL, NULL); g_return_val_if_fail (xml->priv->root_id != NULL, NULL); return (GtkWidget*) moo_glade_xml_get_widget (xml, xml->priv->root_id); } static gboolean cmp_widget (G_GNUC_UNUSED gpointer key, gpointer widget, gpointer dead) { return widget == dead; } static void widget_destroyed (MooGladeXML *xml, gpointer widget) { guint removed; removed = g_hash_table_foreach_remove (xml->priv->widgets, (GHRFunc) cmp_widget, widget); g_return_if_fail (removed == 1); } static gboolean moo_glade_xml_add_widget (MooGladeXML *xml, const char *id, GtkWidget *widget, GError **error) { g_return_val_if_fail (id != NULL, FALSE); g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); if (g_hash_table_lookup (xml->priv->widgets, id)) { g_set_error (error, MOO_GLADE_XML_ERROR, MOO_GLADE_XML_ERROR_FAILED, "duplicated id '%s'", id); return FALSE; } g_assert (!g_hash_table_lookup (xml->priv->widgets, id)); g_hash_table_insert (xml->priv->widgets, g_strdup (id), widget); g_object_weak_ref (G_OBJECT (widget), (GWeakNotify) widget_destroyed, xml); return TRUE; } static void moo_glade_xml_cleanup (MooGladeXML *xml) { if (xml->priv->class_to_type) g_hash_table_destroy (xml->priv->class_to_type); if (xml->priv->id_to_type) g_hash_table_destroy (xml->priv->id_to_type); if (xml->priv->id_to_func) g_hash_table_destroy (xml->priv->id_to_func); if (xml->priv->props) g_hash_table_destroy (xml->priv->props); if (xml->priv->signal_func_data && xml->priv->signal_func_notify) xml->priv->signal_func_notify (xml->priv->signal_func_data); if (xml->priv->prop_func_data && xml->priv->prop_func_notify) xml->priv->prop_func_notify (xml->priv->prop_func_data); xml->priv->class_to_type = NULL; xml->priv->id_to_type = NULL; xml->priv->id_to_func = NULL; xml->priv->props = NULL; xml->priv->signal_func_data = NULL; xml->priv->prop_func_data = NULL; } static void unref_widget (G_GNUC_UNUSED gpointer key, GObject *widget, MooGladeXML *xml) { g_object_weak_unref (widget, (GWeakNotify) widget_destroyed, xml); } static void moo_glade_xml_dispose (GObject *object) { MooGladeXML *xml = MOO_GLADE_XML (object); if (xml->priv) { moo_glade_xml_cleanup (xml); g_hash_table_foreach (xml->priv->widgets, (GHFunc) unref_widget, xml); g_hash_table_destroy (xml->priv->widgets); g_free (xml->priv->translation_domain); g_free (xml->priv->root_id); g_free (xml->priv); xml->priv = NULL; } G_OBJECT_CLASS(moo_glade_xml_parent_class)->dispose (object); } gpointer moo_glade_xml_get_widget (MooGladeXML *xml, const char *id) { gpointer widget; const char *colon; g_return_val_if_fail (xml != NULL, NULL); g_return_val_if_fail (id != NULL, NULL); widget = g_hash_table_lookup (xml->priv->widgets, id); if (!widget && (colon = strchr (id, ':'))) { char *real_id = g_strndup (id, colon - id); widget = g_hash_table_lookup (xml->priv->widgets, real_id); g_free (real_id); } return widget; } void moo_glade_xml_register_type (G_GNUC_UNUSED GType type) { } void moo_glade_xml_map_class (MooGladeXML *xml, const char *class_name, GType type) { g_return_if_fail (xml != NULL); g_return_if_fail (class_name != NULL); if (type) g_hash_table_insert (xml->priv->class_to_type, g_strdup (class_name), GSIZE_TO_POINTER (type)); else g_hash_table_remove (xml->priv->class_to_type, class_name); } void moo_glade_xml_map_id (MooGladeXML *xml, const char *id, GType use_type) { g_return_if_fail (xml != NULL); g_return_if_fail (id != NULL); if (use_type) g_hash_table_insert (xml->priv->id_to_type, g_strdup (id), GSIZE_TO_POINTER (use_type)); else g_hash_table_remove (xml->priv->id_to_type, id); } void moo_glade_xml_map_custom (MooGladeXML *xml, const char *id, MooGladeCreateFunc func, gpointer data) { g_return_if_fail (xml != NULL); g_return_if_fail (id != NULL); if (func) g_hash_table_insert (xml->priv->id_to_func, g_strdup (id), func_data_pair_new (func, data)); else g_hash_table_remove (xml->priv->id_to_func, id); } void moo_glade_xml_set_signal_func (MooGladeXML *xml, MooGladeSignalFunc func, gpointer data, GDestroyNotify notify) { g_return_if_fail (xml != NULL); if (xml->priv->signal_func_data && xml->priv->signal_func_notify) xml->priv->signal_func_notify (xml->priv->signal_func_data); xml->priv->signal_func = func; xml->priv->signal_func_data = data; xml->priv->signal_func_notify = notify; } void moo_glade_xml_set_prop_func (MooGladeXML *xml, MooGladePropFunc func, gpointer data, GDestroyNotify notify) { g_return_if_fail (xml != NULL); if (xml->priv->prop_func_data && xml->priv->prop_func_notify) xml->priv->prop_func_notify (xml->priv->prop_func_data); xml->priv->prop_func = func; xml->priv->prop_func_data = data; xml->priv->prop_func_notify = notify; } static FuncDataPair* func_data_pair_new (MooGladeCreateFunc func, gpointer data) { FuncDataPair *pair; g_return_val_if_fail (func != NULL, NULL); pair = g_new (FuncDataPair, 1); pair->func = func; pair->data = data; return pair; } static void func_data_pair_free (FuncDataPair *pair) { g_free (pair); } static GType get_type_by_name (const char *name) { static GHashTable *types; GType type = g_type_from_name (name); typedef GType (*TypeFunc) (void); TypeFunc type_func; if (type) return type; if (!types) { #define add_type(class_name, func) g_hash_table_insert (types, g_strdup (class_name), (gpointer) func) types = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); add_type ("MooFontButton", moo_font_button_get_type); add_type ("MooFontSelection", moo_font_selection_get_type); add_type ("MooFontSelectionDialog", moo_font_selection_dialog_get_type); add_type ("MooCombo", moo_combo_get_type); add_type ("MooHistoryCombo", moo_history_combo_get_type); add_type ("MooEntry", moo_entry_get_type); add_type ("MooPrefsPage", moo_prefs_page_get_type); add_type ("MooAccelButton", _moo_accel_button_get_type); add_type ("GtkAboutDialog", gtk_about_dialog_get_type); add_type ("GtkCellRendererCombo", gtk_cell_renderer_combo_get_type); add_type ("GtkCellRendererProgress", gtk_cell_renderer_progress_get_type); add_type ("GtkCellView", gtk_cell_view_get_type); add_type ("GtkIconView", gtk_icon_view_get_type); add_type ("GtkMenuToolButton", gtk_menu_tool_button_get_type); add_type ("GtkAccelMap", gtk_accel_map_get_type); add_type ("GtkAction", gtk_action_get_type); add_type ("GtkActionGroup", gtk_action_group_get_type); add_type ("GtkComboBox", gtk_combo_box_get_type); add_type ("GtkComboBoxEntry", gtk_combo_box_entry_get_type); add_type ("GtkExpander", gtk_expander_get_type); add_type ("GtkFileChooser", gtk_file_chooser_get_type); add_type ("GtkFileChooserDialog", gtk_file_chooser_dialog_get_type); add_type ("GtkFileChooserWidget", gtk_file_chooser_widget_get_type); add_type ("GtkFileFilter", gtk_file_filter_get_type); add_type ("GtkRadioAction", gtk_radio_action_get_type); add_type ("GtkRadioToolButton", gtk_radio_tool_button_get_type); add_type ("GtkSeparatorToolItem", gtk_separator_tool_item_get_type); add_type ("GtkToggleAction", gtk_toggle_action_get_type); add_type ("GtkToggleToolButton", gtk_toggle_tool_button_get_type); add_type ("GtkToolButton", gtk_tool_button_get_type); add_type ("GtkToolItem", gtk_tool_item_get_type); add_type ("GtkTreeModelFilter", gtk_tree_model_filter_get_type); add_type ("GtkUiManager", gtk_ui_manager_get_type); add_type ("GtkAccelGroup", gtk_accel_group_get_type); add_type ("GtkAccelLabel", gtk_accel_label_get_type); add_type ("GtkAdjustment", gtk_adjustment_get_type); add_type ("GtkAlignment", gtk_alignment_get_type); add_type ("GtkArrow", gtk_arrow_get_type); add_type ("GtkAspectFrame", gtk_aspect_frame_get_type); add_type ("GtkBin", gtk_bin_get_type); add_type ("GtkBox", gtk_box_get_type); add_type ("GtkButton", gtk_button_get_type); add_type ("GtkButtonBox", gtk_button_box_get_type); add_type ("GtkCalendar", gtk_calendar_get_type); add_type ("GtkCellRenderer", gtk_cell_renderer_get_type); add_type ("GtkCellRendererMode", gtk_cell_renderer_mode_get_type); add_type ("GtkCellRendererPixbuf", gtk_cell_renderer_pixbuf_get_type); add_type ("GtkCellRendererState", gtk_cell_renderer_state_get_type); add_type ("GtkCellRendererText", gtk_cell_renderer_text_get_type); add_type ("GtkCellRendererToggle", gtk_cell_renderer_toggle_get_type); add_type ("GtkCheckButton", gtk_check_button_get_type); add_type ("GtkCheckMenuItem", gtk_check_menu_item_get_type); add_type ("GtkClipboard", gtk_clipboard_get_type); add_type ("GtkColorButton", gtk_color_button_get_type); add_type ("GtkColorSelection", gtk_color_selection_get_type); add_type ("GtkColorSelectionDialog", gtk_color_selection_dialog_get_type); add_type ("GtkCombo", gtk_combo_get_type); add_type ("GtkContainer", gtk_container_get_type); add_type ("GtkCurve", gtk_curve_get_type); add_type ("GtkDialog", gtk_dialog_get_type); add_type ("GtkDrawingArea", gtk_drawing_area_get_type); add_type ("GtkEditable", gtk_editable_get_type); add_type ("GtkEntry", gtk_entry_get_type); add_type ("GtkEventBox", gtk_event_box_get_type); add_type ("GtkFixed", gtk_fixed_get_type); add_type ("GtkFontButton", gtk_font_button_get_type); add_type ("GtkFontSelection", gtk_font_selection_get_type); add_type ("GtkFontSelectionDialog", gtk_font_selection_dialog_get_type); add_type ("GtkFrame", gtk_frame_get_type); add_type ("GtkGammaCurve", gtk_gamma_curve_get_type); add_type ("GtkHBox", gtk_hbox_get_type); add_type ("GtkHButtonBox", gtk_hbutton_box_get_type); add_type ("GtkHPaned", gtk_hpaned_get_type); add_type ("GtkHRuler", gtk_hruler_get_type); add_type ("GtkHScale", gtk_hscale_get_type); add_type ("GtkHScrollbar", gtk_hscrollbar_get_type); add_type ("GtkHSeparator", gtk_hseparator_get_type); add_type ("GtkHandleBox", gtk_handle_box_get_type); add_type ("GtkIconFactory", gtk_icon_factory_get_type); add_type ("GtkIconSet", gtk_icon_set_get_type); add_type ("GtkIconSource", gtk_icon_source_get_type); add_type ("GtkImContext", gtk_im_context_get_type); add_type ("GtkImContextSimple", gtk_im_context_simple_get_type); add_type ("GtkImMulticontext", gtk_im_multicontext_get_type); add_type ("GtkImage", gtk_image_get_type); add_type ("GtkImageMenuItem", gtk_image_menu_item_get_type); add_type ("GtkInputDialog", gtk_input_dialog_get_type); add_type ("GtkInvisible", gtk_invisible_get_type); add_type ("GtkItem", gtk_item_get_type); add_type ("GtkLabel", gtk_label_get_type); add_type ("GtkLayout", gtk_layout_get_type); add_type ("GtkList", gtk_list_get_type); add_type ("GtkListStore", gtk_list_store_get_type); add_type ("GtkMenu", gtk_menu_get_type); add_type ("GtkMenuBar", gtk_menu_bar_get_type); add_type ("GtkMenuItem", gtk_menu_item_get_type); add_type ("GtkMenuShell", gtk_menu_shell_get_type); add_type ("GtkMessageDialog", gtk_message_dialog_get_type); add_type ("GtkMisc", gtk_misc_get_type); add_type ("GtkNotebook", gtk_notebook_get_type); add_type ("GtkObject", gtk_object_get_type); add_type ("GtkOptionMenu", gtk_option_menu_get_type); add_type ("GtkPaned", gtk_paned_get_type); add_type ("GtkProgressBar", gtk_progress_bar_get_type); add_type ("GtkRadioButton", gtk_radio_button_get_type); add_type ("GtkRadioMenuItem", gtk_radio_menu_item_get_type); add_type ("GtkRange", gtk_range_get_type); add_type ("GtkRuler", gtk_ruler_get_type); add_type ("GtkScale", gtk_scale_get_type); add_type ("GtkScrollbar", gtk_scrollbar_get_type); add_type ("GtkScrolledWindow", gtk_scrolled_window_get_type); add_type ("GtkSeparator", gtk_separator_get_type); add_type ("GtkSeparatorMenuItem", gtk_separator_menu_item_get_type); add_type ("GtkSettings", gtk_settings_get_type); add_type ("GtkSizeGroup", gtk_size_group_get_type); add_type ("GtkSpinButton", gtk_spin_button_get_type); add_type ("GtkStatusbar", gtk_statusbar_get_type); add_type ("GtkStyle", gtk_style_get_type); add_type ("GtkTable", gtk_table_get_type); add_type ("GtkTearoffMenuItem", gtk_tearoff_menu_item_get_type); add_type ("GtkTextBuffer", gtk_text_buffer_get_type); add_type ("GtkTextChildAnchor", gtk_text_child_anchor_get_type); add_type ("GtkTextMark", gtk_text_mark_get_type); add_type ("GtkTextTag", gtk_text_tag_get_type); add_type ("GtkTextTagTable", gtk_text_tag_table_get_type); add_type ("GtkTextView", gtk_text_view_get_type); add_type ("GtkToggleButton", gtk_toggle_button_get_type); add_type ("GtkToolbar", gtk_toolbar_get_type); add_type ("GtkTreeDragDest", gtk_tree_drag_dest_get_type); add_type ("GtkTreeDragSource", gtk_tree_drag_source_get_type); add_type ("GtkTreeIter", gtk_tree_iter_get_type); add_type ("GtkTreeModel", gtk_tree_model_get_type); add_type ("GtkTreeModelSort", gtk_tree_model_sort_get_type); add_type ("GtkTreePath", gtk_tree_path_get_type); add_type ("GtkTreeRowReference", gtk_tree_row_reference_get_type); add_type ("GtkTreeSelection", gtk_tree_selection_get_type); add_type ("GtkTreeSortable", gtk_tree_sortable_get_type); add_type ("GtkTreeStore", gtk_tree_store_get_type); add_type ("GtkTreeView", gtk_tree_view_get_type); add_type ("GtkTreeViewColumn", gtk_tree_view_column_get_type); add_type ("GtkTreeViewMode", gtk_tree_view_mode_get_type); add_type ("GtkVBox", gtk_vbox_get_type); add_type ("GtkVButtonBox", gtk_vbutton_box_get_type); add_type ("GtkViewport", gtk_viewport_get_type); add_type ("GtkVPaned", gtk_vpaned_get_type); add_type ("GtkVRuler", gtk_vruler_get_type); add_type ("GtkVScale", gtk_vscale_get_type); add_type ("GtkVScrollbar", gtk_vscrollbar_get_type); add_type ("GtkVSeparator", gtk_vseparator_get_type); add_type ("GtkWidget", gtk_widget_get_type); add_type ("GtkWidgetFlags", gtk_widget_flags_get_type); add_type ("GtkWidgetHelpType", gtk_widget_help_type_get_type); add_type ("GtkWindow", gtk_window_get_type); add_type ("GtkWindowGroup", gtk_window_group_get_type); #ifndef __WIN32__ add_type ("GtkPlug", gtk_plug_get_type); add_type ("GtkSocket", gtk_socket_get_type); #endif #undef add_type } type_func = (TypeFunc) g_hash_table_lookup (types, name); if (type_func) return type_func (); return 0; } #if 0 static void print_widget (Widget *widget, guint offset); static void print_child (Child *child, guint offset); static void print_props (GParameter *params, guint n_params, guint offset); static void print_widget (Widget *widget, guint offset) { GSList *l; char *fill = g_strnfill (offset, ' '); g_print ("%sWidget id '%s' class '%s'\n", fill, widget->id, g_type_name (widget->type)); print_props (widget->props->params, widget->props->n_params, offset); for (l = widget->children; l != NULL; l = l->next) print_child (l->data, offset + 3); } static void print_child (Child *child, guint offset) { char *fill = g_strnfill (offset, ' '); g_print ("%sChild\n", fill); print_props (child->props->params, child->props->n_params, offset); print_widget (child->widget, offset + 3); } static void print_value (GValue *value) { if (value->g_type == G_TYPE_CHAR) { g_print ("%c", g_value_get_char (value)); } else if (value->g_type == G_TYPE_UCHAR) { g_print ("%c", g_value_get_uchar (value)); } else if (value->g_type == G_TYPE_BOOLEAN) { g_print ("%s", g_value_get_boolean (value) ? "TRUE" : "FALSE"); } else if (value->g_type == G_TYPE_INT || value->g_type == G_TYPE_LONG || value->g_type == G_TYPE_INT64 || value->g_type == G_TYPE_UINT || value->g_type == G_TYPE_ULONG || value->g_type == G_TYPE_UINT64) /* XXX */ { int val; if (value->g_type == G_TYPE_INT) val = g_value_get_int (value); else if (value->g_type == G_TYPE_LONG) val = g_value_get_long (value); else if (value->g_type == G_TYPE_INT64) val = g_value_get_int64 (value); else if (value->g_type == G_TYPE_UINT) val = g_value_get_uint (value); else if (value->g_type == G_TYPE_ULONG) val = g_value_get_ulong (value); else if (value->g_type == G_TYPE_UINT64) val = g_value_get_uint64 (value); g_print ("%d", val); } else if (value->g_type == G_TYPE_FLOAT || value->g_type == G_TYPE_DOUBLE) /* XXX */ { double val; if (value->g_type == G_TYPE_FLOAT) val = g_value_get_float (value); else if (value->g_type == G_TYPE_DOUBLE) val = g_value_get_double (value); g_print ("%f", val); } else if (value->g_type == G_TYPE_STRING) { g_print ("%s", g_value_get_string (value)); } else if (G_TYPE_IS_ENUM (value->g_type)) { g_print ("%d", g_value_get_enum (value)); } else if (G_TYPE_IS_FLAGS (value->g_type)) { g_print ("%d", g_value_get_flags (value)); } else { g_print ("<%s>", g_type_name (value->g_type)); } } static void print_props (GParameter *params, guint n_params, guint offset) { guint i; char *fill = g_strnfill (offset, ' '); for (i = 0; i < n_params; ++i) { GParameter *param = ¶ms[i]; g_print ("%s%s = '", fill, param->name); print_value (¶m->value); g_print ("'\n"); } } static void dump_widget (Widget *widget) { print_widget (widget, 0); } #else static void dump_widget (G_GNUC_UNUSED Widget *widget) { } #endif