[PATCH]: Automatic hierarchical bookmark menu



Hi all,

Recently I presented an idea for automatically generating a hierarchy
for the bookmark menu based on the information the user has already
given to us (multiple topic selections per bookmark).

Attached is a patch again ephy-bookmarks-menu.c in Epiphany 1.2.5. The
following links show screenshots of my bookmark menu before and after
applying this patch:

  Before: http://www.dsl.uow.edu.au/~harvey/e/Reference1.png
   After: http://www.dsl.uow.edu.au/~harvey/e/Reference2.png

  Before: http://www.dsl.uow.edu.au/~harvey/e/Research1.png
   After: http://www.dsl.uow.edu.au/~harvey/e/Research2.png

I'll pick on Reference2.png and explain how the 'Reference' menu (shown)
was derived. The complete list of 'Reference' bookmarks were placed into
a list. Topics which could be used to further divide my collection of
Reference bookmarks were selected. 'Programming' was one topic seected,
as it is the largest topic which includes bookmarks within the list.
'ILOG' and 'Applications' were also selected as a way to divide the
'Reference' bookmarks further. The result is what is shown - those
bookmarks which are in the Programming topic are placed a submenu, and
'ILOG' and 'Applications' bookmarks are separated by dividers. The
remaining bookmarks (which don't have any other topic) are left at the
bottom.

There are some interesting results elsewhere. For example, the following
shows how Epiphany separates my 'News' links into those which are in
'Most Visited', and those which aren't. Nice.
  http://www.dsl.uow.edu.au/~harvey/e/News.png

Also, my 'University' links are separated according to whether the links
relate to 'People' or to other things.
  http://www.dsl.uow.edu.au/~harvey/e/University.png

Overall, I think the result is workable and useful. There are still
things to do though. My system for creating the hierarchy relies on a
priority order for topics. At the moment I use a simple heuristic - the
topics with the most bookmarks are the most important. This isn't always
appropriate. I think I'd need help to modify the UI to make the priority
of topics user configurable.

Anyway, this is my first time writing GTK+ code (or for that matter,
plain C code, as I usually write in C++). Took a while to get up to
speed, but I felt an automatically-generated bookmark menu was
interesting enough.

Any comments/criticisms are welcome. There are probably minor memory
leaks.

Thanks.

-- 
Peter Harvey <pah06 uow edu au>
SITACS, University of Wollongong
--- epiphany-1.2.5/src/bookmarks/ephy-bookmarks-menu.c	2004-04-14 06:36:39.000000000 +1000
+++ ../epiphany-1.2.5/src/bookmarks/ephy-bookmarks-menu.c	2004-05-13 22:15:49.000000000 +1000
@@ -150,6 +150,28 @@
 }
 
 static int
+sort_topics_by_priority (gconstpointer a, gconstpointer b)
+{
+	EphyNode *node_a = (EphyNode *)a;
+	EphyNode *node_b = (EphyNode *)b;
+
+        gint count_a = ephy_node_get_n_children (node_a);
+        gint count_b = ephy_node_get_n_children (node_b);
+        if (count_a < count_b)
+        {
+                return 1;
+        }
+        else if (count_a > count_b)
+        {
+                return -1;
+        }
+        else
+        {
+                return 0;
+        }
+}
+
+static int
 sort_bookmarks (gconstpointer a, gconstpointer b)
 {
 	EphyNode *node_a = (EphyNode *)a;
@@ -182,146 +204,331 @@
 	return retval;
 }
 
