[libdazzle] menu-manager: improved position solver
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libdazzle] menu-manager: improved position solver
- Date: Sun, 9 Jul 2017 09:55:48 +0000 (UTC)
commit ba5ddb78f145313f8f677527d596fa0e2e918878
Author: Christian Hergert <chergert redhat com>
Date: Sun Jul 9 02:38:32 2017 -0700
menu-manager: improved position solver
Previously we just had a very rudimentary solver for before-
after which couldn't deal with cases where peers were added
and new solutions needed to be found.
This improves on that with a 2-pass solver which attempts to
relocate items when new facts are found about the desired
positioning.
It's not perfect, but it's vastly better and easier to think
about.
src/menus/dzl-menu-manager.c | 220 ++++++++++++++++++++++++++++--------------
src/menus/dzl-menu-manager.h | 3 +
tests/meson.build | 7 ++
tests/test-menu-manager2.c | 68 +++++++++++++
4 files changed, 224 insertions(+), 74 deletions(-)
---
diff --git a/src/menus/dzl-menu-manager.c b/src/menus/dzl-menu-manager.c
index ccddefc..590eb95 100644
--- a/src/menus/dzl-menu-manager.c
+++ b/src/menus/dzl-menu-manager.c
@@ -32,6 +32,8 @@ struct _DzlMenuManager
G_DEFINE_TYPE (DzlMenuManager, dzl_menu_manager, G_TYPE_OBJECT)
+#define str_equal0(a,b) (g_strcmp0(a,b) == 0)
+
#define DZL_MENU_ATTRIBUTE_BEFORE "before"
#define DZL_MENU_ATTRIBUTE_AFTER "after"
#define DZL_MENU_ATTRIBUTE_MERGE_ID "dazzle-merge-id"
@@ -164,99 +166,137 @@ model_copy_attributes_to_item (GMenuModel *model,
g_object_unref (iter);
}
-static gint
-find_position_for_item (GMenuModel *model,
- GMenuItem *item)
+static void
+model_copy_links_to_item (GMenuModel *model,
+ guint position,
+ GMenuItem *item)
{
- g_autofree gchar *label = NULL;
- g_autofree gchar *id = NULL;
- const gchar *after;
- const gchar *before;
- gint n_items;
- gint before_pos = -1;
- gint after_pos = -1;
+ g_autoptr(GMenuLinkIter) link_iter = NULL;
g_assert (G_IS_MENU_MODEL (model));
g_assert (G_IS_MENU_ITEM (item));
- n_items = g_menu_model_get_n_items (model);
+ link_iter = g_menu_model_iterate_item_links (model, position);
- if (!g_menu_item_get_attribute (item, DZL_MENU_ATTRIBUTE_AFTER, "&s", &after))
- after = NULL;
-
- if (!g_menu_item_get_attribute (item, DZL_MENU_ATTRIBUTE_BEFORE, "&s", &before))
- before = NULL;
-
- if (after != NULL)
+ while (g_menu_link_iter_next (link_iter))
{
- after_pos = find_with_attribute_string (model, G_MENU_ATTRIBUTE_LABEL, after);
+ g_autoptr(GMenuModel) link_model = NULL;
+ const gchar *link_name;
- /* Adding an "id" is non-standard, but can be useful as an alternative to
- * translated strings.
- */
- if (after_pos == -1)
- after_pos = find_with_attribute_string (model, "id", after);
+ link_name = g_menu_link_iter_get_name (link_iter);
+ link_model = g_menu_link_iter_get_value (link_iter);
+
+ g_menu_item_set_link (item, link_name, link_model);
}
+}
- if (before != NULL)
- {
- before_pos = find_with_attribute_string (model, G_MENU_ATTRIBUTE_LABEL, before);
+static void
+menu_move_item_to (GMenu *menu,
+ guint position,
+ guint new_position)
+{
+ g_autoptr(GMenuItem) item = NULL;
- if (before_pos == -1)
- before_pos = find_with_attribute_string (model, "id", before);
- }
+ g_assert (G_IS_MENU (menu));
- /*
- * Now that we've positioned ourselves within the range we care about,
- * we need to see if any item within that range has reverse-preferences
- * about their position related to us.
- */
+ item = g_menu_item_new (NULL, NULL);
+ model_copy_attributes_to_item (G_MENU_MODEL (menu), position, item);
+ model_copy_links_to_item (G_MENU_MODEL (menu), position, item);
+
+ g_menu_remove (menu, position);
+ g_menu_insert_item (menu, new_position, item);
+}
- if (!g_menu_item_get_attribute (item, "id", "s", &id))
- id = NULL;
+static void
+dzl_menu_manager_resolve_constraints (GMenu *menu)
+{
+ GMenuModel *model = (GMenuModel *)menu;
+ gint n_items;
- if (!g_menu_item_get_attribute (item, G_MENU_ATTRIBUTE_LABEL, "s", &label))
- label = NULL;
+ g_assert (G_IS_MENU (menu));
+
+ n_items = (gint)g_menu_model_get_n_items (G_MENU_MODEL (menu));
+
+ /*
+ * We start iterating forwards. As we look at each row, we start
+ * again from the end working backwards to see if we need to be
+ * moved after that row.
+ *
+ * This way we know we see the furthest we might need to jump first.
+ */
for (gint i = 0; i < n_items; i++)
{
- g_autofree gchar *item_before = NULL;
- g_autofree gchar *item_after = NULL;
+ g_autofree gchar *i_after = NULL;
+
+ g_menu_model_get_item_attribute (model, i, "after", "s", &i_after);
+ if (i_after == NULL)
+ continue;
- if (g_menu_model_get_item_attribute (model, i, "before", "s", &item_before))
+ /* Work our way backwards from the end back to
+ * our current position (but not overlapping).
+ */
+ for (gint j = n_items - 1; j > i; j--)
{
- /* If this item requires it is before us, we need to ensure we
- * come after this position.
- */
- if ((item_before && id && strcmp (item_before, id) == 0) ||
- (item_before && label && strcmp (item_before, label) == 0))
+ g_autofree gchar *j_id = NULL;
+ g_autofree gchar *j_label = NULL;
+
+ g_menu_model_get_item_attribute (model, j, "id", "s", &j_id);
+ g_menu_model_get_item_attribute (model, j, "label", "s", &j_label);
+
+ if (str_equal0 (i_after, j_id) || str_equal0 (i_after, j_label))
{
- if (after_pos == -1 || after_pos < i)
- after_pos = i;
+ /* You might think we need to place the item *AFTER*
+ * our position "j". But since we remove the row where
+ * "i" currently is, we get the proper location.
+ */
+ menu_move_item_to (menu, i, j);
+ i--;
+ break;
}
}
+ }
- if (g_menu_model_get_item_attribute (model, i, "after", "s", &item_after))
+ /*
+ * Now we need to apply the same thing but for the "before" links
+ * in our model. To do this, we also want to ensure we find the
+ * furthest jump first. So we start from the end and work our way
+ * towards the front and for each of those nodes, start from the
+ * front and work our way back.
+ */
+
+ for (gint i = n_items - 1; i >= 0; i--)
+ {
+ g_autofree gchar *i_before = NULL;
+
+ g_menu_model_get_item_attribute (model, i, "before", "s", &i_before);
+ if (i_before == NULL)
+ continue;
+
+ /* Work our way from the front back towards our current position
+ * that would cause our position to jump.
+ */
+ for (gint j = 0; j < i; j++)
{
- /* If this item requires it is after us, we need to ensure we
- * come before this position.
- */
- if ((item_after && id && strcmp (item_after, id) == 0) ||
- (item_after && label && strcmp (item_after, label) == 0))
+ g_autofree gchar *j_id = NULL;
+ g_autofree gchar *j_label = NULL;
+
+ g_menu_model_get_item_attribute (model, j, "id", "s", &j_id);
+ g_menu_model_get_item_attribute (model, j, "label", "s", &j_label);
+
+ if (str_equal0 (i_before, j_id) || str_equal0 (i_before, j_label))
{
- if (before_pos == -1 || before_pos > i)
- before_pos = i;
+ /*
+ * This item needs to be placed before this item we just found.
+ * Since that is the furthest we could jump, just stop
+ * afterwards.
+ */
+ menu_move_item_to (menu, i, j);
+ i++;
+ break;
}
}
}
-
- if (before_pos >= 0)
- return MAX (0, before_pos - 1);
-
- if (after_pos >= 0)
- return after_pos + 1;
-
- return -1;
}
static void
@@ -264,14 +304,20 @@ dzl_menu_manager_add_to_menu (DzlMenuManager *self,
GMenu *menu,
GMenuItem *item)
{
- gint position;
-
g_assert (DZL_IS_MENU_MANAGER (self));
g_assert (G_IS_MENU (menu));
g_assert (G_IS_MENU_ITEM (item));
- position = find_position_for_item (G_MENU_MODEL (menu), item);
- g_menu_insert_item (menu, position, item);
+ /*
+ * The proplem here is one that could end up being an infinite
+ * loop if we tried to resolve all the position requirements
+ * until no more position changes were required. So instead we
+ * simplify the problem into an append, and two-passes as trying
+ * to fix up the positions.
+ */
+ g_menu_append_item (menu, item);
+ dzl_menu_manager_resolve_constraints (menu);
+ dzl_menu_manager_resolve_constraints (menu);
}
static void
@@ -375,9 +421,9 @@ dzl_menu_manager_merge_model (DzlMenuManager *self,
}
static void
-dzl_menu_manager_merge (DzlMenuManager *self,
- GtkBuilder *builder,
- guint merge_id)
+dzl_menu_manager_merge_builder (DzlMenuManager *self,
+ GtkBuilder *builder,
+ guint merge_id)
{
const GSList *iter;
GSList *list;
@@ -485,12 +531,38 @@ dzl_menu_manager_add_filename (DzlMenuManager *self,
}
merge_id = ++self->last_merge_id;
- dzl_menu_manager_merge (self, builder, merge_id);
+ dzl_menu_manager_merge_builder (self, builder, merge_id);
g_object_unref (builder);
return merge_id;
}
+guint
+dzl_menu_manager_merge (DzlMenuManager *self,
+ const gchar *menu_id,
+ GMenuModel *menu_model)
+{
+ GMenu *menu;
+ guint merge_id;
+
+ g_return_val_if_fail (DZL_IS_MENU_MANAGER (self), 0);
+ g_return_val_if_fail (menu_id != NULL, 0);
+ g_return_val_if_fail (G_IS_MENU_MODEL (menu_model), 0);
+
+ merge_id = ++self->last_merge_id;
+
+ if (!(menu = g_hash_table_lookup (self->models, menu_id)))
+ {
+ GMenu *new_model = g_menu_new ();
+ g_hash_table_insert (self->models, g_strdup (menu_id), new_model);
+ menu = new_model;
+ }
+
+ dzl_menu_manager_merge_model (self, menu, menu_model, merge_id);
+
+ return merge_id;
+}
+
void
dzl_menu_manager_remove (DzlMenuManager *self,
guint merge_id)
@@ -575,7 +647,7 @@ dzl_menu_manager_add_resource (DzlMenuManager *self,
}
merge_id = ++self->last_merge_id;
- dzl_menu_manager_merge (self, builder, merge_id);
+ dzl_menu_manager_merge_builder (self, builder, merge_id);
g_object_unref (builder);
return merge_id;
diff --git a/src/menus/dzl-menu-manager.h b/src/menus/dzl-menu-manager.h
index 247f4ae..3b15440 100644
--- a/src/menus/dzl-menu-manager.h
+++ b/src/menus/dzl-menu-manager.h
@@ -34,6 +34,9 @@ guint dzl_menu_manager_add_filename (DzlMenuManager *self,
guint dzl_menu_manager_add_resource (DzlMenuManager *self,
const gchar *resource,
GError **error);
+guint dzl_menu_manager_merge (DzlMenuManager *self,
+ const gchar *menu_id,
+ GMenuModel *model);
void dzl_menu_manager_remove (DzlMenuManager *self,
guint merge_id);
GMenu *dzl_menu_manager_get_menu_by_id (DzlMenuManager *self,
diff --git a/tests/meson.build b/tests/meson.build
index c2131ec..57344c3 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -32,6 +32,13 @@ test_menu_manager = executable('test-menu-manager', 'test-menu-manager.c',
dependencies: libdazzle_deps + [libdazzle_dep],
)
+test_menu_manager2 = executable('test-menu-manager2', 'test-menu-manager2.c',
+ c_args: test_cflags,
+ link_args: test_link_args,
+ dependencies: libdazzle_deps + [libdazzle_dep],
+)
+test('test-menu-manager2', test_menu_manager2, env: test_env)
+
test_state_machine = executable('test-state-machine', 'test-state-machine.c',
c_args: test_cflags,
link_args: test_link_args,
diff --git a/tests/test-menu-manager2.c b/tests/test-menu-manager2.c
new file mode 100644
index 0000000..9ea220b
--- /dev/null
+++ b/tests/test-menu-manager2.c
@@ -0,0 +1,68 @@
+#include <dazzle.h>
+
+#define assert_item_at_index(menu, i, label) \
+G_STMT_START { \
+ g_autofree gchar *str = NULL; \
+ gboolean r = g_menu_model_get_item_attribute (G_MENU_MODEL (menu), i, "label", "s", &str); \
+ g_assert (r); \
+ g_assert_cmpstr (str, ==, label); \
+} G_STMT_END
+
+static void
+test_menu_manager (void)
+{
+ g_autoptr(DzlMenuManager) manager = dzl_menu_manager_new ();
+ g_autoptr(GMenu) menu1 = g_menu_new ();
+ g_autoptr(GMenu) menu2 = g_menu_new ();
+ g_autoptr(GMenuItem) item1 = g_menu_item_new ("item1", "item1");
+ g_autoptr(GMenuItem) item2 = g_menu_item_new ("item2", "item2");
+ g_autoptr(GMenuItem) item3 = g_menu_item_new ("item3", "item3");
+ g_autoptr(GMenuItem) item4 = g_menu_item_new ("item4", "item4");
+ g_autoptr(GMenuItem) item5 = g_menu_item_new ("item5", "item5");
+ g_autoptr(GMenuItem) item6 = g_menu_item_new ("item6", "item6");
+ GMenu *merged;
+ guint n_items;
+
+ g_menu_item_set_attribute (item1, "after", "s", "item3");
+ g_menu_item_set_attribute (item1, "before", "s", "item5");
+
+ g_menu_append_item (menu1, item1);
+ g_menu_append_item (menu1, item2);
+ g_menu_append_item (menu1, item3);
+
+ dzl_menu_manager_merge (manager, "menu", G_MENU_MODEL (menu1));
+
+ merged = dzl_menu_manager_get_menu_by_id (manager, "menu");
+
+ assert_item_at_index (merged, 0, "item2");
+ assert_item_at_index (merged, 1, "item3");
+ assert_item_at_index (merged, 2, "item1");
+
+ g_menu_item_set_attribute (item4, "after", "s", "item2");
+ g_menu_item_set_attribute (item4, "before", "s", "item3");
+
+ g_menu_append_item (menu2, item4);
+ g_menu_append_item (menu2, item5);
+ g_menu_append_item (menu2, item6);
+
+ dzl_menu_manager_merge (manager, "menu", G_MENU_MODEL (menu2));
+
+ n_items = g_menu_model_get_n_items (G_MENU_MODEL (merged));
+ g_assert_cmpint (n_items, ==, 6);
+
+ assert_item_at_index (merged, 0, "item2");
+ assert_item_at_index (merged, 1, "item4");
+ assert_item_at_index (merged, 2, "item3");
+ assert_item_at_index (merged, 3, "item1");
+ assert_item_at_index (merged, 4, "item5");
+ assert_item_at_index (merged, 5, "item6");
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+ g_test_add_func ("/Dazzle/MenuManager/basic", test_menu_manager);
+ return g_test_run ();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]