* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* <title>GtkBuilder UI Definitions</title>
* <para>
* GtkBuilder parses textual descriptions of user interfaces which are specified
- * in an XML format which can be roughly described by the DTD below. We refer to
- * these descriptions as <firstterm>GtkBuilder UI definitions</firstterm> or
- * just <firstterm>UI definitions</firstterm> if the context is clear. Do not
+ * in an XML format which can be roughly described by the RELAX NG schema below.
+ * We refer to these descriptions as <firstterm>GtkBuilder UI definitions</firstterm>
+ * or just <firstterm>UI definitions</firstterm> if the context is clear. Do not
* confuse GtkBuilder UI Definitions with
* <link linkend="XML-UI">GtkUIManager UI Definitions</link>, which are more
- * limited in scope.
+ * limited in scope. It is common to use <filename>.ui</filename> as the filename extension for files containing GtkBuilder UI definitions.
* </para>
- * <programlisting><![CDATA[
- * <!ELEMENT interface (requires|object)* >
- * <!ELEMENT object (property|signal|child|ANY)* >
- * <!ELEMENT property PCDATA >
- * <!ELEMENT signal EMPTY >
- * <!ELEMENT requires EMPTY >
- * <!ELEMENT child (object|ANY*) >
- *
- * <!ATTLIST interface domain #IMPLIED >
- * <!ATTLIST object id #REQUIRED
- * class #REQUIRED
- * type-func #IMPLIED
- * constructor #IMPLIED >
- * <!ATTLIST requires lib #REQUIRED
- * version #REQUIRED >
- * <!ATTLIST property name #REQUIRED
- * translatable #IMPLIED
- * comments #IMPLIED
- * context #IMPLIED >
- * <!ATTLIST signal name #REQUIRED
- * handler #REQUIRED
- * after #IMPLIED
- * swapped #IMPLIED
- * object #IMPLIED
- * last_modification_time #IMPLIED >
- * <!ATTLIST child type #IMPLIED
- * internal-child #IMPLIED >
- * ]]></programlisting>
+ * <programlisting>
+ * <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../gtk/gtkbuilder.rnc">
+ * <xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback>
+ * </xi:include>
+ * </programlisting>
* <para>
* The toplevel element is <interface>. It optionally takes a "domain"
* attribute, which will make the builder look for translated strings using
* (can be specified by their name, nick or integer value), flags (can be
* specified by their name, nick, integer value, optionally combined with "|",
* e.g. "GTK_VISIBLE|GTK_REALIZED") and colors (in a format understood by
- * gdk_color_parse()). Objects can be referred to by their name. Pixbufs can be
- * specified as a filename of an image file to load. In general, GtkBuilder
- * allows forward references to objects — an object doesn't have to be
- * constructed before it can be referred to. The exception to this rule is that
- * an object has to be constructed before it can be used as the value of a
- * construct-only property.
+ * gdk_color_parse()). Pixbufs can be specified as a filename of an image file to load.
+ * Objects can be referred to by their name and by default refer to objects declared
+ * in the local xml fragment and objects exposed via gtk_builder_expose_object().
+ *
+ * In general, GtkBuilder allows forward references to objects &mdash declared
+ * in the local xml; an object doesn't have to be constructed before it can be referred to.
+ * The exception to this rule is that an object has to be constructed before
+ * it can be used as the value of a construct-only property.
*
* Signal handlers are set up with the <signal> element. The "name"
* attribute specifies the name of the signal, and the "handler" attribute
* <link linkend="GtkUIManager-BUILDER-UI">GtkUIManager</link>,
* <link linkend="GtkActionGroup-BUILDER-UI">GtkActionGroup</link>.
* <link linkend="GtkMenuItem-BUILDER-UI">GtkMenuItem</link>,
+ * <link linkend="GtkMenuToolButton-BUILDER-UI">GtkMenuToolButton</link>,
* <link linkend="GtkAssistant-BUILDER-UI">GtkAssistant</link>,
- * <link linkend="GtkScale-BUILDER-UI">GtkScale</link>.
- * <link linkend="GtkComboBoxText-BUILDER-UI">GtkComboBoxText</link>.
- * <link linkend="GtkRecentFilter-BUILDER-UI">GtkRecentFilter</link>.
+ * <link linkend="GtkScale-BUILDER-UI">GtkScale</link>,
+ * <link linkend="GtkComboBoxText-BUILDER-UI">GtkComboBoxText</link>,
+ * <link linkend="GtkRecentFilter-BUILDER-UI">GtkRecentFilter</link>,
+ * <link linkend="GtkFileFilter-BUILDER-UI">GtkFileFilter</link>,
+ * <link linkend="GtkTextTagTable-BUILDER-UI">GtkTextTagTable</link>.
+ * </para>
+ * </refsect2>
+ * <refsect2>
+ * <title>Embedding other XML</title>
+ * <para>
+ * Apart from the language for UI descriptions that has been explained
+ * in the previous section, GtkBuilder can also parse XML fragments
+ * of <link linkend="gio-GMenu-Markup">GMenu markup</link>. The resulting
+ * #GMenu object and its named submenus are available via
+ * gtk_builder_get_object() like other constructed objects.
* </para>
* </refsect2>
*/
GSList *delayed_properties;
GSList *signals;
gchar *filename;
+ gchar *resource_prefix;
};
G_DEFINE_TYPE (GtkBuilder, gtk_builder, G_TYPE_OBJECT)
g_free (priv->domain);
g_free (priv->filename);
+ g_free (priv->resource_prefix);
g_hash_table_destroy (priv->objects);
if (G_IS_PARAM_SPEC_OBJECT (pspec) &&
(G_PARAM_SPEC_VALUE_TYPE (pspec) != GDK_TYPE_PIXBUF))
{
- if (pspec->flags & G_PARAM_CONSTRUCT_ONLY)
+ GObject *object = gtk_builder_get_object (builder, prop->data);
+
+ if (object)
+ {
+ g_value_init (¶meter.value, G_OBJECT_TYPE (object));
+ g_value_set_object (¶meter.value, object);
+ }
+ else
{
- GObject *object;
- object = gtk_builder_get_object (builder, prop->data);
- if (!object)
+ if (pspec->flags & G_PARAM_CONSTRUCT_ONLY)
{
g_warning ("Failed to get constuct only property "
"%s of %s with value `%s'",
prop->name, object_name, prop->data);
continue;
}
- g_value_init (¶meter.value, G_OBJECT_TYPE (object));
- g_value_set_object (¶meter.value, object);
- }
- else
- {
+ /* Delay setting property */
property = g_slice_new (DelayedProperty);
property->object = g_strdup (object_name);
property->name = g_strdup (prop->name);
property->value = g_strdup (prop->data);
builder->priv->delayed_properties =
g_slist_prepend (builder->priv->delayed_properties, property);
-
continue;
}
}
continue;
}
- if (pspec->flags & G_PARAM_CONSTRUCT_ONLY)
+ if (pspec->flags & (G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY))
g_array_append_val (*construct_parameters, parameter);
else
g_array_append_val (*parameters, parameter);
return obj;
}
+static inline void
+object_set_name (GObject *object, const gchar *name)
+{
+ if (GTK_IS_BUILDABLE (object))
+ gtk_buildable_set_name (GTK_BUILDABLE (object), name);
+ else
+ g_object_set_data_full (object, "gtk-builder-name", g_strdup (name), g_free);
+}
+
+void
+_gtk_builder_add_object (GtkBuilder *builder,
+ const gchar *id,
+ GObject *object)
+{
+ object_set_name (object, id);
+ g_hash_table_insert (builder->priv->objects, g_strdup (id), g_object_ref (object));
+}
+
GObject *
_gtk_builder_construct (GtkBuilder *builder,
ObjectInfo *info,
g_value_unset (¶m->value);
}
g_array_free (parameters, TRUE);
-
- if (GTK_IS_BUILDABLE (obj))
- gtk_buildable_set_name (buildable, info->id);
- else
- g_object_set_data_full (obj,
- "gtk-builder-name",
- g_strdup (info->id),
- g_free);
- /* we already own a reference to obj. put it in the hash table. */
- g_hash_table_insert (builder->priv->objects, g_strdup (info->id), obj);
+ /* put it in the hash table. */
+ _gtk_builder_add_object (builder, info->id, obj);
+
+ /* we already own a reference to obj. */
+ g_object_unref (obj);
return obj;
}
-
void
_gtk_builder_add (GtkBuilder *builder,
ChildInfo *child_info)
}
g_free (builder->priv->filename);
+ g_free (builder->priv->resource_prefix);
builder->priv->filename = g_strdup (filename);
+ builder->priv->resource_prefix = NULL;
_gtk_builder_parser_parse_buffer (builder, filename,
buffer, length,
}
g_free (builder->priv->filename);
+ g_free (builder->priv->resource_prefix);
builder->priv->filename = g_strdup (filename);
+ builder->priv->resource_prefix = NULL;
_gtk_builder_parser_parse_buffer (builder, filename,
buffer, length,
return 1;
}
+/**
+ * gtk_builder_add_from_resource:
+ * @builder: a #GtkBuilder
+ * @resource_path: the path of the resource file to parse
+ * @error: (allow-none): return location for an error, or %NULL
+ *
+ * Parses a resource file containing a <link linkend="BUILDER-UI">GtkBuilder
+ * UI definition</link> and merges it with the current contents of @builder.
+ *
+ * Upon errors 0 will be returned and @error will be assigned a
+ * #GError from the #GTK_BUILDER_ERROR, #G_MARKUP_ERROR or #G_RESOURCE_ERROR
+ * domain.
+ *
+ * Returns: A positive value on success, 0 if an error occurred
+ *
+ * Since: 3.4
+ **/
+guint
+gtk_builder_add_from_resource (GtkBuilder *builder,
+ const gchar *resource_path,
+ GError **error)
+{
+ GError *tmp_error;
+ GBytes *data;
+ char *filename_for_errors;
+ char *slash;
+
+ g_return_val_if_fail (GTK_IS_BUILDER (builder), 0);
+ g_return_val_if_fail (resource_path != NULL, 0);
+ g_return_val_if_fail (error == NULL || *error == NULL, 0);
+
+ tmp_error = NULL;
+
+ data = g_resources_lookup_data (resource_path, 0, &tmp_error);
+ if (data == NULL)
+ {
+ g_propagate_error (error, tmp_error);
+ return 0;
+ }
+
+ g_free (builder->priv->filename);
+ g_free (builder->priv->resource_prefix);
+ builder->priv->filename = g_strdup (".");
+
+ slash = strrchr (resource_path, '/');
+ if (slash != NULL)
+ builder->priv->resource_prefix =
+ g_strndup (resource_path, slash - resource_path + 1);
+ else
+ builder->priv->resource_prefix =
+ g_strdup ("/");
+
+ filename_for_errors = g_strconcat ("<resource>", resource_path, NULL);
+
+ _gtk_builder_parser_parse_buffer (builder, filename_for_errors,
+ g_bytes_get_data (data, NULL), g_bytes_get_size (data),
+ NULL,
+ &tmp_error);
+
+ g_free (filename_for_errors);
+ g_bytes_unref (data);
+
+ if (tmp_error != NULL)
+ {
+ g_propagate_error (error, tmp_error);
+ return 0;
+ }
+
+ return 1;
+}
+
+/**
+ * gtk_builder_add_objects_from_resource:
+ * @builder: a #GtkBuilder
+ * @resource_path: the path of the resource file to parse
+ * @object_ids: (array zero-terminated=1) (element-type utf8): nul-terminated array of objects to build
+ * @error: (allow-none): return location for an error, or %NULL
+ *
+ * Parses a resource file containing a <link linkend="BUILDER-UI">GtkBuilder
+ * UI definition</link> building only the requested objects and merges
+ * them with the current contents of @builder.
+ *
+ * Upon errors 0 will be returned and @error will be assigned a
+ * #GError from the #GTK_BUILDER_ERROR, #G_MARKUP_ERROR or #G_RESOURCE_ERROR
+ * domain.
+ *
+ * <note><para>
+ * If you are adding an object that depends on an object that is not
+ * its child (for instance a #GtkTreeView that depends on its
+ * #GtkTreeModel), you have to explicitely list all of them in @object_ids.
+ * </para></note>
+ *
+ * Returns: A positive value on success, 0 if an error occurred
+ *
+ * Since: 3.4
+ **/
+guint
+gtk_builder_add_objects_from_resource (GtkBuilder *builder,
+ const gchar *resource_path,
+ gchar **object_ids,
+ GError **error)
+{
+ GError *tmp_error;
+ GBytes *data;
+ char *filename_for_errors;
+ char *slash;
+
+ g_return_val_if_fail (GTK_IS_BUILDER (builder), 0);
+ g_return_val_if_fail (resource_path != NULL, 0);
+ g_return_val_if_fail (object_ids != NULL && object_ids[0] != NULL, 0);
+ g_return_val_if_fail (error == NULL || *error == NULL, 0);
+
+ tmp_error = NULL;
+
+ data = g_resources_lookup_data (resource_path, 0, &tmp_error);
+ if (data == NULL)
+ {
+ g_propagate_error (error, tmp_error);
+ return 0;
+ }
+
+ g_free (builder->priv->filename);
+ g_free (builder->priv->resource_prefix);
+ builder->priv->filename = g_strdup (".");
+
+ slash = strrchr (resource_path, '/');
+ if (slash != NULL)
+ builder->priv->resource_prefix =
+ g_strndup (resource_path, slash - resource_path + 1);
+ else
+ builder->priv->resource_prefix =
+ g_strdup ("/");
+
+ filename_for_errors = g_strconcat ("<resource>", resource_path, NULL);
+
+ _gtk_builder_parser_parse_buffer (builder, filename_for_errors,
+ g_bytes_get_data (data, NULL), g_bytes_get_size (data),
+ object_ids,
+ &tmp_error);
+ g_free (filename_for_errors);
+ g_bytes_unref (data);
+
+ if (tmp_error != NULL)
+ {
+ g_propagate_error (error, tmp_error);
+ return 0;
+ }
+
+ return 1;
+}
+
/**
* gtk_builder_add_from_string:
* @builder: a #GtkBuilder
tmp_error = NULL;
g_free (builder->priv->filename);
+ g_free (builder->priv->resource_prefix);
builder->priv->filename = g_strdup (".");
+ builder->priv->resource_prefix = NULL;
_gtk_builder_parser_parse_buffer (builder, "<input>",
buffer, length,
tmp_error = NULL;
g_free (builder->priv->filename);
+ g_free (builder->priv->resource_prefix);
builder->priv->filename = g_strdup (".");
+ builder->priv->resource_prefix = NULL;
_gtk_builder_parser_parse_buffer (builder, "<input>",
buffer, length,
return builder->priv->domain;
}
+/**
+ * gtk_builder_expose_object:
+ * @builder: a #GtkBuilder
+ * @name: the name of the object exposed to the builder
+ * @object: the object to expose
+ *
+ * Add @object to the @builder object pool so it can be referenced just like any
+ * other object built by builder.
+ *
+ * To make this function even more useful a new special entry point element
+ * <external-object> is defined. It is similar to <object> but has
+ * to reference an external object exposed with this function.
+ * This way you can change properties and even add children to an
+ * external object using builder, not just reference it.
+ *
+ * Since: 3.8
+ **/
+void
+gtk_builder_expose_object (GtkBuilder *builder,
+ const gchar *name,
+ GObject *object)
+{
+ g_return_if_fail (GTK_IS_BUILDER (builder));
+ g_return_if_fail (name && name[0]);
+ g_return_if_fail (gtk_builder_get_object (builder, name) == NULL);
+
+ object_set_name (object, name);
+ g_hash_table_insert (builder->priv->objects,
+ g_strdup (name),
+ g_object_ref (object));
+}
+
+
typedef struct {
GModule *module;
gpointer data;
* It uses #GModule's introspective features (by opening the module %NULL)
* to look at the application's symbol table. From here it tries to match
* the signal handler names given in the interface description with
- * symbols in the application and connects the signals.
+ * symbols in the application and connects the signals. Note that this
+ * function can only be called once, subsequent calls will do nothing.
*
* Note that this function will not work correctly if #GModule is not
* supported on the platform.
* by the gtk_builder_connect_signals() and gtk_builder_connect_signals_full()
* methods. It is mainly intended for interpreted language bindings, but
* could be useful where the programmer wants more control over the signal
- * connection process.
+ * connection process. Note that this function can only be called once,
+ * subsequent calls will do nothing.
*
* Since: 2.12
*/
* initialised beforehand.
*
* This function can handle char, uchar, boolean, int, uint, long,
- * ulong, enum, flags, float, double, string, #GdkColor and
+ * ulong, enum, flags, float, double, string, #GdkColor, #GdkRGBA and
* #GtkAdjustment type values. Support for #GtkWidget type values is
* still to come.
*
return TRUE;
}
+ /*
+ * GParamSpecVariant can specify a GVariantType which can help with
+ * parsing, so we need to take care of that here.
+ */
+ if (G_IS_PARAM_SPEC_VARIANT (pspec))
+ {
+ GParamSpecVariant *variant_pspec = G_PARAM_SPEC_VARIANT (pspec);
+ const GVariantType *type;
+ GVariant *variant;
+
+ g_value_init (value, G_TYPE_VARIANT);
+
+ /* The GVariant parser doesn't deal with indefinite types */
+ if (g_variant_type_is_definite (variant_pspec->type))
+ type = variant_pspec->type;
+ else
+ type = NULL;
+
+ variant = g_variant_parse (type, string, NULL, NULL, error);
+ if (variant == NULL)
+ return FALSE;
+ g_value_take_variant (value, variant);
+ return TRUE;
+ }
+
return gtk_builder_value_from_string_type (builder,
G_PARAM_SPEC_VALUE_TYPE (pspec),
string, value, error);
switch (G_TYPE_FUNDAMENTAL (type))
{
case G_TYPE_CHAR:
- g_value_set_char (value, string[0]);
+ g_value_set_schar (value, string[0]);
break;
case G_TYPE_UCHAR:
g_value_set_uchar (value, (guchar)string[0]);
case G_TYPE_STRING:
g_value_set_string (value, string);
break;
+ case G_TYPE_VARIANT:
+ {
+ GVariant *variant;
+
+ variant = g_variant_parse (NULL, string, NULL, NULL, error);
+ if (value != NULL)
+ g_value_take_variant (value, variant);
+ else
+ ret = FALSE;
+ }
+ break;
case G_TYPE_BOXED:
if (G_VALUE_HOLDS (value, GDK_TYPE_COLOR))
{
{
gchar *filename;
GError *tmp_error = NULL;
- GdkPixbuf *pixbuf;
+ GdkPixbuf *pixbuf = NULL;
if (gtk_builder_get_object (builder, string))
{
return FALSE;
}
- filename = _gtk_builder_get_absolute_filename (builder, string);
- pixbuf = gdk_pixbuf_new_from_file (filename, &tmp_error);
+ filename = _gtk_builder_get_resource_path (builder, string);
+ if (filename != NULL)
+ {
+ GInputStream *stream = g_resources_open_stream (filename, 0, &tmp_error);
+ if (stream != NULL)
+ {
+ pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, &tmp_error);
+ g_object_unref (stream);
+ }
+ }
+ else
+ {
+ filename = _gtk_builder_get_absolute_filename (builder, string);
+ pixbuf = gdk_pixbuf_new_from_file (filename, &tmp_error);
+ }
if (pixbuf == NULL)
{
return g_quark_from_static_string ("gtk-builder-error-quark");
}
+gchar *
+_gtk_builder_get_resource_path (GtkBuilder *builder, const gchar *string)
+{
+ if (g_str_has_prefix (string, "resource:///"))
+ return g_uri_unescape_string (string + 11, "/");
+
+ if (g_path_is_absolute (string) ||
+ builder->priv->resource_prefix == NULL)
+ return NULL;
+
+ return g_build_path ("/", builder->priv->resource_prefix, string, NULL);
+}
+
gchar *
_gtk_builder_get_absolute_filename (GtkBuilder *builder, const gchar *string)
{