-static void
-add_bookmarks_menu (EphyBookmarksMenu *menu, EphyNode *node, const char *path)
+static gint
+get_n_with_topic(EphyBookmarks *bookmarks, EphyNode *topic, GList *bookmark_list)
 {
-	GPtrArray *children;
-	EphyBookmarksMenuPrivate *p = menu->priv;
-	GList *node_list = NULL, *l;
-	int i;
-
-	children = ephy_node_get_children (node);
-	for (i = 0; i < children->len; ++i)
-	{
-		node_list = g_list_prepend (node_list,
-					    g_ptr_array_index (children, i));
-	}
-
-	if (node_list != NULL)
-	{
-		node_list = g_list_sort (node_list, (GCompareFunc)sort_bookmarks);
-		for (l = node_list; l != NULL; l = l->next)
-		{
-			GtkAction *action;
-			EphyNode *child;
-			long id;
-			char verb[30], name[30], accel_path[60];
+        gint count = 0;
+        GList *l;
+        
+        for (l = bookmark_list; l != NULL; l = l->next)
+        {
+                if(ephy_bookmarks_has_keyword (bookmarks, topic, l->data))
+                {
+                        count++;
+                }
+        }
+        return count;
+}
 
-			child = l->data;
-			id = ephy_node_get_id (child);
-			g_snprintf (verb, sizeof (verb), 
-				    "OpenBmk%ld", ephy_node_get_id (child));
-			g_snprintf (name, sizeof (name), "%sName", verb);
-			g_snprintf (accel_path, sizeof (accel_path),
-				    "<Actions>/BookmarksActions/%s", verb);
+static void
+ categorise_bookmarks_menu (EphyBookmarks *bookmarks, GList *topic_list, GList *bookmark_list,
+                            GList **submenu_list, GList **subdivision_list, GList **unsorted_list)
+{
+        GList *l, *m, *n;
+        GList *submenu_size_list = NULL;
+        gint bookmark_list_length;
+        gint size, menu_length = 0;
+        
+        bookmark_list_length = g_list_length (bookmark_list);
+        *unsorted_list = g_list_copy (bookmark_list);
+        
+        /* Determine the lists of submenus and subdivisions we will create. */
+        for (m = topic_list; m != NULL; m = m->next)
+        {
+                /* Check that this is a suitable topic to categorise by. */
+                size = get_n_with_topic (bookmarks, m->data, bookmark_list);
+                if (size == bookmark_list_length ||
+                    get_n_with_topic (bookmarks, m->data, *unsorted_list) == 0)
+                {
+                        continue;
+                }
+                
+                /* Add this topic to the submenu or subdivision list. */
+                if((size >= 4 || subdivision_list == NULL) && submenu_list != NULL)
+                {
+                        *submenu_list = g_list_prepend (*submenu_list, m->data);        
+                        submenu_size_list = g_list_prepend (submenu_size_list, GINT_TO_POINTER (size));
+                        menu_length++;
+                }
+                else if(size >= 2 && subdivision_list != NULL)
+                {
+                        *subdivision_list = g_list_prepend(*subdivision_list, m->data);
+                        menu_length += size;
+                }
+                else
+                {
+                        continue;
+                }
+                
+                /* Remove bookmarks from the unsorted list */
+                l = *unsorted_list;
+                while (l != NULL)
+                {
+                        n = l->next;
+                        if (ephy_bookmarks_has_keyword (bookmarks, m->data, l->data))
+                        {
+                                *unsorted_list = g_list_remove_link(*unsorted_list, l);
+                        }
+                        l = n;
+                }
+        }
+        
+        /* Any submenus which would be too small (less than 7), try to make into
+         * divisions instead, but don't increase the size of this menu beyond 14. */
+        if (submenu_list != NULL && subdivision_list != NULL)
+        {
+                for(size = 2; size <= 7; size++)
+                {
+                        /* Move submenus to subdivisions as needed. */
+                        for (l = *submenu_list, m = submenu_size_list;
+                             menu_length+size <= 14 && l != NULL;
+                             l = l->next, m = m->next)
+                        {
+                                if (GPOINTER_TO_INT (m->data) == size)
+                                {
+                                        *subdivision_list = g_list_prepend (*subdivision_list, l->data);
+                                        menu_length += size-1;
+                                        m->data = 0;
+                                }
+                        }
+                }
+                
+                /* Remove any submenus which are now subdivisions. */
+                l = *submenu_list;
+                for(m = submenu_size_list; m != NULL; m = m->next)
+                {
+                        n = l->next;
+                        if (m->data == 0)
+                        {
+                                *submenu_list = g_list_delete_link (*submenu_list, l);
+                        }
+                        l = n;
+                }
+        }
+        g_list_free (submenu_size_list);
+        
+        if (submenu_list != NULL)
+        {
+                *submenu_list = g_list_sort (*submenu_list, (GCompareFunc)sort_topics);
+        }
+        
+        if (subdivision_list != NULL)
+        {
+                *subdivision_list = g_list_sort (*subdivision_list, (GCompareFunc)sort_topics_by_priority);
+        }
+}
 
-			action = ephy_bookmark_action_new (verb, id);
-			gtk_action_set_accel_path (action, accel_path);
-			gtk_action_group_add_action (p->action_group, action);
-			g_object_unref (action);
-			g_signal_connect (action, "go_location",
-					  G_CALLBACK (go_location_cb), p->window);
+/* Add to menu path 'path' all the bookmarks in 'bookmark_list' which have keyword 'topic' */
+static void
+add_bookmarks_to_menu (EphyBookmarksMenu *menu, EphyNode *topic,
+                       GList *bookmark_list, const char *path)
+{
+        EphyBookmarksMenuPrivate *p = menu->priv;
+        GtkAction *action;
+        long id;
+        gchar verb[30], accel_path[60], topic_id[16] = "-";
+        gchar *name;
+        GList *l;
+        
+        if (topic != NULL)
+        {
+                g_snprintf (topic_id, sizeof (topic_id), "%ld", ephy_node_get_id (topic));
+        }
+        
+        gtk_ui_manager_add_ui (p->merge, p->ui_id, path,
+                               topic_id, NULL,
+                               GTK_UI_MANAGER_SEPARATOR, FALSE);
+        
+        for (l = bookmark_list; l != NULL; l = l->next)
+        {
+                EphyNode *bookmark = l->data;
+                
+                if (topic == NULL || ephy_bookmarks_has_keyword (p->bookmarks, topic, l->data))
+                {
+                        id = ephy_node_get_id (bookmark);
+                        g_snprintf (verb, sizeof (verb),
+                                    "OpenBmk%ld", ephy_node_get_id (bookmark));
+                        g_snprintf (accel_path, sizeof (accel_path),
+                                    "<Actions>/BookmarksActions/%s", verb);
+                        name = g_strconcat(verb, "of", topic_id, NULL);
+                        
+                        action = ephy_bookmark_action_new (verb, id);
+                        gtk_action_set_accel_path (action, accel_path);
+                        gtk_action_group_add_action (p->action_group, action);
+                        g_object_unref (action);
+                        g_signal_connect (action, "go_location",
+                                          G_CALLBACK (go_location_cb), p->window);
+                        
+                        gtk_ui_manager_add_ui (p->merge, p->ui_id,
+                                               path, name, verb,
+                                               GTK_UI_MANAGER_MENUITEM, FALSE);
+                        
+                        g_free(name);
+                }
+        }
+}
 
-			gtk_ui_manager_add_ui (p->merge, p->ui_id, path,
-					       name, verb,
-					       GTK_UI_MANAGER_MENUITEM, FALSE);
-		}
-		g_list_free (node_list);
-	}
+/* Add to menu path 'base_path' a submenu with prefix 'base_verb' and title
+ * derived from 'topic'. Place into it the bookmarks in 'bookmark_list',
+ * categorised according to 'topic_list'. */
+static void
+add_bookmarks_menu (EphyBookmarksMenu *menu, EphyNode *topic, GList *topic_list,
+                    GList *bookmark_list, const char *base_path, const char *base_verb)
+{
+        EphyBookmarksMenuPrivate *p = menu->priv;
+        GList *l, *my_bookmark_list = NULL, *my_unsorted_list = NULL;
+        GList *my_submenu_list = NULL, *my_subdivision_list = NULL;
+        gchar *my_path, *my_verb;
+        
+        if (topic != NULL)
+        {
+                GtkAction *action;
+                gchar id[60], accel_path[60];
+                const gchar *tmp;
+                gchar *title;
+                
+                g_snprintf (id, sizeof (id), "%ld", ephy_node_get_id (topic));
+                
+                /* Construct our verb and path */
+                my_verb = g_strconcat(base_verb, "-", id, NULL);
+                my_path = g_strconcat(base_path, "/", id, NULL);
+                
+                /* Create a submenu entry for this topic */
+                tmp = ephy_node_get_property_string (topic, EPHY_NODE_KEYWORD_PROP_NAME);
+                title = ephy_string_double_underscores (tmp);
+                
+                g_snprintf (accel_path, sizeof (accel_path),
+                            "<Actions>/BookmarksActions/%s", my_verb);
+                
+                action = g_object_new (GTK_TYPE_ACTION,
+                                       "name", my_verb,
+                                       "label", title,
+                                       "hide_if_empty", TRUE,
+                                       NULL);
+                gtk_action_set_accel_path (action, accel_path);
+                gtk_action_group_add_action (p->action_group, action);
+                g_object_unref (action);
+                
+                g_free (title);
+                
+                gtk_ui_manager_add_ui (p->merge, p->ui_id,
+                                       base_path,
+                                       id, my_verb,
+                                       GTK_UI_MANAGER_MENU, FALSE);
+                
+                /* Construct my list of bookmarks */
+                for (l = bookmark_list; l != NULL; l = l->next)
+                {
+                        if (ephy_bookmarks_has_keyword (p->bookmarks, topic, l->data))
+                        {
+                                my_bookmark_list = g_list_prepend (my_bookmark_list, l->data);
+                        }
+                }
+                
+                categorise_bookmarks_menu (p->bookmarks, topic_list, my_bookmark_list,
+                                           &my_submenu_list, &my_subdivision_list, &my_unsorted_list);
+        }
+        else
+        {
+                my_verb = g_strdup (base_verb);
+                my_path = g_strdup (base_path);
+                
+                my_bookmark_list = g_list_copy (bookmark_list);
+                
+                categorise_bookmarks_menu (p->bookmarks, topic_list, my_bookmark_list,
+                                           &my_submenu_list, NULL, &my_unsorted_list);
+        }
+        
+        my_bookmark_list = g_list_sort (my_bookmark_list, (GCompareFunc)sort_bookmarks);
+        my_unsorted_list = g_list_sort (my_unsorted_list, (GCompareFunc)sort_bookmarks);
+        
+        /* Add any submenus. */
+        for (l = my_submenu_list; l != NULL; l = l->next)
+        {
+                add_bookmarks_menu(menu, l->data, topic_list, my_bookmark_list, my_path, my_verb);
+        }
+        
+        /* Add any subdivisions. */
+        for (l = my_subdivision_list; l != NULL; l = l->next)
+        {
+                add_bookmarks_to_menu(menu, l->data, my_bookmark_list, my_path);
+        }
+        
+        /* Add any leftover bookmarks. */
+        add_bookmarks_to_menu(menu, NULL, my_unsorted_list, my_path);
+        
+        g_free (my_verb);
+        g_free (my_path);
+        g_list_free (my_bookmark_list);
+        g_list_free (my_submenu_list);
+        g_list_free (my_subdivision_list);
+        g_list_free (my_unsorted_list);
 }
 
 static void
 ephy_bookmarks_menu_rebuild (EphyBookmarksMenu *menu)
 {
-	EphyBookmarksMenuPrivate *p = menu->priv;
-	gint i;
-	EphyNode *topics;
-	EphyNode *not_categorized;
-	GPtrArray *children;
-	GList *node_list = NULL, *l;
-
-	LOG ("Rebuilding bookmarks menu")
-
-	ephy_bookmarks_menu_clean (menu);
-
-	START_PROFILER ("Rebuilding bookmarks menu")
-
-	topics = ephy_bookmarks_get_keywords (p->bookmarks);
-	not_categorized = ephy_bookmarks_get_not_categorized (p->bookmarks);
-	children = ephy_node_get_children (topics);
-
-	p->ui_id = gtk_ui_manager_new_merge_id (p->merge);
-
-	p->action_group = gtk_action_group_new ("BookmarksActions");
-	gtk_ui_manager_insert_action_group (p->merge, p->action_group, 0);
-
-	for (i = 0; i < children->len; ++i)
-	{
-		EphyNode *kid;
-		EphyNodePriority priority;
-
-		kid = g_ptr_array_index (children, i);
-
-		priority = ephy_node_get_property_int
-			(kid, EPHY_NODE_KEYWORD_PROP_PRIORITY);
-
-		if (priority == EPHY_NODE_NORMAL_PRIORITY)
-		{
-			node_list = g_list_prepend (node_list, kid);
-		}
-	}
-
-	node_list = g_list_sort (node_list, (GCompareFunc)sort_topics);
-
-	for (l = node_list; l != NULL; l = l->next)
-	{
-		char verb[30], name[30], path[60], accel_path[60];
-		const char *tmp;
-		char *title;
-		EphyNode *child;
-		GtkAction *action;
-
-		child = l->data;
-
-		tmp = ephy_node_get_property_string (child, EPHY_NODE_KEYWORD_PROP_NAME);
-		title = ephy_string_double_underscores (tmp);
-		
-		g_snprintf (verb, sizeof (verb),
-			    "OpenTopic%ld", ephy_node_get_id (child));
-		g_snprintf (name, sizeof (name), "%sName", verb);
-		g_snprintf (path, sizeof (path),
-			    BOOKMARKS_MENU_PATH "/%s", name);
-		g_snprintf (accel_path, sizeof (accel_path),
-			    "<Actions>/BookmarksActions/%s", verb);
-
-		action = g_object_new (GTK_TYPE_ACTION,
-				       "name", verb,
-				       "label", title,
-				       "hide_if_empty", FALSE,
-				       NULL);
-		gtk_action_set_accel_path (action, accel_path);
-		gtk_action_group_add_action (p->action_group, action);
-		g_object_unref (action);
-		g_free (title);
-
-		gtk_ui_manager_add_ui (p->merge, p->ui_id,
-				       BOOKMARKS_MENU_PATH,
-				       name, verb,
-				       GTK_UI_MANAGER_MENU, FALSE);
-
-		add_bookmarks_menu (menu, child, path);
-	}
-
-	if (ephy_node_get_n_children (not_categorized) > 0)
-	{
-		add_bookmarks_menu (menu, not_categorized, BOOKMARKS_MENU_PATH);
-	}
-
-	g_list_free (node_list);
-
-	STOP_PROFILER ("Rebuilding bookmarks menu")
+        EphyBookmarksMenuPrivate *p = menu->priv;
+        gint i;
+        EphyNode *topics;
+        EphyNode *bookmarks;
+        GList *topic_list = NULL;
+        GList *bookmark_list = NULL;
+        GPtrArray *children;
+        
+        LOG ("Rebuilding bookmarks menu")
+          
+        ephy_bookmarks_menu_clean (menu);
+        
+        START_PROFILER ("Rebuilding bookmarks menu")
+          
+        p->ui_id = gtk_ui_manager_new_merge_id (p->merge);
+        
+        p->action_group = gtk_action_group_new ("BookmarksActions");
+        gtk_ui_manager_insert_action_group (p->merge, p->action_group, 0);
+        
+        bookmarks = ephy_bookmarks_get_bookmarks (p->bookmarks);
+        children = ephy_node_get_children (bookmarks);
+        for (i = 0; i < children->len; ++i)
+        {
+                bookmark_list = g_list_prepend (bookmark_list, g_ptr_array_index (children, i));
+        }
+        
+        topics = ephy_bookmarks_get_keywords (p->bookmarks);
+        children = ephy_node_get_children (topics);    
+        for (i = 0; i < children->len; ++i)
+        {
+                children = ephy_node_get_children (topics);
+                
+                p->ui_id = gtk_ui_manager_new_merge_id (p->merge);
+                
+                p->action_group = gtk_action_group_new ("BookmarksActions");
+                gtk_ui_manager_insert_action_group (p->merge, p->action_group, 0);
+                
+                for (i = 0; i < children->len; ++i)
+                {
+                        EphyNode *kid;
+                        EphyNodePriority priority;
+                        
+                        kid = g_ptr_array_index (children, i);
+                        
+                        priority = ephy_node_get_property_int
+                          (kid, EPHY_NODE_KEYWORD_PROP_PRIORITY);
+                        
+                        if (priority == EPHY_NODE_NORMAL_PRIORITY)
+                        {
+                                topic_list = g_list_prepend (topic_list, kid);
+                        }
+                }
+        }
+        topic_list = g_list_sort (topic_list, (GCompareFunc)sort_topics_by_priority);
+        topic_list = g_list_append(topic_list, ephy_bookmarks_get_favorites (p->bookmarks));
+    
+        add_bookmarks_menu (menu, NULL, topic_list, bookmark_list,
+                            BOOKMARKS_MENU_PATH, "OpenTopic");
 }
 
+
 static void
 ephy_bookmarks_menu_set_window (EphyBookmarksMenu *menu, EphyWindow *window)
 {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]