# HG changeset patch
# Node ID a53fa17b83b0
# Parent  92438aae2067
diff --git a/libpurple/meson.build b/libpurple/meson.build
--- a/libpurple/meson.build
+++ b/libpurple/meson.build
@@ -57,6 +57,7 @@
 	'purpleimconversation.c',
 	'purplekeyvaluepair.c',
 	'purplemarkup.c',
+	'purplemenu.c',
 	'purplemessage.c',
 	'purplenoopcredentialprovider.c',
 	'purpleoptions.c',
@@ -150,6 +151,7 @@
 	'purpleattachment.h',
 	'purplekeyvaluepair.h',
 	'purplemarkup.h',
+	'purplemenu.h',
 	'purplemessage.h',
 	'purplenoopcredentialprovider.h',
 	'purpleoptions.h',
diff --git a/libpurple/purplemenu.h b/libpurple/purplemenu.h
new file mode 100644
--- /dev/null
+++ b/libpurple/purplemenu.h
@@ -0,0 +1,62 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION)
+# error "only <purple.h> may be included directly"
+#endif
+
+#ifndef PURPLE_MENU_H
+#define PURPLE_MENU_H
+
+#include <glib.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET "dynamic-target"
+
+/**
+ * purple_menu_populate_dynamic_targets:
+ * @menu: The menu instance to modify.
+ * @first_property: The name of the first property of dynamic targets to
+ *                  replace.
+ * @...: The value of the first property, followed optionally by more
+ *       name/value pairs, followed by %NULL.
+ *
+ * Updates @menu by adding a target property when an item with an attribute
+ * named dynamic-property is found.
+ *
+ * The value for the target is set to the matching value from the passed in
+ * parameters.
+ *
+ * For example, if you need to set the target to an account, you would set
+ * the "dynamic-target" attribute of your menu item to "account" and then
+ * call purple_menu_populate_dynamic_targets() with a property pair of
+ * "account" and purple_account_get_id().
+ *
+ * Since: 3.0.0
+ */
+void purple_menu_populate_dynamic_targets(GMenu *menu, const gchar *first_property, ...) G_GNUC_NULL_TERMINATED;
+
+G_END_DECLS
+
+#endif /* PURPLE_MENU_H */
diff --git a/libpurple/purplemenu.c b/libpurple/purplemenu.c
new file mode 100644
--- /dev/null
+++ b/libpurple/purplemenu.c
@@ -0,0 +1,95 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "purplemenu.h"
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+purple_menu_populate_dynamic_targets_internal(GMenu *menu, GHashTable *table) {
+	GMenuModel *model = G_MENU_MODEL(menu);
+	gint n = 0;
+
+	for(n = 0; n < g_menu_model_get_n_items(model); n++) {
+		gchar *property = NULL;
+
+		if(g_menu_model_get_item_attribute(model, n, "dynamic-target", "s",
+		                                   &property))
+		{
+			const gchar *value;
+
+			value = g_hash_table_lookup(table, property);
+			g_free(property);
+
+			if(value != NULL) {
+				GMenuItem *item = NULL;
+
+				item = g_menu_item_new_from_model(model, n);
+				g_menu_item_set_attribute(item, G_MENU_ATTRIBUTE_TARGET, "s",
+				                          value);
+
+				g_menu_remove(menu, n);
+				g_menu_insert_item(menu, n, item);
+            }
+        }
+    }
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+void
+purple_menu_populate_dynamic_targets(GMenu *menu, const gchar *first_property,
+                                     ...)
+{
+	GHashTable *table = NULL;
+	const gchar *property = NULL, *value = NULL;
+	va_list vargs;
+
+	g_return_if_fail(G_IS_MENU(menu));
+	g_return_if_fail(first_property != NULL);
+
+	table = g_hash_table_new(g_str_hash, g_str_equal);
+
+	property = first_property;
+	va_start(vargs, first_property);
+
+	/* Iterate through the vargs adding values when we find one that isn't
+	 * NULL.
+	 */
+	do {
+		value = va_arg(vargs, const gchar *);
+		if(value != NULL) {
+			g_hash_table_insert(table, (gpointer)property, (gpointer)value);
+		}
+
+		/* After adding the value, see if we have another property. */
+		property = va_arg(vargs, const gchar *);
+	} while(property != NULL);
+
+	va_end(vargs);
+
+	purple_menu_populate_dynamic_targets_internal(menu, table);
+
+	g_hash_table_unref(table);
+}
diff --git a/libpurple/tests/meson.build b/libpurple/tests/meson.build
--- a/libpurple/tests/meson.build
+++ b/libpurple/tests/meson.build
@@ -9,6 +9,7 @@
     'image',
     'keyvaluepair',
     'markup',
+    'menu',
     'protocol_action',
     'protocol_xfer',
     'purplepath',
diff --git a/libpurple/tests/test_menu.c b/libpurple/tests/test_menu.c
new file mode 100644
--- /dev/null
+++ b/libpurple/tests/test_menu.c
@@ -0,0 +1,181 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+
+#include <purple.h>
+
+typedef struct {
+	const gchar *dynamic_target;
+	const gchar *target;
+} TestPurpleMenuItemData;
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static TestPurpleMenuItemData *
+test_purple_menu_item_new(const gchar *dynamic_target, const gchar *target) {
+	TestPurpleMenuItemData *data = g_new(TestPurpleMenuItemData, 1);
+
+	data->dynamic_target = dynamic_target;
+	data->target = target;
+
+	return data;
+}
+
+static void
+test_purple_menu_items(GMenu *menu, GList *items) {
+	GList *l = items;
+	gint n = 0;
+
+	while(n < g_menu_model_get_n_items(G_MENU_MODEL(menu)) && l != NULL) {
+		TestPurpleMenuItemData *data = (TestPurpleMenuItemData *)l->data;
+		gchar *actual = NULL;
+
+		/* Check that the dynamic-target value matches our expectations. */
+		g_menu_model_get_item_attribute(G_MENU_MODEL(menu), n,
+		                                PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET,
+		                                "s", &actual);
+
+		g_assert_cmpstr(data->dynamic_target, ==, actual);
+		g_free(actual);
+
+		/* Check that the target value matches our expectations. */
+		g_menu_model_get_item_attribute(G_MENU_MODEL(menu), n,
+		                                G_MENU_ATTRIBUTE_TARGET, "s", &actual);
+
+		g_assert_cmpstr(data->target, ==, actual);
+		g_free(actual);
+
+		/* iterate to the next items */
+		n++;
+		l = l->next;
+	}
+}
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_purple_menu_single_no_dynamic_target(void) {
+	GMenu *menu = g_menu_new();
+	GList *items = NULL;
+
+	g_menu_append(menu, "item1", NULL);
+	items = g_list_append(items, test_purple_menu_item_new(NULL, NULL));
+
+	purple_menu_populate_dynamic_targets(menu, "property", "1", NULL);
+
+	test_purple_menu_items(menu, items);
+
+	g_list_free_full(items, g_free);
+}
+
+static void
+test_purple_menu_single_single_dynamic_target(void) {
+	GMenu *menu = g_menu_new();
+	GMenuItem *item = NULL;
+	GList *items = NULL;
+
+	item = g_menu_item_new("item1", NULL);
+	g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
+	                          "foo");
+	g_menu_append_item(menu, item);
+	items = g_list_append(items, test_purple_menu_item_new("foo", "bar"));
+
+	purple_menu_populate_dynamic_targets(menu, "foo", "bar", NULL);
+
+	test_purple_menu_items(menu, items);
+
+	g_list_free_full(items, g_free);
+}
+
+static void
+test_purple_menu_multiple_multiple_dynamic_target(void) {
+	GMenu *menu = g_menu_new();
+	GMenuItem *item = NULL;
+	GList *items = NULL;
+
+	item = g_menu_item_new("item1", NULL);
+	g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
+	                          "foo");
+	g_menu_append_item(menu, item);
+	items = g_list_append(items, test_purple_menu_item_new("foo", "bar"));
+
+	item = g_menu_item_new("item2", NULL);
+	g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
+	                          "abc");
+	g_menu_append_item(menu, item);
+	items = g_list_append(items, test_purple_menu_item_new("abc", "123"));
+
+	purple_menu_populate_dynamic_targets(menu, "foo", "bar", "abc", "123",
+	                                     NULL);
+
+	test_purple_menu_items(menu, items);
+
+	g_list_free_full(items, g_free);
+}
+
+static void
+test_purple_menu_multiple_mixed(void) {
+	GMenu *menu = g_menu_new();
+	GMenuItem *item = NULL;
+	GList *items = NULL;
+
+	g_menu_append(menu, "item1", NULL);
+	items = g_list_append(items, test_purple_menu_item_new(NULL, NULL));
+
+	item = g_menu_item_new("item2", NULL);
+	g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
+	                          "foo");
+	g_menu_append_item(menu, item);
+	items = g_list_append(items, test_purple_menu_item_new("foo", "bar"));
+
+	item = g_menu_item_new("item3", NULL);
+	g_menu_item_set_attribute(item, PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET, "s",
+	                          "abc");
+	g_menu_append_item(menu, item);
+	items = g_list_append(items, test_purple_menu_item_new("abc", "123"));
+
+	purple_menu_populate_dynamic_targets(menu, "foo", "bar", "abc", "123",
+	                                     NULL);
+
+	test_purple_menu_items(menu, items);
+
+	g_list_free_full(items, g_free);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+gint
+main(gint argc, gchar *argv[]) {
+	g_test_init(&argc, &argv, NULL);
+
+	g_test_add_func("/menu/single/no-dynamic-target",
+	                test_purple_menu_single_no_dynamic_target);
+	g_test_add_func("/menu/single/single_dynamic_target",
+	                test_purple_menu_single_single_dynamic_target);
+
+	g_test_add_func("/menu/multiple/multiple_dynamic_target",
+	                test_purple_menu_multiple_multiple_dynamic_target);
+	g_test_add_func("/menu/multiple/mixed",
+	                test_purple_menu_multiple_mixed);
+
+	return g_test_run();
+}
\ No newline at end of file
