[Xfce4-commits] [panel-plugins/xfce4-whiskermenu-plugin] 56/473: More improvements to search algorithm.

noreply at xfce.org noreply at xfce.org
Mon Feb 16 23:53:46 CET 2015


This is an automated email from the git hooks/post-receive script.

gottcode pushed a commit to branch master
in repository panel-plugins/xfce4-whiskermenu-plugin.

commit 8e469ad405b584842e1b4d518847d134ea494fc0
Author: Graeme Gott <graeme at gottcode.org>
Date:   Tue Jul 2 15:04:03 2013 -0400

    More improvements to search algorithm.
---
 src/CMakeLists.txt  |    1 +
 src/launcher.cpp    |  144 +++++++++--------------------------------
 src/launcher.hpp    |   11 ++--
 src/query.cpp       |  177 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/query.hpp       |   54 ++++++++++++++++
 src/search_page.cpp |   23 ++-----
 src/search_page.hpp |    3 +-
 7 files changed, 278 insertions(+), 135 deletions(-)

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6c45e4f..1951f5f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -68,6 +68,7 @@ add_library(whiskermenu SHARED
     menu.cpp
     page.cpp
     panel_plugin.cpp
+    query.cpp
     recent_page.cpp
     register_plugin.c
     resizer_widget.cpp
diff --git a/src/launcher.cpp b/src/launcher.cpp
index 6e56194..0483a04 100644
--- a/src/launcher.cpp
+++ b/src/launcher.cpp
@@ -16,7 +16,7 @@
 
 #include "launcher.hpp"
 
-#include <vector>
+#include "query.hpp"
 
 extern "C"
 {
@@ -142,11 +142,25 @@ Launcher::Launcher(GarconMenuItem* item) :
 			details = generic_name;
 		}
 		m_text = g_markup_printf_escaped("<b>%s</b>\n%s", display_name, details);
+
+		// Create search text
+		gchar* normalized = g_utf8_normalize(details, -1, G_NORMALIZE_DEFAULT);
+		gchar* utf8 = g_utf8_casefold(normalized, -1);
+		m_search_comment = utf8;
+		g_free(utf8);
+		g_free(normalized);
 	}
 	else
 	{
 		m_text = g_markup_printf_escaped("%s", display_name);
 	}
+
+	// Create search text
+	gchar* normalized = g_utf8_normalize(display_name, -1, G_NORMALIZE_DEFAULT);
+	gchar* utf8 = g_utf8_casefold(normalized, -1);
+	m_search_name = utf8;
+	g_free(utf8);
+	g_free(normalized);
 }
 
 //-----------------------------------------------------------------------------
@@ -160,9 +174,9 @@ Launcher::~Launcher()
 
 //-----------------------------------------------------------------------------
 
-unsigned int Launcher::get_search_results(const std::string& filter_string) const
+unsigned int Launcher::get_search_results(const Query &query) const
 {
-	std::map<std::string, unsigned int>::const_iterator i = m_searches.find(filter_string);
+	std::map<std::string, unsigned int>::const_iterator i = m_searches.find(query.query());
 	return (i != m_searches.end()) ? i->second : UINT_MAX;
 }
 
@@ -251,85 +265,34 @@ void Launcher::run(GdkScreen* screen) const
 
 //-----------------------------------------------------------------------------
 
