[gnome-characters] Rework search to allow multiple invocation
- From: Daiki Ueno <dueno src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-characters] Rework search to allow multiple invocation
- Date: Wed, 11 Nov 2015 03:35:56 +0000 (UTC)
commit fbe6b6f973bbf56bd2599317434299b7c301e7a2
Author: Daiki Ueno <dueno src gnome org>
Date: Wed Nov 11 12:25:28 2015 +0900
Rework search to allow multiple invocation
Now all search functions take a context argument, which remembers the
last search and allows multiple invocations of a search function when
more results are requested.
From the character list UI, more results are requested when the user
scrolls down the list to the bottom.
https://bugzilla.gnome.org/show_bug.cgi?id=757523
lib/gc.c | 715 +++++++++++++++++++++++++++++++-------------------
lib/gc.h | 98 +++++---
src/character.js | 9 +-
src/characterList.js | 115 +++++----
4 files changed, 573 insertions(+), 364 deletions(-)
---
diff --git a/lib/gc.c b/lib/gc.c
index 6f45cce..5c19748 100644
--- a/lib/gc.c
+++ b/lib/gc.c
@@ -18,9 +18,31 @@
#define PANGO_ENABLE_ENGINE 1
#include <pango/pangofc-font.h>
+static gsize all_blocks_initialized;
static const uc_block_t *all_blocks;
static size_t all_block_count;
+#define LATIN_BLOCK_SIZE 4
+static gsize latin_blocks_initialized;
+static const uc_block_t latin_blocks[LATIN_BLOCK_SIZE];
+static const ucs4_t latin_block_starters[LATIN_BLOCK_SIZE] =
+ { 0x0000, 0x0080, 0x0100, 0x0180 };
+static size_t latin_block_count;
+
+#define HIRAGANA_BLOCK_SIZE 1
+static gsize hiragana_blocks_initialized;
+static const uc_block_t hiragana_blocks[HIRAGANA_BLOCK_SIZE];
+static const ucs4_t hiragana_block_starters[HIRAGANA_BLOCK_SIZE] =
+ { 0x3040 };
+static size_t hiragana_block_count;
+
+#define KATAKANA_BLOCK_SIZE 2
+static gsize katakana_blocks_initialized;
+static const uc_block_t katakana_blocks[KATAKANA_BLOCK_SIZE];
+static const ucs4_t katakana_block_starters[KATAKANA_BLOCK_SIZE] =
+ { 0x30A0, 0x31F0 };
+static size_t katakana_block_count;
+
/* Bullets are not specially categorized in the Unicode standard.
Use the character list from UTR#25 "Unicode Support for Mathematics". */
static const gunichar bullet_characters[] =
@@ -90,11 +112,11 @@ struct GcCharacterIter
static gboolean gc_character_iter_next (GcCharacterIter *iter);
static gunichar gc_character_iter_get (GcCharacterIter *iter);
-static void gc_enumerate_character_by_category
+static void gc_character_iter_init_for_category
(GcCharacterIter *iter,
GcCategory category);
-static void gc_enumerate_character_by_keywords
+static void gc_character_iter_init_for_keywords
(GcCharacterIter *iter,
const gchar * const * keywords);
@@ -103,8 +125,8 @@ gc_character_iter_next (GcCharacterIter *iter)
{
ucs4_t uc = iter->uc;
- /* First search in the explicit character list. */
- if (iter->character_index + 1 < iter->character_count)
+ /* First, search in the explicit character list. */
+ if (iter->character_index < iter->character_count)
{
iter->uc = iter->characters[iter->character_index++];
return TRUE;
@@ -232,11 +254,14 @@ gc_character_iter_init_for_scripts (GcCharacterIter *iter,
}
static void
-gc_enumerate_character_by_category (GcCharacterIter *iter,
- GcCategory category)
+gc_character_iter_init_for_category (GcCharacterIter *iter,
+ GcCategory category)
{
- if (!all_blocks)
- uc_all_blocks (&all_blocks, &all_block_count);
+ if (g_once_init_enter (&all_blocks_initialized))
+ {
+ uc_all_blocks (&all_blocks, &all_block_count);
+ g_once_init_leave (&all_blocks_initialized, 1);
+ }
switch (category)
{
@@ -419,9 +444,15 @@ filter_keywords (GcCharacterIter *iter, ucs4_t uc)
}
static void
-gc_enumerate_character_by_keywords (GcCharacterIter *iter,
- const gchar * const * keywords)
+gc_character_iter_init_for_keywords (GcCharacterIter *iter,
+ const gchar * const * keywords)
{
+ if (g_once_init_enter (&all_blocks_initialized))
+ {
+ uc_all_blocks (&all_blocks, &all_block_count);
+ g_once_init_leave (&all_blocks_initialized, 1);
+ }
+
gc_character_iter_init (iter);
iter->blocks = all_blocks;
iter->block_count = all_block_count;
@@ -446,12 +477,12 @@ gc_character_name (gunichar uc)
if (g_once_init_enter (&cjk_blocks_initialized))
{
static const ucs4_t cjk_block_starters[6] =
- {
- 0x4E00, 0x3400, 0x20000, 0x2A700, 0x2B740, 0x2B820
- };
+ {
+ 0x4E00, 0x3400, 0x20000, 0x2A700, 0x2B740, 0x2B820
+ };
for (i = 0; i < G_N_ELEMENTS (cjk_block_starters); i++)
- cjk_blocks[i] = uc_block (cjk_block_starters[i]);
+ cjk_blocks[i] = uc_block (cjk_block_starters[i]);
g_once_init_leave (&cjk_blocks_initialized, 1);
}
@@ -463,6 +494,12 @@ gc_character_name (gunichar uc)
return unicode_character_name (uc, g_new0 (gchar, UNINAME_MAX));
}
+GQuark
+gc_search_error_quark (void)
+{
+ return g_quark_from_static_string ("gc-search-error-quark");
+}
+
G_DEFINE_BOXED_TYPE (GcSearchResult, gc_search_result,
g_array_ref, g_array_unref);
@@ -475,190 +512,162 @@ gc_search_result_get (GcSearchResult *result, gint index)
return g_array_index (result, gunichar, index);
}
-struct SearchData
+typedef enum
+ {
+ GC_SEARCH_CRITERIA_CATEGORY,
+ GC_SEARCH_CRITERIA_KEYWORDS,
+ GC_SEARCH_CRITERIA_SCRIPTS,
+ GC_SEARCH_CRITERIA_RELATED
+ } GcSearchCriteriaType;
+
+struct _GcSearchCriteria
{
- GcCategory category;
- gchar **keywords;
- const uc_script_t **scripts;
- gunichar uc;
- gint max_matches;
+ GcSearchCriteriaType type;
+
+ union {
+ GcCategory category;
+ gchar **keywords;
+ const uc_script_t **scripts;
+ gunichar related;
+ } u;
};
-static void
-search_data_free (struct SearchData *data)
+static gpointer
+gc_search_criteria_copy (gpointer boxed)
{
- if (data->keywords)
- g_strfreev (data->keywords);
- if (data->scripts)
- g_free (data->scripts);
- g_slice_free (struct SearchData, data);
+ GcSearchCriteria *criteria = g_memdup (boxed, sizeof (GcSearchCriteria));
+
+ if (criteria->type == GC_SEARCH_CRITERIA_KEYWORDS)
+ criteria->u.keywords = g_strdupv (criteria->u.keywords);
+
+ return criteria;
}
static void
-gc_search_by_category_thread (GTask *task,
- gpointer source_object,
- gpointer task_data,
- GCancellable *cancellable)
+gc_search_criteria_free (gpointer boxed)
{
- GcCharacterIter iter;
- GArray *result;
- struct SearchData *data = task_data;
-
- if (!all_blocks)
- uc_all_blocks (&all_blocks, &all_block_count);
+ GcSearchCriteria *criteria = boxed;
- result = g_array_new (FALSE, FALSE, sizeof (gunichar));
- gc_enumerate_character_by_category (&iter, data->category);
- while (!g_cancellable_is_cancelled (cancellable)
- && gc_character_iter_next (&iter))
- {
- gunichar uc = gc_character_iter_get (&iter);
- if (data->max_matches < 0 || result->len < data->max_matches)
- g_array_append_val (result, uc);
- }
+ if (criteria->type == GC_SEARCH_CRITERIA_KEYWORDS)
+ g_strfreev (criteria->u.keywords);
- g_task_return_pointer (task, result, (GDestroyNotify) g_array_unref);
+ g_free (criteria);
}
+G_DEFINE_BOXED_TYPE (GcSearchCriteria, gc_search_criteria,
+ gc_search_criteria_copy, gc_search_criteria_free)
+
/**
- * gc_search_by_category:
- * @category: a #GcCategory.
- * @max_matches: the maximum number of results.
- * @cancellable: a #GCancellable.
- * @callback: a #GAsyncReadyCallback.
- * @user_data: a user data passed to @callback.
+ * gc_search_criteria_new_category:
+ * @category: a #GcCategory
+ *
+ * Returns: (transfer full): a new #GcSearchCriteria
*/
-void
-gc_search_by_category (GcCategory category,
- gint max_matches,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
+GcSearchCriteria *
+gc_search_criteria_new_category (GcCategory category)
{
- GTask *task;
- struct SearchData *data;
-
- task = g_task_new (NULL, cancellable, callback, user_data);
+ GcSearchCriteria *result = g_new0 (GcSearchCriteria, 1);
+ result->type = GC_SEARCH_CRITERIA_CATEGORY;
+ result->u.category = category;
+ return result;
+}
- data = g_slice_new0 (struct SearchData);
- data->category = category;
- data->max_matches = max_matches;
- g_task_set_task_data (task, data,
- (GDestroyNotify) search_data_free);
- g_task_run_in_thread (task, gc_search_by_category_thread);
+/**
+ * gc_search_criteria_new_keywords:
+ * @keywords: (array zero-terminated=1) (element-type utf8): an array of keywords
+ *
+ * Returns: (transfer full): a new #GcSearchCriteria
+ */
+GcSearchCriteria *
+gc_search_criteria_new_keywords (const gchar * const * keywords)
+{
+ GcSearchCriteria *result = g_new0 (GcSearchCriteria, 1);
+ result->type = GC_SEARCH_CRITERIA_KEYWORDS;
+ result->u.keywords = g_strdupv ((gchar **) keywords);
+ return result;
}
-static void
-gc_search_by_keywords_thread (GTask *task,
- gpointer source_object,
- gpointer task_data,
- GCancellable *cancellable)
+GcSearchCriteria *
+gc_search_criteria_new_scripts (const gchar * const * scripts)
{
- GcCharacterIter iter;
- GArray *result;
- struct SearchData *data = task_data;
- const gchar * const * keywords = (const gchar * const *) data->keywords;
+ GcSearchCriteria *result = g_new0 (GcSearchCriteria, 1);
+ guint length, i;
- if (!all_blocks)
- uc_all_blocks (&all_blocks, &all_block_count);
+ result->type = GC_SEARCH_CRITERIA_SCRIPTS;
- result = g_array_new (FALSE, FALSE, sizeof (gunichar));
- gc_enumerate_character_by_keywords (&iter, keywords);
- while (!g_cancellable_is_cancelled (cancellable)
- && gc_character_iter_next (&iter))
- {
- gunichar uc = gc_character_iter_get (&iter);
- if (data->max_matches < 0 || result->len < data->max_matches)
- g_array_append_val (result, uc);
- }
+ length = g_strv_length ((gchar **) scripts);
+ result->u.scripts = g_malloc0_n (length + 1, sizeof (uc_script_t *));
+ for (i = 0; i < length; i++)
+ result->u.scripts[i] = uc_script_byname (scripts[i]);
- g_task_return_pointer (task, result, (GDestroyNotify) g_array_unref);
+ return result;
}
-/**
- * gc_search_by_keywords:
- * @keywords: (array zero-terminated=1) (element-type utf8): an array of keywords
- * @max_matches: the maximum number of results.
- * @cancellable: a #GCancellable.
- * @callback: a #GAsyncReadyCallback.
- * @user_data: a user data passed to @callback.
- */
-void
-gc_search_by_keywords (const gchar * const * keywords,
- gint max_matches,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
+GcSearchCriteria *
+gc_search_criteria_new_related (gunichar uc)
{
- GTask *task;
- struct SearchData *data;
-
- task = g_task_new (NULL, cancellable, callback, user_data);
-
- data = g_slice_new0 (struct SearchData);
- data->keywords = g_strdupv ((gchar **) keywords);
- data->max_matches = max_matches;
- g_task_set_task_data (task, data,
- (GDestroyNotify) search_data_free);
- g_task_run_in_thread (task, gc_search_by_keywords_thread);
+ GcSearchCriteria *result = g_new0 (GcSearchCriteria, 1);
+ result->type = GC_SEARCH_CRITERIA_RELATED;
+ result->u.related = uc;
+ return result;
}
-static void
-gc_search_by_scripts_thread (GTask *task,
- gpointer source_object,
- gpointer task_data,
- GCancellable *cancellable)
+enum GcSearchState
+ {
+ GC_SEARCH_STATE_NOT_STARTED,
+ GC_SEARCH_STATE_STEP_STARTED,
+ GC_SEARCH_STATE_STEP_FINISHED,
+ GC_SEARCH_STATE_FINISHED
+ };
+
+struct _GcSearchContext
{
+ GObject parent;
+ GMutex lock;
+ enum GcSearchState state;
GcCharacterIter iter;
- GArray *result;
- struct SearchData *data = task_data;
-
- if (!all_blocks)
- uc_all_blocks (&all_blocks, &all_block_count);
+ GcSearchCriteria *criteria;
+};
- result = g_array_new (FALSE, FALSE, sizeof (gunichar));
- gc_character_iter_init_for_scripts (&iter,
- (const uc_script_t * const *) data->scripts);
- while (!g_cancellable_is_cancelled (cancellable)
- && gc_character_iter_next (&iter))
- {
- gunichar uc = gc_character_iter_get (&iter);
- g_array_append_val (result, uc);
- }
+struct SearchData
+{
+ GcCategory category;
+ gchar **keywords;
+ const uc_script_t **scripts;
+ gunichar uc;
+ gint max_matches;
+ GcSearchContext *context;
+};
- g_task_return_pointer (task, result, (GDestroyNotify) g_array_unref);
+static void
+search_data_free (struct SearchData *data)
+{
+ g_clear_pointer (&data->keywords, (GDestroyNotify) g_strfreev);
+ g_clear_pointer (&data->scripts, g_free);
+ g_clear_object (&data->context);
+ g_slice_free (struct SearchData, data);
}
-/**
- * gc_search_by_scripts:
- * @scripts: (array zero-terminated=1) (element-type utf8): an array of scripts
- * @max_matches: the maximum number of results.
- * @cancellable: a #GCancellable.
- * @callback: a #GAsyncReadyCallback.
- * @user_data: a user data passed to @callback.
- */
-void
-gc_search_by_scripts (const gchar * const * scripts,
- gint max_matches,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
+static void
+add_composited (GArray *result, ucs4_t base,
+ const uc_block_t *blocks, size_t count)
{
- GTask *task;
- struct SearchData *data;
- guint length, i;
+ size_t i;
- task = g_task_new (NULL, cancellable, callback, user_data);
+ for (i = 0; i < count; i++)
+ {
+ const uc_block_t *block = &blocks[i];
+ ucs4_t uc;
- data = g_slice_new0 (struct SearchData);
- length = g_strv_length ((gchar **) scripts);
- data->scripts = g_malloc0_n (length + 1, sizeof (uc_script_t *));
- for (i = 0; i < length; i++)
- data->scripts[i] = uc_script_byname (scripts[i]);
- data->max_matches = max_matches;
- g_task_set_task_data (task, data,
- (GDestroyNotify) search_data_free);
- g_task_run_in_thread (task, gc_search_by_scripts_thread);
+ for (uc = 0; uc < block->end; uc++)
+ {
+ ucs4_t decomposition[UC_DECOMPOSITION_MAX_LENGTH];
+
+ uc_canonical_decomposition (uc, decomposition);
+ if (decomposition[0] == base)
+ g_array_append_val (result, uc);
+ }
+ }
}
static int
@@ -670,13 +679,11 @@ confusable_character_class_compare (const void *a,
}
static void
-gc_add_confusables (struct SearchData *data,
- GArray *result,
- GCancellable *cancellable)
+add_confusables (GArray *result, ucs4_t uc)
{
struct ConfusableCharacterClass key, *res;
- key.uc = data->uc;
+ key.uc = uc;
res = bsearch (&key, confusable_character_classes,
G_N_ELEMENTS (confusable_character_classes),
sizeof (*confusable_character_classes),
@@ -684,15 +691,9 @@ gc_add_confusables (struct SearchData *data,
if (res)
{
const struct ConfusableClass *klass = &confusable_classes[res->index];
- uint16_t i;
-
- for (i = 0; i < klass->length
- && !g_cancellable_is_cancelled (cancellable); i++)
- {
- gunichar uc = confusable_characters[klass->offset + i];
- if (data->max_matches < 0 || result->len < data->max_matches)
- g_array_append_val (result, uc);
- }
+ g_array_append_vals (result,
+ &confusable_characters[klass->offset],
+ klass->length);
}
}
@@ -729,97 +730,64 @@ remove_duplicates (GArray *array)
}
static void
-add_composited (GArray *result,
- ucs4_t uc,
- ucs4_t *block_characters,
- size_t n_block_characters)
-{
- ucs4_t decomposition[UC_DECOMPOSITION_MAX_LENGTH];
- int decomposition_length;
- ucs4_t base;
- size_t i;
-
- decomposition_length = uc_canonical_decomposition (uc, decomposition);
- if (decomposition_length > 0)
- {
- base = decomposition[0];
- g_array_append_val (result, base);
- }
- else
- base = uc;
-
- for (i = 0; i < n_block_characters; i++)
- {
- const uc_block_t *block;
-
- block = uc_block (block_characters[i]);
- for (uc = block->start; uc < block->end; uc++)
- {
- decomposition_length = uc_canonical_decomposition (uc, decomposition);
- if (decomposition_length > 0 && decomposition[0] == base)
- g_array_append_val (result, uc);
- }
- }
-}
-
-static void
-gc_search_related_thread (GTask *task,
- gpointer source_object,
- gpointer task_data,
- GCancellable *cancellable)
+populate_related_characters (GcCharacterIter *iter)
{
GArray *result;
- struct SearchData *data = task_data;
- ucs4_t mirror;
- gunichar uc;
- gint i;
+ ucs4_t related;
+ size_t i;
result = g_array_new (FALSE, FALSE, sizeof (gunichar));
- uc = uc_toupper (data->uc);
- g_array_append_val (result, uc);
+ related = uc_toupper (iter->uc);
+ if (related != iter->uc)
+ g_array_append_val (result, related);
- uc = uc_tolower (data->uc);
- g_array_append_val (result, uc);
+ related = uc_tolower (iter->uc);
+ if (related != iter->uc)
+ g_array_append_val (result, related);
- uc = uc_totitle (data->uc);
- g_array_append_val (result, uc);
+ related = uc_totitle (iter->uc);
+ if (related != iter->uc)
+ g_array_append_val (result, related);
- if (uc_mirror_char (data->uc, &mirror))
- {
- uc = mirror;
- g_array_append_val (result, uc);
- }
+ if (uc_mirror_char (iter->uc, &related) && related != iter->uc)
+ g_array_append_val (result, related);
- if (uc_is_general_category (data->uc, UC_CATEGORY_L))
+ if (uc_is_general_category (iter->uc, UC_CATEGORY_L))
{
+ ucs4_t decomposition[UC_DECOMPOSITION_MAX_LENGTH];
+ int decomposition_length;
+ ucs4_t decomposition_base;
const uc_script_t *script;
- script = uc_script (data->uc);
+ decomposition_length =
+ uc_canonical_decomposition (iter->uc, decomposition);
+ if (decomposition_length > 0)
+ {
+ decomposition_base = decomposition[0];
+ if (decomposition_base != iter->uc)
+ g_array_append_val (result, decomposition_base);
+ }
+ else
+ decomposition_base = iter->uc;
+
+ script = uc_script (iter->uc);
if (script)
{
if (strcmp (script->name, "Latin") == 0)
- {
- ucs4_t block_starters[4] = { 0x0000, 0x0080, 0x0100, 0x0180 };
- add_composited (result, data->uc,
- block_starters, G_N_ELEMENTS (block_starters));
- }
+ add_composited (result, decomposition_base,
+ latin_blocks, latin_block_count);
else if (strcmp (script->name, "Hiragana") == 0)
- {
- ucs4_t block_starters[1] = { 0x3040 };
- add_composited (result, data->uc,
- block_starters, G_N_ELEMENTS (block_starters));
- }
+ add_composited (result, decomposition_base,
+ hiragana_blocks, hiragana_block_count);
else if (strcmp (script->name, "Katakana") == 0)
- {
- ucs4_t block_starters[2] = { 0x30A0, 0x31F0 };
- add_composited (result, data->uc,
- block_starters, G_N_ELEMENTS (block_starters));
- }
+ add_composited (result, decomposition_base,
+ katakana_blocks, katakana_block_count);
}
}
- gc_add_confusables (data, result, cancellable);
+ add_confusables (result, iter->uc);
+
g_array_sort (result, compare_unichar);
remove_duplicates (result);
@@ -828,60 +796,269 @@ gc_search_related_thread (GTask *task,
gunichar *puc;
puc = &g_array_index (result, gunichar, i);
- if (*puc == data->uc)
+ if (*puc == iter->uc)
{
g_array_remove_index (result, i);
break;
}
}
+ iter->character_count = result->len;
+ iter->characters = (gunichar *) g_array_free (result, FALSE);
+}
+
+static size_t
+init_blocks (const uc_block_t *blocks, const ucs4_t *starters, size_t size)
+{
+ size_t i, count;
+
+ for (i = 0, count = 0; i < size; i++)
+ {
+ const uc_block_t *block = uc_block (starters[i]);
+ if (block)
+ memcpy ((uc_block_t *) &blocks[count++], block, sizeof (uc_block_t));
+ }
+ return count;
+}
+
+static void
+gc_character_iter_init_for_related (GcCharacterIter *iter,
+ gunichar uc)
+{
+ if (g_once_init_enter (&latin_blocks_initialized))
+ {
+ latin_block_count =
+ init_blocks (latin_blocks, latin_block_starters,
+ LATIN_BLOCK_SIZE);
+ g_once_init_leave (&latin_blocks_initialized, 1);
+ }
+
+ if (g_once_init_enter (&hiragana_blocks_initialized))
+ {
+ hiragana_block_count =
+ init_blocks (hiragana_blocks, hiragana_block_starters,
+ HIRAGANA_BLOCK_SIZE);
+ g_once_init_leave (&hiragana_blocks_initialized, 1);
+ }
+
+ if (g_once_init_enter (&katakana_blocks_initialized))
+ {
+ katakana_block_count =
+ init_blocks (katakana_blocks, katakana_block_starters,
+ KATAKANA_BLOCK_SIZE);
+ g_once_init_leave (&katakana_blocks_initialized, 1);
+ }
+
+ gc_character_iter_init (iter);
+ iter->uc = uc;
+ populate_related_characters (iter);
+}
+
+enum {
+ SEARCH_CONTEXT_PROP_0,
+ SEARCH_CONTEXT_PROP_CRITERIA,
+ SEARCH_CONTEXT_LAST_PROP
+};
+
+static GParamSpec *search_context_props[SEARCH_CONTEXT_LAST_PROP] = { NULL, };
+
+G_DEFINE_TYPE (GcSearchContext, gc_search_context, G_TYPE_OBJECT);
+
+static void
+gc_search_context_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GcSearchContext *context = GC_SEARCH_CONTEXT (object);
+
+ switch (prop_id)
+ {
+ case SEARCH_CONTEXT_PROP_CRITERIA:
+ context->criteria = g_value_dup_boxed (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gc_search_context_finalize (GObject *object)
+{
+ GcSearchContext *context = GC_SEARCH_CONTEXT (object);
+
+ g_mutex_clear (&context->lock);
+ g_boxed_free (GC_TYPE_SEARCH_CONTEXT, context->criteria);
+
+ G_OBJECT_CLASS (gc_search_context_parent_class)->finalize (object);
+}
+
+static void
+gc_search_context_class_init (GcSearchContextClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gc_search_context_set_property;
+ object_class->finalize = gc_search_context_finalize;
+
+ search_context_props[SEARCH_CONTEXT_PROP_CRITERIA] =
+ g_param_spec_boxed ("criteria", NULL, NULL,
+ GC_TYPE_SEARCH_CRITERIA,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE);
+ g_object_class_install_properties (object_class, SEARCH_CONTEXT_LAST_PROP,
+ search_context_props);
+}
+
+static void
+gc_search_context_init (GcSearchContext *context)
+{
+ g_mutex_init (&context->lock);
+}
+
+GcSearchContext *
+gc_search_context_new (GcSearchCriteria *criteria)
+{
+ return g_object_new (GC_TYPE_SEARCH_CONTEXT, "criteria", criteria, NULL);
+}
+
+static void
+gc_search_context_search_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GArray *result;
+ struct SearchData *data = task_data;
+
+ if (g_once_init_enter (&all_blocks_initialized))
+ {
+ uc_all_blocks (&all_blocks, &all_block_count);
+ g_once_init_leave (&all_blocks_initialized, 1);
+ }
+
+ result = g_array_new (FALSE, FALSE, sizeof (gunichar));
+ while (gc_character_iter_next (&data->context->iter))
+ {
+ gunichar uc;
+
+ if (g_task_return_error_if_cancelled (task))
+ {
+ g_mutex_lock (&data->context->lock);
+ data->context->state = GC_SEARCH_STATE_NOT_STARTED;
+ g_mutex_unlock (&data->context->lock);
+ return;
+ }
+
+ if (result->len == data->max_matches)
+ {
+ g_mutex_lock (&data->context->lock);
+ data->context->state = GC_SEARCH_STATE_STEP_FINISHED;
+ g_mutex_unlock (&data->context->lock);
+
+ g_task_return_pointer (task, result, (GDestroyNotify) g_array_unref);
+ return;
+ }
+
+ uc = gc_character_iter_get (&data->context->iter);
+ g_array_append_val (result, uc);
+ }
+
+ g_mutex_lock (&data->context->lock);
+ data->context->state = GC_SEARCH_STATE_FINISHED;
+ g_mutex_unlock (&data->context->lock);
+
g_task_return_pointer (task, result, (GDestroyNotify) g_array_unref);
}
-/**
- * gc_search_related:
- * @uc: a #gunichar.
- * @max_matches: the maximum number of results.
- * @cancellable: a #GCancellable.
- * @callback: a #GAsyncReadyCallback.
- * @user_data: a user data passed to @callback.
- */
void
-gc_search_related (gunichar uc,
- gint max_matches,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
+gc_search_context_search (GcSearchContext *context,
+ gint max_matches,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
{
GTask *task;
struct SearchData *data;
- task = g_task_new (NULL, cancellable, callback, user_data);
+ g_mutex_lock (&context->lock);
+ task = g_task_new (context, cancellable, callback, user_data);
+ switch (context->state)
+ {
+ case GC_SEARCH_STATE_NOT_STARTED:
+ switch (context->criteria->type)
+ {
+ case GC_SEARCH_CRITERIA_CATEGORY:
+ gc_character_iter_init_for_category (&context->iter,
+ context->criteria->u.category);
+ break;
+ case GC_SEARCH_CRITERIA_KEYWORDS:
+ gc_character_iter_init_for_keywords (&context->iter,
+ (const gchar * const *) context->criteria->u.keywords);
+ break;
+ case GC_SEARCH_CRITERIA_SCRIPTS:
+ gc_character_iter_init_for_scripts (&context->iter,
+ context->criteria->u.scripts);
+ break;
+ case GC_SEARCH_CRITERIA_RELATED:
+ gc_character_iter_init_for_related (&context->iter,
+ context->criteria->u.related);
+ }
+ context->state = GC_SEARCH_STATE_STEP_STARTED;
+ break;
+
+ case GC_SEARCH_STATE_STEP_STARTED:
+ g_mutex_unlock (&context->lock);
+ g_task_return_new_error (task,
+ GC_SEARCH_ERROR,
+ GC_SEARCH_ERROR_INVALID_STATE,
+ "search step already started");
+ return;
+
+ case GC_SEARCH_STATE_STEP_FINISHED:
+ break;
+
+ case GC_SEARCH_STATE_FINISHED:
+ g_mutex_unlock (&context->lock);
+ g_task_return_new_error (task,
+ GC_SEARCH_ERROR,
+ GC_SEARCH_ERROR_INVALID_STATE,
+ "search context destroyed");
+ return;
+ }
+ g_mutex_unlock (&context->lock);
data = g_slice_new0 (struct SearchData);
- data->uc = uc;
+ data->context = g_object_ref (context);
data->max_matches = max_matches;
- g_task_set_task_data (task, data,
- (GDestroyNotify) search_data_free);
- g_task_run_in_thread (task, gc_search_related_thread);
+ g_task_set_task_data (task, data, (GDestroyNotify) search_data_free);
+ g_task_run_in_thread (task, gc_search_context_search_thread);
}
/**
- * gc_search_finish:
+ * gc_search_context_search_finish:
+ * @context: a #GcSearchContext.
* @result: a #GAsyncResult.
* @error: return location of an error.
*
* Returns: (transfer full): an array of characters.
*/
GcSearchResult *
-gc_search_finish (GAsyncResult *result,
- GError **error)
+gc_search_context_search_finish (GcSearchContext *context,
+ GAsyncResult *result,
+ GError **error)
{
- g_return_val_if_fail (g_task_is_valid (result, NULL), NULL);
+ g_return_val_if_fail (g_task_is_valid (result, context), NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
+gboolean
+gc_search_context_is_finished (GcSearchContext *context)
+{
+ return context->state == GC_SEARCH_STATE_FINISHED;
+}
+
/**
* gc_gtk_clipboard_get:
*
diff --git a/lib/gc.h b/lib/gc.h
index 688c3a3..5a074af 100644
--- a/lib/gc.h
+++ b/lib/gc.h
@@ -35,52 +35,76 @@ typedef enum
typedef GArray GcSearchResult;
typedef gboolean (*GcSearchFunc) (gunichar uc, gpointer user_data);
-GType gc_search_result_get_type (void);
-gunichar gc_search_result_get (GcSearchResult *result,
- gint index);
-
-void gc_search_by_category (GcCategory category,
- gint max_matches,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-void gc_search_by_keywords (const gchar * const * keywords,
- gint max_matches,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-void gc_search_by_scripts (const gchar * const * scripts,
- gint max_matches,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-void gc_search_related (gunichar uc,
- gint max_matches,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-GcSearchResult *gc_search_finish (GAsyncResult *result,
- GError **error);
-
-gchar *gc_character_name (gunichar uc);
+#define GC_SEARCH_ERROR (gc_search_error_quark ())
+
+typedef enum
+ {
+ GC_SEARCH_ERROR_FAILED,
+ GC_SEARCH_ERROR_INVALID_STATE
+ } GcSearchError;
+
+#define GC_TYPE_SEARCH_CRITERIA (gc_search_criteria_get_type ())
+
+typedef struct _GcSearchCriteria GcSearchCriteria;
+
+#define GC_TYPE_SEARCH_CONTEXT (gc_search_context_get_type ())
+G_DECLARE_FINAL_TYPE (GcSearchContext, gc_search_context,
+ GC, SEARCH_CONTEXT, GObject)
+
+GType gc_search_result_get_type
+ (void);
+gunichar gc_search_result_get (GcSearchResult *result,
+ gint index);
+
+GType gc_search_criteria_get_type
+ (void);
+
+GcSearchCriteria *gc_search_criteria_new_category
+ (GcCategory category);
+
+GcSearchCriteria *gc_search_criteria_new_keywords
+ (const gchar * const * keywords);
+
+GcSearchCriteria *gc_search_criteria_new_scripts
+ (const gchar * const * scripts);
+
+GcSearchCriteria *gc_search_criteria_new_related
+ (gunichar uc);
+
+GcSearchContext *gc_search_context_new (GcSearchCriteria *criteria);
+void gc_search_context_search
+ (GcSearchContext *context,
+ gint max_matches,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GcSearchResult *gc_search_context_search_finish
+ (GcSearchContext *context,
+ GAsyncResult *result,
+ GError **error);
+gboolean gc_search_context_is_finished
+ (GcSearchContext *context);
+
+gchar *gc_character_name (gunichar uc);
/* GTK+ support. gtk_clipboard_get() takes an GdkAtom as the first
argument, but GdkAtom is not accessible through GI. */
-GtkClipboard *gc_gtk_clipboard_get (void);
+GtkClipboard *gc_gtk_clipboard_get (void);
/* Pango support. PangoAttrFallback is not accessible from GI. */
-void gc_pango_layout_disable_fallback
- (PangoLayout *layout);
+void gc_pango_layout_disable_fallback
+ (PangoLayout *layout);
-gboolean gc_pango_context_font_has_glyph
- (PangoContext *context,
- PangoFont *font,
- gunichar uc);
+gboolean gc_pango_context_font_has_glyph
+ (PangoContext *context,
+ PangoFont *font,
+ gunichar uc);
-gchar *gc_get_current_language (void);
+gchar *gc_get_current_language
+ (void);
const gchar * const * gc_get_scripts_for_language
- (const gchar *language);
+ (const gchar *language);
G_END_DECLS
diff --git a/src/character.js b/src/character.js
index 7f93af9..8c25102 100644
--- a/src/character.js
+++ b/src/character.js
@@ -115,13 +115,14 @@ const CharacterDialog = new Lang.Class({
this._cancellable.cancel();
this._cancellable.reset();
- Gc.search_related(
- this._character,
+ let criteria = Gc.SearchCriteria.new_related(this._character);
+ let context = new Gc.SearchContext({ criteria: criteria });
+ context.search(
-1,
this._cancellable,
- Lang.bind(this, function(source_object, res, user_data) {
+ Lang.bind(this, function(context, res, user_data) {
try {
- let result = Gc.search_finish(res);
+ let result = context.search_finish(res);
this._finishSearch(result);
} catch (e) {
log("Failed to search related: " + e);
diff --git a/src/characterList.js b/src/characterList.js
index 2f79f43..14a1a57 100644
--- a/src/characterList.js
+++ b/src/characterList.js
@@ -270,22 +270,18 @@ const CharacterListView = new Lang.Class({
this._characters = [];
this._spinnerTimeoutId = 0;
+ this._searchContext = null;
this._cancellable = new Gio.Cancellable();
+ this._cancellable.connect(Lang.bind(this, function () {
+ this._stopSpinner();
+ this._searchContext = null;
+ this._characters = [];
+ this.updateCharacterList();
+ }));
+ scroll.connect('edge-reached', Lang.bind(this, this._onEdgeReached));
},
- _stopSpinner: function() {
- if (this._spinnerTimeoutId > 0) {
- GLib.source_remove(this._spinnerTimeoutId);
- this._spinnerTimeoutId = 0;
- this._loading_spinner.stop();
- }
- this._cancellable.reset();
- },
-
- _startSearch: function() {
- this._cancellable.cancel();
- this._stopSpinner();
-
+ _startSpinner: function() {
this._spinnerTimeoutId =
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000,
Lang.bind(this, function () {
@@ -295,6 +291,14 @@ const CharacterListView = new Lang.Class({
}));
},
+ _stopSpinner: function() {
+ if (this._spinnerTimeoutId > 0) {
+ GLib.source_remove(this._spinnerTimeoutId);
+ this._spinnerTimeoutId = 0;
+ this._loading_spinner.stop();
+ }
+ },
+
_finishSearch: function(result) {
this._stopSpinner();
@@ -343,63 +347,66 @@ const CharacterListView = new Lang.Class({
this.show_all();
},
- searchByCategory: function(category) {
- if ('scripts' in category) {
- this.searchByScripts(category.scripts);
- return;
+ _onEdgeReached: function(scrolled, pos) {
+ if (pos == Gtk.PositionType.BOTTOM &&
+ this._searchContext != null && !this._searchContext.is_finished()) {
+ this._searchWithContext(this._searchContext, MAX_SEARCH_RESULTS);
+ }
+ },
+
+ _addSearchResult: function(result) {
+ for (let index = 0; index < result.len; index++) {
+ this._characters.push(Gc.search_result_get(result, index));
}
- this._startSearch()
- Gc.search_by_category(
- category.category,
- -1,
- this._cancellable,
- Lang.bind(this,
- function(source_object, res, user_data) {
- try {
- let result = Gc.search_finish(res);
- this._finishSearch(result);
- } catch (e) {
- log("Failed to search by category: " + e);
- }
- }));
+ this.updateCharacterList()
},
- searchByKeywords: function(keywords) {
- this._startSearch()
- Gc.search_by_keywords(
- keywords,
- MAX_SEARCH_RESULTS,
+ _searchWithContext: function(context, count) {
+ this._startSpinner();
+ context.search(
+ count,
this._cancellable,
- Lang.bind(this, function(source_object, res, user_data) {
+ Lang.bind(this, function(context, res, user_data) {
+ this._stopSpinner();
try {
- let result = Gc.search_finish(res);
- this._finishSearch(result);
+ let result = context.search_finish(res);
+ this._addSearchResult(result);
} catch (e) {
- log("Failed to search by keywords: " + e);
+ log("Failed to search: " + e);
}
}));
},
+ searchByCategory: function(category) {
+ if ('scripts' in category) {
+ this.searchByScripts(category.scripts);
+ return;
+ }
+
+ this._characters = [];
+ let criteria = Gc.SearchCriteria.new_category(category.category);
+ this._searchContext = new Gc.SearchContext({ criteria: criteria });
+ this._searchWithContext(this._searchContext, MAX_SEARCH_RESULTS);
+ },
+
+ searchByKeywords: function(keywords) {
+ this._characters = [];
+ let criteria = Gc.SearchCriteria.new_keywords(keywords);
+ this._searchContext = new Gc.SearchContext({ criteria: criteria });
+ this._searchWithContext(this._searchContext, MAX_SEARCH_RESULTS);
+ },
+
searchByScripts: function(scripts) {
- this._startSearch()
- Gc.search_by_scripts(
- scripts,
- -1,
- this._cancellable,
- Lang.bind(this, function(source_object, res, user_data) {
- try {
- let result = Gc.search_finish(res);
- this._finishSearch(result);
- } catch (e) {
- log("Failed to search by scripts: " + e);
- }
- }));
+ this._characters = [];
+ var criteria = Gc.SearchCriteria.new_scripts(scripts);
+ this._searchContext = new Gc.SearchContext({ criteria: criteria });
+ this._searchWithContext(this._searchContext, MAX_SEARCH_RESULTS);
},
cancelSearch: function() {
this._cancellable.cancel();
- this._finishSearch([]);
+ this._cancellable.reset();
},
setFilterFont: function(family) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]