Made buildable and added support for adding children of type "submenu"

* gtk/gtkmenuitem.c: Made buildable and added support for adding children
	of type "submenu"

	* gtk/gtkwindow.c: Added support for custom tag "accel-groups" to add GtkAccelGroups
	to the window.

	* gtk/gtkcontainer.c: Added builder contextual warnings in buildable_add_child()

	* gtk/tests/builder.c: Added tests for buildable menus (test that accelerators are
	properly connected on stock items, test the menu hierarchy, test permission to
	add alien/custom menuitem children).

	* docs/reference/gtk/tmpl/gtkbuilder.sgml, docs/reference/gtk/tmpl/gtkwindow.sgml,
	docs/reference/gtk/tmpl/gtkmenuitem.sgml: Updated docs for buildable submenus
	and accel groups.


svn path=/trunk/; revision=21767
This commit is contained in:
Tristan Van Berkom 2008-11-06 17:34:30 +00:00
parent 70a5f5388e
commit 4858ae47e2
8 changed files with 380 additions and 6 deletions

View File

@ -1,3 +1,21 @@
2008-11-06 Tristan Van Berkom <tvb@gnome.org>
* gtk/gtkmenuitem.c: Made buildable and added support for adding children
of type "submenu"
* gtk/gtkwindow.c: Added support for custom tag "accel-groups" to add GtkAccelGroups
to the window.
* gtk/gtkcontainer.c: Added builder contextual warnings in buildable_add_child()
* gtk/tests/builder.c: Added tests for buildable menus (test that accelerators are
properly connected on stock items, test the menu hierarchy, test permission to
add alien/custom menuitem children).
* docs/reference/gtk/tmpl/gtkbuilder.sgml, docs/reference/gtk/tmpl/gtkwindow.sgml,
docs/reference/gtk/tmpl/gtkmenuitem.sgml: Updated docs for buildable submenus
and accel groups.
2008-11-06 Tristan Van Berkom <tvb@gnome.org> 2008-11-06 Tristan Van Berkom <tvb@gnome.org>
* gtk/gtkmenuitem.[ch]: added new apis gtk_menu_item_[set/get]_label() and * gtk/gtkmenuitem.[ch]: added new apis gtk_menu_item_[set/get]_label() and

View File

@ -212,6 +212,7 @@ These XML fragments are explained in the documentation of the
respective objects, see respective objects, see
<link linkend="GtkWidget-BUILDER-UI">GtkWidget</link>, <link linkend="GtkWidget-BUILDER-UI">GtkWidget</link>,
<link linkend="GtkLabel-BUILDER-UI">GtkLabel</link>, <link linkend="GtkLabel-BUILDER-UI">GtkLabel</link>,
<link linkend="GtkWindow-BUILDER-UI">GtkWindow</link>,
<link linkend="GtkContainer-BUILDER-UI">GtkContainer</link>, <link linkend="GtkContainer-BUILDER-UI">GtkContainer</link>,
<link linkend="GtkDialog-BUILDER-UI">GtkDialog</link>, <link linkend="GtkDialog-BUILDER-UI">GtkDialog</link>,
<link linkend="GtkCellLayout-BUILDER-UI">GtkCellLayout</link>, <link linkend="GtkCellLayout-BUILDER-UI">GtkCellLayout</link>,
@ -227,6 +228,7 @@ respective objects, see
<link linkend="GtkTreeView-BUILDER-UI">GtkTreeView</link>, <link linkend="GtkTreeView-BUILDER-UI">GtkTreeView</link>,
<link linkend="GtkUIManager-BUILDER-UI">GtkUIManager</link>, <link linkend="GtkUIManager-BUILDER-UI">GtkUIManager</link>,
<link linkend="GtkActionGroup-BUILDER-UI">GtkActionGroup</link>. <link linkend="GtkActionGroup-BUILDER-UI">GtkActionGroup</link>.
<link linkend="GtkMenuItem-BUILDER-UI">GtkMenuItem</link>.
</para> </para>
</refsect2> </refsect2>

View File

