static void
build_button_bar (RBButtonBar *bar)
{
	GtkWidget *waste;

	append_menu (bar, bar->priv->model, FALSE);

	waste = gtk_label_new ("");
	gtk_widget_set_hexpand (waste, TRUE);
	gtk_widget_show (waste);
	gtk_grid_attach (GTK_GRID (bar), waste, bar->priv->position++, 0, 1, 1);
}
static gboolean
append_menu (RBButtonBar *bar, GMenuModel *menu, gboolean need_separator)
{
	int i;
	gulong id;

	id = g_signal_connect (menu, "items-changed", G_CALLBACK (items_changed_cb), bar);
	g_hash_table_insert (bar->priv->handlers, (gpointer)id, g_object_ref (menu));

	for (i = 0; i < g_menu_model_get_n_items (menu); i++) {
		char *label_text;
		char *accel;
		GtkWidget *button;
		GtkWidget *label;
		GMenuModel *submenu;

		/* recurse into sections */
		submenu = g_menu_model_get_item_link (menu, i, G_MENU_LINK_SECTION);
		if (submenu != NULL) {
			need_separator = append_menu (bar, submenu, TRUE);
			continue;
		}

		/* if this item and the previous item are in different sections, add
		 * a separator between them.  this may not be a good idea.
		 */
		if (need_separator) {
			GtkWidget *sep;

			if (bar->priv->position > 0) {
				sep = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
				gtk_widget_show (sep);
				g_object_set (sep, "margin-start", 6, "margin-end", 6, NULL);
				gtk_grid_attach (GTK_GRID (bar), sep, bar->priv->position++, 0, 1, 1);
			}

			need_separator = FALSE;
		}

		button = NULL;

		/* submenus become menu buttons, normal items become buttons */
		submenu = g_menu_model_get_item_link (menu, i, G_MENU_LINK_SUBMENU);

		if (submenu != NULL) {
			button = gtk_menu_button_new ();
			gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (button), submenu);

			g_object_set_data_full (G_OBJECT (button), "rb-menu-model", g_object_ref (submenu), (GDestroyNotify)g_object_unref);
		} else {
			GMenuAttributeIter *iter;
			const char *name;
			GVariant *value;
			char *str;
			guint signal_id;
		
			/* we can't do more than one of action and rb-property-bind
			 * and rb-signal-bind, so just do whichever turns up first
			 * in the iterator
			 */
			iter = g_menu_model_iterate_item_attributes (menu, i);
			while (g_menu_attribute_iter_get_next (iter, &name, &value)) {
				if (g_str_equal (name, "action")) {
					button = gtk_button_new ();
					g_variant_get (value, "s", &str, NULL);
					gtk_actionable_set_action_name (GTK_ACTIONABLE (button), str);
					/* action target too somehow? */
					g_free (str);
					break;
				} else if (g_str_equal (name, "rb-property-bind")) {
					/* property has to be a boolean, can't do inverts, etc. etc. */
					button = gtk_toggle_button_new ();
					g_variant_get (value, "s", &str, NULL);
					g_object_bind_property (bar->priv->target, str,
								button, "active",
								G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
					g_free (str);
					break;
				} else if (g_str_equal (name, "rb-signal-bind")) {
					button = gtk_button_new ();
					g_variant_get (value, "s", &str, NULL);
					signal_id = g_signal_lookup (str, G_OBJECT_TYPE (bar->priv->target));
					if (signal_id != 0) {
						g_object_set_data (G_OBJECT (button), "rb-signal-bind-id", GUINT_TO_POINTER (signal_id));
						g_signal_connect (button, "clicked", G_CALLBACK (signal_button_clicked_cb), bar);
					}
					g_free (str);
					break;
				}
			}

			g_object_unref (iter);
		}

		if (button == NULL) {
			g_warning ("no idea what's going on here");
			continue;
		}

		gtk_widget_set_hexpand (button, FALSE);
		gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);

		label_text = NULL;
		g_menu_model_get_item_attribute (menu, i, "label", "s", &label_text);
		label = gtk_label_new (g_dgettext (NULL, label_text));
		g_object_set (label, "margin-left", 6, "margin-right", 6, NULL);
		gtk_container_add (GTK_CONTAINER (button), label);

		if (g_menu_model_get_item_attribute (menu, i, "accel", "s", &accel)) {
			g_object_set_data_full (G_OBJECT (button), "rb-accel", accel, (GDestroyNotify) g_free);
		}

		gtk_widget_show_all (button);
		gtk_size_group_add_widget (bar->priv->size_group, button);
		gtk_grid_attach (GTK_GRID (bar), button, bar->priv->position++, 0, 1, 1);

		g_free (label_text);
	}

	return need_separator;
}
/* Build a menu of the given bookmarks categorised by the given topics.
 * Shows categorisation using subdivisions, submenus, or a mix of both. */
