[Bf-blender-cvs] [80a7efd] master: UI: multi word filtering in search menu

Campbell Barton noreply at git.blender.org
Wed Mar 23 08:51:29 CET 2016


Commit: 80a7efdc1d395f44a14e7bf1085a5d1b3fe45547
Author: Campbell Barton
Date:   Wed Mar 23 18:45:32 2016 +1100
Branches: master
https://developer.blender.org/rB80a7efdc1d395f44a14e7bf1085a5d1b3fe45547

UI: multi word filtering in search menu

D1080 by @rockets, with own improvements to tests

===================================================================

M	source/blender/blenlib/BLI_string.h
M	source/blender/blenlib/intern/string.c
M	source/blender/editors/interface/interface_templates.c
M	tests/gtests/blenlib/BLI_string_test.cc
M	tests/gtests/testing/testing.h

===================================================================

diff --git a/source/blender/blenlib/BLI_string.h b/source/blender/blenlib/BLI_string.h
index 8278039..d137806 100644
--- a/source/blender/blenlib/BLI_string.h
+++ b/source/blender/blenlib/BLI_string.h
@@ -74,6 +74,7 @@ size_t BLI_str_format_int_grouped(char dst[16], int num) ATTR_NONNULL();
 
 int BLI_strcaseeq(const char *a, const char *b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
 char *BLI_strcasestr(const char *s, const char *find) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
+char *BLI_strncasestr(const char *s, const char *find, size_t len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
 int BLI_strcasecmp(const char *s1, const char *s2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
 int BLI_strncasecmp(const char *s1, const char *s2, size_t len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
 int BLI_natstrcmp(const char *s1, const char *s2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
@@ -97,6 +98,11 @@ size_t BLI_str_partition_ex(
         const char *str, const char *end, const char delim[], const char **sep, const char **suf, const bool from_right)
         ATTR_NONNULL(1, 3, 4, 5);
 
+int BLI_string_find_split_words(
+        const char *str, const size_t len,
+        const char delim, int r_words[][2], int words_max)
+        ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/source/blender/blenlib/intern/string.c b/source/blender/blenlib/intern/string.c
index aad4649..2f67b0e 100644
--- a/source/blender/blenlib/intern/string.c
+++ b/source/blender/blenlib/intern/string.c
@@ -525,6 +525,26 @@ char *BLI_strcasestr(const char *s, const char *find)
 	return ((char *) s);
 }
 
+/**
+ * Variation of #BLI_strcasestr with string length limited to \a len
+ */
+char *BLI_strncasestr(const char *s, const char *find, size_t len)
+{
+	register char c, sc;
+
+	if ((c = *find++) != 0) {
+		c = tolower(c);
+		do {
+			do {
+				if ((sc = *s++) == 0)
+					return (NULL);
+				sc = tolower(sc);
+			} while (sc != c);
+		} while (BLI_strncasecmp(s, find, len - 1) != 0);
+		s--;
+	}
+	return ((char *)s);
+}
 
 int BLI_strcasecmp(const char *s1, const char *s2)
 {
@@ -962,3 +982,49 @@ size_t BLI_str_format_int_grouped(char dst[16], int num)
 
 	return (size_t)(p_dst - dst);
 }
+
+/**
+ * Find the ranges needed to split \a str into its individual words.
+ *
+ * \param str: The string to search for words.
+ * \param len: Size of the string to search.
+ * \param delim: Character to use as a delimiter.
+ * \param r_words: Info about the words found. Set to [index, len] pairs.
+ * \param words_max: Max number of words to find
+ * \return The number of words found in \a str
+ */
+int BLI_string_find_split_words(
+        const char *str, const size_t len,
+        const char delim, int r_words[][2], int words_max)
+{
+	int n = 0, i;
+	bool charsearch = true;
+
+	/* Skip leading spaces */
+	for (i = 0; (i < len) && (str[i] != '\0'); i++) {
+		if (str[i] != delim) {
+			break;
+		}
+	}
+
+	for (; (i < len) && (str[i] != '\0') && (n < words_max); i++) {
+		if ((str[i] != delim) && (charsearch == true)) {
+			r_words[n][0] = i;
+			charsearch = false;
+		}
+		else {
+			if ((str[i] == delim) && (charsearch == false)) {
+				r_words[n][1] = i - r_words[n][0];
+				n++;
+				charsearch = true;
+			}
+		}
+	}
+
+	if (charsearch == false) {
+		r_words[n][1] = i - r_words[n][0];
+		n++;
+	}
+
+	return n;
+}
diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c
index 7bc9bea..e48b09d 100644
--- a/source/blender/editors/interface/interface_templates.c
+++ b/source/blender/editors/interface/interface_templates.c
@@ -39,6 +39,7 @@
 #include "DNA_texture_types.h"
 
 #include "BLI_utildefines.h"
+#include "BLI_alloca.h"
 #include "BLI_string.h"
 #include "BLI_ghash.h"
 #include "BLI_rect.h"
@@ -3273,15 +3274,28 @@ static void operator_call_cb(bContext *C, void *UNUSED(arg1), void *arg2)
 static void operator_search_cb(const bContext *C, void *UNUSED(arg), const char *str, uiSearchItems *items)
 {
 	GHashIterator iter;
+	const size_t str_len = strlen(str);
+	const int words_max = (str_len / 2) + 1;
+	int (*words)[2] = BLI_array_alloca(words, words_max);
+
+	const int words_len = BLI_string_find_split_words(str, str_len, ' ', words, words_max);
 
 	for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter); BLI_ghashIterator_step(&iter)) {
 		wmOperatorType *ot = BLI_ghashIterator_getValue(&iter);
 		const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name);
+		int index;
 
 		if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0)
 			continue;
 
-		if (BLI_strcasestr(ot_ui_name, str)) {
+		/* match name against all search words */
+		for (index = 0; index < words_len; index++) {
+			if (!BLI_strncasestr(ot_ui_name, str + words[index][0], words[index][1])) {
+				break;
+			}
+		}
+
+		if (index == words_len) {
 			if (WM_operator_poll((bContext *)C, ot)) {
 				char name[256];
 				int len = strlen(ot_ui_name);
diff --git a/tests/gtests/blenlib/BLI_string_test.cc b/tests/gtests/blenlib/BLI_string_test.cc
index fa10e21..5559b8d 100644
--- a/tests/gtests/blenlib/BLI_string_test.cc
+++ b/tests/gtests/blenlib/BLI_string_test.cc
@@ -363,3 +363,103 @@ TEST(string, StrFormatIntGrouped)
 	BLI_str_format_int_grouped(num_str, num = -999);
 	EXPECT_STREQ("-999", num_str);
 }
+
+#define STRING_FIND_SPLIT_WORDS_EX(word_str_src, word_str_src_len, limit_words, ...) \
+{ \
+	int word_info[][2] = \
+		{{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}; \
+	const int word_cmp[][2] = __VA_ARGS__; \
+	const int word_cmp_size_input = ARRAY_SIZE(word_cmp) - (limit_words ? 1 : 0); \
+	const int word_cmp_size = ARRAY_SIZE(word_cmp); \
+	const int word_num = BLI_string_find_split_words( \
+	        word_str_src, word_str_src_len, ' ', word_info, word_cmp_size_input); \
+	EXPECT_EQ(word_num, word_cmp_size - 1); \
+	EXPECT_EQ_ARRAY_ND(word_cmp, word_info, word_cmp_size, 2); \
+} ((void)0)
+
+#define STRING_FIND_SPLIT_WORDS(word_str_src, ...) \
+	STRING_FIND_SPLIT_WORDS_EX(word_str_src, strlen(word_str_src), false, __VA_ARGS__)
+
+/* BLI_string_find_split_words */
+TEST(string, StringFindSplitWords_Single)
+{
+	STRING_FIND_SPLIT_WORDS("t",    {{0, 1}, {-1, -1}});
+	STRING_FIND_SPLIT_WORDS("test", {{0, 4}, {-1, -1}});
+}
+TEST(string, StringFindSplitWords_Triple)
+{
+	STRING_FIND_SPLIT_WORDS("f t w",            {{0, 1}, {2, 1}, {4, 1}, {-1, -1}});
+	STRING_FIND_SPLIT_WORDS("find three words", {{0, 4}, {5, 5}, {11, 5}, {-1, -1}});
+}
+TEST(string, StringFindSplitWords_Spacing)
+{
+	STRING_FIND_SPLIT_WORDS("# ## ### ####",   {{0, 1}, {2, 2}, {5, 3}, {9, 4}, {-1, -1}});
+	STRING_FIND_SPLIT_WORDS("#  #   #    #",   {{0, 1}, {3, 1}, {7, 1}, {12, 1}, {-1, -1}});
+}
+TEST(string, StringFindSplitWords_Trailing_Left)
+{
+	STRING_FIND_SPLIT_WORDS("   t",    {{3, 1}, {-1, -1}});
+	STRING_FIND_SPLIT_WORDS("   test", {{3, 4}, {-1, -1}});
+}
+TEST(string, StringFindSplitWords_Trailing_Right)
+{
+	STRING_FIND_SPLIT_WORDS("t   ",    {{0, 1}, {-1, -1}});
+	STRING_FIND_SPLIT_WORDS("test   ", {{0, 4}, {-1, -1}});
+}
+TEST(string, StringFindSplitWords_Trailing_LeftRight)
+{
+	STRING_FIND_SPLIT_WORDS("   surrounding space test   123   ", {{3, 11}, {15, 5}, {21, 4}, {28, 3}, {-1, -1}});
+}
+TEST(string, StringFindSplitWords_Blank)
+{
+	STRING_FIND_SPLIT_WORDS("", {{-1, -1}});
+}
+TEST(string, StringFindSplitWords_Whitespace)
+{
+	STRING_FIND_SPLIT_WORDS(" ",    {{-1, -1}});
+	STRING_FIND_SPLIT_WORDS("    ", {{-1, -1}});
+}
+TEST(string, StringFindSplitWords_LimitWords)
+{
+	const char *words = "too many words";
+	const int words_len = strlen(words);
+	STRING_FIND_SPLIT_WORDS_EX(words, words_len, false, {{0, 3}, {4, 4}, {9, 5}, {-1, -1}});
+	STRING_FIND_SPLIT_WORDS_EX(words, words_len, true,  {{0, 3}, {4, 4}, {-1, -1}});
+	STRING_FIND_SPLIT_WORDS_EX(words, words_len, true,  {{0, 3}, {-1, -1}});
+	STRING_FIND_SPLIT_WORDS_EX(words, words_len, true,  {{-1, -1}});
+}
+TEST(string, StringFindSplitWords_LimitChars)
+{
+	const char *words = "too many chars";
+	const int words_len = strlen(words);
+	STRING_FIND_SPLIT_WORDS_EX(words, words_len,      false, {{0, 3}, {4, 4}, {9, 5}, {-1, -1}});
+	STRING_FIND_SPLIT_WORDS_EX(words, words_len -  1, false, {{0, 3}, {4, 4}, {9, 4}, {-1, -1}});
+	STRING_FIND_SPLIT_WORDS_EX(words, words_len -  5, false, {{0, 3}, {4, 4}, {-1, -1}});
+	STRING_FIND_SPLIT_WORDS_EX(words, 1,              false, {{0, 1}, {-1, -1}});
+	STRING_FIND_SPLIT_WORDS_EX(words, 0,              false, {{-1, -1}});
+}
+
+#undef STRING_FIND_SPLIT_WORDS
+
+
+/* BLI_strncasestr */
+TEST(string, StringStrncasestr)
+{
+	const char *str_test0 = "search here";
+	const char *res;
+
+	res = BLI_strncasestr(str_test0, "", 0);
+	EXPECT_EQ(str_test0, res);
+
+	res = BLI_strncasestr(str_test0, "her", 3);
+	EXPECT_EQ(str_test0 + 7, res);
+
+	res = BLI_strncasestr(str_test0, "ARCh", 4);
+	EXPECT_EQ(str_test0 + 2, res);
+
+	res = BLI_strncasestr(str_test0, "earcq", 4);
+	EXPECT_EQ(str_test0 + 1, res);
+
+	res = BLI_strncasestr(str_test0, "not there", 9);
+	EXPECT_EQ(NULL, res);
+}
diff --git a/tests/gtests/testing/testing.h b/tests/gtests/testing/testing.h
index b0a6379..1594ed3 100644
--- a/tests/gtests/testing/testing.h
+++ b/tests/gtests/testing/testing.h
@@ -82,4 +82,13 @@ inline void EXPECT_EQ_ARRAY(const T *expected, T *actual, const size_t N) {
   }
 }
 
+template <typename T>
+inline void EXPECT_EQ_ARRAY_ND(const T *expected, T *actual, const size_t N, const size_t D) {
+  for(size_t i = 0; i < N; ++i) {
+    for(size_t j = 0; j < D; ++j) {
+      EXPECT_EQ(expected[i][j], actual[i][j]);
+    }
+  }
+}
+
 #endif  // __BLENDER_TESTING_H__




More information about the Bf-blender-cvs mailing list