@ -14,6 +14,24 @@ alignment, events and submenus.
As it derives from #GtkBin it can hold any valid child widget, altough As it derives from #GtkBin it can hold any valid child widget, altough
only a few are really useful. only a few are really useful.
</para> </para>
<refsect2 id="GtkMenuItem-BUILDER-UI">
<title>GtkMenuItem as GtkBuildable</title>
<para>
The GtkMenuItem implementation of the GtkBuildable interface
supports adding a submenu by specifying "submenu" as the "type"
attribute of a &lt;child&gt; element.
</para>
<example>
<title>A UI definition fragment with submenus</title>
<programlisting><![CDATA[
<object class="GtkMenuItem">
<child type="submenu">
<object class="GtkMenu"/>
</child>
</object>
]]></programlisting>
</example>
</refsect2>
<!-- ##### SECTION See_Also ##### --> <!-- ##### SECTION See_Also ##### -->
<para> <para>

View File

@ -8,6 +8,29 @@ Toplevel which can contain other widgets
<para> <para>
</para> </para>
<refsect2 id="GtkWindow-BUILDER-UI">
<title>GtkWindow as GtkBuildable</title>
<para>
The GtkWindow implementation of the GtkBuildable interface supports a
custom &lt;accel-groups&gt; element, which supports any number of &lt;group&gt;
elements representing the GtkAccelGroup objects you want to add to your
window (synonymous with gtk_window_add_accel_group().
</para>
<example>
<title>A UI definition fragment with accel groups</title>
<programlisting><![CDATA[
<object class="GtkWindow">
<accel-groups>
<group name="accelgroup1"/>
</accel-groups>
</object>
...
<object class="GtkAccelGroup" id="accelgroup1"/>
]]></programlisting>
</example>
</refsect2>
<!-- ##### SECTION See_Also ##### --> <!-- ##### SECTION See_Also ##### -->
<para> <para>

View File

@ -307,9 +307,17 @@ gtk_container_buildable_add_child (GtkBuildable *buildable,
GObject *child, GObject *child,
const gchar *type) const gchar *type)
{ {
g_return_if_fail (GTK_IS_WIDGET (child)); if (type)
{
gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child)); GTK_BUILDER_WARN_INVALID_CHILD_TYPE (buildable, type);
}
else if (GTK_IS_WIDGET (child) && GTK_WIDGET_TOPLEVEL (child) == FALSE)
{
gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child));
}
else
g_warning ("Cannot add an object of type %s to a container of type %s",
g_type_name (G_OBJECT_TYPE (child)), g_type_name (G_OBJECT_TYPE (buildable)));
} }
static void static void

View File