static void
append_menu (GString *string, const GPtrArray *topics, const GPtrArray *bookmarks, guint flags)
{
	GPtrArray *uncovered;
	guint i, j;
	
	gboolean use_subdivis = flags & BUILD_SUBDIVIS;
	gboolean use_submenus = flags & BUILD_SUBMENUS;        

	if (use_subdivis || use_submenus)
	{
		GPtrArray *subset, *covering, *subdivisions, *submenus, *unused;
		GArray *sizes = 0;
		EphyNode *topic;
		gint size, total;
		gboolean separate = FALSE;
		char name[EPHY_TOPIC_ACTION_NAME_BUFFER_SIZE];
		
		/* Get the subtopics, uncovered bookmarks, and subtopic sizes. */
		sizes = g_array_sized_new (FALSE, FALSE, sizeof(int), topics->len);
		uncovered = g_ptr_array_sized_new (bookmarks->len);
		covering = ephy_nodes_get_covering (topics, bookmarks, 0, uncovered, sizes);

		/* Preallocate arrays for submenus, subdivisions, and bookmark subsets. */
		subdivisions = g_ptr_array_sized_new (topics->len);
		submenus = g_ptr_array_sized_new (topics->len);
		subset = g_ptr_array_sized_new (bookmarks->len);
		unused = g_ptr_array_sized_new (bookmarks->len);

		/* Get the total number of items in the menu. */
		total = uncovered->len;
		for (i = 0; i < covering->len; i++)
		  total += g_array_index (sizes, int, i);
		
		/* Seperate covering into list of submenus and subdivisions */
		for (i = 0; i < covering->len; i++)
		{
			topic = g_ptr_array_index (covering, i);
			size = g_array_index (sizes, int, i);
			
			if (!use_submenus || (use_subdivis && (size < MIN_MENU_SIZE || total < MAX_MENU_SIZE)))
			{
				g_ptr_array_add (subdivisions, topic);
			}
			else
			{
				g_ptr_array_add (submenus, topic);
				total = total - size + 1;
			}
		}
		
		/* Sort the list of submenus and subdivisions. */
		g_ptr_array_sort (submenus, ephy_bookmarks_compare_topic_pointers);
		g_ptr_array_sort (subdivisions, ephy_bookmarks_compare_topic_pointers);
		
		if (flags & BUILD_CHILD_SUBDIVIS) flags |= BUILD_SUBDIVIS;
		if (flags & BUILD_CHILD_SUBMENUS) flags |= BUILD_SUBMENUS;
		
		/* Create each of the submenus. */
		for (i = 0; i < submenus->len; i++)
		{
			topic = g_ptr_array_index (submenus, i);
			ephy_nodes_get_covered (topic, bookmarks, subset);

			EPHY_TOPIC_ACTION_NAME_PRINTF (name, topic);
				
			g_string_append_printf (string, "<menu action=\"%s\">",
						name);
			append_menu (string, topics, subset, flags);
			g_string_append (string, "</menu>");
			separate = TRUE;
		}
		
		/* Build a list of bookmarks which don't appear in any subdivision yet. */
		for (i = 0; i < bookmarks->len; i++)
		{
			g_ptr_array_add (unused, g_ptr_array_index (bookmarks, i));
		}

		/* Create each of the subdivisions. */
		for (i = 0; i < subdivisions->len; i++)
		{
			topic = g_ptr_array_index (subdivisions, i);
			ephy_nodes_get_covered (topic, unused, subset);
			g_ptr_array_sort (subset, ephy_bookmarks_compare_bookmark_pointers);
			
			if (separate) g_string_append (string, "<separator/>");
			append_bookmarks (string, subset);
			separate = TRUE;
			
			/* Record that each bookmark has been added. */
			for (j = 0; j < subset->len; j++)
			{
				g_ptr_array_remove_fast (unused, g_ptr_array_index (subset, j));
			}
		}

		g_array_free (sizes, TRUE);
		g_ptr_array_free (covering, TRUE);
		g_ptr_array_free (subdivisions, TRUE);
		g_ptr_array_free (submenus, TRUE);
		g_ptr_array_free (subset, TRUE);
		g_ptr_array_free (unused, TRUE);
		
		if (separate && uncovered->len) g_string_append (string, "<separator/>");
	}