-void Launcher::search(const std::string& filter_string)
+void Launcher::search(const Query& query)
 {
-	// Check if search has been done before
-	std::map<std::string, unsigned int>::const_iterator i = m_searches.find(filter_string);
-	if (i != m_searches.end())
+	if (query.empty())
 	{
 		return;
 	}
 
-	// Check if search will fail because a shorter version has failed before
-
-	// Perform search
-	unsigned int index = UINT_MAX;
-	std::vector<unsigned int> spaces;
-
-	const gchar* filter_string_c = filter_string.c_str();
-	const gchar* filter_string_ind = filter_string_c;
-	size_t filter_len = filter_string.length();
-
-	const gchar* search_text = get_search_text();
-	size_t len = strlen(search_text);
-	for (const gchar* pos = search_text; *pos; pos = g_utf8_next_char(pos))
+	// Check if search has been done or if a shorter version has failed before
+	for (std::map<std::string, unsigned int>::const_iterator i = m_searches.begin(), end = m_searches.end(); i != end; ++i)
 	{
-		gunichar c = g_utf8_get_char(pos);
-		len -= (pos - search_text);
-		if ((len >= filter_len) && (memcmp(pos, filter_string_c, filter_len) == 0))
-		{
-			index = pos - search_text;
-			break;
-		}
-		else if (c == g_utf8_get_char(filter_string_ind))
-		{
-			filter_string_ind = g_utf8_next_char(filter_string_ind);
-		}
-		else if (g_unichar_isspace(c))
-		{
-			spaces.push_back(pos - search_text);
-		}
-		else if ((c == '\n') && (*filter_string_ind != 0))
+		if ( ((i->second == UINT_MAX) && (query.query().find(i->first) == 0))
+				|| (i->first == query.query()) )
 		{
-			filter_string_ind = filter_string_c;
+			return;
 		}
 	}
 
-	// Check if search text starts with filter string
-	if (index == 0)
+	unsigned int match = query.match(m_search_name);
+	if ((match == UINT_MAX) && f_show_description)
 	{
-		// Do nothing
-	}
-	// Check if search text contains filter string
-	else if (index != UINT_MAX)
-	{
-		// Check if a word in search text starts with filter string
-		unsigned int space_index = 0;
-		for (std::vector<unsigned int>::const_reverse_iterator i = spaces.rbegin(), end = spaces.rend(); i != end; ++i)
-		{
-			if (*i < index)
-			{
-				space_index = *i;
-				break;
-			}
-		}
-		unsigned int delta = index - space_index;
-		if (delta == 1)
+		match = query.match(m_search_comment);
+		if (match != UINT_MAX)
 		{
-			index += 0x10000000;
-		}
-		else
-		{
-			index += 0x20000000 + delta;
+			// Sort matches in comments after matches in names
+			match += 10;
 		}
 	}
-	// Check if search text contains characters of string
-	else if (*filter_string_ind == 0)
-	{
-		index = UINT_MAX - 1;
-	}
-
-	m_searches.insert(std::make_pair(filter_string, index));
+	m_searches.insert(std::make_pair(query.query(), match));
 }
 
 //-----------------------------------------------------------------------------
@@ -361,48 +324,3 @@ void Launcher::set_show_description(bool show)
 }
 
 //-----------------------------------------------------------------------------
-
-const gchar* Launcher::get_search_text()
-{
-	if (!m_search_text.empty())
-	{
-		return m_search_text.c_str();
-	}
-
-	// Combine name, comment, and generic name into single casefolded string
-	const gchar* name = garcon_menu_item_get_name(m_item);
-	if (name)
-	{
-		gchar* normalized = g_utf8_normalize(name, -1, G_NORMALIZE_DEFAULT);
-		gchar* utf8 = g_utf8_casefold(normalized, -1);
-		m_search_text += utf8;
-		g_free(utf8);
-		g_free(normalized);
-		m_search_text += '\n';
-	}
-
-	const gchar* generic_name = garcon_menu_item_get_generic_name(m_item);
-	if (generic_name)
-	{
-		gchar* normalized = g_utf8_normalize(generic_name, -1, G_NORMALIZE_DEFAULT);
-		gchar* utf8 = g_utf8_casefold(normalized, -1);
-		m_search_text += utf8;
-		g_free(utf8);
-		g_free(normalized);
-		m_search_text += '\n';
-	}
-
-	const gchar* comment = garcon_menu_item_get_comment(m_item);
-	if (comment)
-	{
-		gchar* normalized = g_utf8_normalize(comment, -1, G_NORMALIZE_DEFAULT);
-		gchar* utf8 = g_utf8_casefold(normalized, -1);
-		m_search_text += utf8;
-		g_free(utf8);
-		g_free(normalized);
-	}
-
-	return m_search_text.c_str();
-}
-
-//-----------------------------------------------------------------------------
diff --git a/src/launcher.hpp b/src/launcher.hpp
index cdbae15..39fb921 100644
--- a/src/launcher.hpp
+++ b/src/launcher.hpp
@@ -29,6 +29,8 @@ extern "C"
 namespace WhiskerMenu
 {
 
+class Query;
+
 class Launcher
 {
 public:
@@ -50,11 +52,11 @@ public:
 		return m_item;
 	}
 
-	unsigned int get_search_results(const std::string& filter_string) const;
+	unsigned int get_search_results(const Query& query) const;
 
 	void run(GdkScreen* screen) const;
 
-	void search(const std::string& filter_string);
+	void search(const Query& query);
 
 	static bool get_show_name();
 	static bool get_show_description();
@@ -65,13 +67,12 @@ private:
 	Launcher(const Launcher& launcher);
 	Launcher& operator=(const Launcher& launcher);
 
-	const gchar* get_search_text();
-
 private:
 	GarconMenuItem* m_item;
 	gchar* m_icon;
 	gchar* m_text;
-	std::string m_search_text;
+	std::string m_search_name;
+	std::string m_search_comment;
 	std::map<std::string, unsigned int> m_searches;
 };
 