@ -36,6 +36,7 @@
#include "gtkmenubar.h" #include "gtkmenubar.h"
#include "gtkseparatormenuitem.h" #include "gtkseparatormenuitem.h"
#include "gtkprivate.h" #include "gtkprivate.h"
#include "gtkbuildable.h"
#include "gtkintl.h" #include "gtkintl.h"
#include "gtkalias.h" #include "gtkalias.h"
@ -113,9 +114,19 @@ static void gtk_real_menu_item_set_label (GtkMenuItem *menu_item,
static G_CONST_RETURN gchar * gtk_real_menu_item_get_label (GtkMenuItem *menu_item); static G_CONST_RETURN gchar * gtk_real_menu_item_get_label (GtkMenuItem *menu_item);
static void gtk_menu_item_buildable_interface_init (GtkBuildableIface *iface);
static void gtk_menu_item_buildable_add_child (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const gchar *type);
static guint menu_item_signals[LAST_SIGNAL] = { 0 }; static guint menu_item_signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE (GtkMenuItem, gtk_menu_item, GTK_TYPE_ITEM) static GtkBuildableIface *parent_buildable_iface;
G_DEFINE_TYPE_WITH_CODE (GtkMenuItem, gtk_menu_item, GTK_TYPE_ITEM,
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
gtk_menu_item_buildable_interface_init))
static void static void
gtk_menu_item_class_init (GtkMenuItemClass *klass) gtk_menu_item_class_init (GtkMenuItemClass *klass)
@ -474,6 +485,26 @@ gtk_menu_item_detacher (GtkWidget *widget,
menu_item->submenu = NULL; menu_item->submenu = NULL;
} }
static void
gtk_menu_item_buildable_interface_init (GtkBuildableIface *iface)
{
parent_buildable_iface = g_type_interface_peek_parent (iface);
iface->add_child = gtk_menu_item_buildable_add_child;
}
static void
gtk_menu_item_buildable_add_child (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const gchar *type)
{
if (type && strcmp (type, "submenu") == 0)
gtk_menu_item_set_submenu (GTK_MENU_ITEM (buildable),
GTK_WIDGET (child));
else
parent_buildable_iface->add_child (buildable, builder, child, type);
}
/** /**
* gtk_menu_item_set_submenu: * gtk_menu_item_set_submenu:
* @menu_item: a #GtkMenuItem * @menu_item: a #GtkMenuItem

View File

@ -310,6 +310,7 @@ static GQuark quark_gtk_embedded = 0;
static GQuark quark_gtk_window_key_hash = 0; static GQuark quark_gtk_window_key_hash = 0;
static GQuark quark_gtk_window_default_icon_pixmap = 0; static GQuark quark_gtk_window_default_icon_pixmap = 0;
static GQuark quark_gtk_window_icon_info = 0; static GQuark quark_gtk_window_icon_info = 0;
static GQuark quark_gtk_buildable_accels = 0;
static GtkBuildableIface *parent_buildable_iface; static GtkBuildableIface *parent_buildable_iface;
@ -330,6 +331,17 @@ static void gtk_window_buildable_set_buildable_property (GtkBuildable *bu
const GValue *value); const GValue *value);
static void gtk_window_buildable_parser_finished (GtkBuildable *buildable, static void gtk_window_buildable_parser_finished (GtkBuildable *buildable,
GtkBuilder *builder); GtkBuilder *builder);
static gboolean gtk_window_buildable_custom_tag_start (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const gchar *tagname,
GMarkupParser *parser,
gpointer *data);
static void gtk_window_buildable_custom_finished (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const gchar *tagname,
gpointer user_data);
G_DEFINE_TYPE_WITH_CODE (GtkWindow, gtk_window, GTK_TYPE_BIN, G_DEFINE_TYPE_WITH_CODE (GtkWindow, gtk_window, GTK_TYPE_BIN,
@ -415,6 +427,7 @@ gtk_window_class_init (GtkWindowClass *klass)
quark_gtk_window_key_hash = g_quark_from_static_string ("gtk-window-key-hash"); quark_gtk_window_key_hash = g_quark_from_static_string ("gtk-window-key-hash");
quark_gtk_window_default_icon_pixmap = g_quark_from_static_string ("gtk-window-default-icon-pixmap"); quark_gtk_window_default_icon_pixmap = g_quark_from_static_string ("gtk-window-default-icon-pixmap");
quark_gtk_window_icon_info = g_quark_from_static_string ("gtk-window-icon-info"); quark_gtk_window_icon_info = g_quark_from_static_string ("gtk-window-icon-info");
quark_gtk_buildable_accels = g_quark_from_static_string ("gtk-window-buildable-accels");
gobject_class->dispose = gtk_window_dispose; gobject_class->dispose = gtk_window_dispose;
gobject_class->finalize = gtk_window_finalize; gobject_class->finalize = gtk_window_finalize;
@ -1137,7 +1150,8 @@ gtk_window_buildable_interface_init (GtkBuildableIface *iface)
parent_buildable_iface = g_type_interface_peek_parent (iface); parent_buildable_iface = g_type_interface_peek_parent (iface);
iface->set_buildable_property = gtk_window_buildable_set_buildable_property; iface->set_buildable_property = gtk_window_buildable_set_buildable_property;
iface->parser_finished = gtk_window_buildable_parser_finished; iface->parser_finished = gtk_window_buildable_parser_finished;
iface->custom_tag_start = gtk_window_buildable_custom_tag_start;
iface->custom_finished = gtk_window_buildable_custom_finished;
} }
static void static void
@ -1159,11 +1173,118 @@ gtk_window_buildable_parser_finished (GtkBuildable *buildable,
GtkBuilder *builder) GtkBuilder *builder)
{ {
GtkWindowPrivate *priv = GTK_WINDOW_GET_PRIVATE (buildable); GtkWindowPrivate *priv = GTK_WINDOW_GET_PRIVATE (buildable);
GObject *object;
GSList *accels, *l;
if (priv->builder_visible) if (priv->builder_visible)
gtk_widget_show (GTK_WIDGET (buildable)); gtk_widget_show (GTK_WIDGET (buildable));
parent_buildable_iface->parser_finished (buildable, builder); accels = g_object_get_qdata (G_OBJECT (buildable), quark_gtk_buildable_accels);
for (l = accels; l; l = l->next)
{
object = gtk_builder_get_object (builder, l->data);
if (!object)
{
g_warning ("Unknown accel group %s specified in window %s",
(const gchar*)l->data, gtk_buildable_get_name (buildable));
continue;
}
gtk_window_add_accel_group (GTK_WINDOW (buildable),
GTK_ACCEL_GROUP (object));
g_free (l->data);
}
g_object_set_qdata (G_OBJECT (buildable), quark_gtk_buildable_accels, NULL);
parent_buildable_iface->parser_finished (buildable, builder);
}
typedef struct {
GObject *object;
GSList *items;
} GSListSubParserData;
static void
window_start_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **names,
const gchar **values,
gpointer user_data,
GError **error)
{
guint i;
GSListSubParserData *data = (GSListSubParserData*)user_data;
if (strcmp (element_name, "group") == 0)
{
for (i = 0; names[i]; i++)
{
if (strcmp (names[i], "name") == 0)
data->items = g_slist_prepend (data->items, g_strdup (values[i]));
}
}
else if (strcmp (element_name, "accel-groups") == 0)
return;
else
g_warning ("Unsupported tag type for GtkWindow: %s\n",
element_name);
}
static const GMarkupParser window_parser =
{
window_start_element
};
static gboolean
gtk_window_buildable_custom_tag_start (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const gchar *tagname,
GMarkupParser *parser,
gpointer *data)
{
GSListSubParserData *parser_data;
if (parent_buildable_iface->custom_tag_start (buildable, builder, child,
tagname, parser, data))
return TRUE;
if (strcmp (tagname, "accel-groups") == 0)
{
parser_data = g_slice_new0 (GSListSubParserData);
parser_data->items = NULL;
parser_data->object = G_OBJECT (buildable);
*parser = window_parser;
*data = parser_data;
return TRUE;
}
return FALSE;
}
static void
gtk_window_buildable_custom_finished (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const gchar *tagname,
gpointer user_data)
{
GSListSubParserData *data;
parent_buildable_iface->custom_finished (buildable, builder, child,
tagname, user_data);
if (strcmp (tagname, "accel-groups") != 0)
return;
data = (GSListSubParserData*)user_data;
g_object_set_qdata_full (G_OBJECT (buildable), quark_gtk_buildable_accels,
data->items, (GDestroyNotify) g_slist_free);
g_slice_free (GSListSubParserData, data);
} }
/** /**

View File

@ -2282,6 +2282,158 @@ test_add_objects (void)
g_object_unref (builder); g_object_unref (builder);
} }
GtkWidget *
get_parent_menubar (GtkWidget *menuitem)
{
GtkMenuShell *menu_shell = (GtkMenuShell *)menuitem->parent;
GtkWidget *attach = NULL;
g_assert (GTK_IS_MENU_SHELL (menu_shell));
while (menu_shell && !GTK_IS_MENU_BAR (menu_shell))
{
if (GTK_IS_MENU (menu_shell) &&
(attach = gtk_menu_get_attach_widget (GTK_MENU (menu_shell))) != NULL)
menu_shell = (GtkMenuShell *)attach->parent;
else
menu_shell = NULL;
}
return menu_shell ? GTK_WIDGET (menu_shell) : NULL;
}
static void
test_menus (void)
{
gchar *buffer =
"<interface>"
" <object class=\"GtkWindow\" id=\"window1\">"
" <accel-groups>"
" <group name=\"accelgroup1\"/>"
" </accel-groups>"
" <child>"
" <object class=\"GtkVBox\" id=\"vbox1\">"
" <property name=\"visible\">True</property>"
" <property name=\"orientation\">vertical</property>"
" <child>"
" <object class=\"GtkMenuBar\" id=\"menubar1\">"
" <property name=\"visible\">True</property>"
" <child>"
" <object class=\"GtkMenuItem\" id=\"menuitem1\">"
" <property name=\"visible\">True</property>"
" <property name=\"label\" translatable=\"yes\">_File</property>"
" <property name=\"use_underline\">True</property>"
" <child type=\"submenu\">"
" <object class=\"GtkMenu\" id=\"menu1\">"
" <property name=\"visible\">True</property>"
" <child>"
" <object class=\"GtkImageMenuItem\" id=\"imagemenuitem1\">"
" <property name=\"label\">gtk-new</property>"
" <property name=\"visible\">True</property>"
" <property name=\"use_stock\">True</property>"
" <property name=\"accel_group\">accelgroup1</property>"
" </object>"
" </child>"
" </object>"
" </child>"
" </object>"
" </child>"
" </object>"
" </child>"
" </object>"
" </child>"
" </object>"
"<object class=\"GtkAccelGroup\" id=\"accelgroup1\"/>"
"</interface>";
gchar *buffer1 =
"<interface>"
" <object class=\"GtkWindow\" id=\"window1\">"
" <accel-groups>"
" <group name=\"accelgroup1\"/>"
" </accel-groups>"
" <child>"
" <object class=\"GtkVBox\" id=\"vbox1\">"
" <property name=\"visible\">True</property>"
" <property name=\"orientation\">vertical</property>"
" <child>"
" <object class=\"GtkMenuBar\" id=\"menubar1\">"
" <property name=\"visible\">True</property>"
" <child>"
" <object class=\"GtkImageMenuItem\" id=\"imagemenuitem1\">"
" <property name=\"visible\">True</property>"
" <child>"
" <object class=\"GtkLabel\" id=\"custom1\">"
" <property name=\"visible\">True</property>"
" <property name=\"label\">a label</property>"
" </object>"
" </child>"
" </object>"
" </child>"
" </object>"
" </child>"
" </object>"
" </child>"
" </object>"
"<object class=\"GtkAccelGroup\" id=\"accelgroup1\"/>"
"</interface>";
GtkBuilder *builder;
GtkWidget *window, *item;
GtkAccelGroup *accel_group;
GtkWidget *item_accel_label, *sample_accel_label, *sample_menu_item, *custom;
/* Check that the item has the correct accel label string set
*/
builder = builder_new_from_string (buffer, -1, NULL);
window = (GtkWidget *)gtk_builder_get_object (builder, "window1");
item = (GtkWidget *)gtk_builder_get_object (builder, "imagemenuitem1");
accel_group = (GtkAccelGroup *)gtk_builder_get_object (builder, "accelgroup1");
gtk_widget_show_all (window);
sample_menu_item = gtk_image_menu_item_new_from_stock (GTK_STOCK_NEW, accel_group);
g_assert (GTK_BIN (sample_menu_item)->child);
g_assert (GTK_IS_ACCEL_LABEL (GTK_BIN (sample_menu_item)->child));
sample_accel_label = GTK_WIDGET (GTK_BIN (sample_menu_item)->child);
gtk_widget_show (sample_accel_label);
g_assert (GTK_BIN (item)->child);
g_assert (GTK_IS_ACCEL_LABEL (GTK_BIN (item)->child));
item_accel_label = GTK_WIDGET (GTK_BIN (item)->child);
gtk_accel_label_refetch (GTK_ACCEL_LABEL (sample_accel_label));
gtk_accel_label_refetch (GTK_ACCEL_LABEL (item_accel_label));
g_assert (GTK_ACCEL_LABEL (sample_accel_label)->accel_string != NULL);
g_assert (GTK_ACCEL_LABEL (item_accel_label)->accel_string != NULL);
g_assert (strcmp (GTK_ACCEL_LABEL (item_accel_label)->accel_string,
GTK_ACCEL_LABEL (sample_accel_label)->accel_string) == 0);
/* Check the menu heirarchy worked here */
g_assert (get_parent_menubar (item));
gtk_widget_destroy (GTK_WIDGET (window));
gtk_widget_destroy (sample_menu_item);
g_object_unref (builder);
/* Check that we can add alien children to menu items via normal
* GtkContainer apis.
*/
builder = builder_new_from_string (buffer1, -1, NULL);
window = (GtkWidget *)gtk_builder_get_object (builder, "window1");
item = (GtkWidget *)gtk_builder_get_object (builder, "imagemenuitem1");
custom = (GtkWidget *)gtk_builder_get_object (builder, "custom1");
g_assert (custom->parent == item);
gtk_widget_destroy (GTK_WIDGET (window));
g_object_unref (builder);
}
static void static void
test_file (const gchar *filename) test_file (const gchar *filename)
{ {
@ -2367,6 +2519,7 @@ main (int argc, char **argv)
g_test_add_func ("/Builder/PangoAttributes", test_pango_attributes); g_test_add_func ("/Builder/PangoAttributes", test_pango_attributes);
g_test_add_func ("/Builder/Requires", test_requires); g_test_add_func ("/Builder/Requires", test_requires);
g_test_add_func ("/Builder/AddObjects", test_add_objects); g_test_add_func ("/Builder/AddObjects", test_add_objects);
g_test_add_func ("/Builder/Menus", test_menus);
return g_test_run(); return g_test_run();
} }