diff --git a/src/query.cpp b/src/query.cpp
new file mode 100644
index 0000000..fe91ee3
--- /dev/null
+++ b/src/query.cpp
@@ -0,0 +1,177 @@
+// Copyright (C) 2013 Graeme Gott <graeme at gottcode.org>
+//
+// This library 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 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this library.  If not, see <http://www.gnu.org/licenses/>.
+
+
+#include "query.hpp"
+
+#include <sstream>
+
+#include <climits>
+#include <cstring>
+
+extern "C"
+{
+#include <glib.h>
+}
+
+using namespace WhiskerMenu;
+
+//-----------------------------------------------------------------------------
+
+static inline bool is_start_word(const std::string& string, std::string::size_type pos)
+{
+	return (pos == 0) || g_unichar_isspace(g_utf8_get_char(g_utf8_prev_char(&string.at(pos))));
+}
+
+//-----------------------------------------------------------------------------
+
+Query::Query(const std::string& query)
+{
+	set(query);
+}
+
+//-----------------------------------------------------------------------------
+
+Query::~Query()
+{
+	clear();
+}
+
+//-----------------------------------------------------------------------------
+
+unsigned int Query::match(const std::string& haystack) const
+{
+	// Make sure haystack is longer than query
+	if (m_query.empty() || (m_query.length() > haystack.length()))
+	{
+		return UINT_MAX;
+	}
+
+	// Check if haystack begins with or is query
+	std::string::size_type pos = haystack.find(m_query);
+	if (pos == 0)
+	{
+		return haystack.length() != m_query.length();
+	}
+	// Check if haystack contains query starting at a word boundary
+	else if ((pos != std::string::npos) && is_start_word(haystack, pos))
+	{
+		return 2;
+	}
+
+	if (m_query_words.size() > 1)
+	{
+		// Check if haystack contains query as words
+		std::string::size_type search_pos = 0;
+		for (std::vector<std::string>::const_iterator i = m_query_words.begin(), end = m_query_words.end(); i != end; ++i)
+		{
+			search_pos = haystack.find(*i, search_pos);
+			if ((search_pos == std::string::npos) || !is_start_word(haystack, search_pos))
+			{
+				search_pos = std::string::npos;
+				break;
+			}
+		}
+		if (search_pos != std::string::npos)
+		{
+			return 3;
+		}
+
+		// Check if haystack contains query as words in any order
+		std::vector<std::string>::size_type found_words = 0;
+		for (std::vector<std::string>::const_iterator i = m_query_words.begin(), end = m_query_words.end(); i != end; ++i)
+		{
+			search_pos = haystack.find(*i);
+			if ((search_pos != std::string::npos) && is_start_word(haystack, search_pos))
+			{
+				++found_words;
+			}
+			else
+			{
+				break;
+			}
+		}
+		if (found_words == m_query_words.size())
+		{
+			return 4;
+		}
+	}
+
+	// Check if haystack contains query
+	if (pos != std::string::npos)
+	{
+		return 5;
+	}
+
+	// Check if haystack contains query as characters
+	bool characters_start_words = true;
+	bool start_word = true;
+	const gchar* query_string = m_query.c_str();
+	for (const gchar* pos = haystack.c_str(); *pos; pos = g_utf8_next_char(pos))
+	{
+		gunichar c = g_utf8_get_char(pos);
+		if (c == g_utf8_get_char(query_string))
+		{
+			characters_start_words &= start_word;
+			query_string = g_utf8_next_char(query_string);
+			start_word = false;
+		}
+		else if (g_unichar_isspace(c))
+		{
+			start_word = true;
+		}
+		else
+		{
+			start_word = false;
+		}
+	}
+	if (*query_string == 0)
+	{
+		return characters_start_words ? 6 : 7;
+	}
+
+	// Failed to find any matches
+	return UINT_MAX;
+}
+
+//-----------------------------------------------------------------------------
+
+void Query::clear()
+{
+	m_query.clear();
+	m_query_words.clear();
+}
+
+//-----------------------------------------------------------------------------
+
+void Query::set(const std::string& query)
+{
+	m_query_words.clear();
+
+	m_query = query;
+	if (m_query.empty())
+	{
+		return;
+	}
+
+	std::string buffer;
+	std::stringstream ss(query);
+	while (ss >> buffer)
+	{
+		m_query_words.push_back(buffer);
+	}
+}
+
+//-----------------------------------------------------------------------------
diff --git a/src/query.hpp b/src/query.hpp
new file mode 100644
index 0000000..4e04b22
--- /dev/null
+++ b/src/query.hpp
@@ -0,0 +1,54 @@
+// Copyright (C) 2013 Graeme Gott <graeme at gottcode.org>
+//
+// This library 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 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this library.  If not, see <http://www.gnu.org/licenses/>.
+
+
+#ifndef WHISKERMENU_QUERY_HPP
+#define WHISKERMENU_QUERY_HPP
+
+#include <string>
+#include <vector>
+
+namespace WhiskerMenu
+{
+
+class Query
+{
+public:
+	Query(const std::string& query = std::string());
+	~Query();
+
+	bool empty() const
+	{
+		return m_query.empty();
+	}
+
+	unsigned int match(const std::string& haystack) const;
+
+	std::string query() const
+	{
+		return m_query;
+	}
+
+	void clear();
+	void set(const std::string& query);
+
+private:
+	std::string m_query;
+	std::vector<std::string> m_query_words;
+};
+
+}
+
+#endif // WHISKERMENU_QUERY_HPP
diff --git a/src/search_page.cpp b/src/search_page.cpp
index 498eee6..58f646d 100644
--- a/src/search_page.cpp
+++ b/src/search_page.cpp
@@ -52,24 +52,17 @@ SearchPage::~SearchPage()
 void SearchPage::set_filter(const gchar* filter)
 {
 	// Store filter string
-	std::string old_filter_string = m_filter_string;
-	if (filter)
-	{
-		m_filter_string = filter;
-	}
-	else
-	{
-		m_filter_string.clear();
-	}
-	if (m_filter_string == old_filter_string)
+	std::string query(filter ? filter : "");
+	if (m_query.query() == query)
 	{
 		return;
 	}
+	m_query.set(query);
 
 	// Create search results
 	for (std::vector<Launcher*>::iterator i = m_launchers.begin(), end = m_launchers.end(); i != end; ++i)
 	{
-		(*i)->search(m_filter_string);
+		(*i)->search(m_query);
 	}
 
 	// Apply filter
@@ -128,7 +121,7 @@ void SearchPage::unset_menu_items()
 
 bool SearchPage::on_filter(GtkTreeModel* model, GtkTreeIter* iter)
 {
-	if (m_filter_string.empty())
+	if (m_query.empty())
 	{
 		return false;
 	}
@@ -136,9 +129,7 @@ bool SearchPage::on_filter(GtkTreeModel* model, GtkTreeIter* iter)
 	// Check if launcher search string contains text
 	Launcher* launcher = NULL;
 	gtk_tree_model_get(model, iter, LauncherModel::COLUMN_LAUNCHER, &launcher, -1);
-	unsigned int index = launcher->get_search_results(m_filter_string);
-
-	return index != UINT_MAX;
+	return launcher->get_search_results(m_query) != UINT_MAX;
 }
 
 //-----------------------------------------------------------------------------
@@ -151,7 +142,7 @@ gint SearchPage::on_sort(GtkTreeModel* model, GtkTreeIter* a, GtkTreeIter* b, Se
 	Launcher* launcher_b = NULL;
 	gtk_tree_model_get(model, b, LauncherModel::COLUMN_LAUNCHER, &launcher_b, -1);
 
-	return launcher_a->get_search_results(page->m_filter_string) - launcher_b->get_search_results(page->m_filter_string);
+	return launcher_a->get_search_results(page->m_query) - launcher_b->get_search_results(page->m_query);
 }
 
 //-----------------------------------------------------------------------------
diff --git a/src/search_page.hpp b/src/search_page.hpp
index 16f660a..020b1cd 100644
--- a/src/search_page.hpp
+++ b/src/search_page.hpp
@@ -18,6 +18,7 @@
 #define WHISKERMENU_SEARCH_PAGE_HPP
 
 #include "filter_page.hpp"
+#include "query.hpp"
 #include "slot.hpp"
 
 #include <string>
@@ -49,7 +50,7 @@ private:
 	void unset_search_model();
 
 private:
-	std::string m_filter_string;
+	Query m_query;
 	GtkTreeModelSort* m_sort_model;
 	std::vector<Launcher*> m_launchers;
 };

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the Xfce4-commits mailing list